TypeScript - 递归泛型和叶分支问题 (Ultimate TicTacToe)
Posted
技术标签:
【中文标题】TypeScript - 递归泛型和叶分支问题 (Ultimate TicTacToe)【英文标题】:TypeScript - Recursive generics and leaf branch-like problem (Ultimate TicTacToe) 【发布时间】:2019-12-19 02:13:22 【问题描述】:我正在制作一个Ultimate Tic-Tac-Toe 游戏并且我正在创建网格/板。
我如何递归地使用泛型让我的网格内部(在其单元格中)有其他网格? 深度级别未知,这可能有超过 2 的深度......它可能是一个网格的一个网格的一个网格的一个网格的网格,即
这与叶/分支问题大致相同。一个分支可以有叶子或其他分支(在这种情况下不能同时有)。
我已经尝试了一些方法(使用接口、定义自定义类型等),但它总是给出相同的错误(见下文,在错误部分)。
这是我最终得到的代码...
Cell.ts
import CellValue from "./CellValue";
export class Cell
private value: CellValue;
constructor()
public getValue(): CellValue
return this.value;
井字游戏.ts
import Cell from "./Cell";
/**
* Represents the Tic Tac Toe grid.
*/
export class TicTacToeGrid<T = TicTacToeGrid|Cell>
/**
* The grid can contain cells or other grids
* making it a grid of grids.
*/
private grid: Array<Array<T>> = [];
/**
* Creates a new grid and initiailzes it.
* @param [number, number][] sizes an array containing the sizes of each grid.
* The first grid has the size defined in the first element of the array. i.e. [3, 3] for a 3x3 grid.
* The others elements are the sizes for the sub-grids.
*/
constructor(sizes: [number, number][])
const size: [number, number] = sizes.splice(-1)[0]; // Get the size of this grid
for(let y = 0; y < size[1]; y++)
this.grid.push();
for(let x = 0; x < size[0]; x++)
this.grid[y].push();
this.grid[y][x] = sizes.length > 0 ? new TicTacToeGrid(sizes) : new Cell(); // if there still are size left in the array, create a new sub-grid with these sizes
/**
* Returns the content of the specified cell
* @param number x the x position of the cell whose content wants to be retrieved
* @param number y the y position of the cell whose content wants to be retrieved
*/
public getElement(x: number, y: number): T
return this.grid[y][x];
index.ts
import TicTacToeGrid from "./TicTacToeGrid ";
import Cell from "./Cell";
const board: TicTacToeGrid<TicTacToeGrid<Cell>> = new TicTacToeGrid<TicTacToeGrid<Cell>>([[3, 3], [5, 5]]);
const value: number= board.getElement(2, 2).getElement(1, 1).getValue();
一切正常,除了编译器在 TicTacToeGrid.js 中抛出 2 个错误:
Type parameter 'T' has a circular default.
-> 好的,但是我该如何解决呢?
Type 'Cell | TicTacToeGrid<unknown>' is not assignable to type 'T'.
'Cell | TicTacToeGrid<unknown>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint ''.
Type 'Cell' is not assignable to type 'T'.
'Cell' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint ''.
-> 我该如何处理呢?究竟是什么意思?
【问题讨论】:
相关:***.com/a/45999529/135978 【参考方案1】:问题 #1:循环默认
声明class TicTacToeGrid<T = TicTacToeGrid|Cell>
意味着T
可以是任何东西,但是如果你只写类型TicTacToeGrid
而不指定类型参数,它将使用TicTacToeGrid|Cell
。这是一种不好的循环,因为编译器被要求急切地评估类型TicTacToeGrid<TicTacToeGrid<TicTacToeGrid<...|Cell>|Cell>|Cell>
,它不知道该怎么做。您可以通过将其更改为 class TicTacToeGrid<T = TicTacToeGrid<any>|Cell>
来解决此问题,这会打破循环。
但是,我认为您实际上根本不想要default generic parameter。你不希望T
可能是string
,对吧?您正在寻找的是 generic constraint,而是使用 extends
关键字:
class TicTacToeGrid<T extends TicTacToeGrid<any> | Cell> ...
现在你的行为会更合理。
问题 #2:'Cell | TicTacToeGrid<...>' is not assignable to type 'T'
这是由于一个长期存在的问题(请参阅microsoft/TypeScript#24085),其中扩展联合类型的泛型类型参数没有通过控制流分析缩小范围。如果你有一个具体的联合类型,比如string | number
,你可以检查一个变量sn
,比如(typeof sn === "string") ? sn.charAt(0) : sn.toFixed()
,在三元运算符的分支内,sn
的类型将缩小为string
或number
。但是如果sn
是像T extends string | number
这样的泛型类型的值,那么检查(typeof sn === "string")
不会对T
类型做任何事情。在您的情况下,您想检查sizes.length
并将其缩小为T
到Cell
或一些TicTacToeGrid
。但这不会自动发生,编译器会警告您它无法确定您为网格元素分配了正确的值。它会看到您分配 Cell | TicTacToeGrid<XXX>
,并警告您 T
可能比这更窄,而且不安全。
这里最简单的解决方法是通过type assertion 告诉编译器你确定你正在做的事情是可以接受的:
this.grid[y][x] = (sizes.length > 0 ? new TicTacToeGrid(sizes) : new Cell()) as T;
最后的as T
告诉编译器你有责任确保前面的表达式是一个有效的T
。如果在运行时发现你错了,那么你已经用断言对编译器撒了谎,这就是你要处理的问题。事实上,这个问题很容易发生在您定义构造函数的方式中,因为无法从 sizes
参数推断出 T
... 所以对编译器撒谎很简单:
const lyingBoard: TicTacToeGrid<
TicTacToeGrid<TicTacToeGrid<Cell>>
> = new TicTacToeGrid([[3, 3]]); // no error
lyingBoard
.getElement(0, 0)
.getElement(0, 0)
.getElement(0, 0)
.getValue(); // no error at compile time, kablooey at runtime
但大概你不会那样做。可能有一种方法可以确保sizes
参数与T
匹配,或者甚至可以从sizes
参数中推断出T
,但这使我们离你的问题更远,这个答案已经很长了够了。
Link to code
【讨论】:
以上是关于TypeScript - 递归泛型和叶分支问题 (Ultimate TicTacToe)的主要内容,如果未能解决你的问题,请参考以下文章