typescript - 在 Typescript 中,如何使用所述类型的文字类型属性从联合中选择类型?

标签 typescript

我有一个 reducer react 。 Action 可以是 8 种类型之一,但为了简单起见,让我们假设只有 2 种类型

type Add = {
  type: 'add';
  id: string;
  value: string;
}

type Remove = {
  type: 'remove';
  id: string;
}

type Action = Add | Remove;
我没有使用 switch case,而是使用了一个处理程序对象,其中每个处理程序都是一个处理特定操作的函数
const handlers = {
  add: (state, action) => state,
  remove: (state, action) => state,
  default: (state, action) => state,
}

const reducer = (state, action) => {
  const handler = handlers[action.type] || handlers.default;
  return handler(state, action);
}
现在我想输入 handlers适本地反对​​。因此,处理函数应该接受与其在 handlers 中的键对应的类型的操作。目的。
type Handlers = {
  [key in Action["type"]]: (state: State, action: Action) => State
//                                                ↑this here should be the action which has type
//                                                matching to it's key. So when the key is
//                                                'add', it should be of type Add, and so on.
}
我能想到的就是明确说明键和匹配的操作类型。有没有办法根据键的值从联合中“挑选”类型?

最佳答案

仅提取类型很简单。

type Add = {
    type: 'add';
    id: string;
    value: string;
}
  
type Remove = {
    type: 'remove';
    id: string;
}
  
type Action = Add | Remove;

type addType = Extract<Action, {type: 'add'}>;
您可以创建一个映射类型来为联合的每个元素自动执行此操作。
type OfUnion<T extends {type: string}> = {
    [P in T['type']]: Extract<T, {type: P}>
}
这是一种映射类型。它就像一个类型函数,它接受一个类型,转换它,然后返回那个新类型。所以OfUnion期待形状像 T extends {type: string} 的东西.这意味着它会接受 {type: 'add'}或者像 type Action = Add | Remove 这样的联合类型.重要的是该联合类型的每个元素都有一个 type: string属性(property)。
当您有一个联合类型并且每个选项都有一个共同的属性时,您可以安全地访问该属性(在本例中为 type )。这被分发,所以 Action['type']给我们 'add' | 'remove' .映射类型正在创建一个具有键 [P in T['type']] 的对象。 ,又名 'add' | 'remove' !add的类型属性应该是该操作的类型,Add .你可以用那个 Extract<> 来解决这个问题调用我。这实质上过滤了联合以仅包含匹配特定类型的元素。在这种情况下,过滤到仅匹配 {type: 'add'} 的内容.与此匹配的唯一选项是 {type: 'add', id: string, value: string}对象,所以这就是它的设置。
现在你有一个看起来像这样的对象:
{
    add: {
        type: 'add';
        id: string;
        value: string;
    },
    remove: {
        type: 'remove';
        id: string;
    }
}
那太棒了!但是我们需要将其转换为处理程序。所以处理程序仍然会有 addremove键,但不是对象类型,它们将是采用对象类型并返回任何内容的函数。
type Handler<T> = {
    [P in keyof T]: (variant: T[P]) => any
}
这里是 P 的例子将是 'add'T[P]就是 Add类型。所以现在处理程序应该接收一个 Add对象,并用它做一些事情。这正是我们想要的。
现在您可以编写一个带有自动完成功能的类型安全的匹配函数:
function match<
    T extends {type: string},
    H extends Handler<OfUnion<T>>,
> (
    obj: T,
    handler: H,
): ReturnType<H[keyof H]> {
    return handler[obj.type as keyof H]?.(obj as any);
}
请注意,您需要在此处使用 extends 来推断 H 的特定类型。您需要完全指定的类型来获取每个分支的返回类型。
edited playground linkmatch()函数需要接受一个对象,该对象将有一个联合类型,如 Action然后将它期望的处理程序对象限制为某个契约(Contract)(我们刚刚创建的处理程序类型)。最好将其作为函数而不是每种情况来执行,因为这样您就不需要不断重写类型定义!该功能将带来它们。最好在没有状态对象的情况下以不可知的方式执行此操作,因为您仍然可以在处理程序分支内使用状态对象。查找“闭包”以获取更多信息,但您也可以相信我,state对象将在范围内。但是现在,我们也可以使用 matchstate的地方不相关,就像在 .jsx 中一样。
const reducer = (state: State, action: Action) => match(action, {
  add: ({id, value}) => state,
  remove: ({id}) => state,
})
我希望这有帮助!相信我,使用这些类型比编写它们更容易。您需要高级 typescript 知识来编写它们,但任何初学者都可以使用它们。

您可能感兴趣 my library variant , which does this exact thing .事实上,这些例子只是 simplified versions of my library code .我的 match函数将自动限制您传入的处理程序对象,返回类型将是所有处理程序分支的联合。
欢迎你偷这个,或者,自己试试这个库。它将在同一方向上为您提供更多内容。

关于typescript - 在 Typescript 中,如何使用所述类型的文字类型属性从联合中选择类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64527150/

相关文章:

visual-studio - 我可以在 Visual Studio 2013 中使 "Go to definition"跳转到 TypeScript 代码吗?

javascript - 在 typescript 中使用任何 Prop 定义对象

angular - typescript 中的变量类型验证不起作用

javascript - 使用 .catch 代替 try/catch block

javascript - 无法在 typescript 中调用 svgdotjs rect 方法

typescript - 在 TypeScript 中对非类型化数据强制类型化

TypeScript 文件中的 JavaScript 引用

TypeScript 装饰器,获取构造函数参数类型和注入(inject)

typescript - 将 Typescript 更新到最新版本时无法构建项目。 (以代码 1 退出)

node.js - 类型检查字符串而不是使用 instanceof