Angular2 之 @ngrx/store

RxJs驱动Angular应用程序的状态管理,其灵感来自于Redux。

有人这样说…

如果说RxJS是Angular2开发中的倚天剑,那么Redux就是屠龙刀了。而且这两种神兵利器都是不依赖于平台的,左手倚天右手屠龙……

什么是Redux?

Redux是为了解决应用程序状态(State)管理而提出的一种解决方案。那么什么是状态呢?简单来说对于应用开发来讲,UI上显示的数据、控件状态、登陆状态、数据加载画面的不同状态等等全部可以看作状态。

如果应用程序中没有明确的状态的管理,很多项目随着需求的迭代,代码规模逐渐扩大、团队人员水平参差不齐就会遇到各种状态管理极其混乱,导致代码的可维护性和扩展性降低。

那么Redux是怎么解决这个问题的呢?它提出了几个概念:Reducer、Action、Store。

Store

可以把Store想象成一个数据库,就像我们平时在开发过程中使用的Oracle数据库一样,Store是一个你应用内的数据(状态)中心。Store在Redux中有一个基本原则:它是一个唯一的、状态不可修改的树,状态的更新只能通过显性定义的Action发送后触发。

Store中一般负责:保存应用状态、提供访问状态的方法、派发Action的方法以及对于状态订阅者的注册和取消等。

遵守这个约定的话,任何时间点的Store的快照都可以提供一个完整当时的应用状态。

Reducer

我一直觉得Reducer这个单词不好理解,它的英文解释有:减速器、还原剂还有一个是异形接头。这几个单词的意思,我都不能直接拿过来用。

还好今天看到这边文章,文章中如下描述:

其二是我看了Redux的作者的一段视频,里面他用数组的reduce方法来做类比,而我之前对reduce的理解是reduce就是对数组元素进行累加计算成为一个值。

我觉得这个描述非常好,因为数组的reduce操作就是给出不断的用序列中的值经过累加器计算得到新的值,这和旧状态进入reducer经处理返回新状态是一样的。

很多网上的文章说可以把Reducer想象成数据库中的表,也就是Store是数据库,而一个reducer就是其中一张表。我其实觉得Reducer不太像表,我还是觉得这个累加器我比较好理解。

Reducer其实就是用来维护状态的

Reducer是一个纯javascript函数,接收2个参数:第一个是处理之前的状态,第二个是一个可能携带数据的动作(Action)。

export interface Reducer<T> { (state: T,action: Action): T; }

那么纯函数是意味着什么呢?意味着我们理论上可以把reducer移植到所有支持Redux的框架上,不用做改动。

什么是纯函数呢?

  • 纯函数具有幂等性,也就是给出同样的参数值,该函数总是求出同样的结果。
  • 结果的求值不会促使任何可语义上可观察的副作用或输出,例如易变对象的变化或输出到I/O装置。

下面我们来看一段简单的代码:

export const counter: Reducer<number> = (state = 0,action) => { 
  switch(action.type){ 
      case 'INCREMENT': return state + action.payload;          

      case 'DECREMENT': return state - action.payload;     

      default: return state; }};

上面的代码定义了一个计数器的Reducer,一开始的状态初始值为0((state = 0,action) 中的 state=0 给state赋了一个初始状态值)根据Action类型的不同返回不同的状态。这段代码就是非常简单的javascript,不依赖任何框架,可以在React中使用,也可以在接下来的我们要学习的Angular2中使用。

Action

Store中存储了我们的应用状态,Reducer接收之前的状态并输出新状态,但是我们如何让Reducer和Store之间通信呢?这就是Action的职责所在。在Redux规范中,所有的会引发状态更新的交互行为都必须通过一个显性定义的Action来进行。

下面的示意图描述了如果使用上面代码的Reducer,显性定义一个Action{type: ‘INCREMENT’,payload: 2}并且dispatch这个Action后的流程。

比如说之前的计数器状态是1,我们派送这个Action后,reducer接收到之前的状态1作为第一个参数,这个Action作为第二个参数。在Switch分支中走的是INCRMENT这个流程,也就是state+action.payload,输出的新状态为3.这个状态保存到Store中。

注意:

export interface Action { 
  type: string; 
  payload?: any; // 这个值可有可没有
}

为什么要在Angular2中使用?

angular1.x中没有一个统一的状态管理机制,所以造成状态很混乱。

