web-dev-qa-db-ja.com

TypeScriptでReduxアクションとReduxレデューサーを入力する方法は?

Typescriptでredux reduceractionパラメーターをキャストする最良の方法は何ですか?発生する可能性のある複数のアクションインターフェイスがあり、それらはすべてプロパティタイプを持つベースインターフェイスを拡張します。拡張アクションインターフェースには、アクションインターフェース間ですべて異なるプロパティを追加できます。以下に例を示します。

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         
    }
}

問題は、actionIActionAIActionBの両方にアクセスできる型としてキャストする必要があるため、リデューサーがaction.aおよびaction.aエラーをスローせずに。

この問題を回避する方法はいくつかあります。

  1. actionanyにキャストします。
  2. オプションのインターフェイスメンバーを使用します。

例:

interface IAction {
    type: string
    a?: string
    b?: string
}
  1. アクションタイプごとに異なるレデューサーを使用します。

タイプスクリプトでアクション/リデューサーを整理する最良の方法は何ですか?前もって感謝します!

28
Roman Klimenko

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)          
    }
}
32
Sven Efftinge

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;
}
12
Elmer

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');

ユーザー定義のタイプガードを使用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'}))

いくつかの同等の優れたソリューションが提示されているため、コメントスレッド全体を読んで他のオプションを見つけることをお勧めします。

8
Jussi K

比較的単純なレデューサーの場合は、おそらくタイプガードを使用できます。

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
Vadim Macagon

ここに私がそれをする方法があります:

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

5
codeBelt

問題の2つの部分

上記のいくつかのコメントは、コンセプト/機能「actionCreator」に言及しています-問題の最初の部分を解決する redux-actions パッケージ(および対応する TypeScript定義 )を見てください。アクションペイロードタイプを指定するTypeScriptタイプ情報を持つアクションクリエーター関数。

問題の2番目の部分は、定型コードなしでタイプセーフな方法でレデューサー関数を単一のレデューサーに結合することです(TypeScriptについて質問されたとき)。

ソリューション

redux-actionsredux-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型の注釈を記述する必要はありませんが、すべての型が推論されます(したがって、これはnoImplicitAnyTypeScriptコンパイラオプション でも動作します)

_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の著者です。

4
atsu85

TypeScript v2では、 タイプガード付きのユニオン型 およびRedux独自の Action および Reducer を使用する必要のない型を使用して、これを非常に簡単に行うことができます。追加のサードパーティライブラリ、およびすべてのアクションに共通の形状を強制することなし(たとえば、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が提案するものですが、さらにリデューサーの戻り値の型をチェックします。

3
James Conkling

次のことができます

IActionAまたはIActionBのいずれかのみが必要な場合、少なくとも型を制限し、関数を次のように定義できます。

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

さて、問題は、それがどのタイプであるかをまだ確認する必要があるということです。 typeプロパティを完全に追加できますが、それをどこかに設定する必要があり、インターフェイスはオブジェクト構造のオーバーレイにすぎません。アクションクラスを作成し、アクターにタイプを設定させることができます。

それ以外の場合は、別の方法でオブジェクトを確認する必要があります。あなたの場合、hasOwnPropertyを使用し、それに応じて、正しい型にキャストできます。

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

    return (<IActionB>action).b;
}

これは、JavaScriptにコンパイルしても機能します。

2
MichaC

すべてのアクションのインターフェイスを記述することなく暗黙の型安全性を得るには、このアプローチを使用できます(ここからの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
}
2
huesforalice

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 }));
2
Max Desiatov

@Jussi_Kが参照するソリューションは、汎用的であるためニースです。

ただし、次の5つの点で、より良い方法を見つけました。

  1. これは、「ペイロード」オブジェクトではなく、より短いアクションオブジェクトにアクションプロパティを直接持っています。 (「ペイロード」プロップを好む場合は、コンストラクターの余分な行のコメントを外してください)
  2. 不安定なaction.Is(Type)の代わりに、単純なisType(action, createType)を使用してレデューサーで型チェックできます。
  3. ロジックは、_type Action<TPayload>_、_interface IActionCreator<P>_、function actionCreator<P>()function isType<P>()のように1つのクラスに含まれています。
  4. 「アクションクリエーター」とインターフェースの代わりに、シンプルで実際のクラスを使用します。私の意見では、これらはより読みやすく拡張可能です。新しいアクションタイプを作成するには、_class MyAction extends Action<{myProp}> {}_を実行します。
  5. typeをクラス/コンストラクター名として計算するだけで、クラス名とtypeプロパティ間の一貫性を保証します。 helloWorldAction関数と_HELLO_WORLD_ "マジックストリング"の両方を持つ他のソリューションとは異なり、これはDRY原則に準拠しています。

とにかく、この代替セットアップを実装するには:

まず、この汎用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}> {}
_

次に、リデューサー関数で使用するには:

_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;
}
_

もう1つのオプションは、Action.Is()メソッドを_Object.prototype_を使用してグローバル_Object.defineProperty_に追加することです。これは私が現在行っていることです-プロトタイプを汚染するため、ほとんどの人はこれを嫌います。

編集2

とにかく動作するという事実にもかかわらず、Reduxは「アクションはプレーンオブジェクトでなければならない。非同期アクションにはカスタムミドルウェアを使用する」と文句を言います。

これを修正するには、次のいずれかを実行できます。

  1. ReduxのisPlainObject()チェックを削除します。
  2. 上記の編集で変更の1つを行い、さらにActionクラスのコンストラクターの最後にこの行を追加します(インスタンスとクラス間のランタイムリンクを削除します)
_Object.setPrototypeOf(this, Object.getPrototypeOf({}));
_
2
Venryx

私は 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;
2
teroneko
1
Hitmands

次のようなアクションを定義できます。

// 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

1
eriknyk

最近、私はこのアプローチを使用しています:

_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 /)

1
Elmer

投稿したとおりに実装を修正する必要がある場合、これは、以下に示すように、それぞれ型アサーションを使用して修正し、動作させる方法です。

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

1
eriknyk

公平を期すために、アクションを入力する多くの方法がありますが、私は this one が非常に単純であり、同様に可能なボイラープレートも少なくなっています(既にこのトピックで説明しています)。

このアプローチは、アクションの「ペイロード」と呼ばれるキーを入力しようとします。

このサンプルを確認してください

1
PRAISER

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) {
  // ...
}
0
CMCDragonkai