游乐游手机版
首页/前端开发/文章详情

@ngrx/store MemoizedSelector 详解

时间:2026-06-12 06:56
1 认识 Memoized Selector 的核心价值 在 @ngrx store featureState someData ); 这里的 memoizedSelector 会先从 featureSelector 获取 featureState,然后只提取出 someData 部分。如果输入

1. 认识 Memoized Selector 的核心价值

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

详解什么是@ngrx/store开发包中的MemoizedSelector

接下来,本文将带你深入剖析 MemoizedSelector 的工作原理、具体用法,以及它在真实项目中所带来的切实收益。最后,会通过一个完整的购物车示例,帮助你轻松将理论落实为代码实践。

在 Redux 应用中,状态管理的地位不言而喻。@ngrx/store 让 Angular 项目能够无缝接入 Redux 模式。而选择器(Selector)正是那把钥匙——用于从状态树中精准提取所需的数据片段。选择器允许我们集中组织状态的读取逻辑,显著提升代码的复用性。

@ngrx/store 中,选择器主要分为两种类型:

  • 普通选择器:每次调用都会重新计算并返回全新结果。如果状态处理逻辑较为复杂,频繁调用极易引发性能瓶颈。
  • Memoized 选择器:基于 reselect 实现,内部内置缓存机制。只要输入条件没有发生变化,就会直接返回缓存结果,而不会重新执行计算。这正是性能优化的关键所在。

2. Memoized Selector 的用法详解

在 Angular 项目中,我们主要通过 createFeatureSelectorcreateSelector 这两个函数来定义 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 订阅了 selectCartItemsselectCartTotal 这两个 Memoized 选择器,模板中使用 async 管道实时展示数据。当用户添加或删除商品时,只有真正依赖的状态片段发生变更,选择器才会推送新值,组件也随之更新。整个过程既高效又清晰。

5. 总结

MemoizedSelector@ngrx/store 中一个非常实用的特性,它通过记忆化机制极大优化了状态选择器的性能。借助 createFeatureSelectorcreateSelector,你可以轻松组合出复杂但高效的状态提取逻辑。

在实际项目中,将状态选择逻辑抽象为可复用的 Memoized 选择器,既能让组件保持简洁,又能提升整体性能。当然,也要注意避免滥用——如果创建过多细粒度的选择器,反而会增加内存开销。在性能与维护性之间找到平衡,才是最佳实践。

来源:https://www.jb51.net/javascript/293737mw3.htm
上一篇Spartacus CMS功能选择器实现解析 下一篇Angular路由守卫AuthGuard的创建与使用指南
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Layui弹出层监听子页面键盘快捷键实现方法
前端开发 · 2026-07-06

Layui弹出层监听子页面键盘快捷键实现方法

子页面键盘事件监听需在DOM加载完成后绑定,父页无法直接监听子页按键,必须由子页自身监听后通过parent或postMessage通知父页。典型写法为子页调用父页已定义的关闭函数。需注意焦点状态、输入法及layui版本兼容性等陷阱。

Layui表单提交时携带当前页面Meta信息的实现方法
前端开发 · 2026-07-06

Layui表单提交时携带当前页面Meta信息的实现方法

Layui表单提交不会自动携带页面Meta信息,需在form on( submit )回调中手动读取meta内容并拼接到表单数据,注意后端字段映射及特殊字符编码,多meta时按需选取。

HTML5拖拽事件流状态转移监控调试
前端开发 · 2026-07-06

HTML5拖拽事件流状态转移监控调试

HTML5拖拽事件流易因漏监听或未调用preventDefault而中断。需掌握dragstart设置数据、dragover接受放置、drop触发条件等关键点。通过统一日志捕获事件上下文、识别常见状态丢失场景并配合可视化面板,可清晰定位拖拽过程断点。

uni-app实现小红书商品详情图卡片切换
前端开发 · 2026-07-06

uni-app实现小红书商品详情图卡片切换

通过手写touch事件与transform控制五张卡片,动态计算translateX、scale、opacity及z-index模拟层叠滑动效果。滑动距离超过80rpx触发切换,否则复位。图片仅渲染当前及前后两张,有效优化加载性能与渲染效率。

图像旋转倾斜与扭曲的Canvas像素矩阵变换
前端开发 · 2026-07-06

图像旋转倾斜与扭曲的Canvas像素矩阵变换

Canvas图像变形本质是操作坐标系,图像被动跟随。旋转需先平移原点至目标中心再旋转后复位;倾斜通过仿射变换矩阵实现;扭曲无原生API,可用分块模拟或转用WebGL。每次变换前保存状态,完成后恢复,避免坐标系偏移。