Angular 状态管理方案调研

更新日期: 2020-04-07阅读: 2.1k标签: 状态

1 / 状态管理

RxJs + Service 组件内管理状态:在组件中可以声明一个属性,作为组件的内存存储。每次操作时调用服务(service)中的方法,然后手动更新状态。

export class TodoComponent {
  todos : Todo[] = []; // 在组件中建立一个内存TodoList数组

  constructor(
    @Inject('todoService') private service,
  ) {}

  addTodo(){
    this.service
      .addTodo('test') // 通过服务新增数据到服务器数据库
      .then(todo => { // 更新todos的状态
        this.todos.push(todo); // 使用了可改变的数组操作方式
      });
  }
}

RxJs + Service 组件只需访问,状态在服务中存储管理:在服务中定义一个内存存储,然后在更新服务数据后手动更新内存存储,组件中只需要访问该属性即可。

export class TodoService {
  private _todos: BehaviorSubject; 
  private dataStore: {  // 我们自己实现的内存数据存储
    todos: Todo[]
  };
  constructor() {
    this.dataStore = { todos: [] };
    this._todos = new BehaviorSubject([]);
  }
  get todos(){
    return this._todos.asObservable();
  }

  addTodo(desc:string){
    let todoToAdd = {};
    this.http
      .post(...)
      .map(res => res.json() as Todo) //通过服务新增数据到服务器数据库
      .subscribe(todo => {
        this.dataStore.todos = [...this.dataStore.todos, todo];
        //推送给订阅者新的内存存储数据
        this._todos.next(Object.assign({}, this.dataStore).todos);
      });
  }
}

类 Redux 管理方案- ngrx & ngxs

其他未调研产品- Akita & mobX & Redux & Flux


2 / ngrx

ngrx/store的灵感来源于Redux,是一款集成RxJS的angular状态管理库,由Angular的布道者Rob Wormald开发。它和Redux的核心思想相同,但使用RxJS实现观察者模式。它遵循Redux核心原则,但专门为Angular而设计。


基本原则/概念

State(状态) 是指单一不可变数据

Action(行为) 描述状态的变化

Reducer(归约器/归约函数) 根据先前状态以及当前行为来计算出新的状态

状态用State的可观察对象,Action的观察者——Store来访问

Actions- Actions是信息的载体,它发送数据到reducer,然后reducer更新store。Actions是store能接受数据的唯一方式。在ngrx/store里,Action的 接口 是这样的:

export interface Action {
   type: string;
   payload?: any;
 }

Reducers- Reducers规定了行为对应的具体状态变化。它是纯函数,通过接收前一个状态和派发行为返回新对象作为下一个状态的方式来改变状态,新对象通常用Object.assign和扩展语法来实现。

export const todoReducer = (state = [], action) => {
   switch(action.type) {
     case 'ADD_TODO':
       return [...state, action.payload];
     default:
       return state;
   }
 }

Store - store中储存了应用中所有的不可变状态。ngrx/store中的store是RxJS状态的 可观察对象 ,以及行为的 观察者 。我们可以利用Store来派发行为。当然,我们也可以用Store的select()方法获取可观察对象,然后订阅观察,在状态变化之后做出反应。

Selector - 可见示例代码

Effects- Redux 中的 Reducer 已经是一个纯函数,而且是完全的只对状态数据进行处理的纯函数。在发出某个 Action 之后,Reducer 会对状态数据进行处理然后返回。但一般来说,其实在执行 Action 后我们还是经常会可以称为 Effect 的动作,比如:进行 HTTP 请求,导航,写文件等等。而这些事情恰恰是 Redux 本身无法解决的,@ngrx/effects 用于解决这类场景,一个 http 请求的示例如下 https://gist.github.com/hijiangtao/d4def77867ff4aec2740ba6ab83b24bf

@Component({
   template: `
     <div *ngFor="let movie of movies$ | async">
          
     </div>
   `
 })
 export class MoviesPageComponent {
   movies$: Observable<Movie[]> = this.store.select(state => state.movies);
     
   constructor(private store: Store<{ movies: Movie[] }>) {}
     
   ngOnInit() {
     this.store.dispatch({ type: '[Movies Page] Load Movies' });
   }
 }

最佳实践

