如何在 TypeScript 中键入 Redux 操作和 Redux reducer?

Posted

技术标签:

【中文标题】如何在 TypeScript 中键入 Redux 操作和 Redux reducer?【英文标题】:How to type Redux actions and Redux reducers in TypeScript? 【发布时间】:2016-05-30 15:57:17 【问题描述】:

使用打字稿将action 参数转换为redux reducer 的最佳方法是什么?可能会出现多个操作接口,它们都扩展了具有属性类型的基本接口。扩展的操作接口可以具有更多的属性,这些属性在操作接口之间都是不同的。下面是一个例子:

interface IAction 
    type: string


interface IActionA extends IAction 
    a: string


interface IActionB extends IAction 
    b: string


const reducer = (action: IAction) 
    switch (action.type) 
        case 'a':
            return console.info('action a: ', action.a) // property 'a' does not exists on type IAction

        case 'b':
            return console.info('action b: ', action.b) // property 'b' does not exists on type IAction         
    

问题是action 需要被转换为可以访问IActionAIActionB 的类型,因此reducer 可以同时使用action.aaction.a 而不会引发错误。

我有几个想法可以解决这个问题:

    action 转换为any。 使用可选的接口成员。

示例:

interface IAction 
    type: string
    a?: string
    b?: string

    为每种操作类型使用不同的减速器。

在 typescript 中组织 Action/Reducers 的最佳方式是什么?提前谢谢!

【问题讨论】:

检查这个,spin.atomicobject.com/2017/07/24/… 严格来说,根据我的阅读,redux 会在每个可能的操作上运行每个 reducer。所以动作类型应该是任何动作类型。见:redux.js.org/faq/… 所以正确的类型是AnyAction 这个问题非常有帮助!由于我使用的是遗留代码,我最终使用了这些解决方案的组合,并参考了这篇文章medium.com/@pie6k/… 你找到解决这个问题的办法了吗?我已经尝试过 Typescript 2 中的 Tagged Union Types,但这仍然对我不起作用。 自 2016 年以来,我已经更改了几个项目和框架,遗憾的是不记得我是如何解决这个问题的。这里有很多正确和好的答案。如果我可以选择多个最佳答案,我会在此页面上至少选择五个。 【参考方案1】:

你可以做以下事情

如果您只期望IActionAIActionB 之一,您可以至少限制类型并将您的函数定义为

const reducer = (action: (IActionA | IActionB)) => 
   ...

现在,问题是,您仍然需要找出它是哪种类型。你完全可以添加一个type 属性,但是你必须在某个地方设置它,并且接口只是覆盖在对象结构上。您可以创建动作类并让 ctor 设置类型。

否则,您必须通过其他方式验证对象。 在您的情况下,您可以使用 hasOwnProperty 并根据此将其转换为正确的类型:

const reducer = (action: (IActionA | IActionB)) => 
    if(action.hasOwnProperty("a"))
        return (<IActionA>action).a;
    

    return (<IActionB>action).b;

这在编译成 javascript 时仍然有效。

【讨论】:

【参考方案2】:

对于一个相对简单的 reducer,你可能只需要使用类型保护:

function isA(action: IAction): action is IActionA 
  return action.type === 'a';


function isB(action: IAction): action is IActionB 
  return action.type === 'b';


function reducer(action: IAction) 
  if (isA(action)) 
    console.info('action a: ', action.a);
   else if (isB(action)) 
    console.info('action b: ', action.b);
  

【讨论】:

你让我很简单【参考方案3】:

这是来自 https://github.com/reactjs/redux/issues/992#issuecomment-191152574 的 Github 用户 aikoven 的巧妙解决方案:

type Action<TPayload> = 
    type: string;
    payload: TPayload;


interface IActionCreator<P> 
  type: string;
  (payload: P): Action<P>;


function actionCreator<P>(type: string): IActionCreator<P> 
  return Object.assign(
    (payload: P) => (type, payload),
    type
  );


function isType<P>(action: Action<any>,
                          actionCreator: IActionCreator<P>): action is Action<P> 
  return action.type === actionCreator.type;

使用actionCreator&lt;P&gt; 定义您的动作和动作创建者:

export const helloWorldAction = actionCreator<foo: string>('HELLO_WORLD');
export const otherAction = actionCreator<a: number, b: string>('OTHER_ACTION');