所以我们要在Angular2中使用状态管理。

在Angular 2中使用Redux

ngrx是一套利用RxJS的类库,其中的@ngrx/store
(https://github.com/ngrx/store) 就是基于Redux规范制定的Angular2框架。

简介

@ngrx/store是一个旨在提高写性能的控制状态的容器,在angular的应用程序上是一致的。核心原理有:

  • 状态是一个不可变的数据结构
  • 行为描述状态的改变
  • 成为还原剂的纯函数拿到下一个状态和之前的状态来计算一个新的状态。
  • 状态访问store,一个可观测状态和一个观察者的行为

这些核心原则支持构建组件,这些组件可以使用OnPush变化检测策略给你聪明,性能变化检测在您的应用程序。

We will use @ngrx/store to provide use with a store for us to well… store our state.

我们使用@ngrx/store来提供我们使用一个store,来存储我们的状态。

要重申,redux中最重要的一个概念是应用程序的整个状态都集中在一个JavaScript对象树中。

因为我们的对象状态树是只读的,我们对每个动作的响应必须返回一个新的状态对象,而不改变先前的状态对象。在实现redux时,在reducer中实现不可变性是至关重要的,因此将逐步介绍下面的每个操作,并讨论如何完成这一任务。

知识点

  • 状态和组件的关系:

    • 状态驱动组件进行渲染
    • 组件发action来改变状态。
  • 是一个单向数据流

  • 要执行一个动作,我们称之为this.store.dispatch

  • 状态下降
    redux的另一个基石是状态总是向下流。

  • 事件向上
    “状态向下”的另一面是事件总是向上流动的。比如点击了item中的一个删除按钮,当前item组件中没有删除操作,那么就会向上去找删除方法。


* 在@ngrx/store实现使用observables允许我们使用异步和管道来实现我们的模块。
* We created our reducers which are simple functions that take an action and state object and returns a new state object.
* A store is basically a key value value map with some mechanisms(机制) to handle events and emit(发布) state.
* We broadcast(广播) our events using store.emit.
* We subscribe(订阅) to data using store.select.
* With asynchronous calls(异步调用),we pass our results through an observable sequence(可观察序列) and then emit the event to the reducer on completion.

一个简单的使用例子

在您的应用程序创建一个Reducer函数为每个数据类型。这些Reducer将使您的应用程序的组合状态:

// counter.ts
import { ActionReducer,Action } from '@ngrx/store';

export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const RESET = 'RESET';

export function counterReducer(state: number = 0,action: Action) {
    switch (action.type) {
        case INCREMENT:
            return state + 1;

        case DECREMENT:
            return state - 1;

        case RESET:
            return 0;
        // 这是一个例子:添加一个新的元素
        case 'ADD_TODO': 
           return [ ...state,action.payload ];
        default:
            return state;
    }
}
  • 第一个参数是state,就像我们在组件或服务中自己维护了一个内存数组一样,我们的Todo状态其实也是一个number,我们还赋了一个0的初始值(避免出现undefined错误)。
  • 第二个参数是一个有type和payload两个属性的对象,其实就是Action。也就是说我们其实可以不用定义Action,直接给出构造的对象形式即可。内部的话其实reducer就是一个大的switch语句,根据不同的Action类型决定返回什么样的状态。默认状态下我们直接将之前状态返回即可。Reducer就是这么单纯的一个函数。

  • 现在我们来考虑其中一个动作,增加一个Todo,我们需要发送一个Action,这个Action的type是 ’ADD_TODO’ ,payload就是新增加的这个Todo。

逻辑其实就是列表数组增加一个元素,用数组的push方法直接做是不是就行了呢?不行,因为Redux的约定是必须返回一个新状态,而不是更新原来的状态。而push方法其实是更新原来的数组,而我们需要返回一个新的数组。感谢ES7的Object Spread操作符,它可以让我们非常方便的返回一个新的数组。

在APP的主模块中,导入reducers、使用StoreModule.provideStore(reducers)方法去给angular注入提供商:(*In your app’s main module,import those reducers and use the StoreModule.provideStore(reducers)
function to provide them to Angular’s injector:*)

import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter';
@NgModule({ 
  imports: [ 
      BrowserModule,StoreModule.provideStore({ counter: counterReducer })         
]})

export class AppModule {}

你可以在你的组件中注入Storeservice,使用store.select方法去获取状态,当数据改变后,由于store.select是可观察类型的,所以可以多次返回值,like this:counter的值会自动更新。

import { Store } from '@ngrx/store';
import { INCREMENT,DECREMENT,RESET } from './counter';
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';

interface AppState {
  counter: number;
}

@Component({
  selector: 'my-app',template: `
        <button (click)="increment()">Increment</button>
        <div>Current Count: {{ counter | async }}</div>
        <button (click)="decrement()">Decrement</button>

        <button (click)="reset()">Reset Counter</button>
    `
})
export class CounterComponent {
  counter: Observable<number>;

  constructor(private store: Store<AppState>) {
    this.counter = store.select('counter');
  }

  increment() {
    this.store.dispatch({ type: INCREMENT });
  }

  decrement() {
    this.store.dispatch({ type: DECREMENT });
  }

  reset() {
    this.store.dispatch({ type: RESET });
  }
}

参考:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


ANGULAR.JS:NG-SELECTANDNG-OPTIONSPS:其实看英文文档比看中文文档更容易理解,前提是你的英语基础还可以。英文文档对于知识点讲述简明扼要,通俗易懂,而有些中文文档读起来特别费力,基础差、底子薄的有可能一会就会被绕晕了,最起码英文文档中的代码与中文文档中的代码是一致的,但知识点讲述实在是差距太大。Angular.jshasapowerfuldire
AngularJS中使用Chart.js制折线图与饼图实例  Chart.js 是一个令人印象深刻的 JavaScript 图表库,建立在 HTML5 Canvas 基础上。目前,它支持6种图表类型(折线图,条形图,雷达图,饼图,柱状图和极地区域区)。而且,这是一个独立的包,不依赖第三方 JavaScript 库,小于 5KB。   其中用到的软件:   Chart.js框架,版本1.0.2,一
IE浏览器兼容性后续前言 继续尝试解决IE浏览器兼容性问题,结局方案为更换jquery、angularjs、IE的版本。 1.首先尝试更换jquery版本为1.7.2 jquery-1.9.1.js-->jquery-1.7.2.js--> jquery2.1.4.js 无效 2.尝试更换IE版本IE8 IE11-
Angular实现下拉菜单多选写这篇文章时,引用文章地址如下:http://ngmodules.org/modules/angularjs-dropdown-multiselecthttp://dotansimha.github.io/angularjs-dropdown-multiselect/#/AngularJSDropdownMultiselectThisdire
在AngularJS应用中集成科大讯飞语音输入功能前言 根据项目需求,需要在首页搜索框中添加语音输入功能,考虑到科大讯飞语音业务的强大能力,遂决定使用科大讯飞语音输入第三方服务。软件首页截图如下所示: 涉及的源代码如下所示: //语音识别$rootScope.startRecognize = function() {var speech;
Angular数据更新不及时问题探讨前言 在修复控制角标正确变化过程中,发觉前端代码组织层次出现了严重问题。传递和共享数据时自己使用的是rootScope,为此造成了全局变量空间的污染。根据《AngularJs深度剖析与最佳实践》,如果两个控制器的协作存在大量的数据共享和交互可以利用Factory等服务的“单例”特性为它们注入一个共享对象来传递数据。而自己在使用rootScope
HTML:让表单、文本框只读,不可编辑的方法有时候,我们希望表单中的文本框是只读的,让用户不能修改其中的信息,如使中国">的内容,"中国"两个字不可以修改。实现的方式归纳一下,有如下几种。方法1:onfocus=this.blur()中国"onfocus=this.blur()>方法2:readonly中国"readonly>中国"readonly="tru
在AngularJS应用中实现微信认证授权遇到的坑前言 项目开发过程中,移动端新近增加了一个功能“微信授权登录”,由于自己不是负责移动端开发的,但最后他人负责的部分未达到预期效果。不能准确实现微信授权登录。最后还得靠自己做进一步的优化工作,谁让自己是负责人呢?原来负责人就是负责最后把所有的BUG解决掉。 首先,熟悉一下微信授权部分的源代码,如下所示:
AngularJS实现二维码信息的集成思路需求 实现生成的二维码包含订单详情信息。思路获取的内容数据如下: 现在可以获取到第一级数据,第二级数据data获取不到。利用第一级数据的获取方法获取不到第二级数据。for(i in data){alert(i); //获得属性 if(typeof(data[i]) == "o
Cookie'data'possiblynotsetoroverflowedbecauseitwastoolarge(5287>4096bytes)!故事起源 项目开发过程中遇到以上问题,刚开始以为只是个警告,没太在意。后来发现直接影响到了程序的执行效果。果断寻找解决方法。问题分析 根据Chrome浏览器信息定位,显示以下代码存在错误:
AngularJS控制器controller之间如何通信angular控制器通信的方式有三种:1,利用作用域继承的方式。即子控制器继承父控制器中的内容2,基于事件的方式。即$on,$emit,$boardcast这三种方式3,服务方式。写一个服务的单例然后通过注入来使用利用作用域的继承方式由于作用域的继承是基于js的原型继承方式,所以这里分为两种情况,当作用域上面的值
AngularJS路由问题解决遇到了一个棘手的问题:点击优惠详情时总是跳转到药店详情页面中去。再加一层地址解决了,但是后来发现问题还是来了:Couldnotresolve'yhDtlMaintain/yhdetail'fromstate'yhMaintain'药店详情http://192.168.1.118:8088/lmapp/index.html#/0优惠券详情
书海拾贝之特殊的ng-src和ng-href在说明这两个指令的特殊之前,需要先了解一下ng的启动及执行过程,如下:1)浏览器加载静态HTML文件并解析为DOM;2)浏览器加载angular.js文件;3)angular监听DOMContentLoaded事件,监听到时开始启动;4)angular寻找ng-app指令,确定作用范围;
angularjs实现页面跳转并进行参数传递Angular页面传参有多种办法,我在此列举4种最常见的:1.基于ui-router的页面跳转传参(1)在AngularJS的app.js中用ui-router定义路由,比如现在有两个页面,一个页面(producers.html)放置了多个producers,点击其中一个目标,页面跳转到对应的producer页,同时将producerId
AngularJS实现表格数据的编辑,更新和删除效果实现首先,我们先建立一些数据,当然你可以从你任何地方读出你的数据var app = angular.module('plunker', ['ui.bootstrap']);app.controller('MainCtrl', function($scope) { $scope.name = 'World'; $sc
ANGULAR三宗罪之版本陷阱      坑!碰到个大坑,前面由于绑定日期时将angular版本换为angular-1.3.0-beta.1时,后来午睡后,登录系统,发现无论如何都登陆不进去了,经过调试,发现数据视图已经无法实现双向绑定了。自己还以为又碰到了“僵尸程序”了呢,对比药店端的程序发现并没有什么不同之处。后来自己经过一番思索才隐约感觉到是不是angular的版本造成的,将版本换为之前
JS实现分页操作前言 项目开发过程中,进行查询操作时有可能会检索出大量的满足条件的查询结果。在一页中显示全部查询结果会降低用户的体验感,故需要实现分页显示效果。受前面“JS实现时间选择插件”的启发,自己首先需要查看一下HTML5能否实现此效果。 整了半天,不管是用纯CSS3也好,还是用tmpagination.js还是bootstrap组件也好,到最后自己静下心来理
浏览器兼容性解决之道前言 浏览器兼容性一直是前端开发中不得不面对的一个问题。而最突出的就是IE。对绝大多数公司来说,兼容IE6的性价比已经很低,而IE7则几乎已经绝迹。所以,常见的兼容性下限是IE8。这也正是Angular1.2x的兼容性目标,Angular团队声明:Angular的持续集成服务器会在IE8下运行所有的测试。但这些测试不会运行在IE7及以下版本,它们也不会保证An
JS利用正则表达式校验手机号绪 由于项目需求,需要在前端实现手机号码的校验。当然了,对于基本的格式校验应该放在客户端进行,而不需要再将待校验的手机号发送至服务端,在服务端完成校验,然后将校验结果返回给客户端,客户端根据返回的结果再进行进一步的处理。如此反而复杂化了处理过程。 其实,处于安全考虑,应该在服务端进行二次校验。以下为在客户端的JS中校验手机号码格式
基于项目实例解析ng启动加载过程前言 在AngularJS项目开发过程中,自己将遇到的问题进行了整理。回过头来总结一下angular的启动过程。 下面以实际项目为例进行简要讲解。1.载入ng库2.等待,直到DOM树构造完毕。3.发现ng-app,自动进入启动引导阶段。4.根据ng-app名称找到相应的路由。5.加载默认地址。6.Js顺序执行,加载相应模版页sys_tpls/