React中的Action简介

引言

在React开发中,"Action方法"并非React核心库本身定义的一个概念,而是源于状态管理模式和库(如Redux、Flux、MobX以及React内置的 useReducer Hook)中的核心组成部分。它旨在描述应用程序中发生的事件,并将这些事件传达给状态管理器,以触发状态的更新。理解Action方法的作用、实现方式、优缺点,对于构建可维护、可预测的React应用至关重要。

什么是Action方法

Action方法,或者更准确地说是“Action”(动作),是应用程序中发生的事件的纯粹描述。它是一个包含描述事件信息的对象(在Redux/Flux/ useReducer 中),或者是一个修改状态的函数(在MobX中)。

核心思想:

  • 描述发生了什么,而不是如何改变状态。 Action是“意图”的声明,例如“用户点击了增加按钮”或“数据加载成功”。
  • 作为状态更新的触发器。 当一个Action被“分发”(dispatch)时,它会传递给应用程序的状态管理器(如Reducer),由状态管理器根据Action的类型来决定如何更新状态。

常见场景:

  • Redux/Flux: Action是一个带有 type 属性(通常是大写字符串常量)的普通JavaScript对象,用于指示发生的事件类型。它还可以包含一个 payload 属性,用于携带与该事件相关的数据。
  • React的 useReducer Hook: 与Redux类似,useReducer 也接收一个Action对象,通常包含 type 和可选的 payload
  • MobX: MobX中的Action通常是显式标记的函数,它们被允许修改MobX管理的响应式状态。

为什么写成Action

将状态更新的意图描述为"Action"有以下几个重要原因:

  1. 语义化和清晰性: "Action"一词清晰地表达了“发生了什么”的语义。它不是直接修改状态的指令,而是一个事件的记录。这使得代码更易于理解和推理。
  2. 关注点分离: Action将“什么发生”与“如何改变状态”分离。组件只需关心分发Action,而不必知道状态更新的具体逻辑。状态更新的逻辑集中在Reducer(或MobX的Action函数)中。
  3. 可追溯性与调试: 由于所有状态变更都由Action触发,并且Action通常是可序列化的对象,这使得应用程序的状态变化过程变得可预测和可追溯。在开发工具(如Redux DevTools)中,可以清晰地看到每个Action及其导致的状态变化,甚至可以进行时间旅行调试。
  4. 易于测试: Action Creator(生成Action的函数)是纯函数,易于单元测试。Reducer也是纯函数,同样易于测试。这大大提高了代码的可测试性。
  5. 中间件支持: 明确的Action机制为实现中间件(如Redux Thunk、Redux Saga)提供了基础,可以在Action分发前后执行异步操作、日志记录、条件判断等。

如何实现Action方法

我们将通过几种常见的状态管理方案来展示Action的实现。

Redux (经典实现)

Redux是JavaScript应用最流行的状态管理库之一,其核心思想是“单一数据源”、“状态只读”和“使用纯函数来修改状态”。

Action的组成:

  • Action: 一个普通的JavaScript对象,必须包含一个 type 属性,通常是一个字符串常量,描述了事件的类型。可以包含其他数据( payload )来传递事件相关的信息。
  • Action Creator: 一个函数,用于创建并返回一个Action对象。这有助于封装Action的创建过程,避免手动编写重复的Action对象。
  • Dispatch: store.dispatch(action) 是触发状态更新的唯一方式。它会把Action发送给Reducer。

示例代码:

// 1. 定义 Action 类型常量
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// 2. 定义 Action Creator (生成 Action 的函数)
function increment() {
  return {
    type: INCREMENT
  };
}

function decrement(amount) {
  return {
    type: DECREMENT,
    payload: amount
  };
}

// 3. 定义 Reducer (纯函数,根据 Action 更新状态)
const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 };
    case DECREMENT:
      return { ...state, count: state.count - action.payload };
    default:
      return state;
  }
}