在reducer中使用用户定义的类型保护isType&lt;P&gt;

function helloReducer(state: string[] = ['hello'], action: Action<any>): string[] 
    if (isType(action, helloWorldAction))  // type guard
       return [...state, action.payload.foo], // action.payload is now foo: string
     
    else if(isType(action, otherAction)) 
        ...

并调度一个动作:

dispatch(helloWorldAction(foo: 'world')
dispatch(otherAction(a: 42, b: 'moon'))

我建议通读整个评论线程以找到其他选项,因为那里提供了几个同样好的解决方案。

【讨论】:

【参考方案4】:

使用 Typescript 2 的 Tagged Union Types,您可以执行以下操作

interface ActionA 
    type: 'a';
    a: string


interface ActionB 
    type: 'b';
    b: string


type Action = ActionA | ActionB;

function reducer(action:Action) 
    switch (action.type) 
        case 'a':
            return console.info('action a: ', action.a) 
        case 'b':
            return console.info('action b: ', action.b)          
    

【讨论】:

现在official documentation不鼓励这样做,建议使用类型谓词函数或redux-toolkit的createSlice【参考方案5】:

这是我针对这个问题采取的方法:

const reducer = (action: IAction) 

    const actionA: IActionA = action as IActionA;
    const actionB: IActionB = action as IActionB;

    switch (action.type) 
        case 'a':
            // Only ever use actionA in this context
            return console.info('action a: ', actionA.a)

        case 'b':
            // Only ever use actionB in this context
            return console.info('action b: ', actionB.b)
    

我会第一个承认这种方法有一定的丑陋和骇人听闻的地方,但实际上我发现它在实践中效果很好。特别是,我发现它使代码易于阅读和维护,因为动作的意图就在名称中,而且也便于搜索。

【讨论】:

【参考方案6】:

我有一个Action 接口

export interface Action<T, P> 
    readonly type: T;
    readonly payload?: P;

我有一个createAction 函数:

export function createAction<T extends string, P>(type: T, payload: P): Action<T, P> 
    return  type, payload ;

我有一个动作类型常量:

const IncreaseBusyCountActionType = "IncreaseBusyCount";

我有一个操作界面(查看typeof 的酷用法):

type IncreaseBusyCountAction = Action<typeof IncreaseBusyCountActionType, void>;

我有一个动作创建函数:

function createIncreaseBusyCountAction(): IncreaseBusyCountAction 
    return createAction(IncreaseBusyCountActionType, null);

现在我的减速器看起来像这样:

type Actions = IncreaseBusyCountAction | DecreaseBusyCountAction;

function busyCount(state: number = 0, action: Actions) 
    switch (action.type) 
        case IncreaseBusyCountActionType: return reduceIncreaseBusyCountAction(state, action);
        case DecreaseBusyCountActionType: return reduceDecreaseBusyCountAction(state, action);
        default: return state;
    

每个动作都有一个减速器功能:

function reduceIncreaseBusyCountAction(state: number, action: IncreaseBusyCountAction): number 
    return state + 1;

【讨论】:

很好的设置,我会按照你的例子。但是对于动作类型常量,我宁愿使用这样的东西:const AccountActions = GET_CURRENT_ACCOUNT_ASYNC: "GET_CURRENT_ACCOUNT_ASYNC"。为方便起见,我倾向于将它们组合在一个对象中。对于大型应用程序,我倾向于按模块和域对它们进行分组,例如 AccountDataActionsAccountUiActions。这样我在导入动作时就可以少打字了。为了进一步方便,我将动作类型常量和动作创建者放在同一个对象中。 这仍然是一个好方法,但我换了另一种方法,我也把它放在这里作为这个问题的答案!【参考方案7】:

@Jussi_K 引用的解决方案很好,因为它是通用的。

但是,我找到了一种我更喜欢的方式,有五点:

    它直接在动作对象上具有动作属性,而不是在更短的“有效负载”对象中。 (不过如果你更喜欢“payload”属性,只需在构造函数中取消注释多余的行) 可以使用简单的action.Is(Type) 在reducer 中进行类型检查,而不是笨拙的isType(action, createType)。 逻辑包含在单个类中,而不是分散在type Action&lt;TPayload&gt;interface IActionCreator&lt;P&gt;function actionCreator&lt;P&gt;()function isType&lt;P&gt;() 中。 它使用简单、真实的类而不是“动作创建器”和接口,在我看来,它们更具可读性和可扩展性。要创建新的 Action 类型,只需执行 class MyAction extends Action&lt;myProp&gt; 。 它确保类名和type 属性之间的一致性,只需将type 计算为类/构造函数名称。这符合 DRY 原则,不像其他解决方案同时具有 helloWorldAction 函数和 HELLO_WORLD“魔术字符串”。

无论如何,要实现这个备用设置:

首先,复制这个通用的 Action 类:

class Action<Payload> 
    constructor(payload: Payload) 
        this.type = this.constructor.name;
        //this.payload = payload;
        Object.assign(this, payload);
    
    type: string;
    payload: Payload; // stub; needed for Is() method's type-inference to work, for some reason

    Is<Payload2>(actionType: new(..._)=>Action<Payload2>): this is Payload2 
        return this.type == actionType.name;
        //return this instanceof actionType; // alternative
    

然后创建派生的 Action 类:

class IncreaseNumberAction extends Action<amount: number> 
class DecreaseNumberAction extends Action<amount: number> 

然后,在 reducer 函数中使用:

function reducer(state, action: Action<any>) 
    if (action.Is(IncreaseNumberAction))
        return ...state, number: state.number + action.amount;
    if (action.Is(DecreaseNumberAction))
        return ...state, number: state.number - action.amount;
    return state;

当您想要创建和调度一个动作时,只需执行以下操作:

dispatch(new IncreaseNumberAction(amount: 10));

与@Jussi_K 的解决方案一样,每个步骤都是类型安全的。

编辑

如果您希望系统与匿名操作对象兼容(例如,来自遗留代码或反序列化状态),您可以在 reducer 中使用此静态函数:

function IsType<Payload>(action, actionType: new(..._)=>Action<Props>): action is Payload 
    return action.type == actionType.name;

然后像这样使用它:

function reducer(state, action: Action<any>) 
    if (IsType(action, IncreaseNumberAction))
        return ...state, number: state.number + action.amount;
    if (IsType(action, DecreaseNumberAction))
        return ...state, number: state.number - action.amount;
    return state;

另一个选项是使用Object.definePropertyAction.Is() 方法添加到全局Object.prototype。这就是我目前正在做的事情——尽管大多数人不喜欢这样做,因为它会污染原型。

编辑 2

尽管它无论如何都可以工作,但 Redux 抱怨“动作必须是普通对象。使用自定义中间件进行异步动作。”。

要解决此问题,您可以:

    删除 Redux 中的 isPlainObject() 检查。 在我上面的编辑中进行一项修改,并将这一行添加到 Action 类的构造函数的末尾:(它删除了实例和类之间的运行时链接)
Object.setPrototypeOf(this, Object.getPrototypeOf());

【讨论】:

【参考方案8】:

要获得隐式类型安全而不必为每个操作编写接口,您可以使用这种方法(灵感来自这里的 returntypeof 函数:https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)

import  values  from 'underscore'

/**
 * action creator (declaring the return type is optional, 
 * but you can make the props readonly)
 */
export const createAction = <T extends string, P extends >(type: T, payload: P) => 
  return 
    type,
    payload
   as 
    readonly type: T,
    readonly payload: P
  


/**
 * Action types
 */
const ACTION_A = "ACTION_A"
const ACTION_B = "ACTION_B"

/**
 * actions
 */
const actions = 
  actionA: (count: number) => createAction(ACTION_A,  count ),
  actionB: (name: string) => createAction(ACTION_B,  name )


/**
 * create action type which you can use with a typeguard in the reducer
 * the actionlist variable is only needed for generation of TAction
 */
const actionList = values(actions).map(returnTypeOf)
type TAction = typeof actionList[number]

/**
 * Reducer
 */
export const reducer = (state: any, action: TAction) => 
  if ( action.type === ACTION_A ) 
    console.log(action.payload.count)
  
  if ( action.type === ACTION_B ) 
    console.log(action.payload.name)
    console.log(action.payload.count) // compile error, because count does not exist on ACTION_B
  
  console.log(action.payload.name) // compile error because name does not exist on every action

【讨论】:

【参考方案9】:

有些库捆绑了其他答案中提到的大部分代码:aikoven/typescript-fsa 和 dphilipson/typescript-fsa-reducers。

使用这些库,您的所有操作和缩减程序代码都是静态类型且可读的:

import actionCreatorFactory from "typescript-fsa";
const actionCreator = actionCreatorFactory();

interface State 
  name: string;
  balance: number;
  isFrozen: boolean;


const INITIAL_STATE: State = 
  name: "Untitled",
  balance: 0,
  isFrozen: false,
;

const setName = actionCreator<string>("SET_NAME");
const addBalance = actionCreator<number>("ADD_BALANCE");
const setIsFrozen = actionCreator<boolean>("SET_IS_FROZEN");

...

import  reducerWithInitialState  from "typescript-fsa-reducers";

const reducer = reducerWithInitialState(INITIAL_STATE)
  .case(setName, (state, name) => ( ...state, name ))
  .case(addBalance, (state, amount) => (
    ...state,
    balance: state.balance + amount,
  ))
  .case(setIsFrozen, (state, isFrozen) => ( ...state, isFrozen ));

【讨论】:

【参考方案10】:

你可以像这样定义你的动作:

// src/actions/index.tsx
import * as constants from '../constants'

export interface IncrementEnthusiasm 
    type: constants.INCREMENT_ENTHUSIASM;


export interface DecrementEnthusiasm 
    type: constants.DECREMENT_ENTHUSIASM;


export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;

export function incrementEnthusiasm(): IncrementEnthusiasm 
    return 
        type: constants.INCREMENT_ENTHUSIASM
    


export function decrementEnthusiasm(): DecrementEnthusiasm 
    return 
        type: constants.DECREMENT_ENTHUSIASM
    

所以,你可以像下面这样定义你的reducer:

// src/reducers/index.tsx

import  EnthusiasmAction  from '../actions';
import  StoreState  from '../types/index';
import  INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM  from '../constants/index';

export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState 
  switch (action.type) 
    case INCREMENT_ENTHUSIASM:
      return  ...state, enthusiasmLevel: state.enthusiasmLevel + 1 ;
    case DECREMENT_ENTHUSIASM:
      return  ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) ;
  
  return state;

