@ngrx/store中的MemoizedSelector详解

作者:技术拾光

时间:2026-06-11

浏览:452

1. 了解 Memoized Selector 在 @ngrx/store 生态中,MemoizedSelector 是一个核心概念,也是优化性能的重要手段。简单来说,它借助 reselect 库,让状态选择器具备“记忆”能力——缓存计算结果,避免重复计算。这样,应用的响应速度和效率都能得到显著提升

文章目录

1. 了解 Memoized Selector

在 @ngrx/store 生态中,MemoizedSelector 是一个核心概念,也是优化性能的重要手段。简单来说,它借助 reselect 库,让状态选择器具备“记忆”能力——缓存计算结果,避免重复计算。这样,应用的响应速度和效率都能得到显著提升。

@ngrx/store中的MemoizedSelector详解

接下来这篇文章将详细拆解 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。

// cart.actions.ts
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 }>()
);

// cart.reducer.ts
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 选择器:

// cart.selectors.ts
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
);

在组件里使用这些选择器,就像呼吸一样自然:

// cart.component.ts
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 选择器,既能让组件保持简洁,又提升了整体性能。当然,也要避免滥用——如果创建太多细粒度的选择器,反而会增加内存开销。平衡好性能与维护性,才是最佳实践。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章