Skip to content
 

NgRx 完整状态管理流程实战

更新: 8/13/2026字数: 0 字 时长: 0 分钟

本文通过一个完整的「用户管理」案例,从零到一演示 NgRx 状态管理的完整流程:定义状态 → Action → Reducer → Effect → Selector → 组件使用。

一、项目结构约定

一个标准的 NgRx 特性目录结构如下:

src/app/features/users/
├── store/
│   ├── users.actions.ts      # Action 定义
│   ├── users.reducer.ts      # Reducer
│   ├── users.effects.ts      # Effect(副作用)
│   ├── users.selectors.ts    # Selector
│   └── users.state.ts        # 状态模型(可选,单独抽离)
├── users.component.ts        # 组件
└── users.service.ts          # 数据服务

二、定义状态模型

typescript
// users.state.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export interface UsersState {
  users: User[];
  selectedUserId: number | null;
  loading: boolean;
  error: string | null;
}

export const initialState: UsersState = {
  users: [],
  selectedUserId: null,
  loading: false,
  error: null
};

三、定义 Action

typescript
// users.actions.ts
import { createAction, props } from "@ngrx/store";
import { User } from "./users.state";

// 加载
export const loadUsers = createAction("[Users] Load Users");
export const loadUsersSuccess = createAction("[Users] Load Users Success", props<{ users: User[] }>());
export const loadUsersFailure = createAction("[Users] Load Users Failure", props<{ error: string }>());

// 选择
export const selectUser = createAction("[Users] Select User", props<{ id: number }>());

// 新增
export const addUser = createAction("[Users] Add User", props<{ user: User }>());
export const addUserSuccess = createAction("[Users] Add User Success", props<{ user: User }>());

// 删除
export const deleteUser = createAction("[Users] Delete User", props<{ id: number }>());

四、编写 Reducer

typescript
// users.reducer.ts
import { createReducer, on } from "@ngrx/store";
import * as UsersActions from "./users.actions";
import { UsersState, initialState } from "./users.state";

export const usersReducer = createReducer(
  initialState,

  on(UsersActions.loadUsers, (state) => ({
    ...state,
    loading: true,
    error: null
  })),

  on(UsersActions.loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),

  on(UsersActions.loadUsersFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error
  })),

  on(UsersActions.selectUser, (state, { id }) => ({
    ...state,
    selectedUserId: id
  })),

  on(UsersActions.addUserSuccess, (state, { user }) => ({
    ...state,
    users: [...state.users, user]
  })),

  on(UsersActions.deleteUser, (state, { id }) => ({
    ...state,
    users: state.users.filter((u) => u.id !== id)
  }))
);

五、编写 Effect

typescript
// users.effects.ts
import { Injectable } from "@angular/core";
import { Actions, createEffect, ofType } from "@ngrx/effects";
import { of } from "rxjs";
import { map, mergeMap, catchError } from "rxjs/operators";
import * as UsersActions from "./users.actions";
import { UsersService } from "../users.service";

@Injectable()
export class UsersEffects {
  // 加载用户列表
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UsersActions.loadUsers),
      mergeMap(() =>
        this.usersService.getUsers().pipe(
          map((users) => UsersActions.loadUsersSuccess({ users })),
          catchError((error) => of(UsersActions.loadUsersFailure({ error: error.message })))
        )
      )
    )
  );

  // 新增用户
  addUser$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UsersActions.addUser),
      mergeMap(({ user }) =>
        this.usersService.addUser(user).pipe(
          map((created) => UsersActions.addUserSuccess({ user: created })),
          catchError((error) => of(UsersActions.loadUsersFailure({ error: error.message })))
        )
      )
    )
  );

  constructor(private actions$: Actions, private usersService: UsersService) {}
}

六、编写 Selector

typescript
// users.selectors.ts
import { createFeatureSelector, createSelector } from "@ngrx/store";
import { UsersState } from "./users.state";