根 store 模块 - 创建根 store 模块作为一个完整的 Angular 模块,与 NgRx 的 store 逻辑绑定在一起。功能 store 模块将被导入到根 store 中,这样唯一的根 store 模块将被导入到应用程序的主 App Module 模块中。

创建功能 store 模块

方式一:Entity Feature Module - 定义 actions / 创建 state / 创建 reducer / 创建 selector / 创建 effects

方式二:标准的功能模块 - 同上

模块导入 angular - app.module.ts 引入

优势

中心化,状态不可变 - 所有相关应用程序的状态都缓存在一个位置。这样可以很容易地跟踪问题,因为错误时的状态快照可以提供重要的见解,并且可以轻松的重新重现这个问题。这也使得众多困难问题,例如在Store应用程序的上下文中撤消/重做某一步骤,并且实现了更强大的功能的工具

性能 - 由于状态集合中应用程序的顶层,因为数据更新可以通过组件依赖于Store。Angular构建如这样的数据流布置进行优化,并且可以在组件依赖于没有发布新值的Observables的情况下禁用变化检测。

测试 - 所有状态更新都是在recudes中处理的,它们是纯函数。纯函数测试非常简单,因为它只是输入,反对输出。这样可以测试应用程序中最关键的方面,而无需使用mock,或其他的测试技巧,可以使测试复杂且容易出错。

其他

可以结合 Redux Dewvtools 实现在线状态调试

ngrx 存在版本更迭,不少中文教程采用老 api 演示,如 StoreModule.provideStore / StoreModule.forRoot 等,以官方文档为准

官网 https://github.com/ngrx/platform

示例

https://github.com/hijiangtao/ngrx-store-example


3 / ngxs

在ngxs出来之前,angular有ngrx(来自redux的灵感),这很棒,但实际使用起来会非常费力,你会花大量的时间去为每一个action写reducer、effect。当然,付出这些代价的同时,我们的应用程序逻辑变得十分清晰,组件与组件的耦合变得更加松散,最内层的组件甚至只需要使用input和output负责展示数据,因此changedetection也可以使用onpush策略,整个组件也变得更加易于测试和维护。

ngxs更加活用了angular的特性,使用装饰器,并且隐藏了reducer的概念,鼓励程序员使用rxjs进行一系列的流式处理,这在一定程度上大大缩减了我们的代码量,使得一些中小项目使用状态管理框架的成本变得很低。

语法与 Angular 现有的写法及运作方式几乎是一样的,学习门槛变得很低。


基本原则/概念

Store: Global state container, action dispatcher and selector

Actions: Class describing the action to take and its associated metadata

State: Class definition of the state

Selects: State slice selectors

关键使用步骤注解

注册 - 在 app.module.ts 中注册,与 ngrx 类似 NgxsModule.forRoot([ZoosState]) 即可

action 定义- 基本与 ngrx 类似

export class AddAnimal {
   static readonly type = '[Zoo] Add Animal';
   constructor(public name: string) {}
 }

model 定义- 即 state interface 定义

export interface ZooStateModel {}

建立 state- 通过 @State decorator 来描述 state 的内容,Interface 建议以 Model 结尾,例如

@State<ZooStateModel>({
   name: 'zoo',
   defaults: {
     feed: false
   }
 })
 @Injectable() // 也可以依赖注入
 export class ZooState {
   constructor(private zooService: ZooService) {}

   @Action(FeedAnimals)
   feedAnimals(ctx: StateContext<ZooStateModel>) {
     const state = ctx.getState();
     ctx.setState({
       ...state,
       feed: !state.feed
     });
   }
 }

派发 dispatch- 在 comoponent view 上注入 store,然后进行派发 dispatch,操作过程中需要注意的是 dispatch 返回是空,如果需要获取 state 可以使用 @Select 进行链式调用

import { Store, Select } from '@ngxs/store';
 import { Observable } from 'rxjs';
 import { withLatestFrom } from 'rxjs/operators';
 import { AddAnimal } from './animal.actions';

 @Component({ ... })
 export class ZooComponent {
   @Select(state => state.animals) animals$: Observable<any>;

   constructor(private store: Store) {}

   addAnimal(name: string) {
     this.store
       .dispatch([new AddAnimal('Panda'), new AddAnimal('Zebra')])
       .pipe(withLatestFrom(this.animals$))
       .subscribe(([_, animals]) => {
         // do something with animals
         this.form.reset();
       });
   }
 }

