核心理念概述(20%)
基本原则
- 多用 props,少用 state,多写无状态组件
- child::有状态组件的缺点 状态冗余与同步成本
- 通过hook封装状态逻辑,实现关注点分离和组件复用性
- 参考antd组件的设计,如表单table与它对应的useForm
设计模式
- 容器组件(Container Components):负责”如何工作”,管理状态和业务逻辑
- 展示组件(Presentational Components):负责”如何展示”,只接收props并渲染UI
详细解释(80%)
为什么用Hook管理状态而不是直接传Props
问题背景
当需要在父组件中获取子组件的内部状态时,存在两种设计方案:
React推荐第二种方案的核心原因是关注点分离。
容器组件 vs 展示组件模式
展示组件特点
- 职责:只关心”如何展示”
- 特点:
- 接收数据(props)并渲染对应的UI
- 通过回调函数(如
onFieldChange)通知父组件用户操作 - 不包含业务逻辑、不直接请求API、不管理复杂状态
- 不知道数据提交后会发生什么
- 优点:
- 复用性极高,可在任何需要的地方使用
- 容易测试
- 与具体业务逻辑解耦
// 展示组件示例
const DeployStrategyForm = ({ formData, onFieldChange }) => {
return (
<form>
<input
value={formData.strategy}
onChange={(e) => onFieldChange('strategy', e.target.value)}
/>
{/* 其他表单项 */}
</form>
);
};容器组件特点
- 职责:关心”如何工作”
- 特点:
- 负责获取数据、管理state
- 处理业务逻辑(如API调用)
- 知道整个业务流程
- 优点:
- 业务逻辑集中,数据流清晰可预测
- 状态管理统一
// 容器组件示例
const DeployModal = () => {
const { formData, updateField, submitDeploy } = useDeployStrategyForm();
const handleSubmit = () => {
// 调用部署API
deployAPI(formData);
};
return (
<Modal>
<DeployStrategyForm
formData={formData}
onFieldChange={updateField}
/>
<button onClick={handleSubmit}>部署</button>
</Modal>
);
};为什么不在子组件内部使用Hook
如果在子组件(如DeployStrategyForm)内部直接使用hook管理状态,会产生以下问题:
-
数据获取困难:父组件需要获取子组件内部状态时,必须使用复杂的React特性:
forwardRef和useImperativeHandle暴露getFormData()方法- 破坏组件的单向数据流原则
- 增加父子组件耦合度
-
复用性降低:子组件与特定的状态结构绑定,难以在不同场景复用
-
测试复杂:需要测试组件内部状态管理逻辑
Hook vs Redux对比
Hook作为”局部状态管理器”
- 作用范围:每个组件调用hook时创建独立的状态副本,组件间状态不共享
- 使用场景:适合封装组件级别的复用逻辑(如表单处理、定时器、API请求等)
- 复杂度:轻量,基于React原生Hooks(useState、useReducer等)
// 自定义Hook示例 - 像小型的局部Redux store
const useDeployStrategyForm = () => {
const [formData, setFormData] = useState({ strategy: '', replicas: 1 });
const updateField = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
return { formData, updateField };
};Redux作为”全局状态管理器”
- 作用范围:整个应用共享一份状态
- 使用场景:管理跨组件、跨页面的共享状态(如用户信息、购物车数据等)
- 复杂度:有完整规范(action、reducer、store等),通常需要中间件
核心区别总结
- 作用范围:hook是局部的,Redux是全局的
- 状态共享:hook每次调用创建新实例,Redux全局共享
- 使用场景:hook适合组件级逻辑复用,Redux适合跨组件状态共享
- 复杂度:hook轻量简单,Redux功能完整但复杂
Hook状态绑定特性
重要特性
Hook的状态与使用该hook的组件相绑定,这意味着:
- 每个组件实例都有独立的状态副本
- 不能在封装的组件内部使用外部定义的Hook实例
- 如果在子组件内部调用同一个hook,会创建新的状态实例
示例说明
// 错误用法:在子组件内部使用Hook会创建新实例
const ParentComponent = () => {
const { data, updateData } = useCustomHook();
return <ChildComponent />; // 子组件无法访问父组件的Hook状态
};
const ChildComponent = () => {
const { data, updateData } = useCustomHook(); // 这是一个新的状态实例!
return <div>{data}</div>;
};
// 正确用法:通过props传递状态
const ParentComponent = () => {
const { data, updateData } = useCustomHook();
return <ChildComponent data={data} onUpdate={updateData} />;
};
const ChildComponent = ({ data, onUpdate }) => {
return (
<div>
{data}
<button onClick={() => onUpdate('new value')}>更新</button>
</div>
);
};设计优势总结
采用”容器组件+展示组件”模式的优势:
- 高复用性:展示组件可被任意场景复用
- 清晰的数据流:数据和逻辑从父组件流向子组件,易于理解和调试
- 单一职责:每个组件各司其职,职责明确
- 易于维护:业务逻辑集中在容器组件,UI逻辑集中在展示组件
- 测试友好:可以独立测试展示组件的UI渲染和容器组件的业务逻辑
这是React社区广泛认可的成熟设计实践,特别适合构建复杂应用。