Angular综合应用05,深入浅出 NgRx:Angular 状态管理的最佳实践

Angular综合应用05,深入浅出 NgRx:Angular 状态管理的最佳实践

在 Angular 应用开发中,随着业务复杂度提升,组件间的状态共享、数据流转管理会变得越来越棘手。NgRx 作为基于 Redux 模式的 Angular 状态管理库,能帮助我们构建可预测、可维护、可扩展的应用。本文将从核心概念入手,带你入门 NgRx,理解 Store、Action、Reducer 的核心逻辑。

一、为什么需要 NgRx?

在小型 Angular 应用中,我们可以通过@Input()/@Output()、服务单例等方式管理状态,但在中大型应用中,这些方式会暴露明显问题:

  • 状态分散在各个组件 / 服务中,追踪数据变化变得困难;
  • 组件间通信链路复杂,容易出现 "回调地狱";
  • 状态修改无统一规范,难以定位 Bug;
  • 缺乏状态变更的可追溯性,调试成本高。

NgRx 借鉴 Redux 的核心思想,通过单一数据源、状态只读、纯函数修改三大原则,解决上述问题,让应用状态管理更清晰。

二、NgRx 核心概念:Store、Action、Reducer

1. 核心思想铺垫

NgRx 的核心流程可以总结为:

简单来说:组件触发 Action,Reducer 根据 Action 类型修改状态,Store 存储最终状态,组件订阅 Store 获取最新状态。

2. 环境准备

首先需要安装 NgRx 核心依赖(以 Angular 17 为例):

# 安装核心包 npm install @ngrx/store @ngrx/effects --save # 安装开发工具(可选,用于调试) npm install @ngrx/store-devtools --save-dev 

3. Action:状态变更的 "指令"

Action 是描述发生了什么的纯对象,是修改状态的唯一入口。它必须包含type属性(唯一标识),可选包含payload(携带的数据)。

定义 Action

使用 NgRx 提供的createAction函数创建 Action,更简洁且类型安全:

// src/app/store/counter.actions.ts import { createAction, props } from '@ngrx/store'; // 增加计数 export const increment = createAction('[Counter] Increment'); // 减少计数 export const decrement = createAction('[Counter] Decrement'); // 设置计数(携带payload) export const setCount = createAction( '[Counter] Set Count', props<{ count: number }>() ); 
  • [Counter]是命名空间,用于区分不同模块的 Action;
  • props<T>()用于定义 Action 的负载类型,确保类型校验。

4. Reducer:状态修改的 "纯函数"

Reducer 是纯函数,负责根据 Action 的类型,接收当前状态Action,返回新状态。核心规则:

  • 不修改原状态,始终返回新对象;
  • 纯函数:相同输入必有相同输出,无副作用;
  • 只处理同步逻辑。
定义 Reducer
// src/app/store/counter.reducer.ts import { createReducer, on } from '@ngrx/store'; import * as CounterActions from './counter.actions'; // 定义状态类型 export interface CounterState { count: number; } // 初始状态 export const initialState: CounterState = { count: 0 }; // 创建Reducer export const counterReducer = createReducer( initialState, // 处理increment Action on(CounterActions.increment, (state) => ({ ...state, count: state.count + 1 })), // 处理decrement Action on(CounterActions.decrement, (state) => ({ ...state, count: state.count - 1 })), // 处理setCount Action(使用payload) on(CounterActions.setCount, (state, { count }) => ({ ...state, count: count })) ); 
  • createReducer:接收初始状态和多个on函数,构建 Reducer;
  • on(Action, reducerFn):关联 Action 和对应的状态处理逻辑;
  • 状态更新时使用...state解构原状态,保证不可变(Immutable)。

5. Store:状态的 "单一数据源"

Store 是 NgRx 的核心,是应用状态的容器,遵循 "单一数据源" 原则 —— 整个应用的核心状态集中存储在一个 Store 中。

注册 Store 到 Angular 模块

首先在根模块中注册 Reducer,将其关联到 Store:

// src/app/app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { StoreModule } from '@ngrx/store'; import { counterReducer } from './store/counter.reducer'; import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, // 注册Store,指定reducer(key为'counter',对应状态树的节点) StoreModule.forRoot({ counter: counterReducer }) ], providers: [], bootstrap: [AppComponent] }) export class AppModule {} 
  • StoreModule.forRoot():根模块中注册全局 Store,参数是 reducer 映射对象;
  • { counter: counterReducer }:表示状态树中有一个counter节点,由counterReducer管理。
组件中使用 Store

组件通过注入Store服务,订阅状态或分发 Action:

// src/app/app.component.ts import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as CounterActions from './store/counter.actions'; import { CounterState } from './store/counter.reducer'; @Component({ selector: 'app-root', template: ` <h1>NgRx Counter Demo</h1> <p>当前计数:{{ count$ | async }}</p> <button (click)="increment()">+</button> <button (click)="decrement()">-</button> <button (click)="setCount(10)">设置为10</button> ` }) export class AppComponent { // 订阅counter状态(使用async管道自动订阅/取消订阅) count$ = this.store.select(state => state.counter.count); constructor(private store: Store<{ counter: CounterState }>) {} // 分发increment Action increment() { this.store.dispatch(CounterActions.increment()); } // 分发decrement Action decrement() { this.store.dispatch(CounterActions.decrement()); } // 分发带payload的Action setCount(count: number) { this.store.dispatch(CounterActions.setCount({ count })); } } 

核心 API 说明:

  • store.select(selector):选择状态树中的指定节点,返回 Observable;
  • store.dispatch(action):分发 Action,触发 Reducer 修改状态;
  • async管道:简化 Observable 订阅,自动处理订阅 / 取消订阅,避免内存泄漏。

