【问题标题】:Extend the possible types of values of dictionary in TypeScript在 TypeScript 中扩展字典值的可能类型
【发布时间】:2021-01-16 11:46:14
【问题描述】:

我在我的 React Native 项目中使用 TypeScript 有一个强类型样式对象:

const styles = StyleSheet.create({
  container:{
    backgroundColor: 'red',
    flex: 1
  },
});

这与StyleSheet.create 预期任意命名键一样正常,其目标值为ViewStyleTextStyleImageStyle,定义如下:

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 是任意键,backgroundColorViewStyle 中定义为具有ColorValue 类型的值,该类型定义为string | OpaqueColorValue,其中OpaqueColorValue 是唯一符号。

但是,我正在构建一个函数,我想在 ColorValue 之前以原始类型接受的任何地方提供以下类型签名:

ColorValue | { light: ColorValue, dark: ColorValue }

连同提供给我的函数的名为 lightdark 的参数,我将像这样使用它:

const styles = myFunction({
  container:{
    backgroundColor: {
      light: 'red',
      dark: 'blue'
    },
    flex: 1
  },
}, 'dark');

它会选择合适的键,例如它会返回:

{
    container:{
      backgroundColor: 'blue' //as we picked the 'dark' key from original input
      flex: 1
    }
}

简单地说,我希望我的函数的输入类型能够接受StyleSheet.create 接受的任何内容,并且除此之外,无论它接受ColorValue,也接受{light: ColorValue, dark: ColorValue}。然后它选择适当的键,然后在内部调用StyleSheet.create 并返回结果。

我编写了这个函数,它可以工作,但我很难让它接受带有 IntelliSense 和 linter 中的 light/dark 键的对象(当然,不强制转换为 anyunknown)。

我如何做到这一点?

【问题讨论】:

  • 这个问题是 React 特有的吗?如果不是,您可能希望去掉示例代码对它的依赖以构成一个minimal reproducible example,可以将其放入独立的IDE中,例如The TypeScript Playground
  • 或者,如果问题是特定于 React 的,你可能想要这样标记它
  • @jcalz 不,这是 TypeScript 问题,React 只是在利用它。我会尝试提供一个 MCVE 现在更新问题。

标签: typescript dictionary strong-typing typescript4.0


【解决方案1】:

我想你可能会从这样的conditionalmapped 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&lt;T, S, D&gt; 接受一个类型 T、一个源类型 S 和一个目标类型 D 并检查:如果 S 可分配给 T,然后将其替换为D。否则,递归遍历T 并以相同的方式替换所有属性和子属性。 DeepReplaceSubtype&lt;T, S, D&gt; 类似,但它会检查 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。然后,返回值应采用T 并替换任何属于Shade 子类型的子属性并将它们转回ColorValue。它应该像这样工作:

const styles = StyleSheet.myFunction({
    container: {
        backgroundColor: {
            light: 'red',
            dark: 'blue'
        },
        flex: 1
    },
}, 'dark');

/* const styles: {
    container: {
        backgroundColor: ColorValue;
        flex: number;
    };
} */

看起来像你想要的,我想。您可能会变得更加聪明,并让输出类型特别是从 lightdark 中选择的值的类型(所以 string 而不是 ColorValue),但这会更复杂,可能没必要,我就停在那里。

Playground link to code

【讨论】:

    猜你喜欢
    • 2017-05-14
    • 2020-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多