Как ввести действия Redux и редукторы Redux в TypeScript?
Каков наилучший способ приведения параметра action
в редукторном редукторе с машинописью? Будет несколько интерфейсов действий, которые могут расширять базовый интерфейс с типом свойства. Расширенные интерфейсы действий могут иметь больше свойств, которые различаются между интерфейсами действий. Вот пример ниже:
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
чтобы редуктор мог использовать как action.a
и action.a
не action.a
ошибку.
У меня есть несколько идей, как обойти эту проблему:
- Cast
action
на any
. - Используйте дополнительные элементы интерфейса.
пример:
interface IAction {
type: string
a?: string
b?: string
}
- Используйте разные редукторы для каждого типа действия.
Каков наилучший способ организовать Action/Reducers в машинописи? Заранее спасибо!
Ответы
Ответ 1
С 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)
}
}
Ответ 2
У меня есть интерфейс 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;
}
Ответ 3
Вот умное решение от пользователя Github aikoven из https://github.com/reactjs/redux/issues/992#issuecomment-191152574:
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');
Используйте определенный пользователем тип guard 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
Для относительно простого редуктора вы, вероятно, можете просто использовать защитные устройства типа:
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);
}
}
Ответ 5
Вот как я это делаю:
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
Ответ 6
Две части проблемы
В нескольких вышеупомянутых комментариях упоминается концепция/функция 'actionCreator´ - взгляните на пакет redux-actions (и соответствующие определения TypeScript), который решает первую часть проблемы: создание функций создателя действий, которые имеют информацию о типе TypeScript, определяющую тип полезной нагрузки действия.
Вторая часть проблемы заключается в объединении функций редуктора в единый редуктор без шаблонного кода и безопасным для типов способом (как был задан вопрос о 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) Создать редуктор с начальным состоянием и функциями редуктора для всех связанных действий:
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();
Как видно из комментариев, вам не нужно писать какие-либо аннотации типов TypeScript, но все типы выводятся (так что это работает даже с noImplicitAny
компилятора noImplicitAny
TypeScript)
Если вы используете действия из некоторого фреймворка, который не предоставляет создателей действий с 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, который я сегодня открыл.
Ответ 7
вы можете сделать следующее:
если вы ожидаете только одного из 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.
Ответ 8
Решение @Jussi_K, на которое ссылается, хорошо, потому что оно является общим.
Однако, я нашел способ, который мне нравится лучше, по пяти пунктам:
- Он имеет свойства действия непосредственно на объекте действия, а не в объекте "полезной нагрузки", который короче. (хотя, если вы предпочитаете "полезную нагрузку", просто раскомментируйте дополнительную строку в конструкторе)
- Он может быть проверен типом в редукторах с помощью простого
action.Is(Type)
, а не clunkier isType(action, createType)
.
- Логика, содержащаяся в одном классе, вместо разметки amonst
type Action<TPayload>
, interface IActionCreator<P>
, function actionCreator<P>()
, function isType<P>()
.
- Он использует простые, реальные классы вместо "создателей действий" и интерфейсов, которые, на мой взгляд, более читабельны и расширяемы. Чтобы создать новый тип действия, просто
class MyAction extends Action<{myProp}> {}
.
- Он обеспечивает согласованность между свойством class-name и
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 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}> {}
Затем для использования в редукторной функции:
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, каждый из этих шагов безопасен по типу.
ИЗМЕНИТЬ
Если вы хотите, чтобы система была совместима с анонимными объектами действия (например, из устаревшего кода или десериализованного состояния), вы можете вместо этого использовать эту статическую функцию в своих редукторах:
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;
}
Другой вариант - добавить метод Action.Is()
на глобальный Object.prototype
с помощью Object.defineProperty
. Это то, что я сейчас делаю, хотя большинству людей это не нравится, поскольку он загрязняет прототип.
EDIT 2
Несмотря на то, что он все равно будет работать, Redux жалуется, что "Действия должны быть обычными объектами. Используйте специальное промежуточное ПО для асинхронных действий".
Чтобы исправить это, вы можете:
- Удалите проверки
isPlainObject()
в Редуксе.
- Выполните одну из модификаций моего редактирования выше, добавьте эту строку в конец конструктора класса
Action
: (он удаляет связь между экземпляром и классом)
Object.setPrototypeOf(this, Object.getPrototypeOf({}));
Ответ 9
Чтобы получить неявные типы безопасности без необходимости писать интерфейсы для каждого действия, вы можете использовать этот подход (вдохновленный функцией 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
}
Ответ 10
С Typescript v2 вы можете сделать это довольно легко, используя типы объединения со средствами защиты типов и собственными типами действий и редукторов Redux без необходимости использования дополнительных сторонних библиотек и без применения общей формы для всех действий (например, с помощью payload
),
Таким образом, ваши действия будут правильно напечатаны в ваших предложениях 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;
}
В основном это то, что предлагает @Sven Efftinge, дополнительно проверяя тип возвращаемого значения редуктора.
Ответ 11
Я являюсь автором ts-redux-actions-reducer-factory и представляю вам это как еще одно решение поверх других. Этот пакет выводит действие создателем действия или определенным вручную типом действия и - этим новым - состоянием. Таким образом, каждый редуктор принимает во внимание тип возврата предыдущих редукторов и, следовательно, представляет возможное расширенное состояние, которое должно быть инициализировано в конце, если не сделано в начале. Это своего рода особенное в использовании, но может упростить ввод текста.
Но вот полное возможное решение на основе вашей проблемы:
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;
Ответ 12
Существуют библиотеки, которые объединяют большую часть кода, упомянутого в других ответах: aikoven/typescript-fsa и dphilipson/typescript-fsa-redurs.
С этими библиотеками все ваши действия и код редуктора статически типизируются и читаются:
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 }));
Ответ 13
вы можете определить свое действие примерно так:
// 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
}
}
и так, вы можете определить свой редуктор следующим образом:
//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
Ответ 14
Если вам нужно исправить вашу реализацию точно так же, как вы разместили, вот как ее исправить и заставить ее работать, используя утверждения типа, соответственно, как показано ниже:
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
Ответ 15
Чтобы быть справедливым, существует множество способов ввода действий, но я нахожу этот очень прямым и имеет менее возможный шаблон (а) уже обсуждался в этом разделе).
Этот подход пытается ввести ключ, называемый "полезной нагрузкой" действий.
Проверьте этот пример
Ответ 16
В последнее время я использовал этот подход:
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
- все простые объекты. Теперь вы можете делать такие действия: const action = new BeginBusyAction()
. (yay\o/)
Ответ 17
Вот как вы можете сделать это с помощью redux-fluent
:
![enter image description here]()
Ответ 18
Я предлагаю использовать AnyAction
потому что, согласно Redux FAQ, каждый редуктор AnyAction
при каждом действии. Вот почему мы в конечном итоге просто возвращаем состояние ввода, если действие не относится к одному из типов. Иначе у нас никогда не было бы случая по умолчанию в наших коммутаторах в наших редукторах.
Смотрите: 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) {
// ...
}
Ответ 19
Вот такой подход, который я предпринял для этой проблемы:
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)
}
}
Я буду первым, кто признал бы здесь некоторое уродство и взлома этого подхода, но на самом деле я на самом деле нашел его на практике. В частности, я нахожу, что он упрощает чтение и обслуживание кода, поскольку цель действия заключается в имени, а также облегчает поиск.