在 React 应用开发中,深层嵌套的组件结构如果处理不当,很容易变得难以维护。
本文将探讨一种架构模式,确保在处理嵌套组件时,提高应用的可扩展性、可维护性和代码清晰度。这种模式遵循单一职责原则,让子组件专注于自身的逻辑,而父组件则负责处理所有外部操作。
👀 查看演示
👉 演示
📂 查看完整项目
🏗️ 深层嵌套组件的问题
- 组件紧密耦合:子组件处理了本不应由它们处理的操作。
- 调试困难:业务逻辑分散在多个组件中。
- 可复用性降低:组件难以提取和复用。
🎯 目标
我们希望实现以下目标:
- ✅ 保持子组件纯净(只关注 UI 和内部状态)。
- ✅ 将逻辑集中到父组件(处理如保存到数据库等外部操作)。
- ✅ 使用验证工具确保数据完整性。
🛠️ 实现模式
父组件:处理所有操作
父组件负责以下任务:
- 管理应用状态。
- 在保存前验证数据。
- 处理来自子组件的更新。
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》