完整的官方文档:https://github.com/Microsoft/TypeScript-React-Starter#adding-a-reducer

【讨论】:

【参考方案11】:

如果您需要完全按照您发布的方式修复您的实现,这就是如何修复它并使用 type assertions 使其正常工作的方法,如下所示:

interface IAction 
  type: string


interface IActionA extends IAction 
  a: string


interface IActionB extends IAction 
  b: string


const reducer = (action: IAction) => 
  switch (action.type) 
      case 'a':
          return console.info('action a: ', (<IActionA>action).a) // property 'a' exists because you're using type assertion <IActionA>

      case 'b':
          return console.info('action b: ', (<IActionB>action).b) // property 'b' exists because you're using type assertion <IActionB>
  

您可以在“类型保护和区分类型”部分了解更多信息 官方文档:https://www.typescriptlang.org/docs/handbook/advanced-types.html

【讨论】:

【参考方案12】:

公平地说,输入动作的方法有很多,但我发现 this one 非常简单,而且样板文件也较少(已在本主题中讨论过)。

此方法尝试键入称为“有效负载”的键的操作。

Check this sample

【讨论】:

【参考方案13】:

最近我一直在使用这种方法:

export abstract class PlainAction 
    public abstract readonly type: any;
    constructor() 
        return Object.assign(, this);
    


