🌟 正确管理深层嵌套的 React 组件

0 阅读3分钟

在 React 应用开发中,深层嵌套的组件结构如果处理不当,很容易变得难以维护。

本文将探讨一种架构模式,确保在处理嵌套组件时,提高应用的可扩展性、可维护性和代码清晰度。这种模式遵循单一职责原则,让子组件专注于自身的逻辑,而父组件则负责处理所有外部操作。

👀 查看演示

👉 演示

📂 查看完整项目

👉 GitHub 仓库

🏗️ 深层嵌套组件的问题

  • 组件紧密耦合:子组件处理了本不应由它们处理的操作。
  • 调试困难:业务逻辑分散在多个组件中。
  • 可复用性降低:组件难以提取和复用。

🎯 目标

我们希望实现以下目标:

  • ✅ 保持子组件纯净(只关注 UI 和内部状态)。
  • ✅ 将逻辑集中到父组件(处理如保存到数据库等外部操作)。
  • ✅ 使用验证工具确保数据完整性。

🛠️ 实现模式

父组件:处理所有操作

父组件负责以下任务:

  1. 管理应用状态。
  2. 在保存前验证数据。
  3. 处理来自子组件的更新。
import { useCallback, useState } from "react";
import { toast } from "react-toastify";
import { Data } from "../data/Data";
import { Child1 } from "./child1";
import { Child2 } from "./child2";


export const Parent = () => {
  const [data, setData] = useState<Data | undefined>({
    child1: undefined,
    child2: undefined,
    grandChild: undefined,
  });

  const isDataComplete = useCallback(
    (incomingData: Partial<Data> | undefined): incomingData is Data => {
      return (
        !!incomingData?.child1 &&
        incomingData?.child1.trim().length > 0 &&
        !!incomingData?.child2 &&
        !!incomingData?.grandChild
      );
    },
    []
  );

  const onSave = useCallback(
    (data: Partial<Data> | undefined) => {
      if (!isDataComplete(data)) {
        toast("Please fill all fields first", { style: { color: "black", backgroundColor: "#f9b6af" } });
        return;
      }
      toast("You filled all your fields!", { style: { color: "black", backgroundColor: "lightgreen" } });
    },
    [isDataComplete]
  );

  const onUpdate = useCallback(
    (incomingData: Partial<Data>) => {
      setData(prev => ({ ...prev, ...incomingData }));
    },
    []
  );

  return (
    <>
      <Child1 data={data} onUpdate={onUpdate} />
      <Child2 data={data} onUpdate={onUpdate} onSave={onSave} />
    </>
  );
};

子组件 1:委托更新

子组件仅管理自身的输入,并将更新委托给父组件。

import { useState } from "react";
import { Data } from "../data/Data";

export const Child1 = ({ data, onUpdate }: { data: Data | undefined; onUpdate: (parentData: Partial<Data>) => void; }) => {
  const [child1Input, setChild1Input] = useState(data?.child1);

  return (
    <>
      <label>Child 1 input</label>
      <input
        value={child1Input}
        onChange={(e) => {
          setChild1Input(e.target.value);
          onUpdate({ child1: e.target.value });
        }}
        />
    </>
  );
};

子组件 2:嵌套子组件和保存操作

孙子组件使用委托逻辑进行按钮操作。

import { useState } from "react";
import { GrandChild } from "./grandChild";

export const Child2 = ({ data, onUpdate, onSave }) => {
  const [child2Input, setChild2Input] = useState(data?.child2);

  return (
    <>
      <label>Child 2 input</label>
      <input
        value={child2Input}
        onChange={(e) => {
          setChild2Input(e.target.value);
          onUpdate({ child2: e.target.value });
        }}
        />
      <GrandChild onUpdate={onUpdate} data={data} onSave={onSave} />
    </>
  );
};

自定义逻辑和父组件逻辑

import { useState } from "react";

export const GrandChild = ({ onUpdate, data, onSave }) => {
  const [grandChildInput, setGrandChildInput] = useState(data?.grandChild);

  return (
    <>
      <label>Grandchild input</label>
      <input
        value={grandChildInput}
        onChange={(e) => {
          setGrandChildInput(e.target.value);
          onUpdate({ grandChild: e.target.value });
        }}
        />
      <button onClick={() => onSave(data)}>Save</button>
    </>
  );
};

如果子组件或孙子组件在更新父组件之前需要包含自定义逻辑,可以定义一个自定义回调,然后调用委托的父组件操作。例如:

jsxconst customOnUpdate = (value: string, onUpdate: (data: Partial<Data>) => void) => {
  console.log("Custom logic before updating:", value);

  // Then execute parent logic
  onUpdate();

  console.log("Custom logic after updating:", transformedValue);
};

🎯 为什么这样有效

  • 关注点分离:子组件仅处理自身的逻辑。
  • 单一数据源:父组件管理并验证数据。
  • 提高可复用性:任何组件都可以独立复用。
  • 更好的可维护性:调试和扩展功能更加容易。

🚀 编程愉快!

原文:www.yuque.com/fengjutian/… 《🌟 Managing Deeply Nested React Components the Right Way》