export const selectUsersState = createFeatureSelector<UsersState>("users");

export const selectAllUsers = createSelector(selectUsersState, (state) => state.users);
export const selectLoading = createSelector(selectUsersState, (state) => state.loading);
export const selectError = createSelector(selectUsersState, (state) => state.error);
export const selectSelectedUserId = createSelector(selectUsersState, (state) => state.selectedUserId);

// 组合选择器:根据选中的 id 获取具体用户
export const selectSelectedUser = createSelector(selectAllUsers, selectSelectedUserId, (users, id) =>
  users.find((u) => u.id === id) || null
);

七、注册模块

typescript
// app.module.ts(或独立组件应用的 providers)
import { StoreModule } from "@ngrx/store";
import { EffectsModule } from "@ngrx/effects";
import { usersReducer } from "./users/store/users.reducer";
import { UsersEffects } from "./users/store/users.effects";

@NgModule({
  imports: [
    StoreModule.forRoot({ users: usersReducer }),
    EffectsModule.forRoot([UsersEffects])
  ]
})
export class AppModule {}

独立组件应用(app.config.ts):

typescript
import { provideStore } from "@ngrx/store";
import { provideEffects } from "@ngrx/effects";
import { usersReducer } from "./users/store/users.reducer";
import { UsersEffects } from "./users/store/users.effects";

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore({ users: usersReducer }),
    provideEffects([UsersEffects])
  ]
};

八、组件中使用

typescript
// users.component.ts
import { Component, OnInit } from "@angular/core";
import { Store } from "@ngrx/store";
import { Observable } from "rxjs";
import { User } from "./store/users.state";
import * as UsersActions from "./store/users.actions";
import { selectAllUsers, selectLoading, selectError, selectSelectedUser } from "./store/users.selectors";

@Component({
  selector: "app-users",
  template: `
    <h2>用户管理</h2>

    <div *ngIf="loading$ | async">加载中...</div>
    <div *ngIf="error$ | async" style="color: red">{{ error$ | async }}</div>

    <ul>
      <li *ngFor="let user of users$ | async" (click)="onSelect(user.id)">
        {{ user.name }} - {{ user.email }}
      </li>
    </ul>

    <div *ngIf="selectedUser$ | async as selected">
      <p>当前选中:{{ selected.name }}</p>
    </div>
  `
})
export class UsersComponent implements OnInit {
  users$: Observable<User[]> = this.store.select(selectAllUsers);
  loading$ = this.store.select(selectLoading);
  error$ = this.store.select(selectError);
  selectedUser$ = this.store.select(selectSelectedUser);

  constructor(private store: Store) {}

  ngOnInit() {
    this.store.dispatch(UsersActions.loadUsers());
  }

  onSelect(id: number) {
    this.store.dispatch(UsersActions.selectUser({ id }));
  }
}

九、完整流程回顾

  1. 组件 dispatch loadUsers Action
  2. Effect 监听到 loadUsers,调用服务发请求
  3. 请求成功后 Effect dispatch loadUsersSuccess
  4. Reducer 处理 loadUsersSuccess,更新 Store 状态
  5. Selector 从 Store 提取数据
  6. 组件 通过 select 订阅到最新数据并渲染

十、常见问题解答

Q1:为什么加载要拆成 loadUsers / loadUsersSuccess / loadUsersFailure 三个 Action?

  • 这是 NgRx 的推荐模式,把「开始请求」「成功」「失败」三种状态分离,让状态机清晰、便于在 Reducer 中精确处理每种情况。

Q2:State、Action、Reducer 等文件可以合并吗?

  • 可以,但官方推荐按职责拆分文件,遵循「每个特性一个 store 目录」的约定,便于维护和团队协作。

Q3:Effects 里的 mergeMap 能换成 switchMap 吗?

  • 视场景而定:需要取消旧请求(如搜索)用 switchMap,需要并行/不取消(如列表加载)用 mergeMap