具有对象文字和 Typescript 的功能性开关盒
Posted
技术标签:
【中文标题】具有对象文字和 Typescript 的功能性开关盒【英文标题】:Functional switch case with object literal and Typescript 【发布时间】:2019-01-06 00:05:03 【问题描述】:所以我在 todomvc 中有这个经典的 switch case redux reducer,我想使其功能化,但似乎无法为此考虑 ts 类型。
Switch case 非常适合模式匹配,并按类型缩小动作区分联合。但我似乎不知道如何使用功能方法传递缩小的操作,其中对象文字的键应该进行类型缩小。
到目前为止,我得到的是所有函数的联合类型和一些 ts 错误。非常感谢您对此事的任何帮助,以更好地了解如何在 ts 中使用严格类型。
import action as actionCreator from 'typesafe-actions';
import uuid from 'uuid';
import ITodo from 'types/models';
const ADD_TODO = 'todos/ADD_TODO';
const TOGGLE_ALL = 'todos/TOGGLE_ALL';
const REMOVE_TODO = 'todos/REMOVE_TODO';
export const addTodo = (title: string) => actionCreator(ADD_TODO, title );
export const removeTodo = (id: string) => actionCreator(REMOVE_TODO, id );
export const toggleAll = (checked: boolean) =>
actionCreator(TOGGLE_ALL, checked );
type TodosAction =
| ReturnType<typeof addTodo>
| ReturnType<typeof removeTodo>
| ReturnType<typeof toggleAll>;
type TodosState = ReadonlyArray<ITodo>;
// no idea what typings should be
const switchCase = <C>(cases: C) => <D extends (...args: any[]) => any>(
defaultCase: D
) => <K extends keyof C>(key: K): C[K] | D =>
return Object.prototype.hasOwnProperty(key) ? cases[key] : defaultCase;
;
export default function(
state: TodosState = [],
action: TodosAction
): TodosState
// union type of 4 functions
const reducer = switchCase(
// (parameter) payload: any
// How do I get types for these?
[ADD_TODO]: payload => [
...state,
completed: false,
id: uuid.v4(),
title: payload.title,
,
],
[REMOVE_TODO]: payload => state.filter(todo => todo.id !== payload.id),
[TOGGLE_ALL]: payload =>
state.map(todo => (
...todo,
completed: payload.checked,
)),
)(() => state)(action.type);
// [ts] Cannot invoke an expression whose type lacks a call signature. Type
// '((payload: any) => completed: boolean; id: string; title: any; []) |
// ((payload: any) => ITodo[...' has no compatible call signatures.
return reducer(action.payload);
【问题讨论】:
【参考方案1】:一个有趣的打字问题。第一个关于负载类型的问题,我们可以通过传入所有可能的操作 (TodosAction
) 来解决,并要求switchCase
的参数必须是一个映射类型,该类型将包含联合中所有types
的属性对于每种类型,我们可以使用Extract
条件类型来提取有效负载类型。
问题的第二部分是由于当您使用键(即联合类型本身)索引类型时,您会从该类型中获得所有可能值的联合。在这种情况下,这将是函数的联合,打字稿不认为是可调用的。为了解决这个问题,我们可以更改内部函数的公共签名以返回一个函数,该函数将所有有效负载的联合作为参数,而不是每个都采用有效负载的函数的联合。
结果看起来像这样:
import action as actionCreator from 'typesafe-actions';
import * as uuid from 'uuid';
interface ITodo
id: string
const ADD_TODO = 'todos/ADD_TODO';
const TOGGLE_ALL = 'todos/TOGGLE_ALL';
const REMOVE_TODO = 'todos/REMOVE_TODO';
export const addTodo = (title: string) => actionCreator(ADD_TODO, title );
export const removeTodo = (id: string) => actionCreator(REMOVE_TODO, id );
export const toggleAll = (checked: boolean) =>
actionCreator(TOGGLE_ALL, checked );
type TodosAction =
| ReturnType<typeof addTodo>
| ReturnType<typeof removeTodo>
| ReturnType<typeof toggleAll>;
type TodosState = ReadonlyArray<ITodo>;
type Payload<TAll extends type: any; payload: any , P> = Extract<TAll, type: P>['payload']
type Casses<T extends type: any; payload: any , TState> = [P in T['type']]: (payload: Payload<T, P>) => TState
const switchCase = <C extends type: any; payload: any , TState>(cases: Casses<C, TState>) =>
<D extends (payload: any) => TState >(defaultCase: D) =>
function getCase <K extends string>(key: K): (arg: Payload<C, K>) => TState
function getCase <K extends string>(key: K): Casses<C, TState>[K] | D
return cases.hasOwnProperty(key) ? cases[key] : defaultCase;
return getCase;
;
export default function (
state: TodosState = [],
action: TodosAction
): TodosState
// union type of 4 functions
const reducer = switchCase<TodosAction, TodosState>(
[ADD_TODO]: payload => [
...state,
completed: false,
id: uuid.v4(),
title: payload.title,
,
],
[REMOVE_TODO]: payload => state.filter(todo => todo.id !== payload.id),
[TOGGLE_ALL]: payload =>
state.map(todo => (
...todo,
completed: payload.checked,
)),
)(() => state)(action.type);
return reducer(action.payload);
Playground link
【讨论】:
完全错过了那部分,但是如何强制reducer函数的返回对象类型匹配TodosState
类型?现在似乎没有正确检查类型以上是关于具有对象文字和 Typescript 的功能性开关盒的主要内容,如果未能解决你的问题,请参考以下文章
TypeScript,啥是对象文字的调用签名以及它们如何与泛型类型一起使用?
在 TypeScript 中使用类型变量访问对象文字不起作用
Typescript 索引属性约束检查适用于原始类型但不适用于对象文字?
如何在 TypeScript 中指定定义为对象文字的箭头函数的类型?