Hoe Redux-acties en Redux-verkleiners in TypeScript typen?

Wat is de beste manier om de parameter Actionin een redux te casten verloopstukmet typoscript? Er kunnen meerdere actie-interfaces optreden die allemaal een basisinterface uitbreiden met een eigenschapstype. De uitgebreide actie-interfaces kunnen meer eigenschappen hebben die allemaal verschillen tussen de actie-interfaces. Hier is een voorbeeld hieronder:

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

Het probleem is dat Actionmoet worden gecast als een type dat toegang heeft tot zowel IActionAals IActionB, zodat het verloopstuk beide kan gebruiken action.aen action.azonder een foutmelding te geven.

Ik heb verschillende ideeën om dit probleem te omzeilen:

  1. Creëer Actionnaar any.
  2. Gebruik optionele interfaceleden.

voorbeeld:

interface IAction {
    type: string
    a?: string
    b?: string
}
  1. Gebruik verschillende verloopstukken voor elk actietype.

Wat is de beste manier om Action/Reducers in typoscript te ordenen? Bij voorbaat dank!


Antwoord 1, autoriteit 100%

Met Tagged Union Typesvan Typescript 2 kunt u doe het volgende

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

Antwoord 2, autoriteit 32%

Ik heb een Action-interface

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

Ik heb een functie createAction:

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

Ik heb een constante van het actietype:

const IncreaseBusyCountActionType = "IncreaseBusyCount";

En ik heb een interface voor de actie (bekijk het coole gebruik van typeof):

type IncreaseBusyCountAction = Action<typeof IncreaseBusyCountActionType, void>;

Ik heb een functie voor het maken van acties:

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

Nu ziet mijn verloopstuk er ongeveer zo uit:

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

En ik heb een verloopfunctie per actie:

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

Antwoord 3, autoriteit 21%

Hier is een slimme oplossing van Github-gebruiker aikovenvan 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;
}

Gebruik actionCreator<P>om uw acties en actiemakers te definiëren:

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

Gebruik de door de gebruiker gedefinieerde type guard isType<P>in het verloop:

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)) {
        ...

En om een ​​actie te verzenden:

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

Ik raad aan om de hele commentaarthread te lezen om andere opties te vinden, aangezien daar verschillende even goede oplossingen worden gepresenteerd.


Antwoord 4, autoriteit 17%

Dit is hoe ik het doe:

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- reageer-hot-loader-voorbeeld


Antwoord 5, autoriteit 16%

Misschien ben ik te laat voor de dans, maar enum‘s 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)
    }
}

Antwoord 6, autoriteit 10%

Twee delen van het probleem

