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

如何在React JS中使用useReducer维护树形组件的状态关联?

祖孙组件状态关联与useReducer实现方案

需求回顾

  • 祖父组件:默认带输入框、>按钮(打开父组件)、添加按钮(新增输入框)
  • 父组件:结构与祖父一致,点击>按钮打开子组件
  • 最终生成嵌套树形数据:祖父输入值作为顶级key(如category),父组件输入值作为二级key(如agriculture),子组件输入值作为二级key对应的数组值

核心思路

把所有状态统一托管在最顶层的祖父组件,用useReducer管理状态变更逻辑。父、子组件通过props获取对应层级的状态片段和dispatch方法,触发状态更新,保证数据关联的一致性。

1. 定义状态结构

设计匹配最终输出的状态格式,初始状态示例:

const initialState = {
  rootItems: [
    // 每个祖父项:key是输入值,children是父组件的项列表
    { key: "", children: [] }
  ]
};

// 父组件项的结构
// { key: "", values: [""] } // values是子组件的输入值列表

2. 编写Reducer函数

处理所有状态变更动作,覆盖添加输入项、更新输入值、添加子层级等场景:

function treeReducer(state, action) {
  switch (action.type) {
    // 更新祖父项的key
    case "UPDATE_ROOT_KEY": {
      const { index, value } = action.payload;
      const newRootItems = [...state.rootItems];
      newRootItems[index] = { ...newRootItems[index], key: value };
      return { ...state, rootItems: newRootItems };
    }
    // 添加新的祖父输入项
    case "ADD_ROOT_ITEM": {
      return {
        ...state,
        rootItems: [...state.rootItems, { key: "", children: [] }]
      };
    }
    // 更新父组件项的key
    case "UPDATE_PARENT_KEY": {
      const { rootIndex, parentIndex, value } = action.payload;
      const newRootItems = [...state.rootItems];
      const parentItems = [...newRootItems[rootIndex].children];
      parentItems[parentIndex] = { ...parentItems[parentIndex], key: value };
      newRootItems[rootIndex] = { ...newRootItems[rootIndex], children: parentItems };
      return { ...state, rootItems: newRootItems };
    }
    // 添加新的父组件项
    case "ADD_PARENT_ITEM": {
      const { rootIndex } = action.payload;
      const newRootItems = [...state.rootItems];
      newRootItems[rootIndex] = {
        ...newRootItems[rootIndex],
        children: [...newRootItems[rootIndex].children, { key: "", values: [""] }]
      };
      return { ...state, rootItems: newRootItems };
    }
    // 更新子组件的输入值
    case "UPDATE_CHILD_VALUE": {
      const { rootIndex, parentIndex, childIndex, value } = action.payload;
      const newRootItems = [...state.rootItems];
      const parentItems = [...newRootItems[rootIndex].children];
      const childValues = [...parentItems[parentIndex].values];
      childValues[childIndex] = value;
      parentItems[parentIndex] = { ...parentItems[parentIndex], values: childValues };
      newRootItems[rootIndex] = { ...newRootItems[rootIndex], children: parentItems };
      return { ...state, rootItems: newRootItems };
    }
    // 添加新的子组件输入项
    case "ADD_CHILD_ITEM": {
      const { rootIndex, parentIndex } = action.payload;
      const newRootItems = [...state.rootItems];
      const parentItems = [...newRootItems[rootIndex].children];
      parentItems[parentIndex] = {
        ...parentItems[parentIndex],
        values: [...parentItems[parentIndex].values, ""]
      };
      newRootItems[rootIndex] = { ...newRootItems[rootIndex], children: parentItems };
      return { ...state, rootItems: newRootItems };
    }
    default:
      return state;
  }
}

3. 拆分组件实现

祖父组件(Grandparent)

持有全局状态,渲染自身输入项,传递dispatch和对应状态给父组件:

import { useReducer } from "react";
import ParentComponent from "./ParentComponent";