// 4. 在组件中使用 (以一个简化的React组件为例,省略Redux Provider和connect/useSelector/useDispatch)
import React from 'react';
import { createStore } from 'redux';

// 创建Redux Store
const store = createStore(counterReducer);

function Counter() {
  const [count, setCount] = React.useState(store.getState().count);

  React.useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      setCount(store.getState().count);
    });
    return unsubscribe;
  }, []);

  const handleIncrement = () => {
    store.dispatch(increment()); // 分发 increment action
  };

  const handleDecrement = () => {
    store.dispatch(decrement(5)); // 分发 decrement action,并带上 payload
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement by 5</button>
    </div>
  );
}

export default Counter;

useReducer Hook (Context API结合)

useReducer 是React内置的Hook,用于复杂状态逻辑的组件。它的工作方式与Redux非常相似。

Action的组成:

  • Action: 与Redux类似,一个普通的JavaScript对象,通常包含 type 和可选的 payload
  • Dispatch: useReducer 返回的 dispatch 函数,用于分发Action。
  • Reducer: 一个纯函数,接收当前状态和Action,然后返回一个新的状态。

示例代码:

import React, { useReducer } from 'react';

// 1. 定义 Action 类型常量
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// 2. 定义 Reducer 函数
const initialState = { count: 0 };
function counterReducer(state, action) {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 };
    case DECREMENT:
      return { ...state, count: state.count - action.payload };
    default:
      return state;
  }
}

// 3. 在组件中使用 useReducer
function CounterWithReducer() {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  const handleIncrement = () => {
    dispatch({ type: INCREMENT }); // 直接分发 Action 对象
  };

  const handleDecrement = () => {
    dispatch({ type: DECREMENT, payload: 5 }); // 分发带 payload 的 Action 对象
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement by 5</button>
    </div>
  );
}

export default CounterWithReducer;

MobX (声明式实现)

MobX是一个响应式状态管理库,它采取了不同的方法。在MobX中,"Action"通常是修改可观察状态的函数或方法。MobX推荐在严格模式下只允许在Action中修改状态。

Action的组成:

  • Action函数/方法: 显式标记为Action的函数或类方法,它们负责直接修改MobX的可观察状态。
  • @action 装饰器或 action() 工具函数: 用于将普通函数标记为Action。

示例代码:

import React from 'react';
import { makeObservable, observable, action } from 'mobx';
import { observer } from 'mobx-react'; // 用于让React组件响应MobX状态变化

class CounterStore {
  @observable count = 0;

  constructor() {
    makeObservable(this); // 启用 MobX 观察性
  }

  @action // 使用 @action 装饰器标记为 Action
  increment() {
    this.count++;
  }

  @action
  decrement(amount) {
    this.count -= amount;
  }
}

const counterStore = new CounterStore(); // 创建Store实例

// 观察者组件,当 store 状态变化时会自动重新渲染
const MobxCounter = observer(() => {
  const handleIncrement = () => {
    counterStore.increment(); // 调用 Action 方法
  };

  const handleDecrement = () => {
    counterStore.decrement(5); // 调用 Action 方法并传入参数
  };

  return (
    <div>
      <p>Count: {counterStore.count}</p>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement by 5</button>
    </div>
  );
});

export default MobxCounter;

Action方法的优缺点

优点

  1. 可预测性 (Predictability): 所有状态变更都通过Action触发,并且通常由纯函数(Reducer)处理,这使得状态变化过程非常清晰和可预测。你总是知道什么Action会导致什么状态变化。
  2. 可追溯性与调试 (Traceability & Debugging): 由于Action是明确的事件记录,开发工具(如Redux DevTools)可以记录所有Action及其导致的状态变化。这使得调试变得异常强大,支持时间旅行调试、状态快照等。
  3. 关注点分离 (Separation of Concerns):
    • 组件专注于渲染UI和分发Action。
    • Action Creator专注于创建Action对象。
    • Reducer(或MobX Action函数)专注于根据Action更新状态。
      这种分离使得代码更清晰,各部分职责明确。
  4. 可测试性 (Testability): Action Creator和Reducer通常都是纯函数,它们只依赖于输入参数,不产生副作用,因此非常容易进行单元测试。
  5. 团队协作 (Team Collaboration): 在大型团队中,明确的Action和状态管理模式有助于统一开发规范,减少不同开发者之间对状态管理理解的差异。
  6. 中间件和增强功能: 明确的Action机制为集成各种中间件(如用于异步操作的Redux Thunk/Saga,用于日志记录的Logger等)提供了便利。

