TypeScript 泛型 column.id 应该在行 [key] 内
TypeScript Generics column.id should be within row[key]
我正在为表编写 TypeScript 接口:
interface Column {
id: string;
label: string;
}
interface Data {
[key: string]: string;
}
interface Table {
columns: Column[];
data: Data[];
}
我想限制 Column.id
的允许值:每个 Column.id
必须有一个匹配的 Data.key
。 (但是:不是每个Data.key
必须有匹配的Column.id
)
示例:
这应该是允许的,因为每个Column.id
都有匹配的Data.key
。
columns: [
{ id: 'foo', label: 'foo' },
{ id: 'bar', label: 'bar' }
]
data: [
{ foo: '123', bar: '456', baz: '789' }
]
但这应该不允许,因为Data[foo]
不存在。
columns: [
{ id: 'foo', label: 'foo' }
]
data: [
{ bar: '456' }
]
如何编写应用这些约束的 Table
接口?
您不能创建表示该约束的具体类型,但您可以使用generics和辅助函数来推断强制执行该约束的泛型类型约束。
让我们扩展 Column
、Data
和 Table
的定义,使其在我们关心的字符串属性中通用:
interface Column<K extends string = string> {
id: K,
label: string;
}
type Data<K extends string = string> = Record<K, string>
interface Table<K extends string = string, L extends K = K> {
columns: Column<L>[];
data: Data<K>[];
}
注意 Table<K, L>
是如何有效的,假设 K
和 L
是 string literals 的并集并且 K
尽可能窄,表达你想要的约束。由于 L extends K
,这意味着 columns['id']
必须是 keyof data
的子类型。
以下辅助函数将为您进行推理:
const asTable = <K extends string, L extends K>(x: Table<K, L>) => x;
好的,让我们看看它是否有效:
// no error
const goodTable = asTable({
columns: [
{ id: 'foo', label: 'foo' },
{ id: 'bar', label: 'bar' }
],
data: [
{ foo: '123', bar: '456', baz: '789' }
]
})
// error ... Type '"foo"' is not assignable to type '"bar"'
const badTable = asTable({
columns: [
{ id: 'foo', label: 'foo' }
]
,
data: [
{ bar: '456' }
]
})
看起来不错。希望对您有所帮助!
这是另一个似乎有效的解决方案。
我不确定与@jcalz 版本相比有哪些优缺点。
也许有人可以发表评论并解释差异:)
interface Column<K extends string> {
id: K;
label: string;
}
interface Props<D extends {[key: string]: string}> {
columns: Array<Column<keyof D & string>>;
data: D[];
}
基于@jcalz 可能更容易
interface Column<K extends string = string> {
name: K;
friendly_name: string;
type: string;
}
type Data<K extends string = string> = Record<K, any>;
interface UniformedData<K extends string = string> {
columns: Column<K>[];
//rows: Data<ValueOf<Pick<Column<K>, 'name'>>>[];//type ValueOf<T>=T[keyof T];
rows: Data<Column<K>['name']>[];
}
// Runtime type-inferring must on funcs
export const asTable = <K extends string>(x: UniformedData<K>) => x;
const check = asTable({
columns: [{ name: 'c1', friendly_name: 'C1', type: 'string' }],
rows: [{ c2: 'x' }], // error!
});
···
我正在为表编写 TypeScript 接口:
interface Column {
id: string;
label: string;
}
interface Data {
[key: string]: string;
}
interface Table {
columns: Column[];
data: Data[];
}
我想限制 Column.id
的允许值:每个 Column.id
必须有一个匹配的 Data.key
。 (但是:不是每个Data.key
必须有匹配的Column.id
)
示例:
这应该是允许的,因为每个Column.id
都有匹配的Data.key
。
columns: [
{ id: 'foo', label: 'foo' },
{ id: 'bar', label: 'bar' }
]
data: [
{ foo: '123', bar: '456', baz: '789' }
]
但这应该不允许,因为Data[foo]
不存在。
columns: [
{ id: 'foo', label: 'foo' }
]
data: [
{ bar: '456' }
]
如何编写应用这些约束的 Table
接口?
您不能创建表示该约束的具体类型,但您可以使用generics和辅助函数来推断强制执行该约束的泛型类型约束。
让我们扩展 Column
、Data
和 Table
的定义,使其在我们关心的字符串属性中通用:
interface Column<K extends string = string> {
id: K,
label: string;
}
type Data<K extends string = string> = Record<K, string>
interface Table<K extends string = string, L extends K = K> {
columns: Column<L>[];
data: Data<K>[];
}
注意 Table<K, L>
是如何有效的,假设 K
和 L
是 string literals 的并集并且 K
尽可能窄,表达你想要的约束。由于 L extends K
,这意味着 columns['id']
必须是 keyof data
的子类型。
以下辅助函数将为您进行推理:
const asTable = <K extends string, L extends K>(x: Table<K, L>) => x;
好的,让我们看看它是否有效:
// no error
const goodTable = asTable({
columns: [
{ id: 'foo', label: 'foo' },
{ id: 'bar', label: 'bar' }
],
data: [
{ foo: '123', bar: '456', baz: '789' }
]
})
// error ... Type '"foo"' is not assignable to type '"bar"'
const badTable = asTable({
columns: [
{ id: 'foo', label: 'foo' }
]
,
data: [
{ bar: '456' }
]
})
看起来不错。希望对您有所帮助!
这是另一个似乎有效的解决方案。
我不确定与@jcalz 版本相比有哪些优缺点。
也许有人可以发表评论并解释差异:)
interface Column<K extends string> {
id: K;
label: string;
}
interface Props<D extends {[key: string]: string}> {
columns: Array<Column<keyof D & string>>;
data: D[];
}
基于@jcalz 可能更容易
interface Column<K extends string = string> {
name: K;
friendly_name: string;
type: string;
}
type Data<K extends string = string> = Record<K, any>;
interface UniformedData<K extends string = string> {
columns: Column<K>[];
//rows: Data<ValueOf<Pick<Column<K>, 'name'>>>[];//type ValueOf<T>=T[keyof T];
rows: Data<Column<K>['name']>[];
}
// Runtime type-inferring must on funcs
export const asTable = <K extends string>(x: UniformedData<K>) => x;
const check = asTable({
columns: [{ name: 'c1', friendly_name: 'C1', type: 'string' }],
rows: [{ c2: 'x' }], // error!
});
···