我为我的应用程序创建了一个滤镜功能.我需要的一件事是一个更新函数,用于根据id更改选定过滤器的值.下面是我正在做的一个非常简单的例子:

interface DictionaryItem<Type> {
    value: Type;
    stringify: (value: Type) => string | null;
}

interface Dictionary {
    age: DictionaryItem<number>;
    isNice: DictionaryItem<boolean>;
    nickname: DictionaryItem<string>;
}

const items: Dictionary = {
    age: {
        value: 44,
        stringify: value => String(value),
    },
    isNice: {
        value: true,
        stringify: value => value ? "yes" : null,
    },
    nickname: {
        value: "Joey",
        stringify: value => value,
    },
};

type UpdateFunction = <Type extends keyof Dictionary>(id: Type, value: Dictionary[Type]["value"]) => void;

const update: UpdateFunction = (id, value) => {
    items[id].value = value;

    // Here's the problem - TypeScript thinks that
    // stringify signature is stringify(never)
    const stringified = items[id].stringify(value);

    console.log(stringified);
};

update("age", 45);

问题是我不能调用items[id].stringify(value);,因为在本例中,TypeScrip将stringify方法解析为stringify(value: never).

我如何才能让TypeScrip理解我的update()函数中预期的stringify()类型?这never人是从哪里来的呢?

推荐答案

Typescript 不能拿

interface Dictionary {
  age: DictionaryItem<number>;
  isNice: DictionaryItem<boolean>;
  nickname: DictionaryItem<string>;
}

请注意,每一处房产都是DictionaryItem<T>英镑,相当于T英镑.当您编写items[id].stringify时,TypeScrip没有看到那里有任何泛型关系;相反,它只是将泛型扩展到它的约束,并为您提供了union个函数:

  const f = items[id].stringify
  // const f: ((value: number) => string | null) | 
  //          ((value: boolean) => string | null) | 
  //          ((value: string) => string | null)

当您try 调用函数的联合时,编译器将坚持它们的intersection个参数是安全的.有关详细信息,请参阅the TypeScript 3.3 release notes.结果是never(因为number & boolean & string不存在),所以它根本不允许调用该函数.

编译器已经丢失了items[id].stringifyvalue之间的correlation的轨迹.它担心也许items[id].stringify分可能会接受number分,但value分可能是boolean分.即使您可以看到这样的事情是不可能的(或者不太可能不需要担心),编译器也不能.就它所能看到的,你有一个函数的联合和一个参数的联合,它们是不相关的.

因此,这就是microsoft/TypeScript#30581中描述的问题.


在这种情况下,推荐的方法在第microsoft/TypeScript#47109节中描述,涉及重构以使用"基本"映射接口、到该接口的泛型索引以及通过该接口到mapped types的泛型索引.

事实证明,您的代码实际上非常接近所需的重构.我们需要做的唯一真正的更改是将Dictionary重写为该"基本"接口上的映射类型:

interface DictionaryMap {
  age: number;
  isNice: boolean;
  nickname: string;
}

type Dictionary =
  { [K in keyof DictionaryMap]: DictionaryItem<DictionaryMap[K]> }

现在,我们可以说DictionaryMap[Type],而不是Dictionary[Type]["value"]:

type UpdateFunction = <K extends keyof Dictionary>(
  id: K, value: DictionaryMap[K]) => void;

现在一切都正常了.我们可以看到,items[id].stringify现在是单个泛型函数类型,而不是联合类型:

  const f = items[id].stringify
  // const f: (value: DictionaryMap[K]) => string | null

既然valueDictionaryMap[K]类型,那么f(value)是可以接受的.


您可能认为您对Dictionary的定义与更改后的版本没有区别,实际上它们是完全等同的(如果您使用IntelliSense将鼠标悬停在Dictionary上,您将看到与您的相同的类型).但是重构显式地让编译器知道DictionaryDictionaryItem具有泛型关系,而不是隐含地保留这种关系.也许在future 版本的TypeScrip中,编译器将可以观察到这样的隐含关系.但现在,您需要通过引入更基本的类型并在其上进行映射来阐明这一点.

Playground link to code

Typescript相关问答推荐

打印脚本丢失函数的泛型上下文

泛型类型,引用其具有不同类型的属性之一

具有继承的基于类的react 组件:呈现不能分配给基类型中的相同属性

无法从Chrome清除还原的初始状态数据

我可以使用TypeScrip从字符串词典/记录中填充强类型的环境对象吗?

在Typescript 中,有没有`index=unfined的速记?未定义:某个数组[索引]`?

Angular 15使用react 式表单在数组内部创建动态表单数组

部分更新类型的类型相等

Vue3 Uncaught SyntaxError:每当我重新加载页面时,浏览器控制台中会出现无效或意外的令牌

如何使用模板继承从子组件填充基础组件的画布

React router 6(数据路由)使用数据重定向

有没有办法防止类型交集绕过联合类型限制?

回调函数中的TypeScrip类型保护返回Incorect类型保护(具有其他未定义类型的返回保护类型)

使用强类型元组实现[Symbol.iterator]的类型脚本?

VUE 3类型';字符串';不可分配给类型';参考<;字符串&>

类型判断数组或对象的isEmpty

替换typescript错误;X类型的表达式可以';t用于索引类型Y;带有未定义

仅当定义了值时才按值筛选数组

TS 排除带点符号的键

如何从 Select 元素中删除选项