typescript - 在 Typescript 中,如何使用所述类型的文字类型属性从联合中选择一个类型?
问题描述
我有一个减速器在反应。动作可以是 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
属性。
当您有一个联合类型并且每个选项都有一个共同的属性时,您可以安全地访问该属性(在本例中为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;
}
}
那太棒了!但是我们需要将其转换为处理程序。所以处理程序仍然会有add
和remove
键,但不是对象类型,它们将是接受对象类型并返回任何内容的函数。
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);
}
请注意,您需要在此处使用扩展来推断 H 的特定类型。您需要完全指定的类型来获取每个分支的返回类型。
该match()
函数需要接受一个对象,该对象将具有类似的联合类型Action
,然后将其预期的处理程序对象限制为某个合同(我们刚刚创建的处理程序类型)。最好将其作为一个函数而不是针对每种情况执行,因为这样您就不需要继续重写类型定义!该功能将带来它们。最好在不使用状态对象的情况下不可知地执行此操作,因为您仍然可以在处理程序分支内使用状态对象。查找“闭包”以获取更多信息,但您也可以相信我认为该state
对象将在范围内。但是现在,我们也可以在不相关match
的地方使用,比如在 .jsx 中。state
const reducer = (state: State, action: Action) => match(action, {
add: ({id, value}) => state,
remove: ({id}) => state,
})
我希望这会有所帮助!相信我,使用这些类型比编写它们更容易。您需要高级打字稿知识来编写它们,但任何初学者都可以使用它们。
您可能对我的库感兴趣variant
,它确实可以做到这一点。事实上,这些示例只是我的库代码的简化版本。我的match
函数将自动限制您传入的处理程序对象,返回类型将是所有处理程序分支的联合。
欢迎你偷这个,或者自己试试这个库。它将在同一方向为您提供更多内容。
推荐阅读
- windows - 如何使用批处理脚本删除日志文件中字符串中的最后一个字符?
- node.js - Array.prototype.findIndex() 不返回对象的索引
- javascript - JavaScript 从两个长数字构造 UUID
- php - Laravel刀片到pdf转换器(dompdf)内容全部重叠,引导程序不起作用
- python - 转换标签输出(字符串、整数、浮点数)
- javascript - 无法处理通过 Ionic 4 中 modal.onDidDismiss() 捕获的数据
- azure-data-factory-2 - Azure 数据工厂实体依赖项查找
- algorithm - 红黑树 - 按排序顺序返回所有大于 X 的值
- c - xv6中strcat之后的错误输出
- javascript - WooCommerce:根据所选的运输方式隐藏运输计算器