接口中的打字稿相对类型



我有一个接口,像这样:

type DataType = "string" | "number" | "boolean" | "timestamp";
interface Schema {
name?: string
...
columns: {
[column: string]: {
type: DataType
...
foreignKey?: {
schema: Schema
...
join: {from: string, to: string}
}
}
}
}

我基于此接口定义了几个模式,例如:

const schemaA: Schema = {
name: 'entity_A',
columns: {
col_A1: {
type: 'string'
},
col_A2: {
type: 'number'
},
col_A3: {
type: 'boolean'
},
}
}
const schemaB: Schema = {
name: 'entity_B',
columns: {
col_B1: {
type: 'timestamp'
},
col_B2: {
type: 'string'
},
col_B3: {
type: 'string'
},
}
}
const schemaC: Schema = {
name: 'entity_C',
columns: {
col_C1: {
type: 'string'
},
col_C2: {
type: 'string',
foreignKey: {
schema: schemaA,
join: {from: 'col_C1', to: 'col_A1'}
}
},
}
}

如您所见,在schemaC.col_C2.foreignKey中,我已将schemaA作为架构传递。意图相当明显,我正在尝试定义一个相关的数据模型。

我的问题是如何键入检查join。具体来说,我希望将join.from限制为其父架构的列(此处schemaC),并将join.to限制为所提供架构的列(此处schemaA)。但据我了解,为了做到这一点,我需要能够访问某种this参数,告诉打字稿如下:

join: {from: keys of this.columns}    // (the join.to is even more complicated)

游乐场链接

那么,我该怎么办?这可能吗?

我认为这应该是可能的吗?这似乎很有用。

没有满足您需求的特定类型Schema,因为有效的fromto属性取决于当前对象和schema属性的columns键。 这需要表示为泛型类型,例如Schema<T>其中T是要表示的数据类型。 理想情况下,您希望Schema的意思是"Schema<T>我不关心的某些T",但直接表达这一点需要 microsoft/TypeScript#14446 中要求的现有量化泛型。 与其担心尝试模拟它,我们可以定义泛型Schema<T>,然后编写一个从特定架构对象值推断T的帮助程序函数,这样您就不必自己写出来。

这里有一种方法:

type Schema<T> = {
name?: string;
// ...
columns: { [K in keyof T]: {
type: DataType;
// ...
foreignKey?: {
schema: Schema<T[K]>;
// ...
join: { from: keyof T; to: keyof T[K] };
};
} };
};

这里我们说Schema<T>columns属性包含一个对应于T每个属性的属性;它是T键中K的每个键类型的映射类型。 此属性包含一个type属性,以及一个包含Schema对象的可选foreignKey属性。 由于它大概表示该属性类型的Schema对象,即T[K],因此它属于Schema<T[K]>类型。 最后,join.from属性必须从T的键集中选择,而join.to属性必须从T[K]的键集中选择。

那么帮助程序函数看起来像

const asSchema = <T,>(schema: Schema<T>) => schema;

它只是一个返回其输入的通用标识函数。 但是你使用它是为了不写const foo: Schema<SomeComplicatedThing> = {...},你写const foo = asSchema({...}),让编译器推断SomeComplicatedThing如果可以的话。


让我们测试一下:

const schemaA = asSchema({
name: "entity_A",
columns: {
col_A1: {
type: "string",
},
col_A2: {
type: "number",
},
col_A3: {
type: "boolean",
},
},
});
/* const schemaA: Schema<{
col_A1: unknown;
col_A2: unknown;
col_A3: unknown;
}> */

在这里,schemaA被推断为类型Schema<{col_A1: unknown; col_A2: unknown; col_A3: unknown}>。 此类型足以满足您的目的,至少就示例而言。 如果它是Schema<{col_A1: string, col_A2: number, col_A3: boolean}>可能会很好,但是您之前不太关心值类型,因此超出了所问问题的范围。 从某种意义上说,我们真的只想跟踪键类型而不是完整的对象类型,但即使具有unknown属性,在这里使用对象类型也很方便,因为它可以让我们表示您关心的递归类型,而单独的键集则不会。

更多测试:

const schemaC = asSchema({
name: "entity_C",
columns: {
col_C1: {
type: "string",
},
col_C2: {
type: "string",
foreignKey: {
schema: schemaA,
join: { from: "col_C1", to: "col_A1" },
},
},
},
});
/* const schemaC: Schema<{
col_C1: unknown;
col_C2: {
col_A1: unknown;
col_A2: unknown;
col_A3: unknown;
};
}> */

同样,这是一个合理的推论。col_C2属性的类型现在比unknown更具体,并且对应于schemaA的模式中的类型参数。

如果我们犯了一个错误,我们会被提醒:

const schemaCOops = asSchema({
name: "entity_C",
columns: {
col_C1: {
type: "string",
},
col_C2: {
type: "string",
foreignKey: {
schema: schemaA,
join: { from: "col_C1", to: "col_B2" }, // error!
// -------------------> ~~
// Type '"col_B2"' is not assignable to 
// type '"col_A1" | "col_A2" | "col_A3"'
},
},
},
});

这就是本练习的重点。

操场链接到代码

相关内容

最新更新