【发布时间】:2019-12-05 11:19:22
【问题描述】:
我正在使用 GraphQL code generator 从 graphql sdl 模式定义生成 TypesScript 类型。架构的相关部分从四个types 中定义了一个union,看起来像这样:
union Game = GameLobby | GamePlaying | GameOverWin | GameOverTie
type GameLobby {
id: ID!
}
type GamePlaying {
id: ID!
player1:String!
player2:String!
}
type GameOverWin {
id: ID!
winner:String!
}
type GameOverTie {
id: ID!
}
并生成以下 TypeScript 类型定义:
export type Game = GameLobby | GamePlaying | GameOverWin | GameOverTie;
export type GameLobby = {
__typename?: "GameLobby";
readonly id: Scalars["ID"];
};
export type GameOverTie = {
__typename?: "GameOverTie";
readonly id: Scalars["ID"];
};
export type GameOverWin = {
__typename?: "GameOverWin";
readonly id: Scalars["ID"];
readonly winner: String;
};
export type GamePlaying = {
__typename?: "GamePlaying";
readonly player1: String;
readonly player2: String;
};
现在,我希望能够在运行时使用类型联合来区分游戏当前处于哪个状态。我可以这样定义这样的联合:
// assume this gives back the generated types:
import { Game } from "./generated/models";
// we only want the actual discriminants
type GameStatus = Exclude<Game["__typename"], undefined>;
使用这种类型,我可以严格输入任何可能需要GameStatus 的值,例如:
class GameModel {
public readonly id!: number;
public readonly status!: GameStatus;
}
最后,我希望能够将游戏状态映射到持久状态,为此我需要枚举GameStatus 可以列举的所有可能的值其实拿。为了做到这一点,理想情况下,我希望不必重新输入值,但如果必须重新输入,我至少要确保我没有错过任何一个。
现在,这就是我确保涵盖GameStatus 可以采用的所有可能值的方式:
function assertNever(value: never): never {
throw new Error(`unexpected value ${value}`);
}
export const GameModelLobby: GameModelState = "GameLobby";
export const GameModelPlaying: GameModelState = "GamePlaying";
export const GameModelOverWin: GameModelState = "GameOverWin";
export const GameModelOverTie: GameModelState = "GameOverTie";
const gameStatus = [
GameModelLobby,
GameModelPlaying,
GameModelOverWin,
GameModelOverTie
];
// ensure we didn't forget any state
gameStatus.forEach(status => {
switch (status) {
case GameModelLobby:
break;
case GameModelPlaying:
break;
case GameModelOverWin:
break;
case GameModelOverTie:
break;
default:
assertNever(status);
}
});
这使得tsc 检查所有值是否被覆盖或删除,因为底层 GraphQL 架构发生变化。一种运行时/静态检查混合,因为我将代码留在运行时执行,但tsc 也会静态检查...
问题是:是否有可能在运行时从文字类型的联合中生成值?或者:是否可以在运行时从文字类型的联合中生成 TypeScript Enum?
如果这两个都不可能:有没有更简洁的方法来检查类型并确保没有遗漏任何案例?
更新
根据@dezfowler 的回答并进行了一些小改动,这就是我解决问题的方法:
首先从GameState联合类型中提取鉴别器类型:
import { GameState } from "./generated/models";
export type GameStateKind = Exclude<GameState["__typename"], undefined>;
然后构建一个映射类型(这是一种重言式)并以类型安全的方式将 types 映射到 values。该映射强制您使用所有类型作为键并写入所有值,因此除非每个判别式都存在,否则它不会编译:
export const StateKindMap: { [k in GameStateKind]: k } = {
GameLobby: "GameLobby",
GameOverTie: "GameOverTie",
GameOverWin: "GameOverWin",
GamePlaying: "GamePlaying"
};
将所有类型导出为一个数组,然后我可以使用它在数据库模型中创建枚举:
export const AllStateKinds = Object.values(StateKindMap);
最后,我写了一个小测试,以确保我可以直接使用StateKindMap 来区分GameStateKind(这个测试是多余的,因为所有必需的检查都由tsc 完成):
import { StateKindMap, AllStateKinds } from "./model";
describe("StateKindMap", () => {
it("should enumerate all state kinds", () => {
AllStateKinds.forEach(kind => {
switch (kind) {
case StateKindMap.GameLobby:
break;
case StateKindMap.GameOverTie:
break;
case StateKindMap.GameOverWin:
break;
case StateKindMap.GamePlaying:
break;
default:
assertNever(kind);
}
});
});
});
function assertNever(value: never): never {
throw new Error(`unexpected value ${value}`);
}
【问题讨论】:
-
请注意,IE 不支持
Object.values(),因此Object.keys()与此示例的结果相同,但兼容性更安全。
标签: typescript graphql code-generation