Ionic状态管理 #

一、状态管理概述 #

1.1 为什么需要状态管理 #

text
问题:
- 组件间数据共享困难
- 状态变化难以追踪
- 异步数据流复杂
- 代码难以维护

解决方案:
- 集中式状态管理
- 单向数据流
- 可预测的状态变化

1.2 状态管理方案 #

方案 复杂度 适用场景
Service 简单应用
RxJS Subject 中等应用
NgRx 大型应用

二、Service状态管理 #

2.1 基本Service #

typescript
// services/user.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

export interface User {
  id: string;
  name: string;
  email: string;
}

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private usersSubject = new BehaviorSubject<User[]>([]);
  private currentUserSubject = new BehaviorSubject<User | null>(null);
  
  users$ = this.usersSubject.asObservable();
  currentUser$ = this.currentUserSubject.asObservable();
  
  setUsers(users: User[]) {
    this.usersSubject.next(users);
  }
  
  setCurrentUser(user: User | null) {
    this.currentUserSubject.next(user);
  }
  
  addUser(user: User) {
    const users = [...this.usersSubject.value, user];
    this.usersSubject.next(users);
  }
  
  removeUser(id: string) {
    const users = this.usersSubject.value.filter(u => u.id !== id);
    this.usersSubject.next(users);
  }
  
  getUsers(): User[] {
    return this.usersSubject.value;
  }
  
  getCurrentUser(): User | null {
    return this.currentUserSubject.value;
  }
}

2.2 使用Service状态 #

typescript
import { Component, OnInit } from '@angular/core';
import { UserService, User } from './user.service';

@Component({
  selector: 'app-users',
  templateUrl: 'users.page.html'
})
export class UsersPage implements OnInit {
  users$: Observable<User[]>;
  
  constructor(private userService: UserService) {
    this.users$ = this.userService.users$;
  }
  
  ngOnInit() {
    this.loadUsers();
  }
  
  loadUsers() {
    // 从API获取数据
    this.api.getUsers().subscribe(users => {
      this.userService.setUsers(users);
    });
  }
  
  addUser() {
    this.userService.addUser({
      id: '1',
      name: '新用户',
      email: 'user@example.com'
    });
  }
}

三、RxJS状态管理 #

3.1 状态Store #

typescript
// store/store.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, map } from 'rxjs';

export interface State {
  users: User[];
  currentUser: User | null;
  isLoading: boolean;
  error: string | null;
}

const initialState: State = {
  users: [],
  currentUser: null,
  isLoading: false,
  error: null
};

@Injectable({
  providedIn: 'root'
})
export class Store {
  private stateSubject = new BehaviorSubject<State>(initialState);
  
  state$ = this.stateSubject.asObservable();
  
  // 选择器
  users$ = this.state$.pipe(map(state => state.users));
  currentUser$ = this.state$.pipe(map(state => state.currentUser));
  isLoading$ = this.state$.pipe(map(state => state.isLoading));
  error$ = this.state$.pipe(map(state => state.error));
  
  // 获取当前状态
  getState(): State {
    return this.stateSubject.value;
  }
  
  // 更新状态
  setState(partialState: Partial<State>) {
    this.stateSubject.next({
      ...this.getState(),
      ...partialState
    });
  }
  
  // 重置状态
  reset() {
    this.stateSubject.next(initialState);
  }
}

3.2 Actions #

typescript
// store/actions.ts
export enum ActionType {
  LOAD_USERS = '[Users] Load Users',
  LOAD_USERS_SUCCESS = '[Users] Load Users Success',
  LOAD_USERS_FAILURE = '[Users] Load Users Failure',
  ADD_USER = '[Users] Add User',
  SET_CURRENT_USER = '[Users] Set Current User'
}

export interface Action {
  type: ActionType;
  payload?: any;
}

export const loadUsers = (): Action => ({
  type: ActionType.LOAD_USERS
});

export const loadUsersSuccess = (users: User[]): Action => ({
  type: ActionType.LOAD_USERS_SUCCESS,
  payload: users
});

export const loadUsersFailure = (error: string): Action => ({
  type: ActionType.LOAD_USERS_FAILURE,
  payload: error
});

3.3 Reducer #

typescript
// store/reducer.ts
import { State, Action, ActionType } from './types';

export function reducer(state: State, action: Action): State {
  switch (action.type) {
    case ActionType.LOAD_USERS:
      return {
        ...state,
        isLoading: true,
        error: null
      };
    
    case ActionType.LOAD_USERS_SUCCESS:
      return {
        ...state,
        users: action.payload,
        isLoading: false
      };
    
    case ActionType.LOAD_USERS_FAILURE:
      return {
        ...state,
        isLoading: false,
        error: action.payload
      };
    
    case ActionType.ADD_USER:
      return {
        ...state,
        users: [...state.users, action.payload]
      };
    
    case ActionType.SET_CURRENT_USER:
      return {
        ...state,
        currentUser: action.payload
      };
    
    default:
      return state;
  }
}

