我有一个接口,像这样:
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
,因为有效的from
和to
属性取决于当前对象和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"'
},
},
},
});
这就是本练习的重点。
操场链接到代码