NgRx createReducer() 和 on() 报错

piy*_*ain 7 javascript ngrx angular ngrx-store

我是新的。到 NgRx。

当我尝试使用创建减速器createReducer()时出现错误Expected 4 arguments, but got 2. 当我尝试将第 3 和第 4 个参数作为空值传递时,出现错误

Argument of type '(state: any, { updatedValue }: any) => any' is not assignable to parameter of type 'readonly ActionCreator<string, FunctionWithParametersType<any[], object>>[]'.
  Type '(state: any, { updatedValue }: any) => any' is missing the following properties from type 'readonly ActionCreator<string, FunctionWithParametersType<any[], object>>[]': concat, join, slice, indexOf, and 15 more.ts(2345)
Run Code Online (Sandbox Code Playgroud)

减速机代码

import { createReducer, on, State, Action } from '@ngrx/store';

import { Ingredient } from '../../shared/ingredient.model';
import * as ShoppingListAction from './shopping-list.action';

export const initialState = {
  ingredients: [
    new Ingredient('Apples', 5),
    new Ingredient('Tomatoes', 10),
  ]
}

export const shoppingListReducer = createReducer(
  initialState,
  on(
    'ADD_INGREDIENT',
    (state: any, { updatedValue }: any) => ({ ...state, prop: updatedValue }),
    null,
    null
  )
);
Run Code Online (Sandbox Code Playgroud)

动作代码

import { createAction, props } from '@ngrx/store';
import { Ingredient } from '../../shared/ingredient.model';


export const ADD_INGREDIENT = 'ADD_INGREDIENT';

export const AddIngredient = createAction(
  ADD_INGREDIENT,
  props<Ingredient>()
);
Run Code Online (Sandbox Code Playgroud)

app.module.ts

import { SharedModule } from './shared/shared.module';
import { CoreModule } from './core.module';
import { LoggingService } from './logging.service';

// Store
import { shoppingListReducer } from './shopping-list/store/shopping-list.reducer';

@NgModule({
  declarations: [AppComponent, HeaderComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule,
    SharedModule,
    CoreModule,
    StoreModule.forRoot({ 'slReduce': shoppingListReducer })
  ],
  bootstrap: [AppComponent],
  // providers: [LoggingService]
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

包.json

"dependencies": {
    "@angular/animations": "^11.2.1",
    "@angular/common": "^11.2.1",
    "@angular/compiler": "^11.2.1",
    "@angular/core": "^11.2.1",
    "@angular/forms": "^11.2.1",
    "@angular/platform-browser": "^11.2.1",
    "@angular/platform-browser-dynamic": "^11.2.1",
    "@angular/router": "^11.2.1",
    "@ngrx/store": "^11.0.1",
    "bootstrap": "3.3.7",
    "core-js": "^3.1.2",
    "rxjs": "^6.0.0",
    "tslib": "^2.0.0",
    "zone.js": "~0.11.3"
  }
Run Code Online (Sandbox Code Playgroud)

bur*_*unt 1

Your action and your reducer look strange. According to the docs it should be something like:

export interface MyState {
    ingredients: Array<Ingredient>;
}

export const initialState: MyState = {
  ingredients: [
    new Ingredient('Apples', 5),
    new Ingredient('Tomatoes', 10),
  ]
}

// see  https://ngrx.io/guide/store/actions
// note: the prop has a 'name: Type', not just 'Type'
export const AddIngredient = createAction(
    ADD_INGREDIENT,
    props<{ingredient: Ingredient}>()
);

// verbose func to show difference
export const shoppingListReducer = createReducer(
  initialState,
  on(
    AddIngredient, // the action, not the action.type
    (state, { ingredient }) => {
      let ingredients = Array.from(state.ingredients);
      ingredients.push(ingredient);
      return { ingredients };
    }
  )
);
Run Code Online (Sandbox Code Playgroud)

Or if you prefer the shorthand:

// equal shorthand function
export const shoppingListReducer = createReducer(
  initialState,
  on(
    AddIngredient, // the action, not the action.type
    (state, { ingredient }) => ({ingredients: [...state.ingredients, ingredient]})
  )
);
Run Code Online (Sandbox Code Playgroud)

The main points are:

(state: any, { updatedValue }: any) => ({ ...state, prop: updatedValue })
Run Code Online (Sandbox Code Playgroud)
  • would require an action with a prop named updatedValue (compare to action, prop now named ingredient)
  • returns an object assembled from the current state and a field called prop (*)

(*) If this worked, it would lead to a state like

interface MyAccidentalState {
  ingredients: Array<Ingredient>;
  prop: Ingredient;
}
Run Code Online (Sandbox Code Playgroud)

... but it does not work, as your action has no named prop 'updatedValue' and prop: updatedValue was not {prop: updatedValue}.

And because it has no named prop 'updatedValue', the compiler explodes at the object destructuring part of

(state: any, { updatedValue }: any) => ...

And the any-typing in (state: any, { updatedValue }: any) => ... may deny the compiler some knowledge which was already there: updatedValue was intended to be an Ingredient, and state is (now) a MyState.

Side-note:

Keeping the state of a collection is something where ngrx/entity might be useful - but your way is valid, too.