select - 选中 state 的部分内容,具体使用可见上例

获取 snapshot - store.snapshot()

reset - store.reset()

示例与其他

示例略

官网 https://www.ngxs.io/


4 / 对比

ngxs vs ngrx 概念对比


ngrx 这个基本上是把 Redux 强行搬到 Angular 中,本来 Redux 就被吐槽不好用,看到各种 Switch 就高兴不起来,并且繁琐,写起来费劲;多 store 通过 .forFeature() 实现(lazy loading modules);

ngxs 这个框架其实就是使用 RxJS 管理状态,感觉比 ngrx 好用,使用装饰器定义 State 和 Action,组件通过  store.dispatch(new AddTodo('title'))  调用对应的  Action  方法 , 充分利用了 Angular 和 TypeScript 的特质;单一 store;

观点

Difference in performance between ngrx and ngxs? https://stackoverflow.com/questions/50704430/difference-in-performance-between-ngrx-and-ngxs

Why I Prefer NGXS over NGRX https://blog.singular.uk/why-i-prefer-ngxs-over-ngrx-df727cd868b5

NGRX VS. NGXS VS. AKITA VS. RXJS: FIGHT! https://ordina-jworks.github.io/angular/2018/10/08/angular-state-management-comparison.html

Angular + Redux https://medium.com/supercharges-mobile-product-guide/angular-redux-the-lesson-weve-learned-for-you-93bc94391958

Migrating from NGRX to NGXS in Angular 6 https://medium.com/@joshblf/migrating-from-ngrx-to-ngxs-in-angular-6-ddddcdce543e


参考与扩展阅读

链接: https://www.fly63.com/article/detial/8880

Javascript 状态管理工具 DataSet ,实现数据的订阅、查询、撤销和恢复

网页是用户与网站对接的入口,当我们允许用户在网页上进行一些频繁的操作时,对用户而言,误删、误操作是一件令人抓狂的事情,“如果时光可以倒流,这一切可以重来……”。

理解 React 轻量状态管理库 Unstated

在React写应用的时候,难免遇到跨组件通信的问题。现在已经有很多的解决方案。React本身的Context,Redux结合React-redux,Mobx结合mobx-react

你再也不用使用 Redux、Mobx、Flux 等状态管理了

这个库的作者希望使用 React 内置 API ,直接实现状态管理的功能。看完这个库的说明后,没有想到代码可以这个玩。短短几行代码,仅仅使用 React Hooks ,就实现了状态管理的功能。

为什么要使用状态管理

我们平时开发的大部分项目,由于复杂度不够, 很少使用 Vuex、Redux 等状态管理库,就算引入了 Vuex 这些库,也只是当作一个全局数据引用,并非对应用状态进行管理。但一旦页面的复杂度比较高,必然要引入状态管理,今天就聊聊我理解中的状态管理。

React使用Hooks与Context替代Redux状态管理

React Hooks 在 2018 年年底就已经公布了,正式发布是在 2019 年 5 月,关于它到底能做什么用,并不在本文的探讨范围之内,本文旨在摸索,如何基于 Hooks 以及 Context,实现多组件的状态共享,完成一个精简版的 Redux。

如何使用react hooks来进行状态管理?

首先要明确为什么要使用redux,这一点很重要,如果不知道为什么使用redux,那么在开发的过程中肯定不能合理的使用redux.首先来看redux的本质:redux做为一款状态管理工具,主要是为了解决组件间通信的问题。

Flutter基础--状态管理

当我们使用编译器创建一个新Flutter应用的时候,我们可以在主界面看到两个小部件StatelessWidget和StatefulWidget。这是两个最常见使用最频繁的小部件了。StatelessWidget ,StatefulWidget

共享可变状态中出现的问题以及如何避免?

本文回答了以下问题:么是共享可变状态?为什么会出现问题?如何避免其问题?标有(高级)的部分会更深入,如果你想更快地阅读本文,可以跳过。

使用Observable实现Vue全局状态共享

项目不大, 又不想用Vuex, 那么使用Observable来实现状态共享也不失为一个选择。用法 :让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象

node如何实现保持登录状态?

当我们登录成功,在这个页面刷新,页面并没有保存登录状态;今天我们就来看一下如何在后台使用cookie保存用户登录状态。做到刷新页面仍然显示在用户登录界面。node实现保持登录状态的方法如下:

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!