— 1 min read
本文进度
- 完成 NgRx 基本的文档阅读
- 将对应的代码整理到 Nx-Todo-App > NgRx-Practice 中
- 比对 Redux 系列中的库
- 草稿
- 讲人话, 发文章, 西内!
是的, 我又发现了一个很好玩的东西! Redux + Redux-Saga + Connected-React-Router + RxJS + Angular = NgRx!
Flux 式的数据流, 同样是 action >>> reducer >>> Immutable State, 但又有很多差异, 这些差异主要集中在写法上, 比如内置了 createAction createSelector(功能非常强答) createEffect 等等这些 API, 类型提示非常舒服. 整体思路是和在 React 中使用 Redux 差不多的, 主要是把思路更换到 Angular + RxJS 会花费一点时间.
这篇文章简单记录下入门过程中的学习:
基础概念
action
props<T>()
reducer
selectors
createSelector((state) => state.a, (a) => a.b)
with props
createFeatureSelector()
combination
重置缓存
this.store.select() 与 this.store.pipe(selector())
直接调用 select 可以不为 Store 传入泛型
对选取结果进一步的 pipe 处理
Meta-Reducers (Mw in Redux)
注入式 Reducer
effects
同一组 effect 放在一个类中, 多个 effect 由 xxx$ = this.actions$.pipe()的方式分别定义, 通常会注入 service layer 到类中
concatLatestFrom(action => this.store.select(fromBooks.getCollectionBookIds))
这种方式RouterStore
之前的 react-router-redux, 现在的 connected-react-router
作用就是为了在路由切换周期内去自动的 dispatch action, 或者说监听路由的状态. 通常会使用来自路由状态的数据进行一些额外的操作
setup: StoreModule.forRoot 中的 featureReducer 和 routerReducer(导出自'@ngrx/router-store'包), AppRoutingModule, StoreRouterConnectingModule, 均在全局 AppModule 中注册
使用 featureSelector + getSelectors, 获取来自于 routerReducer 专属的选择器
1import { getSelectors, RouterReducerState } from "@ngrx/router-store";2import { createFeatureSelector } from "@ngrx/store";3
4export const selectRouter =5 createFeatureSelector<RouterReducerState>("router");6
7export const {8 selectCurrentRoute, // select the current route9 selectFragment, // select the current route fragment10 selectQueryParams, // select the current route query params11 selectQueryParam, // factory function to select a query param12 selectRouteParams, // select the current route params13 selectRouteParam, // factory function to select a route param14 selectRouteData, // select the current route data15 selectUrl, // select the current url16} = getSelectors(selectRouter);
使用路由选择器进一步封装:
1import { createFeatureSelector, createSelector } from "@ngrx/store";2import { selectRouteParams } from "../router.selectors";3import { carAdapter, CarState } from "./car.reducer";4
5export const carsFeatureSelector =6 createFeatureSelector<CarState>("cars");7
8const { selectEntities, selectAll } = carAdapter.getSelectors();9
10export const selectCarEntities = createSelector(11 carsFeatureSelector,12 selectEntities13);14
15export const selectCar = createSelector(16 selectCarEntities,17 selectRouteParams,18 (cars, { carId }) => cars[carId]19);
1将selectRouteParams和selectCarEntities组合起来, 就能够基于store和路由状态进行选择
Entity
用于管理集合类型的实体状态适配器
提供高性能 CRUD 操作来管理实体集合, addOne, addMany, updateOne, updateMany 等
1// 实体的全局状态2// EntityState的泛型是集合中单个项的类型3export interface BookEntityState extends EntityState<Book> {4 selectedBookId: string | null;5 globalProp: boolean;6}7
8// 集合主键的获取9export const selectBookId = (book: Book): string => book.id;10
11// 集合的排序依据12export const sortByTitle = (bookA: Book, bookB: Book): number =>13 bookA.volumeInfo.title.localeCompare(bookB.volumeInfo.title);14
15// 集合对应的适配器上提供了getInitialState getSelectors 以及集合的操作方法16export const booksAdapter: EntityAdapter<Book> = createEntityAdapter<Book>({17 selectId: selectBookId,18 sortComparer: sortByTitle,19});20
21// 使用适配器的方法修改集合22export const booksEntityReducer = createReducer(23 initialEntityState,24 on(addBookEntity, (state, { book }) => booksAdapter.addOne(book, state)),25 on(addBooksEntity, (state, { books }) =>26 booksAdapter.addMany(books, state)27 ),28 on(updateBookEntity, (state, { update }) =>29 booksAdapter.updateOne(update, state)30 ),31 on(updateBooksEntity, (state, { updates }) =>32 booksAdapter.updateMany(updates, state)33 )34);35
36export const getSelectedBookId = (state: BookEntityState) =>37 state.selectedBookId;38
39// 供selector使用 进一步简化选择器代码40export const {41 selectIds: selectBookIds,42 selectEntities: selectBookEntities,43 selectAll: selectAllBooks,44 selectTotal: selectTotalBooks,45} = booksAdapter.getSelectors();46
47// 首级选择器必须使用EntityState作为泛型48export const selectBooksStateEntity =49 createFeatureSelector<fromBooks.BookEntityState>("books");50
51export const selectBookIds = createSelector(52 selectBooksStateEntity,53 // 相当于fromBooks.selectBookIds(BooksState)54 fromBooks.selectBookIds55);56
57export const selectAllBook = createSelector(58 selectBooksStateEntity,59 fromBooks.selectAllBooks60);61export const selectUserTotal = createSelector(62 selectBooksStateEntity,63 fromBooks.selectTotalBooks64);65
66export const selectBookEntities = createSelector(67 selectBooksStateEntity,68 fromBooks.selectBookEntities69);70
71export const selectCurrentBookId = createSelector(72 selectBooksStateEntity,73 fromBooks.getSelectedBookId74);75
76export const selectCurrentBook = createSelector(77 selectBooksStateEntity,78 selectCurrentBookId,79 (bookEntities, bookId) => bookEntities[bookId]80);
ComponentStore
@ngrx/store
是独立的, 但一起使用也不错,component-store 中可以拿到全局 store 的数据.使用方法更简单, component-store 内部就是 select/updater/effect 这几个方法.
models: 作为其他文件的类型定义
Nx
ng g @nrwl/angular:ngrx counter --module=apps/ngrx-practice/src/app/app.module.ts --root
ng g @nrwl/angular:lib products
ng g @nrwl/angular:ngrx products --module=libs/products/src/lib/products.module.ts --directory +state/products --defaults
Ng 注册
EffectsModule