缺点

  1. 增加复杂性与样板代码 (Increased Complexity & Boilerplate): 特别是对于Redux,即使是简单的状态更新也可能需要定义Action类型、Action Creator、Reducer,以及在组件中连接Store。这会增加代码量和学习曲线。
  2. 学习曲线 (Learning Curve): 对于初学者来说,理解Action、Reducer、Store、Dispatch等概念以及它们如何协同工作可能需要一些时间。
  3. 对小型应用可能过度 (Overkill for Small Apps): 对于状态逻辑非常简单、组件间数据共享不多的应用程序,引入Action和完整的状态管理模式可能会带来不必要的开销和复杂性,而 useStateuseContext 可能已经足够。
  4. 异步操作的额外处理: 默认情况下,Action是同步的。处理异步操作(如API请求)需要额外的库(如Redux Thunk、Redux Saga)或模式,这又会增加一些复杂性。

Action小结

React中的"Action方法"是状态管理模式的核心概念,它并非React框架本身的原生特性,而是由Redux、Flux、MobX以及 useReducer 等状态管理解决方案所采纳和推广。

它作为描述应用程序中发生的“事件”或“意图”的方式,极大地提升了大型复杂应用的状态管理能力。通过将“发生了什么”与“如何改变状态”分离,Action带来了可预测性、可追溯性、良好的关注点分离和可测试性等显著优点。然而,这些优势也伴随着额外的复杂性和样板代码,因此在选择是否以及如何使用Action模式时,需要权衡应用程序的规模和复杂性。

对于需要管理复杂全局状态或跨多个组件共享状态的应用,采用Action模式无疑是一种强大且推荐的实践。而对于状态简单、组件内聚的应用,直接使用React内置的 useStateuseContext 可能更为轻量和高效。

处理异步Action

在Redux或 useReducer 中,核心原则之一是 Reducer必须是纯函数和同步的。这意味着Reducer不能执行任何副作用(如API请求、修改DOM、异步操作),并且给定相同的输入(状态和Action),必须总是返回相同的输出(新状态)。

然而,实际应用中,异步操作(尤其是API请求)是不可避免的。那么,如何将异步操作与Action和Reducer的同步特性结合起来呢?答案是:通过在 Action分发之前或之后 处理异步逻辑,而不是在Reducer内部。

Redux:使用中间件

Redux通过 中间件(Middleware) 机制来处理副作用,包括异步操作。中间件是介于 dispatch 一个Action和Reducer实际接收到这个Action之间的逻辑层。它允许你拦截、检查、修改或取消Actions,甚至可以异步地执行代码并分发新的Actions。

最常用的用于处理异步Action的Redux中间件是:

  1. redux-thunk (最简单和常用)
  2. redux-saga (更强大和复杂)
  3. redux-observable (基于RxJS)

我们将重点介绍 redux-thunk,因为它概念更简单,对于大多数异步请求场景足够。

redux-thunk处理异步Action

redux-thunk 中间件允许你分发 函数 而不仅仅是普通的Action对象。当一个函数被分发时,redux-thunk 会拦截它,并调用这个函数,同时传入 dispatchgetState 作为参数。在这个函数内部,你可以执行异步逻辑,并在适当的时候分发普通Action对象。

1. 核心思想:

