具有对象文字和 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 中指定定义为对象文字的箭头函数的类型?

如何将 TypeScript 文件的对象文字 AST 转换为实际代码?

如何将具有特定功能的对象定义为类型或接口?