export abstract class ActionWithPayload<P extends object = any> extends PlainAction 
    constructor(public readonly payload: P) 
        super();
    


export class BeginBusyAction extends PlainAction 
    public readonly type = "BeginBusy";


export interface SendChannelMessageActionPayload 
    message: string;


export class SendChannelMessageAction
    extends ActionWithPayload<SendChannelMessageActionPayload>

    public readonly type = "SendChannelMessage";
    constructor(
        message: string,
    ) 
        super(
            message,
        );
    

这里是:

constructor() 
    return Object.assign(, this);

确保Actions 都是普通对象。现在您可以进行如下操作:const action = new BeginBusyAction()。 (耶\o/)

【讨论】:

【参考方案14】:

问题的两个部分

上面的几个 cmets 提到了概念/功能 `actionCreator' - 看看redux-actions 包 (以及对应的TypeScript definitions), 解决了问题的第一部分: 创建具有指定动作有效负载类型的 TypeScript 类型信息的动作创建函数。

问题的第二部分是将 reducer 函数组合成单个 reducer,无需样板代码,并且以类型安全的方式 (因为这个问题是关于 TypeScript 的)。

解决办法

结合 redux-actions 和redux-actions-ts-reducer 包:

1) 创建 actionCreator 函数,可用于在调度动作时创建具有所需类型和负载的动作:

import  createAction  from 'redux-actions';

const negate = createAction('NEGATE'); // action without payload
const add = createAction<number>('ADD'); // action with payload type `number`

2) 为所有相关操作创建具有初始状态和 reducer 函数的 reducer:

import  ReducerFactory  from 'redux-actions-ts-reducer';

// type of the state - not strictly needed, you could inline it as object for initial state
class SampleState 
    count = 0;


// creating reducer that combines several reducer functions
const reducer = new ReducerFactory(new SampleState())
    // `state` argument and return type is inferred based on `new ReducerFactory(initialState)`.
    // Type of `action.payload` is inferred based on first argument (action creator)
    .addReducer(add, (state, action) => 
        return 
            ...state,
            count: state.count + action.payload,
        ;
    )
    // no point to add `action` argument to reducer in this case, as `action.payload` type would be `void` (and effectively useless)
    .addReducer(negate, (state) => 
        return 
            ...state,
            count: state.count * -1,
        ;
    )
    // chain as many reducer functions as you like with arbitrary payload types
    ...
    // Finally call this method, to create a reducer:
    .toReducer();

从 cmets 中可以看出,不需要写任何 TypeScript 类型注解,但是所有类型都是推断出来的 (所以这甚至适用于noImplicitAny TypeScript compiler option)

如果您使用来自不公开 redux-action 动作创建者的某些框架中的动作(并且您也不想自己创建它们) 或者有将字符串常量用于操作类型的遗留代码,您也可以为它们添加减速器:

const SOME_LIB_NO_ARGS_ACTION_TYPE = '@@some-lib/NO_ARGS_ACTION_TYPE';
const SOME_LIB_STRING_ACTION_TYPE = '@@some-lib/STRING_ACTION_TYPE';

const reducer = new ReducerFactory(new SampleState())
    ...
    // when adding reducer for action using string actionType
    // You should tell what is the action payload type using generic argument (if You plan to use `action.payload`)
    .addReducer<string>(SOME_LIB_STRING_ACTION_TYPE, (state, action) => 
        return 
            ...state,
            message: action.payload,
        ;
    )
    // action.payload type is `void` by default when adding reducer function using `addReducer(actionType: string, reducerFunction)`
    .addReducer(SOME_LIB_NO_ARGS_ACTION_TYPE, (state) => 
        return new SampleState();
    )
    ...
    .toReducer();

因此无需重构代码库即可轻松上手。

调度动作

即使没有redux,您也可以像这样发送操作:

const newState = reducer(previousState, add(5));

但使用redux 调度操作更简单——照常使用dispatch(...) 函数:

dispatch(add(5));
dispatch(negate());
dispatch( // dispatching action without actionCreator
    type: SOME_LIB_STRING_ACTION_TYPE,
    payload: newMessage,
);

自白:我是我今天开源的 redux-actions-ts-reducer 的作者。

【讨论】:

非常感谢您提供此解决方案!这对我目前的项目帮助很大。 =)【参考方案15】:

使用 Typescript v2,您可以使用 union types with type guards 和 Redux 自己的 Action 和 Reducer 类型轻松完成此操作,无需使用额外的 3rd 方库,并且无需对所有操作强制执行通用形状(例如通过payload)。

这样,您的操作会正确地输入到 reducer 的 catch 子句中,就像返回的状态一样。

import 
  Action,
  Reducer,
 from 'redux';

interface IState 
  tinker: string
  toy: string


type IAction = ISetTinker
  | ISetToy;

const SET_TINKER = 'SET_TINKER';
const SET_TOY = 'SET_TOY';

interface ISetTinker extends Action<typeof SET_TINKER> 
  tinkerValue: string

const setTinker = (tinkerValue: string): ISetTinker => (
  type: SET_TINKER, tinkerValue,
);
interface ISetToy extends Action<typeof SET_TOY> 
  toyValue: string

const setToy = (toyValue: string): ISetToy => (
  type: SET_TOY, toyValue,
);

const reducer: Reducer<IState, IAction> = (
  state =  tinker: 'abc', toy: 'xyz' ,
  action
) => 
  // action is IAction
  if (action.type === SET_TINKER) 
    // action is ISetTinker
    // return  ...state, tinker: action.wrong  // doesn't typecheck
    // return  ...state, tinker: false  // doesn't typecheck
    return 
      ...state,
      tinker: action.tinkerValue,
    ;
   else if (action.type === SET_TOY) 
    return 
      ...state,
      toy: action.toyValue
    ;
  

  return state;

Things 基本上是 @Sven Efftinge 所建议的,同时还检查了 reducer 的返回类型。

【讨论】:

感谢您更新 @Sven Efftinge 答案以使用 redux 类型!【参考方案16】:

这是我的做法:

IAction.ts

import Action from 'redux';

/**
 * https://github.com/acdlite/flux-standard-action
 */
export default interface IAction<T> extends Action<string> 
    type: string;
    payload?: T;
    error?: boolean;
    meta?: any;

UserAction.ts

import IAction from '../IAction';
import UserModel from './models/UserModel';

export type UserActionUnion = void | UserModel;

export default class UserAction 

    public static readonly LOAD_USER: string = 'UserAction.LOAD_USER';
    public static readonly LOAD_USER_SUCCESS: string = 'UserAction.LOAD_USER_SUCCESS';

    public static loadUser(): IAction<void> 
        return 
            type: UserAction.LOAD_USER,
        ;
    

    public static loadUserSuccess(model: UserModel): IAction<UserModel> 
        return 
            payload: model,
            type: UserAction.LOAD_USER_SUCCESS,
        ;
    


UserReducer.ts

import UserAction, UserActionUnion from './UserAction';
import IUserReducerState from './IUserReducerState';
import IAction from '../IAction';
import UserModel from './models/UserModel';

export default class UserReducer 

    private static readonly _initialState: IUserReducerState = 
        currentUser: null,
        isLoadingUser: false,
    ;

    public static reducer(state: IUserReducerState = UserReducer._initialState, action: IAction<UserActionUnion>): IUserReducerState 
        switch (action.type) 
            case UserAction.LOAD_USER:
                return 
                    ...state,
                    isLoadingUser: true,
                ;
            case UserAction.LOAD_USER_SUCCESS:
                return 
                    ...state,
                    isLoadingUser: false,
                    currentUser: action.payload as UserModel,
                ;
            default:
                return state;
        
    


IUserReducerState.ts

import UserModel from './models/UserModel';

export default interface IUserReducerState 
    readonly currentUser: UserModel;
    readonly isLoadingUser: boolean;

UserSaga.ts

import IAction from '../IAction';
import UserService from './UserService';
import UserAction from './UserAction';
import put from 'redux-saga/effects';
import UserModel from './models/UserModel';

export default class UserSaga 

    public static* loadUser(action: IAction<void> = null) 
        const userModel: UserModel = yield UserService.loadUser();

        yield put(UserAction.loadUserSuccess(userModel));
    


UserService.ts

import HttpUtility from '../../utilities/HttpUtility';
import AxiosResponse from 'axios';
import UserModel from './models/UserModel';
import RandomUserResponseModel from './models/RandomUserResponseModel';
import environment from 'environment';

export default class UserService 

    private static _http: HttpUtility = new HttpUtility();

    public static async loadUser(): Promise<UserModel> 
        const endpoint: string = `$environment.endpointUrl.randomuser?inc=picture,name,email,phone,id,dob`;
        const response: AxiosResponse = await UserService._http.get(endpoint);
        const randomUser = new RandomUserResponseModel(response.data);

        return randomUser.results[0];
    


https://github.com/codeBelt/typescript-hapi-react-hot-loader-example

【讨论】:

【参考方案17】:

我是ts-redux-actions-reducer-factory 的作者,我会向您介绍这个作为其他解决方案之上的另一种解决方案。 这个包通过动作创建者或手动定义的动作类型推断动作,并且 - 这是新的 - 状态。因此,每个 reducer 都知道先前 reducer 的返回类型,因此表示一个可能的扩展状态,必须在最后初始化,除非在开始时完成。它的使用有点特殊,但可以简化打字。

但根据您的问题,这里有一个完整的可能解决方案:

import  createAction  from "redux-actions";
import  StateType  from "typesafe-actions";
import  ReducerFactory  from "../../src";

// Type constants
const aType = "a";
const bType = "b";

// Container a
interface IActionA 
    a: string;


// Container b
interface IActionB 
    b: string;


// You define the action creators:
// - you want to be able to reduce "a"
const createAAction = createAction<IActionA, string>(aType, (a) => ( a ));
// - you also want to be able to reduce "b"
const createBAction = createAction<IActionB, string>(aType, (b) => ( b ));

/*
 * Now comes a neat reducer factory into the game and we
 * keep a reference to the factory for example purposes
 */
const factory = ReducerFactory
    .create()
    /*
     * We need to take care about other following reducers, so we normally want to include the state
     * by adding "...state", otherwise only property "a" would survive after reducing "a".
     */
    .addReducer(createAAction, (state, action) => (
        ...state,
        ...action.payload!,
    ))
    /*
     * By implementation you are forced to initialize "a", because we
     * now know about the property "a" by previous defined reducer.
     */
    .addReducer(createBAction, (state, action) => (
        ...state,
        ...action.payload!,
    ))
    /**
     * Now we have to call `acceptUnknownState` and are forced to initialize the reducer state.
     */
    .acceptUnknownState(
        a: "I am A by default!",
        b: "I am B by default!",
    );

// At the very end, we want the reducer.
const reducer = factory.toReducer();

const initialState = factory.initialKnownState;
//  a: "I am A by default!", b: "I am B by default!" 

const resultFromA = reducer(initialState, createAAction("I am A!"));
//  a: "I am A!", b: "I am B by default!" 

const resultFromB = reducer(resultFromA, createBAction("I am B!"));
//  a: "I am A!", b: "I am B!" 

// And when you need the new derived type, you can get it with a module like @typesafe-actions
type DerivedType = StateType<typeof reducer>;

// Everything is type-safe. :)
const derivedState: DerivedType = initialState;

【讨论】:

【参考方案18】:

这里是你如何使用redux-fluent

【讨论】:

【参考方案19】:

我建议使用AnyAction,因为根据 Redux 常见问题解答,每个 reducer 都会在每个操作上运行。这就是为什么如果操作不是其中一种类型,我们最终只会返回输入状态。否则,我们的 reducer 中的开关将永远不会有默认情况。

见:https://redux.js.org/faq/performance#won-t-calling-all-my-reducers-for-each-action-be-slow

因此,可以这样做:

import  AnyAction  from 'redux';

function myReducer(state, action: AnyAction) 
  // ...

【讨论】:

【参考方案20】:

我可能会迟到,但 enum 的 FTW!

enum ActionTypes 
  A = 'ANYTHING_HERE_A',
  B = 'ANYTHING_HERE_B',


interface IActionA 
  type: ActionTypes.A;
  a: string;


interface IActionB 
  type: ActionTypes.B;
  b: string;


type IAction = IActionA | IActionB

const reducer = (action: IAction) 
  switch (action.type) 
    case ActionTypes.A:
      return console.info('action a: ', action.a)

    case ActionTypes.B:
      return console.info('action b: ', action.b)
    

【讨论】:

这是一种干净的方式 我是 redux 的初学者,但我想知道为什么我们不能将 ActionType 定义为一个对象?考虑到它在 switch 中的用法,我们可以将它定义为一个对象,因为ActionTypes.A 的意思是一样的,不是吗? 我可能会迟到,但枚举的 FTW! enum ActionTypes A: 'ANYTHING_HERE_A', B: 'ANYTHING_HERE_B', 应该是我可能迟到了,但 enum 的 FTW!枚举 ActionTypes A = 'ANYTHING_HERE_A', B = 'ANYTHING_HERE_B',

以上是关于如何在 TypeScript 中键入 Redux 操作和 Redux reducer?的主要内容,如果未能解决你的问题,请参考以下文章

typescript TypeScript中键入的Redux操作和Reducer的问题

强烈键入 react-redux 与 typescript 连接

Typescript 无状态 HOC react-redux 注入道具打字

如何在打字稿中使用中间件键入 redux thunk

如何在 react-redux 中正确键入映射到道具的动作创建者

如何键入 Redux thunk 动作创建者以返回承诺