如何在 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
需要被转换为可以访问IActionA
和IActionB
的类型,因此reducer 可以同时使用action.a
和action.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】:
你可以做以下事情
如果您只期望IActionA
或IActionB
之一,您可以至少限制类型并将您的函数定义为
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<P>
定义您的动作和动作创建者:
export const helloWorldAction = actionCreator<foo: string>('HELLO_WORLD');
export const otherAction = actionCreator<a: number, b: string>('OTHER_ACTION');
在reducer中使用用户定义的类型保护isType<P>
:
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"
。为方便起见,我倾向于将它们组合在一个对象中。对于大型应用程序,我倾向于按模块和域对它们进行分组,例如 AccountDataActions
和 AccountUiActions
。这样我在导入动作时就可以少打字了。为了进一步方便,我将动作类型常量和动作创建者放在同一个对象中。
这仍然是一个好方法,但我换了另一种方法,我也把它放在这里作为这个问题的答案!【参考方案7】:
@Jussi_K 引用的解决方案很好,因为它是通用的。
但是,我找到了一种我更喜欢的方式,有五点:
-
它直接在动作对象上具有动作属性,而不是在更短的“有效负载”对象中。 (不过如果你更喜欢“payload”属性,只需在构造函数中取消注释多余的行)
可以使用简单的
action.Is(Type)
在reducer 中进行类型检查,而不是笨拙的isType(action, createType)
。
逻辑包含在单个类中,而不是分散在type Action<TPayload>
、interface IActionCreator<P>
、function actionCreator<P>()
、function isType<P>()
中。
它使用简单、真实的类而不是“动作创建器”和接口,在我看来,它们更具可读性和可扩展性。要创建新的 Action 类型,只需执行 class MyAction extends Action<myProp>
。
它确保类名和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.defineProperty
将Action.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);
确保Action
s 都是普通对象。现在您可以进行如下操作: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 注入道具打字