如何从 TypeScript 中的字符串值数组推断类型?
How to infer types from array of string values in TypeScript?
瞄准
- 我希望 TS 根据传递给字符串数组的值(静态)推断类型。
- 实际的函数实现也应该给出正确的结果
问题
创建一个函数,名为:
export function CreateColoursPalette(tints, palette) {
//...
}
用法
const tints = {
sun: createColour({
10: '#FEF0D6',
30: '#FCE0AC',
50: '#FBD183',
80: '#F8BA44',
100: '#F7AB1B',
}),
azure: createColour<110>({
10: '#FEF0D6',
30: '#FCE0AC',
50: '#FBD183',
80: '#F8BA44',
100: '#F7AB1B',
110: '#FFE0AC',
})
};
const colours = CreateColoursPalette(tints, {
primary: ['sun', 'azure'],
secondary: ['sun'],
tertiary: {
neutral: ['sun', 'azure'],
},
});
- tints 是一个包含所有可用色调的对象
- palette 是一个包含字符串值数组的对象,这些字符串值是来自 tints 对象的键
该函数应该转换 palette 对象,方法是将数组中的每个字符串键替换为来自 tints 对象的相应值。
智能感知
当我们调用颜色时,TS 应该推断出正确的类型,即
colours.
//| primary
//| secondary
//| tertiary
// here TS should infer only 3 possible options due to the array above
colours.primary.
//| sun
//| azure
const [baseColour, tints] = colours.primary.sun;
tints.
//| 10 | 30 | 50 | 80 | 100
colours.secondary.
//| sun
colours.tertiary.
//| neutral
const [baseColour, tints] = colours.tertiary.neutral.azure
tints.
//| 10 | 30 | 50 | 80 | 100 | 110
我目前的解决方案(TS 输入错误)
为了让这个问题不那么令人生畏,我已将我的解决方案放入 Typescript playground。
我正在努力让 TS 根据数组中的值推断类型。
对于打字,您似乎想要这样:
function CreateColoursPalette<T, P extends Palette<keyof T>>(
tints: T, palette: P): ToColoursPalette<T, P>;
其中 Palette<K>
和 ToColoursPalette<T, P>
的定义如下:
type Palette<K extends PropertyKey> = {
[k: string]: Array<K> | Palette<K>;
}
type ToColoursPalette<T, P> = {
[K in keyof P]: (P[K] extends Array<any> ?
{ [L in P[K][number]]: [baseColour: BaseColour, tints: T[L]] } :
ToColoursPalette<T, P[K]>
) };
A Palette<K>
是一个对象,其 属性 值是 K
中的元素数组或另一个 Palette<K>
中的元素数组。这是一个递归定义,应该允许您传入嵌套的调色板。
ToColoursPalette<T, P>
表示所需的调色板类型转换 P
。它也是递归的。让我们来看看它:{[K in keyof P]: ...}
意味着它是一个映射类型,其中输出值将具有与 P
相同的键。对于每个这样的键 K
,我们检查它是否是一个数组:P[K] extends Array<any> ?
。如果不是,那么我们向下递归,我们希望键 K
处的 属性 值为 ToColoursPalette<T, P[K]>
。如果是这样,那么我们想要获取该数组的元素(即 P[K][number]
,如果您使用数字键索引到 P[K]
则得到的类型)并使用这些元素创建一个新的对象类型作为键。对于 P[K][number]
中的每个这样的键 L
,我们将 属性 值设为二元 tuple。第一个元素是 BaseColour
,其类型和详细信息不是您要询问的内容。第二个元素是 T[L]
类型,这是当您查找名为 L
的着色对象类型 T
的 属性 时得到的。
JavaScript 中 CreateColoursPalette()
的实现应该类似于:
function CreateColoursPalette(tints, palette) {
return Object.fromEntries(Object.entries(palette).map(([k, v]) => {
if (Array.isArray(v)) {
return [k, Object.fromEntries(v.map(p => [p, [baseColour, tints[p]]]))];
} else {
return [k, CreateColoursPalette(tints, v)];
}
}));
}
我正在使用 Object.entries()
and Object.fromEntries()
将对象转换为数组,反之亦然。
JS 代码做的事情与打字非常相似;我们正在将 palette
转换为具有相同键 k
的新对象,其输出值取决于输入值 v
是否为数组。如果v
不是数组,那么我们向下递归调用CreateColoursPalette(tints, v)
。否则,我们想要生成一个对象,它的键是 v
的元素,它的值是一个二元组,它的第一个元素是一些我们不关心的 baseColour
东西,它的第二个元素是 tints[p]
.
这基本上就是全部内容,除了没有很好的方法让编译器查看 JavaScript 代码并验证它是否符合 TypeScript 类型。如果您尝试,您将倾向于 运行 进入编译器错误。要将 JS 实现转换为 TS 接受的东西,最简单的方法是使用 type assertions or the any
type 或类似的方法对实现进行 loosen/disable 类型检查。本质上,我们自己负责确保实现和类型兼容。
它可能看起来像这样:
function CreateColoursPalette<T, P extends Palette<keyof T>>(
tints: T, palette: P): ToColoursPalette<T, P>;
function CreateColoursPalette(tints: any, palette: any): any {
return Object.fromEntries(Object.entries(palette).map(([k, v]: [string, any]) => {
if (Array.isArray(v)) {
return [k, Object.fromEntries(v.map(p => [p, [baseColour, tints[p]]]))];
} else {
return [k, CreateColoursPalette(tints, v)];
}
}));
}
我正在使用单一呼叫签名 overloaded function 来声明类型,并有意松散 any
实现。没有编译器错误。
我们现在可以验证它是否按预期运行:
console.log(colours.primary.azure[1]["110"]) // "#FFE0AC"
console.log(colours.tertiary.neutral.sun[1]["10"]) // "#FEF0D6"
IntelliSense 一路用正确的键提示,控制台日志输出表明该实现也符合我们的预期。看起来不错!
瞄准
- 我希望 TS 根据传递给字符串数组的值(静态)推断类型。
- 实际的函数实现也应该给出正确的结果
问题
创建一个函数,名为:
export function CreateColoursPalette(tints, palette) {
//...
}
用法
const tints = {
sun: createColour({
10: '#FEF0D6',
30: '#FCE0AC',
50: '#FBD183',
80: '#F8BA44',
100: '#F7AB1B',
}),
azure: createColour<110>({
10: '#FEF0D6',
30: '#FCE0AC',
50: '#FBD183',
80: '#F8BA44',
100: '#F7AB1B',
110: '#FFE0AC',
})
};
const colours = CreateColoursPalette(tints, {
primary: ['sun', 'azure'],
secondary: ['sun'],
tertiary: {
neutral: ['sun', 'azure'],
},
});
- tints 是一个包含所有可用色调的对象
- palette 是一个包含字符串值数组的对象,这些字符串值是来自 tints 对象的键
该函数应该转换 palette 对象,方法是将数组中的每个字符串键替换为来自 tints 对象的相应值。
智能感知
当我们调用颜色时,TS 应该推断出正确的类型,即
colours.
//| primary
//| secondary
//| tertiary
// here TS should infer only 3 possible options due to the array above
colours.primary.
//| sun
//| azure
const [baseColour, tints] = colours.primary.sun;
tints.
//| 10 | 30 | 50 | 80 | 100
colours.secondary.
//| sun
colours.tertiary.
//| neutral
const [baseColour, tints] = colours.tertiary.neutral.azure
tints.
//| 10 | 30 | 50 | 80 | 100 | 110
我目前的解决方案(TS 输入错误)
为了让这个问题不那么令人生畏,我已将我的解决方案放入 Typescript playground。
我正在努力让 TS 根据数组中的值推断类型。
对于打字,您似乎想要这样:
function CreateColoursPalette<T, P extends Palette<keyof T>>(
tints: T, palette: P): ToColoursPalette<T, P>;
其中 Palette<K>
和 ToColoursPalette<T, P>
的定义如下:
type Palette<K extends PropertyKey> = {
[k: string]: Array<K> | Palette<K>;
}
type ToColoursPalette<T, P> = {
[K in keyof P]: (P[K] extends Array<any> ?
{ [L in P[K][number]]: [baseColour: BaseColour, tints: T[L]] } :
ToColoursPalette<T, P[K]>
) };
A Palette<K>
是一个对象,其 属性 值是 K
中的元素数组或另一个 Palette<K>
中的元素数组。这是一个递归定义,应该允许您传入嵌套的调色板。
ToColoursPalette<T, P>
表示所需的调色板类型转换 P
。它也是递归的。让我们来看看它:{[K in keyof P]: ...}
意味着它是一个映射类型,其中输出值将具有与 P
相同的键。对于每个这样的键 K
,我们检查它是否是一个数组:P[K] extends Array<any> ?
。如果不是,那么我们向下递归,我们希望键 K
处的 属性 值为 ToColoursPalette<T, P[K]>
。如果是这样,那么我们想要获取该数组的元素(即 P[K][number]
,如果您使用数字键索引到 P[K]
则得到的类型)并使用这些元素创建一个新的对象类型作为键。对于 P[K][number]
中的每个这样的键 L
,我们将 属性 值设为二元 tuple。第一个元素是 BaseColour
,其类型和详细信息不是您要询问的内容。第二个元素是 T[L]
类型,这是当您查找名为 L
的着色对象类型 T
的 属性 时得到的。
JavaScript 中 CreateColoursPalette()
的实现应该类似于:
function CreateColoursPalette(tints, palette) {
return Object.fromEntries(Object.entries(palette).map(([k, v]) => {
if (Array.isArray(v)) {
return [k, Object.fromEntries(v.map(p => [p, [baseColour, tints[p]]]))];
} else {
return [k, CreateColoursPalette(tints, v)];
}
}));
}
我正在使用 Object.entries()
and Object.fromEntries()
将对象转换为数组,反之亦然。
JS 代码做的事情与打字非常相似;我们正在将 palette
转换为具有相同键 k
的新对象,其输出值取决于输入值 v
是否为数组。如果v
不是数组,那么我们向下递归调用CreateColoursPalette(tints, v)
。否则,我们想要生成一个对象,它的键是 v
的元素,它的值是一个二元组,它的第一个元素是一些我们不关心的 baseColour
东西,它的第二个元素是 tints[p]
.
这基本上就是全部内容,除了没有很好的方法让编译器查看 JavaScript 代码并验证它是否符合 TypeScript 类型。如果您尝试,您将倾向于 运行 进入编译器错误。要将 JS 实现转换为 TS 接受的东西,最简单的方法是使用 type assertions or the any
type 或类似的方法对实现进行 loosen/disable 类型检查。本质上,我们自己负责确保实现和类型兼容。
它可能看起来像这样:
function CreateColoursPalette<T, P extends Palette<keyof T>>(
tints: T, palette: P): ToColoursPalette<T, P>;
function CreateColoursPalette(tints: any, palette: any): any {
return Object.fromEntries(Object.entries(palette).map(([k, v]: [string, any]) => {
if (Array.isArray(v)) {
return [k, Object.fromEntries(v.map(p => [p, [baseColour, tints[p]]]))];
} else {
return [k, CreateColoursPalette(tints, v)];
}
}));
}
我正在使用单一呼叫签名 overloaded function 来声明类型,并有意松散 any
实现。没有编译器错误。
我们现在可以验证它是否按预期运行:
console.log(colours.primary.azure[1]["110"]) // "#FFE0AC"
console.log(colours.tertiary.neutral.sun[1]["10"]) // "#FEF0D6"
IntelliSense 一路用正确的键提示,控制台日志输出表明该实现也符合我们的预期。看起来不错!