Verschillende opmerkingen hierboven hebben het concept/functie `actionCreator´ genoemd –
bekijk het redux-actions-pakket
(en bijbehorende TypeScript-definities),
dat het eerste deel van het probleem oplost:
het creëren van functies voor het maken van acties die TypeScript-type-informatie hebben die het type payload van de actie specificeert.

Het tweede deel van het probleem is het combineren van reduceerfuncties in één reducer zonder standaardcode en op een typeveilige manier
(omdat de vraag werd gesteld over TypeScript).

De oplossing

Combineren
redux-actions
en redux-actions-ts-reducer-pakketten:

1) Maak actionCreator-functies die kunnen worden gebruikt voor het maken van een actie met het gewenste type en lading bij het verzenden van de actie:

import { createAction } from 'redux-actions';
const negate = createAction('NEGATE'); // action without payload
const add = createAction<number>('ADD'); // action with payload type `number`

2) Maak een verloopstuk met initiële status en verloopfuncties voor alle gerelateerde acties:

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

Zoals u kunt zien in de opmerkingen, hoeft u geen TypeScript-annotaties te schrijven, maar alle typen worden afgeleid
(dus dit werkt zelfs met noImplicitAnyTypeScript-compileroptie)

Als je acties gebruikt uit een raamwerk dat de actiemakers van redux-actionniet blootlegt (en je wilt ze ook niet zelf maken)
of oudere code hebt die strings-constanten gebruikt voor actietypes, je zou er ook reductiemiddelen voor kunnen toevoegen:

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

het is dus gemakkelijk om aan de slag te gaan zonder uw codebase te herstructureren.

Verzendacties

Je kunt acties zelfs zonder reduxals volgt verzenden:

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

maar het verzenden van actie met reduxis eenvoudiger – gebruik de functie dispatch(...)zoals gewoonlijk:

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

Bekentenis: ik ben de auteur van redux-actions-ts-reducer die ik vandaag open source heb gemaakt.


Antwoord 7, autoriteit 8%

Voor een relatief eenvoudig verloopstuk zou je waarschijnlijk gewoon typebescherming kunnen gebruiken:

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

Antwoord 8, autoriteit 5%

Met Typescript v2 kun je dit vrij eenvoudig doen met union-types met type guardsen Redux’s eigen Actieen Verloopstukkentypen zonder dat ze extra bibliotheken van derden hoeven te gebruiken en zonder een gemeenschappelijke vorm voor alle acties af te dwingen (bijv. via payload).

Op deze manier worden je acties correct getypt in de catch-clausules van je reducer, net als de geretourneerde status.

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

Dingen is eigenlijk wat @Sven Efftinge suggereert, terwijl ze bovendien het retourtype van het verloopstuk controleert.


Antwoord 9, autoriteit 5%

Ik raad aan om AnyActionte gebruiken omdat volgens Redux FAQ, elke reducer bij elke actie wordt uitgevoerd. Dit is de reden waarom we uiteindelijk de invoerstatus retourneren als de actie niet een van de typen is. Anders zouden we nooit een standaardbehuizing hebben in onze schakelaars in onze verloopstukken.

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

Dus daarom is het prima om gewoon te doen:

import { AnyAction } from 'redux';
function myReducer(state, action: AnyAction) {
  // ...
}

Antwoord 10, autoriteit 3%

je zou de volgende dingen kunnen doen

als u alleen een van IActionAof IActionBverwacht, kunt u het type op zijn minst beperken en uw functie definiëren als

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

Het punt is dat je nog steeds moet uitzoeken welk type het is. U kunt een eigenschap typevolledig toevoegen, maar dan moet u deze ergens instellen, en interfaces zijn slechts overlays over objectstructuren. U kunt actieklassen maken en de ctor het type laten instellen.

Anders moet u het object door iets anders verifiëren.
In jouw geval zou je hasOwnPropertykunnen gebruiken en afhankelijk daarvan naar het juiste type casten:

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

Dit zou nog steeds werken wanneer gecompileerd naar JavaScript.


Antwoord 11, autoriteit 3%

De oplossing waarnaar @Jussi_K verwijst is leuk omdat het generiek is.

Ik heb echter een manier gevonden die ik beter vind, op vijf punten:

  1. Het heeft de actie-eigenschappen direct op het actieobject, in plaats van in een “payload” -object — dat korter is. (maar als je de voorkeur geeft aan de “payload”-prop, verwijder dan gewoon de extra regel in de constructor)
  2. Het kan typegecontroleerd worden in verloopstukken met een simpele action.Is(Type), in plaats van het onhandigere isType(action, createType).
  3. De logica zit in een enkele klasse, in plaats van verspreid over type Action<TPayload>, interface IActionCreator<P>, function actionCreator<P>(), function isType<P>().
  4. Het gebruikt eenvoudige, echte klassen in plaats van “action creators” en interfaces, die naar mijn mening beter leesbaar en uitbreidbaar zijn. Om een ​​nieuw actietype aan te maken, doet u gewoon class MyAction extends Action<{myProp}> {}.
  5. Het zorgt voor consistentie tussen de class-name en typeeigenschap, door gewoon typete berekenen als de class/constructornaam. Dit voldoet aan het DRY-principe, in tegenstelling tot de andere oplossing die zowel een helloWorldAction-functie als een HELLO_WORLD“magische string” heeft.

Hoe dan ook, om deze alternatieve configuratie te implementeren:

Kopieer eerst deze algemene actieklasse:

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

Maak vervolgens uw afgeleide actieklassen:

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

Vervolgens, om in een verloopfunctie te gebruiken:

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

Als u een actie wilt maken en verzenden, doet u het volgende:

dispatch(new IncreaseNumberAction({amount: 10}));

Net als bij de oplossing van @Jussi_K is elk van deze stappen typeveilig.

BEWERKEN

Als u wilt dat het systeem compatibel is met anonieme actieobjecten (bijv. van oude code of gedeserialiseerde staat), kunt u in plaats daarvan deze statische functie in uw verloopstukken gebruiken:

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

En gebruik het zo:

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

De andere optie is om de methode Action.Is()toe te voegen aan het globale Object.prototypemet behulp van Object.defineProperty. Dit is wat ik momenteel doe — hoewel de meeste mensen dit niet leuk vinden omdat het het prototype vervuilt.

BEWERK 2

Ondanks het feit dat het toch zou werken, klaagt Redux dat “Acties gewone objecten moeten zijn. Gebruik aangepaste middleware voor asynchrone acties.”.

Om dit op te lossen, kunt u:

  1. Verwijder de isPlainObject()-controles in Redux.
  2. Voer een van de wijzigingen in mijn bewerking hierboven uit en voeg deze regel toe aan het einde van de constructor van de Action-klasse: (het verwijdert de runtime-link tussen instantie en klasse)
Object.setPrototypeOf(this, Object.getPrototypeOf({}));

Antwoord 12, autoriteit 3%

Om impliciete typeveiligheid te krijgen zonder interfaces te hoeven schrijven voor elke actie, kun je deze aanpak gebruiken (geïnspireerd door de returntypeof-functie van hier: 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
}

Antwoord 13, autoriteit 3%

Er zijn bibliotheken die de meeste code bundelen die in andere antwoorden wordt genoemd: aikoven/typescript-fsaen dphilipson/typescript-fsa-reducers.

Met deze bibliotheken is al uw code voor acties en reducers statisch getypt en leesbaar:

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

Antwoord 14, autoriteit 2%

u kunt uw actie als volgt definiëren:

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

en dus kunt u uw verloopstuk als volgt definiëren:

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

Volledige officiële documenten: https://github.com/Microsoft /TypeScript-React-Starter#adding-a-reducer


Antwoord 15, autoriteit 2%

Als u uw implementatie precies zo wilt repareren zoals u hebt gepost, is dit de manier om het te repareren en werkend te krijgen met respectievelijk type assertions , zoals ik in het volgende laat zien:

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

Je kunt meer te weten komen in de sectie “Typebeschermingen en differentiërende typen”
van de officiële documentatie: https://www.typescriptlang.org/docs/handbook /advanced-types.html


Antwoord 16, autoriteit 2%

Om eerlijk te zijn zijn er veel manieren om acties te typen, maar ik vind dezeheel eenvoudig en heeft ook de minder mogelijke boilerplate (reeds besproken in dit onderwerp).

Deze aanpak probeert de sleutel genaamd “payload” van acties te typen.

Bekijk dit voorbeeld


Antwoord 17, autoriteit 2%

De laatste tijd gebruik ik deze aanpak:

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,
        });
    }
}

Dit hier:

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

zorgt ervoor dat de Actions allemaal gewone objecten zijn. Nu kunt u acties als volgt uitvoeren: const action = new BeginBusyAction(). (yay \o/)


Antwoord 18, autoriteit 2%

Ik ben de auteur van ts-redux-actions-reducer-factoryen zou u dit presenteren als een andere oplossing bovenop de andere.
Dit pakket leidt de actie af op de maker van de actie of op het handmatig gedefinieerde actietype en – dat is nieuw – de status. Dus elk verloopstuk houdt rekening met het retourtype van eerdere verloopstukken en vertegenwoordigt daarom een ​​mogelijke uitgebreide status die aan het einde moet worden geïnitialiseerd, tenzij dit aan het begin wordt gedaan. Het is een beetje speciaal in het gebruik, maar kan typen vereenvoudigen.

Maar hier een complete mogelijkeoplossing op basis van uw probleem:

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;

Antwoord 19, autoriteit 2%

Hier leest u hoe u het kunt doen met redux-fluent:

voer hier de afbeeldingsbeschrijving in
voer hier de afbeeldingsbeschrijving in


Antwoord 20

Dit is de benadering die ik voor dit probleem heb gekozen:

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

Ik zal de eerste zijn om toe te geven dat deze aanpak een zekere lelijkheid en slordigheid heeft, maar ik heb in de praktijk ervaren dat het redelijk goed werkt. Ik vind met name dat het de code gemakkelijk te lezen en te onderhouden maakt, omdat de bedoeling van de actie in de naam zit en dat maakt het ook gemakkelijk om naar te zoeken.

Other episodes