一个异步操作通常会触发至少三个同步Action:

  • _REQUEST Action: 表示异步操作开始,通常用于设置加载状态( isLoading: true )。
  • _SUCCESS Action: 表示异步操作成功,携带返回的数据,并清除加载状态( isLoading: false )。
  • _FAILURE Action: 表示异步操作失败,携带错误信息,并清除加载状态( isLoading: false )。

2. 安装 redux-thunk

npm install redux-thunk
# 或者
yarn add redux-thunk

3. 配置 Store:

redux-thunk 作为中间件应用到Redux Store中。

// store/index.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'; // 导入 redux-thunk
import rootReducer from './reducers'; // 你的根 Reducer

const store = createStore(
  rootReducer,
  applyMiddleware(thunk) // 应用 redux-thunk 中间件
);

export default store;

4. 编写异步 Action Creator (Thunk):

这是一个返回函数的Action Creator,这个函数就是所谓的“thunk”。

// store/actions/userActions.js
import axios from 'axios'; // 假设你使用axios进行API请求

// Action 类型常量
export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';

// 普通 Action Creators
export const fetchUsersRequest = () => ({
  type: FETCH_USERS_REQUEST
});

export const fetchUsersSuccess = (users) => ({
  type: FETCH_USERS_SUCCESS,
  payload: users
});

export const fetchUsersFailure = (error) => ({
  type: FETCH_USERS_FAILURE,
  payload: error
});

// 异步 Action Creator (Thunk)
export const fetchUsers = () => {
  // 返回一个函数,这个函数会被 redux-thunk 拦截并执行
  return async (dispatch, getState) => {
    // 1. 分发请求开始的Action,更新UI加载状态
    dispatch(fetchUsersRequest());

    try {
      // 2. 执行异步操作(API请求)
      const response = await axios.get('https://jsonplaceholder.typicode.com/users');
      const users = response.data;

      // 3. 异步操作成功,分发成功的Action,携带数据
      dispatch(fetchUsersSuccess(users));
    } catch (error) {
      // 4. 异步操作失败,分发失败的Action,携带错误信息
      dispatch(fetchUsersFailure(error.message));
    }
  };
};

5. 编写 Reducer:

Reducer会处理这些同步的 _REQUEST, _SUCCESS, _FAILURE Actions。

// store/reducers/userReducer.js
import {
  FETCH_USERS_REQUEST,
  FETCH_USERS_SUCCESS,
  FETCH_USERS_FAILURE
} from '../actions/userActions';

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

const userReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_USERS_REQUEST:
      return {
        ...state,
        isLoading: true,
        error: null // 清除之前的错误信息
      };
    case FETCH_USERS_SUCCESS:
      return {
        ...state,
        isLoading: false,
        users: action.payload,
        error: null
      };
    case FETCH_USERS_FAILURE:
      return {
        ...state,
        isLoading: false,
        error: action.payload,
        users: [] // 请求失败通常清空数据或保持不变
      };
    default:
      return state;
  }
};

export default userReducer;

6. 在组件中使用:

组件只需分发这个异步Action Creator(thunk),而不需要关心内部的异步逻辑。

// components/UserList.jsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUsers } from '../store/actions/userActions';

function UserList() {
  const dispatch = useDispatch();
  const { users, isLoading, error } = useSelector(state => state.users); // 假设 rootReducer 中包含了 userReducer

  useEffect(() => {
    dispatch(fetchUsers()); // 分发异步 Action
  }, [dispatch]);

  if (isLoading) {
    return <div>Loading users...</div>;
  }

  if (error) {
    return <div>Error: {error}</div>;
  }

  return (
    <div>
      <h1>User List</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name} ({user.email})</li>
        ))}
      </ul>
    </div>
  );
}

export default UserList;

