【问题标题】:Argument of type {...} is not assignable to parameter of type 'never'{...} 类型的参数不可分配给“从不”类型的参数
【发布时间】:2019-10-17 18:20:56
【问题描述】:

我正在尝试基于具有相同键的另一个类型创建一个类型,并且其值的类型基于但不等于另一种类型。它类似于React 的setState,它接受一个值或一个获取当前值并返回新值的函数。

经过研究,与此错误相关的所有问题似乎都与缺少类型提示有关。我已经尝试为所有next、current 和newTheme[key] 变量指定类型。没用。

type Mode = "light" | "dark";

interface Theme {
  mode: Mode;
  test: number;
}

type ThemeUpdate = {
  [K in keyof Theme]: Theme[K] | ((current: Theme[K]) => Theme[K])
};

const reducer = (currentTheme: Theme, action: Partial<ThemeUpdate>): Theme => {
  const newTheme: Partial<Theme> = {};

  (Object.keys(action) as (keyof Theme)[]).forEach((key: keyof Theme) => {
    const next = action[key];
    const current = currentTheme[key];
    newTheme[key] = typeof next === "function" ? next(current) : next;
    //                                                ^^^^^^^
    // Argument of type 'number | "light" | "dark"' is not assignable to parameter of type 'never'. Type 'number' is not assignable to type 'never'
  });

  return { ...currentTheme, ...newTheme };
};

我期望next 函数将根据当前键解析参数类型。相反,参数被解析为 never 类型,我收到错误消息:

类型参数'number | “光” | "dark"' 不可分配给 “从不”类型的参数。类型“数字”不可分配给类型 '从不'`

next 函数应该将current 参数推断为Mode,当key === "mode" 时,key === "test" 应该推断为number

【问题讨论】:

    标签: typescript types


    【解决方案1】:

    是的,这是我一直在调用 correlated types 的 TypeScript 中的痛点之一。问题是您有一组联合类型的值,它们彼此不独立。 next 与 current 的关联方式取决于 key 的值。但是 TypeScript 只是将 next 视为值或函数或未定义的联合(这是正确的),将 current 视为值的联合(这也是正确的),而没有意识到 next 是不可能的同时对应"mode"和current对应"test"。 improved support for calling unions of functions 只会让这个问题更加混乱,因为 never 交叉点并没有提供太多关于发生了什么的线索。

    这里没有很好的解决方案。我发现处理这个问题的方法是手动引导编译器通过不同的情况,如:

      (Object.keys(action) as (keyof Theme)[]).forEach(key => {
        switch (key) {
          case "mode": {
            const next = action[key];
            const current = currentTheme[key];
            newTheme[key] = typeof next === "function" ? next(current) : next; // okay
            break;
          }
          case "test": {
            const next = action[key];
            const current = currentTheme[key];
            newTheme[key] = typeof next === "function" ? next(current) : next; // okay
            break;
          }
        }
      });
    

    这是相当安全的类型,但很乏味......

    或者,否则您将需要在某处进行类型断言以使编译器相信您正在做的事情是安全的(这是您对安全负责;编译器正在放弃)。对于这个特定问题,我会考虑将 forEach() 回调设为键类型 K 中的通用函数,然后使用您的断言告诉编译器 next 以它理解的方式依赖于 K:

      (Object.keys(action) as (keyof Theme)[]).forEach(
        // generic
        <K extends keyof Theme>(key: K) => {
          const next = action[key] as  // assert here
            | Theme[K]
            | ((current: Theme[K]) => Theme[K])
            | undefined;
          const current = currentTheme[key];
          newTheme[key] = typeof next === "function" ? next(current) : next; // okay
        }
      );
    

    这更方便但不安全。

    我通常在这里推荐断言 (link to code),并希望有一天能在 TypeScript 中引入对相关类型的更好支持。

    好的,希望对您有所帮助。祝你好运!

    【讨论】:

    • 我现在会选择类型断言解决方案。我想我需要重新考虑我正在尝试实现的 API,以停止与 TSC 的斗争。通常,是最好的选择。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2019-06-24
    • 2021-06-14
    • 1970-01-01
    • 1970-01-01
    • 2021-02-03
    • 1970-01-01
    • 2018-08-10
    相关资源
    最近更新 更多