function GrandparentComponent() {
  const [state, dispatch] = useReducer(treeReducer, initialState);

  // 将状态转换为最终需要的格式
  const getFinalStructure = () => {
    return state.rootItems.reduce((acc, item) => {
      if (!item.key) return acc;
      acc[item.key] = item.children.reduce((childAcc, childItem) => {
        if (!childItem.key) return childAcc;
        childAcc[childItem.key] = childItem.values.filter(val => val); // 过滤空值
        return childAcc;
      }, {});
      return acc;
    }, {});
  };

  return (
    <div>
      {state.rootItems.map((rootItem, rootIndex) => (
        <div key={`root-${rootIndex}`} style={{ margin: "10px 0", padding: "10px", border: "1px solid #ccc" }}>
          <input
            value={rootItem.key}
            onChange={(e) => dispatch({
              type: "UPDATE_ROOT_KEY",
              payload: { index: rootIndex, value: e.target.value }
            })}
            placeholder="输入顶级分类"
          />
          <button onClick={() => dispatch({
            type: "ADD_PARENT_ITEM",
            payload: { rootIndex }
          })}>
            &gt;
          </button>
          {/* 渲染该祖父项对应的父组件列表 */}
          {rootItem.children.map((parentItem, parentIndex) => (
            <ParentComponent
              key={`parent-${rootIndex}-${parentIndex}`}
              rootIndex={rootIndex}
              parentIndex={parentIndex}
              parentItem={parentItem}
              dispatch={dispatch}
            />
          ))}
        </div>
      ))}
      <button onClick={() => dispatch({ type: "ADD_ROOT_ITEM" })}>添加顶级项</button>
      {/* 查看最终结构 */}
      <pre>{JSON.stringify(getFinalStructure(), null, 2)}</pre>
    </div>
  );
}

export default GrandparentComponent;

父组件(Parent)

渲染自身输入项,传递dispatch和对应状态给子组件:

import ChildInputComponent from "./ChildInputComponent";

function ParentComponent({ rootIndex, parentIndex, parentItem, dispatch }) {
  return (
    <div style={{ marginLeft: "20px", margin: "10px 0", padding: "10px", border: "1px solid #eee" }}>
      <input
        value={parentItem.key}
        onChange={(e) => dispatch({
          type: "UPDATE_PARENT_KEY",
          payload: { rootIndex, parentIndex, value: e.target.value }
        })}
        placeholder="输入二级分类"
      />
      <button onClick={() => dispatch({
        type: "ADD_CHILD_ITEM",
        payload: { rootIndex, parentIndex }
      })}>
        &gt;
      </button>
      {/* 渲染该父项对应的子输入框列表 */}
      {parentItem.values.map((value, childIndex) => (
        <ChildInputComponent
          key={`child-${rootIndex}-${parentIndex}-${childIndex}`}
          rootIndex={rootIndex}
          parentIndex={parentIndex}
          childIndex={childIndex}
          value={value}
          dispatch={dispatch}
        />
      ))}
    </div>
  );
}

export default ParentComponent;

子组件(ChildInput)

渲染输入框,处理值更新:

function ChildInputComponent({ rootIndex, parentIndex, childIndex, value, dispatch }) {
  return (
    <div style={{ marginLeft: "40px", margin: "5px 0" }}>
      <input
        value={value}
        onChange={(e) => dispatch({
          type: "UPDATE_CHILD_VALUE",
          payload: { rootIndex, parentIndex, childIndex, value: e.target.value }
        })}
        placeholder="输入值"
      />
    </div>
  );
}

export default ChildInputComponent;

方案优势

  • 状态集中管理,所有数据变更都通过dispatch触发,避免组件间状态不一致
  • 组件职责清晰:祖父管全局状态,父组件管二级层级,子组件管具体值输入
  • 容易扩展:后续要加更多层级,只需要在reducer中新增对应动作,新增组件即可

内容的提问来源于stack exchange,提问作者Jaskirat Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 22:02:02