流程总结:

  1. 组件调用 dispatch(fetchUsers())
  2. redux-thunk 中间件拦截到 fetchUsers 返回的函数。
  3. redux-thunk 执行这个函数,传入 dispatchgetState
  4. 在函数内部,首先 dispatch(fetchUsersRequest()),这使得 isLoading 变为 true
  5. 然后执行 axios.get() 进行API请求。
  6. 请求成功后,dispatch(fetchUsersSuccess(users)),这使得 isLoading 变为 false,并更新 users 数据。
  7. 请求失败后,dispatch(fetchUsersFailure(error.message)),这使得 isLoading 变为 false,并更新 error 信息。
  8. Reducer只处理这些同步的Actions,更新状态。

useReducer处理异步Action

useReducer 本身没有像Redux那样的中间件系统。因此,异步逻辑通常是在 组件内部(例如 useEffect 或事件处理函数) 执行,或者通过 自定义Hooks 封装。我们仍然遵循“将异步操作拆解成多个同步Action”的核心思想。

1. 核心思想:

与Redux类似,一个异步操作会触发多个同步 dispatch 调用。

2. 在组件内部处理异步(最常见方式):

import React, { useReducer, useEffect } from 'react';
import axios from 'axios';

// Action 类型
const FETCH_START = 'FETCH_START';
const FETCH_SUCCESS = 'FETCH_SUCCESS';
const FETCH_ERROR = 'FETCH_ERROR';

// Reducer
const dataReducer = (state, action) => {
  switch (action.type) {
    case FETCH_START:
      return { ...state, isLoading: true, error: null };
    case FETCH_SUCCESS:
      return { ...state, isLoading: false, data: action.payload };
    case FETCH_ERROR:
      return { ...state, isLoading: false, error: action.payload };
    default:
      return state;
  }
};

const initialState = {
  data: [],
  isLoading: false,
  error: null,
};

function DataLoader() {
  const [state, dispatch] = useReducer(dataReducer, initialState);

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: FETCH_START }); // 1. 异步开始前,分发开始Action

      try {
        const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
        dispatch({ type: FETCH_SUCCESS, payload: response.data }); // 2. 成功后,分发成功Action
      } catch (error) {
        dispatch({ type: FETCH_ERROR, payload: error.message }); // 3. 失败后,分发失败Action
      }
    };

    fetchData();
  }, []); // 空数组表示只在组件挂载时运行一次

  if (state.isLoading) {
    return <div>Loading data...</div>;
  }

  if (state.error) {
    return <div>Error: {state.error}</div>;
  }

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {state.data.slice(0, 5).map(post => ( // 只显示前5个
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default DataLoader;

3. 封装为自定义Hook(更好的可重用性):

为了避免在多个组件中重复相同的异步逻辑,可以将其封装在一个自定义Hook中。

// hooks/useDataFetching.js
import { useReducer, useEffect } from 'react';
import axios from 'axios';

// Action 类型
const FETCH_START = 'FETCH_START';
const FETCH_SUCCESS = 'FETCH_SUCCESS';
const FETCH_ERROR = 'FETCH_ERROR';

// Reducer
const dataReducer = (state, action) => {
  switch (action.type) {
    case FETCH_START:
      return { ...state, isLoading: true, error: null };
    case FETCH_SUCCESS:
      return { ...state, isLoading: false, data: action.payload };
    case FETCH_ERROR:
      return { ...state, isLoading: false, error: action.payload };
    default:
      return state;
  }
};

const initialState = {
  data: [],
  isLoading: false,
  error: null,
};

const useDataFetching = (url) => {
  const [state, dispatch] = useReducer(dataReducer, initialState);

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: FETCH_START });
      try {
        const response = await axios.get(url);
        dispatch({ type: FETCH_SUCCESS, payload: response.data });
      } catch (error) {
        dispatch({ type: FETCH_ERROR, payload: error.message });
      }
    };
    fetchData();
  }, [url]); // 当url变化时重新 fetching

  return state;
};

export default useDataFetching;

在组件中使用自定义Hook:

// components/PostList.jsx
import React from 'react';
import useDataFetching from '../hooks/useDataFetching';

