【问题标题】:Functional switch case with object literal and Typescript具有对象文字和 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);
}

【问题讨论】:

    标签: typescript redux functional-programming switch-statement


    【解决方案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类型?现在似乎没有正确检查类型
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-24
    • 2012-02-12
    相关资源
    最近更新 更多