1. 认识 Memoized Selector 的核心价值
在 @ngrx/storeMemoizedSelector 是尤为关键的核心概念,也是优化 Angular 应用性能的利器。简而言之,它借助 reselect 库,让状态选择器拥有“记忆”能力——缓存计算结果,避免重复执行计算逻辑。如此一来,应用的响应速度与运行效率都能够获得显著提升。

接下来,本文将带你深入剖析 MemoizedSelector 的工作原理、具体用法,以及它在真实项目中所带来的切实收益。最后,会通过一个完整的购物车示例,帮助你轻松将理论落实为代码实践。
在 Redux 应用中,状态管理的地位不言而喻。@ngrx/store 让 Angular 项目能够无缝接入 Redux 模式。而选择器(Selector)正是那把钥匙——用于从状态树中精准提取所需的数据片段。选择器允许我们集中组织状态的读取逻辑,显著提升代码的复用性。
在 @ngrx/store 中,选择器主要分为两种类型:
- 普通选择器:每次调用都会重新计算并返回全新结果。如果状态处理逻辑较为复杂,频繁调用极易引发性能瓶颈。
- Memoized 选择器:基于
reselect实现,内部内置缓存机制。只要输入条件没有发生变化,就会直接返回缓存结果,而不会重新执行计算。这正是性能优化的关键所在。
2. Memoized Selector 的用法详解
在 Angular 项目中,我们主要通过 createFeatureSelector 和 createSelector 这两个函数来定义 Memoized 选择器。前者用于锁定某个 feature 模块的状态,后者则用于创建具体的记忆化选择器。
2.1 创建 Feature 选择器(createFeatureSelector)
createFeatureSelector 接收一个特征标识符(字符串),返回一个选择器函数,该函数能从根状态中提取出对应 feature 模块下的完整状态片段。
import { createFeatureSelector } from '@ngrx/store';
const featureSelector = createFeatureSelector('featureName');
上面这段代码创建了一个名为 featureSelector 的 Feature 选择器,并将其绑定到 'featureName' 标识符。之后只需调用它,就能直接获取 featureName 模块下的全部状态内容。
2.2 创建 Memoized 选择器(createSelector)
createSelector 是真正的核心函数。它接收若干个输入选择器(可以是 Feature 选择器或其他 Memoized 选择器),然后附加一个输出函数,用于从输入结果中提取或计算最终所需的数据。
import { createSelector } from '@ngrx/store';
const memoizedSelector = createSelector(
featureSelector,
(featureState) => featureState.someData
);
这里的 memoizedSelector 会先从 featureSelector 获取 featureState,然后只提取出 .someData 部分。如果输入条件(即 featureState 对象引用)没有发生变化,它会直接返回上次缓存的结果,不再重复执行读取操作。
2.3 使用 Memoized 选择器
实际使用非常自然——像调用普通函数一样调用选择器,然后将其交给 store 的 select 方法即可。
store.select(memoizedSelector).subscribe((data) => {
console.log('Selected data:', data);
});
当状态发生变化时,memoizedSelector 会自动判断是否需要重新计算;只有依赖的输入发生了变更,才会产生新结果。这保证了组件不会因为无关的状态改动而频繁重渲染。
3. Memoized Selector 的核心优势
清楚了用法之后,再聊聊它究竟强在哪里。
3.1 显著提升性能
记忆化缓存直接省掉了大量重复计算。特别是在大型应用或复杂的状态推导场景中,效果立竿见影。输入条件不变时,结果直接取自缓存,响应速度自然大幅提升。
3.2 减少不必要的状态更新
由于缓存机制的存在,只有当真正依赖的数据发生改变时,选择器才会返回新值。这能够有效降低组件和管道的重新渲染次数,整个应用因此运行更加流畅。
3.3 支持复杂的状态计算
通过组合多个选择器和输出函数,你可以轻松实现多层嵌套的状态转换。而缓存机制能确保这种复杂计算仅在必要时执行,不会白白浪费性能资源。
3.4 提升代码复用与组织能力
将状态选择逻辑从组件中剥离出来,集中到选择器文件中,然后在多个组件间复用。组件变得更加轻量,维护成本也直线下降。
4. 实例:使用 Memoized 选择器管理购物车状态
理论讲再多,不如一个实际例子来得直观。下面我们用购物车场景来演示 Memoized 选择器如何高效管理复杂状态。
假设购物车包含多个商品,每个商品有 ID、名称、价格和数量。购物车状态则是所有商品的集合。
先定义接口:
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
interface CartState {
items: CartItem[];
total: number;
}
接着创建 NgRx 特性模块,定义 action 和 reducer。
// 购物车 actions 文件
import { createAction, props } from '@ngrx/store';
import { CartItem } from './cart-state.interface';
export const addToCart = createAction(
'[Cart] Add To Cart',
props<{ item: CartItem }>()
);
export const removeFromCart = createAction(
'[Cart] Remove From Cart',
props<{ itemId: number }>()
);
// 购物车 reducer 文件
import { createReducer, on } from '@ngrx/store';
import { CartState, CartItem } from './cart-state.interface';
import { addToCart, removeFromCart } from './cart.actions';
const initialState: CartState = {
items: [],
total: 0,
};
export const cartReducer = createReducer(
initialState,
on(addToCart, (state, { item }) => {
const existingItem = state.items.find((i) => i.id === item.id);
if (existingItem) {
const updatedItems = state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
);
return {
...state,
items: updatedItems,
total: state.total + item.price,
};
} else {
const newItem: CartItem = { ...item, quantity: 1 };
return {
...state,
items: [...state.items, newItem],
total: state.total + item.price,
};
}
}),
on(removeFromCart, (state, { itemId }) => {
const updatedItems = state.items.filter((i) => i.id !== itemId);
const removedItem = state.items.find((i) => i.id === itemId);
return {
...state,
items: updatedItems,
total: state.total - (removedItem ? removedItem.price * removedItem.quantity : 0),
};
})
);
然后是最关键的部分——创建 Memoized 选择器:
// 购物车选择器文件
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { CartState } from './cart-state.interface';
export const selectCartState = createFeatureSelector('cart');
export const selectCartItems = createSelector(
selectCartState,
(cartState) => cartState.items
);
export const selectCartTotal = createSelector(
selectCartState,
(cartState) => cartState.total
);
在组件中使用这些选择器,就像呼吸一样自然:
// 购物车组件文件
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { CartItem } from './cart-state.interface';
import { addToCart, removeFromCart } from './cart.actions';
import { selectCartItems, selectCartTotal } from './cart.selectors';
import { Observable } from 'rxjs';
@Component({
selector: 'app-cart',
template: `
Shopping Cart
{{ item.name }} - Quantity: {{ item.quantity }} - Price: ${{ item.price }}
Total Price: ${{ cartTotal$ | async }}
`,
})
export class CartComponent {
cartItems$: Observable;
cartTotal$: Observable;
constructor(private store: Store) {
this.cartItems$ = this.store.select(selectCartItems);
this.cartTotal$ = this.store.select(selectCartTotal);
}
addItemToCart(item: CartItem) {
this.store.dispatch(addToCart({ item }));
}
removeItemFromCart(itemId: number) {
this.store.dispatch(removeFromCart({ itemId }));
}
}
组件通过 store.select 订阅了 selectCartItems 和 selectCartTotal 这两个 Memoized 选择器,模板中使用 async 管道实时展示数据。当用户添加或删除商品时,只有真正依赖的状态片段发生变更,选择器才会推送新值,组件也随之更新。整个过程既高效又清晰。
5. 总结
MemoizedSelector 是 @ngrx/store 中一个非常实用的特性,它通过记忆化机制极大优化了状态选择器的性能。借助 createFeatureSelector 和 createSelector,你可以轻松组合出复杂但高效的状态提取逻辑。
在实际项目中,将状态选择逻辑抽象为可复用的 Memoized 选择器,既能让组件保持简洁,又能提升整体性能。当然,也要注意避免滥用——如果创建过多细粒度的选择器,反而会增加内存开销。在性能与维护性之间找到平衡,才是最佳实践。