function PostList() {
  const { data: posts, isLoading, error } = useDataFetching('https://jsonplaceholder.typicode.com/posts');

  if (isLoading) {
    return <div>Loading posts...</div>;
  }

  if (error) {
    return <div>Error: {error}</div>;
  }

  return (
    <div>
      <h1>Posts from Custom Hook</h1>
      <ul>
        {posts.slice(0, 5).map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default PostList;

4. 模拟 redux-thunkdispatch 包装器 (高级技巧):

如果你想让 useReducerdispatch 也能接受函数(像thunk一样),可以手动创建一个 dispatch 的包装器。

// hooks/useReducerWithThunk.js
import { useReducer, useCallback } from 'react';

const useReducerWithThunk = (reducer, initialState) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const enhancedDispatch = useCallback(
    action => {
      if (typeof action === 'function') {
        // 如果 action 是函数,执行它,并传入增强的 dispatch 和当前 state
        return action(enhancedDispatch, () => state);
      } else {
        // 否则,像普通 dispatch 一样分发 action 对象
        return dispatch(action);
      }
    },
    [dispatch, state] // state 也作为依赖,因为 thunk 可能会用到最新的 state
  );

  return [state, enhancedDispatch];
};

export default useReducerWithThunk;

使用 useReducerWithThunk

// components/PostListWithThunk.jsx
import React, { useEffect } from 'react';
import useReducerWithThunk from '../hooks/useReducerWithThunk';
import axios from 'axios';

// Action 类型
const FETCH_START = 'FETCH_START';
const FETCH_SUCCESS = 'FETCH_SUCCESS';
const FETCH_ERROR = 'FETCH_ERROR';

// Reducer
const dataReducer = (state, action) => {
  switch (action.type) {
    case FETCH_START:
      return { ...state, isLoading: true, error: null };
    case FETCH_SUCCESS:
      return { ...state, isLoading: false, data: action.payload };
    case FETCH_ERROR:
      return { ...state, isLoading: false, error: action.payload };
    default:
      return state;
  }
};

const initialState = {
  data: [],
  isLoading: false,
  error: null,
};

// 异步 Action Creator (Thunk-like function for useReducer)
const fetchPostsThunk = () => {
  return async (dispatch, getState) => { // getState 在这里不是必须的,但为了和 redux 保持一致性
    dispatch({ type: FETCH_START });
    try {
      const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
      dispatch({ type: FETCH_SUCCESS, payload: response.data });
    } catch (error) {
      dispatch({ type: FETCH_ERROR, payload: error.message });
    }
  };
};

function PostListWithThunk() {
  const [state, dispatch] = useReducerWithThunk(dataReducer, initialState);

  useEffect(() => {
    dispatch(fetchPostsThunk()); // 分发一个函数
  }, [dispatch]);

  if (state.isLoading) {
    return <div>Loading posts with custom thunk...</div>;
  }

  if (state.error) {
    return <div>Error: {state.error}</div>;
  }

  return (
    <div>
      <h1>Posts from `useReducerWithThunk`</h1>
      <ul>
        {state.data.slice(0, 5).map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default PostListWithThunk;

异步处理小结

无论是Redux还是 useReducer,处理异步Action的关键在于:

  1. Reducer始终保持同步和纯粹。 它们只根据Action和当前状态计算并返回新的状态。
  2. 异步逻辑在Reducer之外执行。
    • Redux: 主要通过中间件(如 redux-thunk)在Action分发前后拦截并处理异步操作,然后分发普通的、同步的Action给Reducer。
    • useReducer 异步逻辑通常直接在组件的 useEffect 或事件处理函数中完成,或者封装在自定义Hook中。每一个异步操作的阶段(开始、成功、失败)都会对应一个同步的 dispatch 调用。
  3. 将一个复杂的异步操作拆分为多个简单的、同步的Actions。 每个同步Action负责更新状态的某一部分,例如加载状态、数据本身或错误信息。

理解并正确应用这些模式,对于构建可预测、可维护的React应用至关重要。