三、NgRx 调试:Store Devtools

为了更直观地调试状态变化,我们可以集成 NgRx Store Devtools(需配合浏览器扩展 Redux DevTools):

  1. 安装浏览器扩展:Chrome/Edge 商店搜索 "Redux DevTools" 并安装;
  2. 模块中注册 Devtools:
// app.module.ts import { StoreDevtoolsModule } from '@ngrx/store-devtools'; @NgModule({ imports: [ // ...其他模块 StoreModule.forRoot({ counter: counterReducer }), // 注册Devtools,仅在开发环境启用 StoreDevtoolsModule.instrument({ maxAge: 25, // 保留最近25次状态变更 logOnly: false // 开发环境可修改状态,生产环境设为true }) ] }) export class AppModule {} 

通过 Devtools,你可以:

  • 查看每一次 Action 分发的时间、类型、payload;
  • 回溯状态变更历史,甚至 "时光旅行"(回滚到指定状态);
  • 监控状态树的完整结构。

四、NgRx 核心原则回顾

  1. 单一数据源:整个应用的核心状态集中在一个 Store 中,便于统一管理;
  2. 状态只读:不能直接修改 Store 中的状态,只能通过分发 Action 触发修改;
  3. 纯函数修改:Reducer 是纯函数,只负责根据 Action 计算新状态,无副作用。

五、什么时候用 NgRx?

NgRx 虽好,但并非所有 Angular 应用都需要:

  • ✅ 适合:中大型应用、多组件共享状态、状态变更需可追溯、团队协作开发;
  • ❌ 不适合:小型应用、简单的组件间通信、快速原型开发(会增加冗余代码)。

总结

  1. NgRx 的核心是Store(状态容器)、Action(状态变更指令)、Reducer(纯函数修改状态),三者形成闭环,确保状态变更可预测;
  2. Action 是修改状态的唯一入口,必须包含type,可携带payload;Reducer 是纯函数,始终返回新状态,不修改原状态;
  3. Store 是单一数据源,组件通过dispatch分发 Action、select订阅状态,实现与状态的解耦。

掌握 NgRx 的核心逻辑后,你还可以进一步学习 Effects(处理异步逻辑)、Entity(管理集合数据)、Selector(优化状态选择)等高级特性,让 Angular 应用的状态管理更上一层楼。

Read more

Visual C++运行库终极修复指南:从诊断到部署的完整解决方案

Visual C++运行库终极修复指南:从诊断到部署的完整解决方案 【免费下载链接】vcredistAIO Repack for latest Microsoft Visual C++ Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 痛点诊断:识别运行库问题的根源 当你遇到程序闪退、dll文件缺失或安装失败时,首先要准确判断问题类型。通过观察错误现象,可以快速定位到具体的解决方案。 典型故障现象分析: * 静默失败型:安装程序运行后无任何提示,但软件依然无法启动 * 进度卡死型:安装进度条停滞在特定百分比,长时间无响应 * 版本冲突型:提示0x80070666错误,但无法在控制面板中找到对应组件 智能修复矩阵:精准匹配解决方案 日常维护方案 一键智能修复 - 适合大多数用户场景: VisualCppRedist_AIO_x86_x64.exe

By Ne0inhk
【C++】unordered系列容器使用及封装

【C++】unordered系列容器使用及封装

目录 一、unordered_map和unordered_set的使用 1. unordered_set系列的使用 1.1 unordered_set和unordered_multiset参考文档 1.2 unordered_set类的介绍 1.3 unordered_set和set的使用差异 1.4 unordered_map和map的使用差异 1.5 unordered_multimap/unordered_multiset 1.6 unordered_xxx的哈希相关接口 二、用哈希表封装myunordered_map和myunordered_set 1. 源码及框架分析 2. 模拟实现unordered_map和unordered_set 2.1 实现出复用哈希表的框架,并支持insert 2.

By Ne0inhk
C++进阶:(十六)从裸指针到智能指针,C++ 内存管理的 “自动驾驶” 进化之路

C++进阶:(十六)从裸指针到智能指针,C++ 内存管理的 “自动驾驶” 进化之路

目录 前言 一、裸指针的 “血泪史”:为什么我们需要智能指针? 1.1 内存泄漏:最常见的 “噩梦” 1.2 二次释放:致命的 “双重打击” 1.3 野指针:潜伏的 “幽灵” 1.4 异常安全:被忽略的 “隐形杀手” 1.5 智能指针的核心使命 二、智能指针的 “三驾马车”:unique_ptr、shared_ptr、weak_ptr 2.1 unique_ptr:独占所有权的 “独行侠” 2.1.1 unique_ptr 的核心原理

By Ne0inhk

C++ 动态 DP(Dynamic DP)详解:从入门到实战

一、什么是动态 DP? 动态 DP(Dynamic DP,简称 DDP)是一种结合了动态规划(DP) 和数据结构优化的高级算法。它主要解决带动态修改的 DP 问题—— 当问题中的参数(如节点权值、边权)发生变化时,能够高效更新 DP 结果,而无需重新计算整个 DP 过程。 普通的静态 DP 适用于参数固定的场景(如一次性计算树上的最大独立集),但如果参数频繁修改(如多次修改节点权值后重新求最大独立集),静态 DP 会因重复计算导致效率极低(时间复杂度可能从 O (n) 退化到 O (nq),q 为修改次数)。 动态 DP 的核心思想是:将 DP 状态的转移关系转化为可被数据结构(如线段树、树链剖分)

By Ne0inhk