12 - 状态管理
📖 学习目标
通过本章节学习,您将掌握:
- 状态管理的概念和重要性
- NgRx Store基础
- Actions、Reducers、Effects
- Selectors和DevTools
- 状态管理模式
- 与RxJS的集成
🎯 核心概念
1. 什么是状态管理?
状态管理是管理应用程序数据流和状态的模式,主要解决:
- 数据一致性:确保应用状态的一致性
- 可预测性:状态变化可追踪和调试
- 可维护性:复杂状态逻辑的集中管理
- 可测试性:状态逻辑的独立测试
2. 状态管理模式
用户操作 → Action → Reducer → Store → Selector → Component
↓
Effect → 异步操作 → Action → Reducer
3. NgRx核心概念
- Store:应用状态的单一数据源
- Action:描述状态变化的纯对象
- Reducer:纯函数,根据Action更新状态
- Effect:处理副作用(如HTTP请求)
- Selector:从Store中选择特定数据
🏗️ NgRx基础
1. 安装和配置
bash
# 安装NgRx
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools
# 安装RxJS(如果未安装)
npm install rxjs
2. 基本Store配置
typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { environment } from '../environments/environment';
import { AppComponent } from './app.component';
import { counterReducer } from './store/counter.reducer';
import { CounterEffects } from './store/counter.effects';
@NgModule({
declarations: [AppComponent],
imports: [
StoreModule.forRoot({ counter: counterReducer }),
EffectsModule.forRoot([CounterEffects]),
StoreDevtoolsModule.instrument({
maxAge: 25,
logOnly: environment.production
})
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
🔧 Actions
1. 定义Actions
typescript
// store/counter.actions.ts
import { createAction, props } from '@ngrx/store';
// 简单Action
export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const reset = createAction('[Counter] Reset');
// 带参数的Action
export const incrementBy = createAction(
'[Counter] Increment By',
props<{ amount: number }>()
);
export const setValue = createAction(
'[Counter] Set Value',
props<{ value: number }>()
);
// 异步Action
export const loadCounter = createAction('[Counter] Load');
export const loadCounterSuccess = createAction(
'[Counter] Load Success',
props<{ value: number }>()
);
export const loadCounterFailure = createAction(
'[Counter] Load Failure',
props<{ error: string }>()
);
2. 使用Actions
typescript
// counter.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { increment, decrement, reset, incrementBy } from '../store/counter.actions';
import { selectCount } from '../store/counter.selectors';
@Component({
selector: 'app-counter',
template: `
<div>
<h2>计数器: {{ count$ | async }}</h2>
<button (click)="onIncrement()">增加</button>
<button (click)="onDecrement()">减少</button>
<button (click)="onReset()">重置</button>
<button (click)="onIncrementBy(5)">增加5</button>
</div>
`
})
export class CounterComponent {
count$: Observable<number>;
constructor(private store: Store) {
this.count$ = this.store.select(selectCount);
}
onIncrement() {
this.store.dispatch(increment());
}
onDecrement() {
this.store.dispatch(decrement());
}
onReset() {
this.store.dispatch(reset());
}
onIncrementBy(amount: number) {
this.store.dispatch(incrementBy({ amount }));
}
}
🔄 Reducers
1. 定义Reducer
typescript
// store/counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset, incrementBy, setValue } from './counter.actions';
export interface CounterState {
value: number;
loading: boolean;
error: string | null;
}
export const initialState: CounterState = {
value: 0,
loading: false,
error: null
};
export const counterReducer = createReducer(
initialState,
on(increment, (state) => ({
...state,
value: state.value + 1
})),
on(decrement, (state) => ({
...state,
value: state.value - 1
})),
on(reset, (state) => ({
...state,
value: 0
})),
on(incrementBy, (state, { amount }) => ({
...state,
value: state.value + amount
})),
on(setValue, (state, { value }) => ({
...state,
value
}))
);
2. 复杂Reducer示例
typescript
// store/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import {
loadUsers,
loadUsersSuccess,
loadUsersFailure,
addUser,
updateUser,
deleteUser
} from './user.actions';
export interface User {
id: number;
name: string;
email: string;
role: string;
}
export interface UserState {
users: User[];
loading: boolean;
error: string | null;
selectedUser: User | null;
}
export const initialState: UserState = {
users: [],
loading: false,
error: null,
selectedUser: null
};
export const userReducer = createReducer(
initialState,
on(loadUsers, (state) => ({
...state,
loading: true,
error: null
})),
on(loadUsersSuccess, (state, { users }) => ({
...state,
users,
loading: false,
error: null
})),
on(loadUsersFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
on(addUser, (state, { user }) => ({
...state,
users: [...state.users, user]
})),
on(updateUser, (state, { user }) => ({
...state,
users: state.users.map(u => u.id === user.id ? user : u)
})),
on(deleteUser, (state, { id }) => ({
...state,
users: state.users.filter(u => u.id !== id)
}))
);
⚡ Effects
1. 定义Effects
typescript
// store/counter.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { map, catchError, switchMap } from 'rxjs/operators';
import { CounterService } from '../services/counter.service';
import { loadCounter, loadCounterSuccess, loadCounterFailure } from './counter.actions';
@Injectable()
export class CounterEffects {
loadCounter$ = createEffect(() =>
this.actions$.pipe(
ofType(loadCounter),
switchMap(() =>
this.counterService.getCounter().pipe(
map(value => loadCounterSuccess({ value })),
catchError(error => of(loadCounterFailure({ error: error.message })))
)
)
)
);
constructor(
private actions$: Actions,
private counterService: CounterService
) {}
}
2. 复杂Effects示例
typescript
// store/user.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { map, catchError, switchMap, tap } from 'rxjs/operators';
import { UserService } from '../services/user.service';
import {
loadUsers,
loadUsersSuccess,
loadUsersFailure,
addUser,
addUserSuccess,
addUserFailure
} from './user.actions';
@Injectable()
export class UserEffects {
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUsers),
switchMap(() =>
this.userService.getUsers().pipe(
map(users => loadUsersSuccess({ users })),
catchError(error => of(loadUsersFailure({ error: error.message })))
)
)
)
);
addUser$ = createEffect(() =>
this.actions$.pipe(
ofType(addUser),
switchMap(({ user }) =>
this.userService.createUser(user).pipe(
map(newUser => addUserSuccess({ user: newUser })),
catchError(error => of(addUserFailure({ error: error.message })))
)
)
)
);
// 副作用:显示成功消息
addUserSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(addUserSuccess),
tap(() => {
// 显示成功消息
console.log('用户添加成功');
})
),
{ dispatch: false }
);
constructor(
private actions$: Actions,
private userService: UserService
) {}
}
🎯 Selectors
1. 基本Selectors
typescript
// store/counter.selectors.ts
import { createSelector, createFeatureSelector } from '@ngrx/store';
import { CounterState } from './counter.reducer';
// 选择器函数
export const selectCounterState = createFeatureSelector<CounterState>('counter');
export const selectCount = createSelector(
selectCounterState,
(state: CounterState) => state.value
);
export const selectLoading = createSelector(
selectCounterState,
(state: CounterState) => state.loading
);
export const selectError = createSelector(
selectCounterState,
(state: CounterState) => state.error
);
2. 复杂Selectors
typescript
// store/user.selectors.ts
import { createSelector, createFeatureSelector } from '@ngrx/store';
import { UserState } from './user.reducer';
export const selectUserState = createFeatureSelector<UserState>('user');
export const selectUsers = createSelector(
selectUserState,
(state: UserState) => state.users
);
export const selectLoading = createSelector(
selectUserState,
(state: UserState) => state.loading
);
export const selectError = createSelector(
selectUserState,
(state: UserState) => state.error
);
export const selectSelectedUser = createSelector(
selectUserState,
(state: UserState) => state.selectedUser
);
// 派生选择器
export const selectActiveUsers = createSelector(
selectUsers,
(users) => users.filter(user => user.role === 'active')
);
export const selectUserCount = createSelector(
selectUsers,
(users) => users.length
);
// 组合选择器
export const selectUserStats = createSelector(
selectUsers,
selectUserCount,
(users, count) => ({
totalUsers: count,
activeUsers: users.filter(u => u.role === 'active').length,
inactiveUsers: users.filter(u => u.role === 'inactive').length
})
);
🎮 实践练习
练习1:创建用户管理状态
创建一个完整的用户管理状态管理系统,包括:
- 用户列表的加载、添加、更新、删除
- 加载状态和错误处理
- 用户搜索和过滤功能
练习2:实现购物车状态管理
实现一个购物车状态管理系统,包括:
- 添加/删除商品
- 更新商品数量
- 计算总价
- 持久化存储
📚 详细示例
完整的用户管理应用
typescript
// store/user.actions.ts
import { createAction, props } from '@ngrx/store';
import { User } from '../models/user';
export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction(
'[User] Load Users Success',
props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
'[User] Load Users Failure',
props<{ error: string }>()
);
export const addUser = createAction(
'[User] Add User',
props<{ user: Omit<User, 'id'> }>()
);
export const addUserSuccess = createAction(
'[User] Add User Success',
props<{ user: User }>()
);
export const addUserFailure = createAction(
'[User] Add User Failure',
props<{ error: string }>()
);
export const updateUser = createAction(
'[User] Update User',
props<{ user: User }>()
);
export const updateUserSuccess = createAction(
'[User] Update User Success',
props<{ user: User }>()
);
export const updateUserFailure = createAction(
'[User] Update User Failure',
props<{ error: string }>()
);
export const deleteUser = createAction(
'[User] Delete User',
props<{ id: number }>()
);
export const deleteUserSuccess = createAction(
'[User] Delete User Success',
props<{ id: number }>()
);
export const deleteUserFailure = createAction(
'[User] Delete User Failure',
props<{ error: string }>()
);
export const selectUser = createAction(
'[User] Select User',
props<{ user: User }>()
);
export const clearSelectedUser = createAction('[User] Clear Selected User');
export const searchUsers = createAction(
'[User] Search Users',
props<{ query: string }>()
);
typescript
// store/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { User } from '../models/user';
import * as UserActions from './user.actions';
export interface UserState {
users: User[];
selectedUser: User | null;
loading: boolean;
error: string | null;
searchQuery: string;
filteredUsers: User[];
}
export const initialState: UserState = {
users: [],
selectedUser: null,
loading: false,
error: null,
searchQuery: '',
filteredUsers: []
};
export const userReducer = createReducer(
initialState,
// 加载用户
on(UserActions.loadUsers, (state) => ({
...state,
loading: true,
error: null
})),
on(UserActions.loadUsersSuccess, (state, { users }) => ({
...state,
users,
filteredUsers: users,
loading: false,
error: null
})),
on(UserActions.loadUsersFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
// 添加用户
on(UserActions.addUser, (state) => ({
...state,
loading: true,
error: null
})),
on(UserActions.addUserSuccess, (state, { user }) => ({
...state,
users: [...state.users, user],
filteredUsers: [...state.filteredUsers, user],
loading: false,
error: null
})),
on(UserActions.addUserFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
// 更新用户
on(UserActions.updateUser, (state) => ({
...state,
loading: true,
error: null
})),
on(UserActions.updateUserSuccess, (state, { user }) => ({
...state,
users: state.users.map(u => u.id === user.id ? user : u),
filteredUsers: state.filteredUsers.map(u => u.id === user.id ? user : u),
selectedUser: state.selectedUser?.id === user.id ? user : state.selectedUser,
loading: false,
error: null
})),
on(UserActions.updateUserFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
// 删除用户
on(UserActions.deleteUser, (state) => ({
...state,
loading: true,
error: null
})),
on(UserActions.deleteUserSuccess, (state, { id }) => ({
...state,
users: state.users.filter(u => u.id !== id),
filteredUsers: state.filteredUsers.filter(u => u.id !== id),
selectedUser: state.selectedUser?.id === id ? null : state.selectedUser,
loading: false,
error: null
})),
on(UserActions.deleteUserFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
// 选择用户
on(UserActions.selectUser, (state, { user }) => ({
...state,
selectedUser: user
})),
on(UserActions.clearSelectedUser, (state) => ({
...state,
selectedUser: null
})),
// 搜索用户
on(UserActions.searchUsers, (state, { query }) => {
const filteredUsers = state.users.filter(user =>
user.name.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase())
);
return {
...state,
searchQuery: query,
filteredUsers
};
})
);
typescript
// store/user.selectors.ts
import { createSelector, createFeatureSelector } from '@ngrx/store';
import { UserState } from './user.reducer';
export const selectUserState = createFeatureSelector<UserState>('user');
export const selectUsers = createSelector(
selectUserState,
(state: UserState) => state.users
);
export const selectFilteredUsers = createSelector(
selectUserState,
(state: UserState) => state.filteredUsers
);
export const selectSelectedUser = createSelector(
selectUserState,
(state: UserState) => state.selectedUser
);
export const selectLoading = createSelector(
selectUserState,
(state: UserState) => state.loading
);
export const selectError = createSelector(
selectUserState,
(state: UserState) => state.error
);
export const selectSearchQuery = createSelector(
selectUserState,
(state: UserState) => state.searchQuery
);
export const selectUserCount = createSelector(
selectFilteredUsers,
(users) => users.length
);
export const selectUserStats = createSelector(
selectUsers,
(users) => ({
total: users.length,
active: users.filter(u => u.role === 'active').length,
inactive: users.filter(u => u.role === 'inactive').length
})
);
🔧 高级模式
1. 实体状态管理
typescript
// store/entity.state.ts
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import { User } from '../models/user';
export interface UserEntityState extends EntityState<User> {
loading: boolean;
error: string | null;
selectedUserId: number | null;
}
export const userAdapter: EntityAdapter<User> = createEntityAdapter<User>({
selectId: (user: User) => user.id,
sortComparer: (a: User, b: User) => a.name.localeCompare(b.name)
});
export const initialState: UserEntityState = userAdapter.getInitialState({
loading: false,
error: null,
selectedUserId: null
});
2. 元数据状态
typescript
// store/metadata.state.ts
export interface MetadataState {
lastUpdated: Date | null;
version: string;
isLoading: boolean;
error: string | null;
}
export const initialMetadataState: MetadataState = {
lastUpdated: null,
version: '1.0.0',
isLoading: false,
error: null
};
✅ 学习检查
完成本章节后,请确认您能够:
- [ ] 理解状态管理的概念和重要性
- [ ] 使用NgRx Store管理应用状态
- [ ] 创建和使用Actions
- [ ] 编写Reducers处理状态变化
- [ ] 使用Effects处理副作用
- [ ] 创建Selectors选择数据
- [ ] 使用DevTools调试状态
🚀 下一步
完成本章节学习后,请继续学习13-RxJS与异步编程。