我正在try 编写一段代码,该代码将为一个类型的每个成员生成一个有区别的联合,以及一个函数,该函数将接受该类型的对象和一个联合成员的实例.

以下是我到目前为止拥有的代码

interface RickAndMortyCharacter {
  species: string;
  origin: {
    name: string;
    url: string;
  };

}

type RickAndMortyCharacterChange = {
  [Prop in keyof RickAndMortyCharacter]: {
    updatedProps: Prop;
    newValue: RickAndMortyCharacter[Prop];
  };
}[keyof RickAndMortyCharacter];


function applyPropertyChange(
  state: RickAndMortyCharacter,
  payload: RickAndMortyCharacterChange
) {
  // Error on this line
  state[payload.updatedProps] = payload.newValue;

  return state;
}


也可以在这里与打印稿playground一起使用

TypeScrip显示以下错误

类型‘字符串|{名称:字符串;url:字符串;}’不能分配给类型‘字符串&{名称:字符串;url:字符串;}’. 类型‘字符串’不可赋值给类型‘字符串&{名称:字符串;url:字符串;}’. 类型‘字符串’不可赋值给类型‘{name:string;url:string;}’.(2322)

有趣的是,当我将鼠标悬停在RickAndMortyCharacterChange类型上时,我看到声明了以下类型:

type RickAndMortyCharacterChange = {
    updatedProps: "species";
    newValue: string;
} | {
    updatedProps: "origin";
    newValue: {
        name: string;
        url: string;
    };
}

这对我来说似乎是正确的,因为Typescript 阻止了我写这篇文章

const s: RickAndMortyCharacterChange = {
  updatedProps: "origin",
  newValue: "bonjour"
};

const s2: RickAndMortyCharacterChange = {
  updatedProps: "species",
  newValue: {
    name: "Hello",
    url: "Hoi"
  }
}

我最近在一个Angular 项目中使用了一个非常非常类似的方法,它工作得很好.我不能理解为什么TypeScrip在这里抱怨

推荐答案

这本质上是打字脚本类型判断算法的一个限制,潜在的问题在第microsoft/TypeScript#30581节中描述.如果您有一个union type的表达式,并且编写了该表达式多次出现的代码,编译器会通过将该表达式的每个语句视为独立于原始语句来对其进行分析.所以,假设有这样的事情

declare const state: RickAndMortyCharacter;
declare const payload: {
    updatedProps: "species";
    newValue: string;
} | {
    updatedProps: "origin";
    newValue: {
        name: string;
        url: string;
    };
}

你会得到这个:

const up = payload.updatedProps;
// const up: "species" | "origin"
const nv = payload.newValue;
// const nv: string | { name: string; url: string; }

请注意,upnv都是联合类型.但这已经丢失了你所关心的信息.如果您所知道的关于up的全部信息是它的类型"species" | "origin",而您所知道的nv的所有信息是它的类型string | { name: string; url: string},那么您不可能再编写这样的代码而不出错:

state[up] = nv; // error!

因为如果up"origin",而nvstring呢?你只知道这是不可能的,因为upnv都来自同一个payload.但TypeScrip没有分析payload个中的identity个,它只分析了type个.Typescript 中没有"相关的联合"这回事.

所以赋值失败,即使它是this twitter thread by the TS Team dev lead%类型安全的.他们不能仅仅通过让编译器多次分析state[up] = nv来修复它,而不会大规模地 destruct 编译器的性能或使编译器变得不可预测.请参见this twitter thread by the TS Team dev lead.


幸运的是,有一种方法可以重构代码,使编译器确实遵循逻辑.它被描述为microsoft/TypeScript#47109.方法是将联合替换为范围在类键类型上的generics,然后将泛型indexes转换为适当的mapped types.这些索引到映射的类型可以发生在单个distributive object type中(正如在GitHub问题中所创造的那样).

您的RickAndMortyCharacterChange基本上就是这样一种类型,但您可以将其更改为通用类型(仍默认为完整并集):

type RickAndMortyCharacterChange<K extends keyof RickAndMortyCharacter
  = keyof RickAndMortyCharacter> = {
    [P in K]: {
      updatedProps: P;
      newValue: RickAndMortyCharacter[P];
    };
  }[K];

现在,applyPropertyChange()可以在K中成为泛型,约束为keyof RickAndMortyCharacter.您将传入一个类型为RickAndMortyCharacterChange<K>的值,然后突然一切都正常了:

function applyPropertyChange<K extends keyof RickAndMortyCharacter>(
  state: RickAndMortyCharacter,
  payload: RickAndMortyCharacterChange<K>
) {
  state[payload.updatedProps] = payload.newValue; // okay
  return state;
}

如果你把它分解成upnv,你就会明白为什么:

function applyPropertyChange<K extends keyof RickAndMortyCharacter>(
  state: RickAndMortyCharacter,
  payload: RickAndMortyCharacterChange<K>
) {
  const up = payload.updatedProps;
  // const up: K
  const nv = payload.newValue;
  // RickAndMortyCharacter[K]
  state[up] = nv; // okay
  return state;
}

现在upK类型,nvRickAndMortyCharacter[K]类型.它们都是通用的.而这一分配被视为将右侧的RickAndMortyCharacter[K]分配给左侧的RickAndMortyCharacter[K].因为这些类型是相同的泛型,所以一切都按预期工作.


Playground link to code

Typescript相关问答推荐

TypScript中的算法运算式

如何在方法中定义TypeScript返回类型,以根据参数化类型推断变量的存在?

在类型内部使用泛型类型时,不能使用其他字符串索引

TypeScript类型不匹配:在返回类型中处理已经声明的Promise Wrapper

Redux—Toolkit查询仅在不为空的情况下添加参数

打字类型保护递归判断

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

Angular:ngx-echart 5.2.2与Angular 9集成错误:NG 8002:无法绑定到选项,因为它不是div的已知属性'

重载函数的T helper参数

@TANSTACK/REACT-QUERY中发生Mutations 后的数据刷新问题

TYPE FOR ARRAY,其中每个泛型元素是单独的键类型对,然后在该数组上循环

是否可以强制静态方法将同一类型的对象返回其自己的类?

Typescript 中相同类型的不同结果

Typescript -返回接口中函数的类型

为什么发送空字段?

如何键入对象转换器

如何在Reaction Native中关注屏幕时正确更新状态变量?

为什么`map()`返回类型不能维护与输入相同数量的值?

类型为 `(callback: (...args: T) => void, params: T)` 的函数的泛型

为什么我们在条件语句中使用方括号[]?