How to type Redux actions and Redux reducers in TypeScript?

后端 未结 20 1999
旧时难觅i
旧时难觅i 2020-12-13 03:39

What is the best way to cast the action parameter in a redux reducer with typescript? There will be multiple action interfaces that can occur that all extend a

20条回答
  •  天涯浪人
    2020-12-13 04:36

    I am the author of ts-redux-actions-reducer-factory and would present you this as an another solution on top of the others. This package infers the action by action creator or by manually defined action type and - that's new - the state. So each reducer takes aware of the return type of previous reducers and represents therefore a possible extended state that must be initialized at the end, unless done at beginning. It is kind of special in its use, but can simplify typings.

    But here a complete possible solution on base of your problem:

    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(aType, (a) => ({ a }));
    // - you also want to be able to reduce "b"
    const createBAction = createAction(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;
    
    // Everything is type-safe. :)
    const derivedState: DerivedType = initialState;
    

提交回复
热议问题