扩展 TypeScript 中字典值的可能类型
Extend the possible types of values of dictionary in TypeScript
我在 TypeScript 中的 React Native 项目中有一个强类型样式对象:
const styles = StyleSheet.create({
container:{
backgroundColor: 'red',
flex: 1
},
});
这正常工作,因为 StyleSheet.create
期望任意命名的键具有 ViewStyle
、TextStyle
和 ImageStyle
的目标值,定义如下:
export namespace StyleSheet {
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
/**
* Creates a StyleSheet style reference from the given object.
*/
export function create<T extends NamedStyles<T> | NamedStyles<any>>(styles: T | NamedStyles<T>): T;
[...]
}
container
是任意键,backgroundColor
在ViewStyle
中定义了一个ColorValue
类型的值,定义为string | OpaqueColorValue
其中 OpaqueColorValue
是唯一符号。
但是,我正在构建一个函数,我想在以前以原始类型接受 ColorValue
的地方使用以下类型签名:
ColorValue | { light: ColorValue, dark: ColorValue }
连同一个名为 light
或 dark
的参数提供给我的函数,我将像这样使用它:
const styles = myFunction({
container:{
backgroundColor: {
light: 'red',
dark: 'blue'
},
flex: 1
},
}, 'dark');
它会选择合适的密钥,例如它将 return:
{
container:{
backgroundColor: 'blue' //as we picked the 'dark' key from original input
flex: 1
}
}
用简单的英语来说,我希望我的函数的输入类型接受 StyleSheet.create
接受的任何内容,并且 除了 之外,还接受任何地方的 {light: ColorValue, dark: ColorValue}
它接受了 ColorValue
。然后它会选择适当的键,然后在内部调用 StyleSheet.create
并 return 得到结果。
我写了这个函数并且它可以工作,但我正在努力让它在 IntelliSense 和 linter 中接受带有 light/dark
键的对象(当然,没有强制转换为 any
或 unknown
).
如何实现?
我想你可能会受益于一些像这样的conditional, mapped types:
type DeepReplaceSupertype<T, S, D> = [S] extends [T] ? D : {
[K in keyof T]: DeepReplaceSupertype<T[K], S, D>
}
type DeepReplaceSubtype<T, S, D> = [T] extends [S] ? D : {
[K in keyof T]: DeepReplaceSubtype<T[K], S, D>
}
想法是 DeepReplaceSupertype<T, S, D>
采用类型 T
、源类型 S
和目标类型 D
并检查:如果 S
可分配给 T
,然后将其替换为 D
。否则,递归遍历 T
并以相同方式替换所有属性和子属性。 DeepReplaceSubtype<T, S, D>
类似,但它检查 T
是否可分配给 S
.
那么您的函数签名可能如下所示:
interface Shade { light: ColorValue, dark: ColorValue }
export declare function myFunction<
T extends DeepReplaceSupertype<NamedStyles<any>, ColorValue, Shade>>(
styles: T, shade: 'light' | 'dark'
): DeepReplaceSubtype<T, Shade, ColorValue>;
含义:接受任何 T
类型的内容,其中您接受 NameStyles
并替换接受 ColorValue
的子属性并将它们更改为 Shade
。然后,return 值应采用 T
并替换任何属于 Shade
子类型的子属性,并将它们变回 ColorValue
。它应该像这样工作:
const styles = StyleSheet.myFunction({
container: {
backgroundColor: {
light: 'red',
dark: 'blue'
},
flex: 1
},
}, 'dark');
/* const styles: {
container: {
backgroundColor: ColorValue;
flex: number;
};
} */
看起来像你想要的,我想。你可能会变得更聪明,让输出类型具体是从 light
或 dark
中选择的值的类型(所以 string
而不是 ColorValue
),但这会更复杂,而且可能没有必要,所以我就到此为止。
我在 TypeScript 中的 React Native 项目中有一个强类型样式对象:
const styles = StyleSheet.create({
container:{
backgroundColor: 'red',
flex: 1
},
});
这正常工作,因为 StyleSheet.create
期望任意命名的键具有 ViewStyle
、TextStyle
和 ImageStyle
的目标值,定义如下:
export namespace StyleSheet {
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
/**
* Creates a StyleSheet style reference from the given object.
*/
export function create<T extends NamedStyles<T> | NamedStyles<any>>(styles: T | NamedStyles<T>): T;
[...]
}
container
是任意键,backgroundColor
在ViewStyle
中定义了一个ColorValue
类型的值,定义为string | OpaqueColorValue
其中 OpaqueColorValue
是唯一符号。
但是,我正在构建一个函数,我想在以前以原始类型接受 ColorValue
的地方使用以下类型签名:
ColorValue | { light: ColorValue, dark: ColorValue }
连同一个名为 light
或 dark
的参数提供给我的函数,我将像这样使用它:
const styles = myFunction({
container:{
backgroundColor: {
light: 'red',
dark: 'blue'
},
flex: 1
},
}, 'dark');
它会选择合适的密钥,例如它将 return:
{
container:{
backgroundColor: 'blue' //as we picked the 'dark' key from original input
flex: 1
}
}
用简单的英语来说,我希望我的函数的输入类型接受 StyleSheet.create
接受的任何内容,并且 除了 之外,还接受任何地方的 {light: ColorValue, dark: ColorValue}
它接受了 ColorValue
。然后它会选择适当的键,然后在内部调用 StyleSheet.create
并 return 得到结果。
我写了这个函数并且它可以工作,但我正在努力让它在 IntelliSense 和 linter 中接受带有 light/dark
键的对象(当然,没有强制转换为 any
或 unknown
).
如何实现?
我想你可能会受益于一些像这样的conditional, mapped types:
type DeepReplaceSupertype<T, S, D> = [S] extends [T] ? D : {
[K in keyof T]: DeepReplaceSupertype<T[K], S, D>
}
type DeepReplaceSubtype<T, S, D> = [T] extends [S] ? D : {
[K in keyof T]: DeepReplaceSubtype<T[K], S, D>
}
想法是 DeepReplaceSupertype<T, S, D>
采用类型 T
、源类型 S
和目标类型 D
并检查:如果 S
可分配给 T
,然后将其替换为 D
。否则,递归遍历 T
并以相同方式替换所有属性和子属性。 DeepReplaceSubtype<T, S, D>
类似,但它检查 T
是否可分配给 S
.
那么您的函数签名可能如下所示:
interface Shade { light: ColorValue, dark: ColorValue }
export declare function myFunction<
T extends DeepReplaceSupertype<NamedStyles<any>, ColorValue, Shade>>(
styles: T, shade: 'light' | 'dark'
): DeepReplaceSubtype<T, Shade, ColorValue>;
含义:接受任何 T
类型的内容,其中您接受 NameStyles
并替换接受 ColorValue
的子属性并将它们更改为 Shade
。然后,return 值应采用 T
并替换任何属于 Shade
子类型的子属性,并将它们变回 ColorValue
。它应该像这样工作:
const styles = StyleSheet.myFunction({
container: {
backgroundColor: {
light: 'red',
dark: 'blue'
},
flex: 1
},
}, 'dark');
/* const styles: {
container: {
backgroundColor: ColorValue;
flex: number;
};
} */
看起来像你想要的,我想。你可能会变得更聪明,让输出类型具体是从 light
或 dark
中选择的值的类型(所以 string
而不是 ColorValue
),但这会更复杂,而且可能没有必要,所以我就到此为止。