You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于TypeScript实现React Context状态的批量数据加载

解决React Context批量加载数据的TypeScript问题

我明白你现在的痛点——逐个dispatch导致组件反复渲染,而且TypeScript的类型定义总是报错。别担心,我们可以通过重构Action的类型定义和扩展reducer的批量处理逻辑来解决这个问题,同时保持TypeScript的类型安全。

核心思路

原来的IAction把所有动作的payload都限定为单个NewsItemType,直接限制了批量操作的可能性。我们需要用**区分联合类型(Discriminated Union)**为不同动作指定对应payload类型,这样TypeScript能精准推断每个动作的payload,避免类型冲突。同时,让load动作直接接收数组,一次性更新状态,彻底减少渲染次数。

步骤1:更新Action类型定义

把原来单一的IAction拆分成多个对应不同动作的接口,再合并成联合类型:

// 替换原来的IAction接口
type IAction = 
  | { type: ActionType.add; payload: NewsItemType }
  | { type: ActionType.load; payload: NewsItemType[] }
  | { type: ActionType.update; payload: NewsItemType }
  | { type: ActionType.delete; payload: NewsItemType };

这样每个动作的payload类型都明确了:add/update/delete对应单个NewsItemType,load对应NewsItemType[],TypeScript会在reducer的每个case里自动推断正确的payload类型。

步骤2:修改Reducer的load逻辑

根据你的需求,load动作可以选两种实现:

  • 替换现有数组(适合初始化加载场景):
case ActionType.load:
  // 直接用新数组覆盖原数组
  return { newsList: action.payload };
  • 追加到现有数组并去重(和add动作逻辑保持一致):
case ActionType.load:
  // 先过滤掉已存在的项,再合并新数组到前面
  const existingIds = new Set(state.newsList.map(item => item._id));
  const newItems = action.payload.filter(item => !existingIds.has(item._id));
  return { newsList: [...newItems, ...state.newsList] };

步骤3:修改useEffect的批量加载逻辑

把原来的forEach逐个dispatch改成一次性传入整个数组:

React.useEffect(() => {
  apiRequest("http://localhost:5000/news/?limit=" + MAX_ITEMS, "get", true)
    .then(news => {
      if (news) {
        // 一次性dispatch load动作,传入完整数组
        updateNewsList({ 
          type: ActionType.load, 
          payload: news as NewsItemType[] 
        });
      }
    })
    .catch(error => {
      console.error("加载新闻失败:", error);
    });
}, []);

如果你的apiRequest返回类型不够明确,这里用as NewsItemType[]做类型断言,确保TypeScript不会报错。

完整修改后的代码

import * as React from "react";
/** Custom types */
import { ActionType } from "../custom-types";
import { NewsItemType } from "../custom-types";
import { apiRequest } from "../utils/Helpers";

const MAX_ITEMS = 30;

interface IState {
  newsList: Array<NewsItemType>;
}

// 用区分联合类型定义Action
type IAction = 
  | { type: ActionType.add; payload: NewsItemType }
  | { type: ActionType.load; payload: NewsItemType[] }
  | { type: ActionType.update; payload: NewsItemType }
  | { type: ActionType.delete; payload: NewsItemType };

interface InewsContextInterface {
  state: { newsList: Array<NewsItemType> };
  updateNewsList: React.Dispatch<IAction>;
}

const initialState: IState = {
  newsList: []
};

const reducer: React.Reducer<IState, IAction> = (state, action) => {
  switch (action.type) {
    case ActionType.add:
      return {
        newsList: [
          action.payload,
          ...state.newsList.filter(
            newsitem => newsitem._id !== action.payload._id
          )
        ]
      };
    case ActionType.load:
      // 这里选择替换现有数组,你可以根据需求改成追加+去重
      return { newsList: action.payload };
    case ActionType.update:
      return {
        newsList: state.newsList.map(item => {
          return item._id === action.payload._id ? action.payload : item;
        })
      };
    case ActionType.delete:
      return {
        newsList: state.newsList.filter(
          newsitem => newsitem._id !== action.payload._id
        )
      };
    default:
      throw new Error(`未知的动作类型: ${(action as never).type}`);
  }
};

export const newsContext = React.createContext<InewsContextInterface>({
  state: { newsList: [] },
  updateNewsList: () => {}
});

const { Provider } = newsContext;

const NewsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [newsList, updateNewsList] = React.useReducer(reducer, initialState);

  React.useEffect(() => {
    apiRequest("http://localhost:5000/news/?limit=" + MAX_ITEMS, "get", true)
      .then(news => {
        if (news) {
          updateNewsList({ 
            type: ActionType.load, 
            payload: news as NewsItemType[] 
          });
        }
      })
      .catch(error => {
        console.error("加载新闻数据失败:", error);
      });
  }, []);

  return (
    <Provider value={{ state: newsList, updateNewsList }}>{children}</Provider>
  );
};

export default NewsProvider;

这样解决的优势

  1. 类型安全:区分联合类型让TypeScript能准确识别每个动作的payload类型,彻底解决返回类型不匹配的错误。
  2. 性能优化:原来的forEach会触发最多30次reducer更新,现在只触发1次,监听Context的组件只会渲染一次,效率大幅提升。
  3. 扩展性强:以后需要新增其他批量操作时,只需扩展联合类型即可,代码可维护性更高。

内容的提问来源于stack exchange,提问作者John Barrett

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 18:52:47