四、NgRx状态管理 #

4.1 安装NgRx #

bash
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools

4.2 State定义 #

typescript
// store/user/user.state.ts
export interface UserState {
  users: User[];
  currentUser: User | null;
  loading: boolean;
  error: string | null;
}

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

4.3 Actions #

typescript
// store/user/user.actions.ts
import { createAction, props } from '@ngrx/store';
import { User } from '../../models/user.model';

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 addUser = createAction('[Users] Add User', props<{ user: User }>());
export const setCurrentUser = createAction('[Users] Set Current User', props<{ user: User | null }>());

4.4 Reducer #

typescript
// store/user/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { UserState, initialState } from './user.state';
import * as UserActions from './user.actions';

export const userReducer = createReducer(
  initialState,
  
  on(UserActions.loadUsers, (state) => ({
    ...state,
    loading: true,
    error: null
  })),
  
  on(UserActions.loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),
  
  on(UserActions.loadUsersFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error
  })),
  
  on(UserActions.addUser, (state, { user }) => ({
    ...state,
    users: [...state.users, user]
  })),
  
  on(UserActions.setCurrentUser, (state, { user }) => ({
    ...state,
    currentUser: user
  }))
);

4.5 Selectors #

typescript
// store/user/user.selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { UserState } from './user.state';

export const selectUserState = createFeatureSelector<UserState>('users');

export const selectUsers = createSelector(
  selectUserState,
  (state) => state.users
);

export const selectCurrentUser = createSelector(
  selectUserState,
  (state) => state.currentUser
);

export const selectLoading = createSelector(
  selectUserState,
  (state) => state.loading
);

export const selectError = createSelector(
  selectUserState,
  (state) => state.error
);

export const selectUserById = (id: string) => createSelector(
  selectUsers,
  (users) => users.find(user => user.id === id)
);

4.6 Effects #

typescript
// store/user/user.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 { UserService } from '../../services/user.service';
import * as UserActions from './user.actions';

@Injectable()
export class UserEffects {
  
  loadUsers$ = createEffect(() => this.actions$.pipe(
    ofType(UserActions.loadUsers),
    mergeMap(() => this.userService.getUsers().pipe(
      map(users => UserActions.loadUsersSuccess({ users })),
      catchError(error => of(UserActions.loadUsersFailure({ error: error.message })))
    ))
  ));
  
  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

4.7 Store配置 #

typescript
// store/index.ts
import { ActionReducerMap } from '@ngrx/store';
import { UserState, userReducer } from './user/user.reducer';

export interface AppState {
  users: UserState;
}

export const reducers: ActionReducerMap<AppState> = {
  users: userReducer
};

// app.module.ts
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { reducers } from './store';
import { UserEffects } from './store/user/user.effects';

@NgModule({
  imports: [
    StoreModule.forRoot(reducers),
    EffectsModule.forRoot([UserEffects]),
    StoreDevtoolsModule.instrument({ maxAge: 25 })
  ]
})
export class AppModule {}

4.8 使用Store #

typescript
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { AppState } from './store';
import * as UserActions from './store/user/user.actions';
import * as UserSelectors from './store/user/user.selectors';

@Component({
  selector: 'app-users',
  templateUrl: 'users.page.html'
})
export class UsersPage implements OnInit {
  users$: Observable<User[]>;
  loading$: Observable<boolean>;
  
  constructor(private store: Store<AppState>) {
    this.users$ = this.store.select(UserSelectors.selectUsers);
    this.loading$ = this.store.select(UserSelectors.selectLoading);
  }
  
  ngOnInit() {
    this.store.dispatch(UserActions.loadUsers());
  }
  
  addUser() {
    this.store.dispatch(UserActions.addUser({
      user: {
        id: '1',
        name: '新用户',
        email: 'user@example.com'
      }
    }));
  }
}

五、最佳实践 #

5.1 状态设计原则 #

text
原则:
- 单一数据源
- 状态只读
- 纯函数修改
- 扁平化结构

5.2 性能优化 #

typescript
// 使用memoized选择器
export const selectUserCount = createSelector(
  selectUsers,
  (users) => users.length
);

// 使用distinctUntilChanged
this.users$.pipe(
  distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
);

六、总结 #

6.1 方案选择 #

应用规模 推荐方案
小型 Service
中型 RxJS Subject
大型 NgRx

6.2 下一步 #

掌握了状态管理后,接下来让我们学习 Capacitor基础,了解Ionic的原生功能!

最后更新:2026-03-28