在Angular之store浅学(一)中已经对store的相关概念和用法进行了比较全面的浅析,在本篇博文中我们将着重学习Store中的selector。
在文章正式开始前,让我们先借鉴一下Angular 17+NGRX 状态管理大佬的分析图进行简单的 复习及预习:
Store概念复习:
-
基本概念
- Store: 是一个集中式的状态容器,它保存了应用程序的状态,并允许组件通过dispatch 方法发送操作(actions)来更改状态。
- Actions: 是表示应用程序事件的对象,通常用于描述将要发生的事情。例如:{type: 'ADD_TODO', payload: {text:'Learn NgRx'}}。
- Reducers: 是纯函数,定义了如何根据不同的 action 更新 state。
- Selectors: 是纯函数,用于从 Store 中选择和派生特定的数据片段。
-
设置 Store
- 定义 State: 定义一个接口来描述状态的形状。
- 创建 Actions: 定义不同的操作类型。
- 创建 Reducers: 编写 reducer 函数来处理不同的 actions 并更新 state。
- 创建 Selectors: 编写 selectors 来从 state 中选择特定的数据。
- 使用 Store: 在组件中注入 Store 并使用它来分发 actions 或选择数据。
Selector 方法的触发时机
Selector是用来从 Store 中获取状态的工具函数。它们会根据状态的变化来派发新的数据,但并不是立即触发的,而是有一些触发时机:
-
状态变化: 当 Store 中的状态通过 Reducer 更新时,所有依赖该状态的 selectors 都会重新计算。如果一个 selector 依赖于另一个 selector,并且后者的状态发生变化,那么前者也会重新计算。
-
组件订阅: 在组件中,通常通过select 方法来获取数据流。如果组件订阅了某个 selector,那么当 Store 中的状态发生变化并且 selector 重新计算时,组件会接收到新的值。
-
Selector 缓存: NgRx 使用reselect 库提供的 memoization 功能来缓存 selector 的计算结果,只有当 selector 的输入发生变化时才会重新计算。这确保了性能优化。
谈到selector的触发时机,那么就不得不提一下订阅store的时候不同的写法之间的稍许差异,就以上面第五点中的三种方法来看,具体的解析如下:
示例的公共代码如下:
// state 中初始化了两个变量 a, b
export interface AppState {
a: number;
b: string;
}
export const initialState: AppState = {
a: 1,
b: 'madsion'
};
// reducer 函数
function myReducer(state = initialState, action: SomeAction): AppState {
switch (action.type) {
case 'MY_ACTION':
// Just return the current state
return state;
// other cases
default:
return state;
}
}
第一种订阅store的写法:
// selector 选择器中的代码
this.store.select(state => state).subscribe(state => {
console.log(state); // Logging the value of `a`
});
默认行为
-
Selector 触发次数:
- this.store.select(state => state)是一个直接返回整个 state 的选择器。由于state 是整个应用程序的状态,每次 store 更新时,选择器函数都会被调用。
- 每当有新的 action 被派发并处理完毕(即 reducer 执行并返回新的 state),this.store.select(state => state) 会检测到 state 的变化并发出新的值。如果 action 只是返回当前的 state(即不修改 state),状态实际上不会发生变化,但store.select 仍然会重新发出 state 作为新的值。因此,每个派发的 action 都会导致select函数触发。
-
console.log(state)
执行次数:- 因为state => state返回的是整个state,每次 store 更新时这个选择器都会发出新的值,即(整个状态对象)被发送到subscribe函数中,因此每次 store 更新时,console.log(state)都会执行一次,即使你没有显式地修改state,只要有新的 action 触发,subscribe会响应每次state的发出console.log(state)都会执行。
总结
- Selector 函数触发次数:每次 store 更新时,无论状态是否实际发生变化,选择器都会触发,并发出当前的 state。
console.log(state)
执行次数:每次 store 更新时(即每次有 action 被处理时),console.log(state)会执行一次。即使 action 不改变状态,这个日志输出也会因为选择器每次都发出新的 state 而被执行。- 这种设置适用于需要监控整个 store 状态的情况。
第二种/第三种订阅store的写法:
// selector 选择器中的代码
this.store.select(state => state.a).subscribe(state => {
console.log(state.a); // Logging the value of `a`
});
默认行为
Selector 触发次数:
- 在@ngrx/store中,默认情况下,select操作会根据选择器的输入进行 memoization。也就是说,如果选择器的输入值没有变化(例如,state.a 没有变化),selector的订阅不会触发新的通知。选择器会返回缓存的结果。
- 如果你的 action 只是简单地返回当前的 state,没有对state.a 做出修改,那么state.a 在 store 中的值没有变化。因此,this.store.select(state => state.a) 不会重新发出值。
console.log(state.a)
执行次数:
- 如果state.a 没有变化,那么console.log(state.a)不会被执行,因为selector 没有发出新的值。
- 如果state.a 发生了变化,console.log(state.a)会被执行一次,输出新值。
总结
selector
函数的调用次数:不会多次触发selector 函数的调用,前提是state.a没有发生变化。选择器的 memoization 会防止不必要的重新计算。console.log(a)
的执行次数:console.log(state.a) 只会在state.a 变化时执MY_ACTION 没有改变state.a ,那么console.log(state.a) 不会执行。
Memoization机制
在 Angular 的 @ngrx/store 中,memoization 是用于优化选择器(selectors)的性能的一种技术。选择器是从 store 中派生数据的函数,而 memoization 可以避免在相同的输入值下重新计算结果。通常,memoization 是启用的,帮助提高应用程序的性能。这意味着,选择器只会在其输入值发生变化时重新计算结果。如果选择器的输入值没有变化,它会返回缓存的结果。