ngrxstore综合介绍,读完马上精通angular中的ngrxstore
像传统数据⼀样,代表着应⽤的记录点,你的Store可以被认为是客户端"真实的数据来源" 或数据库。在设计应⽤时都遵守⼀个Store的约定,Store任何时刻的存储快照都将只是呈现应⽤程序的完整状态。
⼀个单⼀的、不可变的状树,只有通过显式定义和调度才能更新。
中⼼化,不可变状态
Reducers
Store应⽤程序的第⼆个组成部分是reducers。A2 reducer 是⼀个 a3纯函数,前⼀个状态和⼀个与事件相关联的类型和可选数据(playload)的Action。使⽤以前的说法是,如果Store被认为是客户端的数据库,则reducers可以被认为是数据库中数据表。Reducers代表着应⽤程序的部分或状态⽚段,应相应的进⾏结构化和组合。
Reducer 接⼝2
export interface Reducer<T> {
(state: T, action: Action): T;
}
A3函数的返回值类型由其输⼊值的类型决定的。
⼀个简单的Reducer
export const counter: Reducer<number> = (state: number = 0, action: Action) => {
pe) {
ca 'INCREMENT':
return state + 1;
ca 'DECREMENT':
return state -1;
default:
return state;
}
}
Actions
Store包含了我们应⽤程序的state和Reducers的输出部分,但是当状态需要更新时,我们如何与reducers通信呢?这是actions4,在Store应⽤程序中,所有导致状态更新的⽤户交互都必须以actions的形式表⽰。所有与⽤户相关的事件都被分派为action,经过Store的4个action通道,然后输出⼀个新的状态表⽰。每次调度⼀个action时都会发⽣这个过程,留下应⽤程序状态随时间变化的完整的,可序列话的表⽰形式。
Action接⼝4
export interface Action {
type: string;
payload?: any;
}
派发Action的流⽔线5
Actions简单⽰例
//没带数据的action
dispatch({type: 'DECREMENT'});
//带数据的action
dispatch({type:ADD_TODO, payload: {id: 1, message: 'Learn ngrx/store', completed: true}});九下文言文
数据投影
最后,我们需要从Store中提取、组合和投影数据以显⽰在我们的视图中。因为Store本⾝是可观察的,所以我们可以访问你习惯的典型JS集合操作(map, filter, reduce等)以及强⼤的基于RxJS的可观察操作符。这将使得将Store数据分割成你希望很容易的投影。
状态投影
//最简单的⽰例,从state获取people
store.lect('people');
//合并多个state
store.lect('people'),
store.lect('events'),
(people, events) => {
// 在此投影
}
)
Not Your Classic Angular
在上⼀节中,我提到了在开发应⽤程序时遵守的约定。在传统的设置和⼯作流程,你已经习惯了吗,这是什么意思?让我们⼀起来看看。
如果你是从Angular1转过来的,你会很熟悉数据的双向绑定6。控制器把model绑定到视图,反之亦然。这种⽅法的问题出现在你的视图变得更新复杂时,需要控制器和指令来管理并表⽰重要的状态随时间的变化。这很快变成⼀个噩梦,⽆论是推理还是调度,因为⼀个变化会影响另⼀个变化,另⼀个⼜影响另⼀个......等等。
Store提升了单向数据流7和显式调度操作的概念。的感受状态更新都缓存在组件中,委托给reducer。在应⽤程序中启动状态更新的唯⼀办法是通过调度操作,对应于特定的reducer案例。这不仅使你应⽤程序的状态改变变得简单,因为更新是集合在⼀起的,它会在出现错误时留下清晰的线索。
双向数据绑定6
6 Two-Way Data Binding
单向数据绑定
不使⽤Store的Counter⽰例
(在线演⽰)
@Component({
lector:'counter',
template: `
<div class='counter'>
<button (click)='increment()'>+</button>
<button (click)='decrement()'>-</button>
舒耐<h3>{{counter}}</h3>
</div>
`
})
export class Counter {
counter = 0;
increment() {
}
decrement() {
}
}
使⽤Store的Counter⽰例
(演⽰)
@Component({
lector: 'counter',
template: `
<div class='content'>
<button (click)="increment()">+</button>
<button (click)="decrement()">-</button>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Counter {
counter$: Obrvable<number>;
constructor(
private store: Store<number>
){
}
increment(){
this.store.dispatch({type:'INCREMENT'});
}
decrement(){
this.store.dispatch({type:'DECREMENT'});
}
}
Store的优势
在整个概述中,我们简要介绍了利⽤Store在⼀种典型的Angular 1风格⽅法的优势,现在让我们发⼀点时间来回顾⼀下。为什么要花时间在这个特定的库,构建和曲线上投资
呢?Store的优势是状态中⼼化,性能,测试。
中⼼化,状态不可变
所有相关应⽤程序的状态都缓存在⼀个位置。这样可以很容易地跟踪问题,因为错误时的状态快照可以提供重要的见解,并且可以轻松的重新重现这个问题。这也使得众多困难问题,例如在Store应⽤程序的上下⽂中撤消/重做某⼀步骤,并且实现了更强⼤的功能的⼯具。
性能
由于状态集合中应⽤程序的顶层,因为数据更新可以通过组件依赖于Store。Angular构建如这样的数据流布置进⾏优化,并且可以在组件依赖于没有发布新值的Obrvables的情况下禁⽤变化检测。在最佳的缓存解决⽅案中,这将是绝⼤多数组件。
测试
所有状态更新都是在recudes中处理的,它们是纯函数。纯函数测试⾮常简单,因为它只是输⼊,反对输出。这样可以测试应⽤程序中最关键的⽅⾯,⽽⽆需使⽤mock,或其他的测试技巧,可以使测试复杂且容易出错。
⼯具与⽣态系统
中⼼化的,不可变的状态还可以实现更强⼤的⼯具。⼀个这样的盒⼦是ngrx开发⼯具,它提供了action和状态变化的历史,允许在开发过程中进⾏8次遍历。Store提供的模式还允许⼀个易于实现中间件的丰富的⽣态系统。因为Store在分派action之前和之后都提供⼀个⼊⼝点,所以应⽤程序减少,如同步⽚状态到本地Store,⾼级⽇志记录和实现sagas这样的问题可以通过快速包和⼏⾏代理来解决。这个⽣态系统只会在未来⼏个⽉内增长。
操作调度action和状态更改的历史,以模拟应⽤程序交互的时间点。
@ngrx/store的构建模块
在构建Store应⽤程序之前,⾸先来看看构建@ngrx/store的RxJS概念。⾸先理解这些概念,我们将来可以更有效地利⽤这个库。要详细说明下⾯的每个主题,请查看这些额外的资源。
声明:Mike Ryan和Rob Wormald的实际@ngrx/store代码显着更强⼤。这些⽰例旨在演⽰涉及的RxJ
S概念,并从库中移出"magic"
Subject/Dispatch的探索
Rx的信使们,你告诉我,我会告诉他们的.....
(演⽰)
(演⽰)
@ngrx/store的两个⽀柱,Store和Dispatcher都扩展了RxJS主题。主题即是观察者(Obrvables)和观察者(Obrvers),这意味着你可以订阅Subject,但也可以将主题订阅源。在⾼级别科⽬可以被认为是信使或代理⼈。
因为Subject是Obrvables,你可以 "next" 或直接将值传递到流中。然后,该Subject的订阅将被通知发出值。在Store的上下⽂中,这些⽤户可能是⼀个Angular 服务,组件或需要访问应⽤程序状态的任何内容。
订阅主题
//创建⼀个主题
const mySubject = new Rx.Subject();
//添加订阅者
const subscriberOne = mySubject.subscribe(val => {
console.log('***SUBSCRIBER ONE***',val);
});
const subscriberTwo = mySUbject.subscribe(val => {
console.log('***SUBSCRIBER TWO***',val);
});
//发射subject的值到obrvers
<('FIRST VALUE!');// ***SUBSCRIBER ONE*** FIRST VALUE! ** SUBSCRIBER TWO*** FIRST VALUE!
<('SECOND VALUE!');//***SUBSCRIBER ONE*** SECOND VALUE! ***SUBSCRIBER TWO*** SECOND VALUE
在Store或Redux中,将action发送到应⽤程序中的Store是⼀种惯例。为了维护此API,Dispatcher扩展⾄Subject,将派⽣⽅法作为传递添加到传统的下⼀个⽅法。这被⽤于将值发送到Subject中,然后将这些值发送给⼦对象。
将Dispatcher继承⾃Subject
/*
redux/ngrx-store 有⼀个dispatcher的概念,或者是⾯向应⽤程序Store发送操作的⽅法允许扩展Rx.Subject与我们的Dispatcher类来维护熟悉的术语。
*/
//从Subject中继承
class Dispatcher extends Rx.Subject {
dispatcher(value: any): void{
<(value);
}
}
//创建⼀个dispatcher(只是⼀个包含next的SUbject⽅法)
const dispatcher = new Dispatcher();
//添加订阅
const subscribeOne = dispatcher.subscribe(val => {
console.log('***SUBSCRIBER ONE***', val);
});
const subscribeTwo = dispatcher.subscribe(val => {
console.log('***SUBSCRIBER TWO***', val);
});
//将值发射到obrvers
dispatcher.dispatch('FIRST DISPATCHED VALUE!');
dispatcher.dispatch('SECOND DISPATCHED VALUE!');
BehaviorSubject/Store探索
与Subject类似,但你说的最后⼀件事是什么?...
(演⽰)
虽然Subject作为dispatcher完美地⼯作,但它们有⼀个问题可以防⽌他们适合Store。订阅Subject时,只接收订阅后发出的值。在不断添加和删除组件的环境中,这是不可接受的,在订阅时需要应⽤程序Store的最新的按需状态部分。
Subjects只接受订阅后发出的值
/*
现在我们有⼀个dispatcher,让我们创建我们的Store来接收已经发送的action。
*/
class FirstStore extends Rx.Subject{}
const myFirstStore = new FirstStore();
//添加订阅者
const subscriberOne = myFirstStore.subscribe(val => {
console.log('***SUBSCRIBER ONE***', val);
});
const subscriberTwo = myFirstStore.subscribe(val => {
console.log('***SUBSCRIBER TWO***', val);
});
/
/现在,让超级dispatcher发布值到store
<('FIRST VALUE!');
/*
我们在添加⼀个订阅者。
由于我们第⼀次实施Store是⼀个subject,订阅者只能看到发布的价值*AFTER*他们订阅之后。在这种情况下,订阅者3将不了解'FIRST VALUE!'
*/
const subscriberThree = myFirstStore.suscribe(val => {
console.log('***SUBSCRIBER THREE***', val);
});
幸运的是,RxJS为Subject处理这个问题提供了BehaviorSubject。即BehviorSubject 封装了Subject的所有功能,但也可以在订阅后将改后发布的值返回给订阅都。这意味着组件和服务将始终可以访问最
新(或初始值)应⽤程序状态和所有将来的更新。
BehaviorSubject订阅接收上⼀次发布的值
/*
因为我们的组件需要查询当前状态,所以BehaviorSubject更适合Store。BehaviorSubjects具有Subject的所有功能,还允许设置初始值,以及在订阅时将所接收的最后⼀个值输出给所有观察者。
*/
class Store extends Rx.BehaviorSubject {
class Store extends Rx.BehaviorSubject {
constructor(initialState: any){
super(initialState);
}
}
const store = new Store('INITIAL VALUE');
//添加⼀些订阅者
const storeSubscriberOne = store.subscribe(val => {
console.log('***STORE SUBSCRIBER ONE***', val);
});
//为了演⽰,⼿动发布值到store
const storeSubscriberTwo = store.subscribe(val => {
console.log('***STORE SUBSCRIBER TWO***', val);
});
//在'FIRST VALUE!' 发布之后添加另⼀个订阅者
//输出:***STORE SUBSCRIBER THREE*** FIRST STORE VALUE!
const subscriberThree = store.subscribe(val => {
console.log('***STORE SUBSCRIBER THREE***', val);
});
Store + Dispatcher数据流
单状态树和单向数据流在Angular ...
(演⽰)
为了store的上下⽂正常运⾏,dispatcher仍然需要⼀些⼯作。在Store应⽤程序中,所有dispatch的action必须通过特定的管道传递,才能将新的状态表⽰传递到store中,并发送给所有观察者。你可以将此视为⼯⼚装配线,在这种情况下,线上的站是pre-middleare->reducers->post->middleware->store。
三七种子这个流⽔线的创建是在创建时dispatch传递给store处理的。然后,store下⼀个⽅法被覆盖,以便将新的状态表⽰传递到store之前,⾸先将所有的action都dispatch管道。这也允许通过dispatch汇集接收到的action。
现在,中间件和reducers的实现将被删除。西班牙海鲜烩饭
将Dispatcher与Store关联⼀起
/*
所有action都应通过管道,然后新计算的状态通过store。
1.) Dispatched Action
2.) Pre-Middleware
3.) Reducers (return new state)
4.) Post-Middleware
5.) (newState)
*/
class Dispatcher extends Rx.Subject{
dispatcher(value: any): void{
<(value);
}
}
class Store extends Rx.BehaviorSubject{
constructor(
private dispatcher,
initialState
){
super(initialState);
/*
所有dispatch的action在通过新状态之前通过action管道传递到store
*/
this.dispatcher
//pre-middleware
//reducers
//post-middleware
.subscribe(state => (state));
}
//⾸先通过分派action到管道并委托给store.dispatch
dispatch(value){
this.dispatcher.dispatch(value);
}
//覆盖store允许直接订阅action注通过store
next(value){
this.dispatcher.dispatch(value);
}
}
const dispatcher = new Dispatcher();
const store = new Store(dispatcher,'INITIAL STATE');
const subscriber = store.subscribe(val => console.log('VALUE FROM STORE: ${val}'));
/*
所有分派action⾸先流经管道,计算新状态然后传递到store。总结⼀下,我们的理想⾏为分派action->premiddleware->reducers->post-middleware-&(newState)
*/
//两种⽅法在幕后都是相同的
寒窗是什么意思dispatcher.dispatch('DISPATCHED VALUE!');
store.dispatch('ANOTHER DISPATCHED VALUE!');
const actionStream$ = new Rx.Subject();
/*
覆盖store下⼀个⽅法允许我们将store直接订阅到action流,提供与⼿动调⽤store.dispatch或dispatcher.dispatch相同的⾏为
*/
actionStream$.subscribe(store);
actionStream$.next('NEW ACTION!');
什么是Reducer?
像雪球⼀样下滑,reducer通过迭代累加...
(演⽰)
Reducers是基于任何store或Redux的应⽤基础,描述基于分派action类型的状态部分及其潜在转换。你的reducer的组合是在任何给定时间组成应⽤程序状态的表⽰。
在讨论如何创建和实现reducers之前,我们先来看看reduce函数。reduce需要⼀个数组,根据累加值和当前值运⾏⼀个函数,在完成后将数组递减⼀个值。你可以把reducers看成⼀个滚雪⽽下的雪橇,每⼀次变⾰都会变得很⼤。以相同的⽅式,减少reduce是通过迭代定义的函数应⽤于当前值的结果。
标准的Reduce
/*
你可以想⼀下滚雪球的场景。每⼀次翻滚都会累加质量和体积直到到达底部。reduce也类似,返回的值传递给所有提供函数的下⼀个调⽤,直到源数组中的所有值都耗尽为⽌。让我们看看⼀些巩固概念的盒⼦。
*/
const numberArray = [1,2,3];
/*
1.) accumulator:1, current:2
2.) accumulator:3, current:3
Final: 6
*/
申请免费域名const total = duce((accumulator, current) => accumulator + current);
console.log('***TOTAL***:',${total});
//reduce操作的对象
const personInfo = [{name:'Joe'},{age:31},{birthday:'1/1/1985'}];
/*
背部肌肉酸痛1.) accumulator: {name: 'Joe'}, current: {age: 31}
2.) accumulator: {name: 'Joe', age:31}, current: {birthday: '1/1/1985'}
Final: {name: 'Joe', age:31, birthday: '1/1/1985'}
*/
const fullPerson = duce(accumulator, current) => {
return Object.assign({}, accumulator, current);
}
console.log('*** FULL PERSON***:',fullPerson);
const personInfoStart = [{name:'Joe'},{age: 31},{birthday:'1/1/1985'}];
/*
1.) accumulator: {favoriteLangue: 'JavaScript'}, current: {name: 'Joe'}
2.) accumulator: {favoriteLangue: 'JavaScript', name: 'Joe'}, current: {age: 31}
3.) accumulator: {favoriteLange: 'JavaScript', name: 'Joe', age: 31}, current: {birthday: '1/1/1985'}
Final: {favoriteLangue: 'JavaScript', name: 'Joe', age: 31, birthday: '1/1/1985'}
*/
const fullPersonStart = duce((accumulator, current) => {
return Object.assign({}, accumulator, current);
},{favoriteLangue:'JavaScript'});
console.log('***FULL PERSON START:', fullPersonStart);
受Redux的启发,@ngrx/store具有操纵特定状态的Reducer功能的概念。Reducer接受⼀个state和action作为参数,暴露⼀个switch语句(⼀般来说,尽管这可以通过多种⽅式处理)定义reducer所涉及的action类型。每次分派⼀个action时,将调⽤注册到store的每个reducer(通过根reducer, 在应⽤程序引导时在provideStore中创建),传递该状态⽚段(累加器)的当前状态和已分派的action。如果reduce
r没有被注册来处理该action类型,则将执⾏适当的状态计算和状态输出的表⽰。如果没有那么该部分的当前状态将被返回。这是Store和Redux的状态管理核⼼。
Store / Redux 风格的Reducer
// Redux风格的Reducer
const person = (state = {}, action ) => {
pe){
ca 'ADD_INFO':
return Object.assign({}, state, action.payload);
default:
return state;
}
}
const infoAction = {type: 'ADD_INFO', payload: {name:'Brian', framework:'Angular'}};
const anotherPersonInfo = person(undefined, infoAction);
console.log('***REDUX STYLE PERSON***:', anotherPersonInfo);
//添加其他reducer
const hoursWorked = (state = 0, action) => {
pe) {
ca 'ADD_HOUR':
宝宝语录
return state + 1;
ca 'SUBTRACT_HOUR':
return state -1;
default:
return state;
}
}
//组合Reducers更新数据
const myReducers = { person, hoursWorked};
const combineReducers = reducers => (state = {}, action) => {
return Object.keys(reducers).reduce((nextState, key) => {
nextState[key] = reducers[key](state[key],action);
return nextState;
}, {});
};
/
*
这让我们⼤部的⽅式在那⾥,但真正希望我们想要的是第⼀个和第⼆个的值累加随着action随着时间推移。幸运的是,RxJS为这处情况提供了完美的操作符,将在下⼀课中讨论。
*/
const rootReducer = combineReducers(myReducers);
const firstState = rootReducer(undefined, {type:'ADD_INFO', payload:{name: 'Brian'}});
const condState = rootReducer({hoursWorked: 10, person: {name: 'Joe'}},{type:'ADD_HOUR'});
console.log('***FIRST STATE***:',firstState);
console.log('***SECOND STATE***:',condState);
通过根Reducer分派action9
使⽤scan操作符聚合状态
类似于reduce,但值随着时间的推移累加。。。