依赖于映射来为判别联合的每个变体赋予值。

huangapple go评论62阅读模式
英文:

Rely on a mapping to value each variant of a discriminated union

问题

以下是您要翻译的部分:

"When consuming all variants of a discriminated union with some if statements, TypeScript narrows down the type to the specifics of the variant. I understand that to do the same while expressing the logic with a mapping from the discriminant to a function to process this variant, the mapping should be seen as distributive type, and not just a mapped type. I read https://stackoverflow.com/a/73262902/2924547 and https://github.com/microsoft/TypeScript/pull/47109, but still, I do not manage to apply this to my use case.

type Message =
  | { kind: "mood"; isHappy: boolean }
  | { kind: "age"; value: number };

// Could be derived from `Message` with
// type ExtractMessage<M, P> = M extends { kind: P } ? Omit<M, "kind"> : never;
//
// type Mapping = {
//   [key in Message["kind"]]: (
//     payload: Omit<ExtractMessage<Message, key>, "kind">
//   ) => void;
// };
type Mapping = {
  mood: (payload: { isHappy: boolean }) => void;
  age: (payload: { value: number }) => void;
};

// OK: TypeScript complains if a key is missing or the signature is wrong 
const mapping: Mapping = {
  mood: ({ isHappy }) => {
    console.log(isHappy);
  },
  age: ({ value }) => {
    console.log(value + 1);
  },
};

const process = (message: Message) => {
  // NOT OK: TypeScript complains because it does not now we are dealing with the right message
  mapping[message.kind](message);
};
英文:

When consuming all variants of a discriminated union with some if statements, TypeScript narrows down the type to the specifics of the variant. I understand that to do the same while expressing the logic with a mapping from the discriminant to a function to process this variant, the mapping should be seen as distributive type, and not just a mapped type. I read https://stackoverflow.com/a/73262902/2924547 and https://github.com/microsoft/TypeScript/pull/47109, but still, I do not manage to apply this to my use case.

type Message =
  | { kind: "mood"; isHappy: boolean }
  | { kind: "age"; value: number };

// Could be derived from `Message` with
// type ExtractMessage<M, P> = M extends { kind: P } ? Omit<M, "kind"> : never;
//
// type Mapping = {
//   [key in Message["kind"]]: (
//     payload: Omit<ExtractMessage<Message, key>, "kind">
//   ) => void;
// };
type Mapping = {
  mood: (payload: { isHappy: boolean }) => void;
  age: (payload: { value: number }) => void;
};

// OK: TypeScript complains if a key is missing or the signature is wrong 
const mapping: Mapping = {
  mood: ({ isHappy }) => {
    console.log(isHappy);
  },
  age: ({ value }) => {
    console.log(value + 1);
  },
};

const process = (message: Message) => {
  // NOT OK: TypeScript complains because it does not now we are dealing with the right message
  mapping[message.kind](message);
};

答案1

得分: 1

以下是翻译的内容:

microsoft/TypeScript#47109中描述的重构希望您将所有内容都写成一个相当简单的“基本”对象类型,例如

interface Mapping {
  mood: {
    isHappy: boolean;
  };
  age: {
    value: number;
  };
}

在您的情况下,可以从您的原始Message类型生成,我会将其重命名:

type _Message =
  | { kind: "mood"; isHappy: boolean }
  | { kind: "age"; value: number };

type Mapping = { [T in _Message as T["kind"]]: 
  { [K in keyof T as Exclude<K, "kind">]: T[K] } 
}

其他类型应该要么是在该对象类型上的映射类型,例如

type MappingCallbacks =
  { [K in keyof Mapping]: (payload: Mapping[K]) => void }

const mappingCallbacks: MappingCallbacks = {
  mood: ({ isHappy }) => {
    console.log(isHappy);
  },
  age: ({ value }) => {
    console.log(value + 1);
  },
};

要么是作为分布式对象类型,这是可以立即进行索引访问以获取联合类型的映射类型,例如:

type Message<K extends keyof Mapping = keyof Mapping> =
  { [P in K]: { kind: P } & Mapping[P] }[K]

type M = Message
/* type M = 
     ({ kind: "mood"; } & { isHappy: boolean; }) | 
     ({ kind: "age"; } & { value: number; }) 
*/

然后,您的操作必须是泛型的,基本接口的键类型:

const process = <K extends keyof Mapping>(message: Message<K>) => {
  mappingCallbacks[message.kind](message);
};

尽管从概念上讲,非泛型版本的process应该可以工作,其中message只是一个联合类型,但编译器无法遵循不同联合类型的每个成员都符合相似的约束这一逻辑。实际上,正是由于无法使用纯联合类型实现这一点,才出现了microsoft/TypeScript#30581中描述的问题,而microsoft/TypeScript#47109正是为了解决这个问题而编写的。

代码示例链接

英文:

The refactoring described in microsoft/TypeScript#47109 wants you to write everything in terms of a fairly simple "basic" object type like

interface Mapping {
  mood: {
    isHappy: boolean;
  };
  age: {
    value: number;
  };
}

which, in your case, could be generated from your original Message type, which I'll rename out of the way:

type _Message =
  | { kind: &quot;mood&quot;; isHappy: boolean }
  | { kind: &quot;age&quot;; value: number };

type Mapping = { [T in _Message as T[&quot;kind&quot;]]: 
  { [K in keyof T as Exclude&lt;K, &quot;kind&quot;&gt;]: T[K] } 
}

Other types should either be mapped types on that object type, such as

type MappingCallbacks =
  { [K in keyof Mapping]: (payload: Mapping[K]) =&gt; void }

const mappingCallbacks: MappingCallbacks = {
  mood: ({ isHappy }) =&gt; {
    console.log(isHappy);
  },
  age: ({ value }) =&gt; {
    console.log(value + 1);
  },
};

or as distributive object types which are mapped types into which one has immediately indexed with all the keys to get a union, such as:

type Message&lt;K extends keyof Mapping = keyof Mapping&gt; =
  { [P in K]: { kind: P } &amp; Mapping[P] }[K]

type M = Message
/* type M = 
     ({ kind: &quot;mood&quot;; } &amp; { isHappy: boolean; }) | 
     ({ kind: &quot;age&quot;; } &amp; { value: number; }) 
*/

Then your operations must be generic in the key type of your basic interface:

const process = &lt;K extends keyof Mapping&gt;(message: Message&lt;K&gt;) =&gt; {
  mappingCallbacks[message.kind](message);
};

Even though conceptually the non-generic version of process should work, where message is just a union, the compiler just can't follow that logic that the different members of the union each obey an analogous constraint. Indeed, it was the failure to do this with pure unions that was the subject of microsoft/TypeScript#30581, the problem which microsoft/TypeScript#47109 was written to solve.

Playground link to code

huangapple
  • 本文由 发表于 2023年2月14日 22:37:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/75449373.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定