原文地址:https://medium.com/@hritvikom/react-component-design-9-architecture-patterns-that-make-your-ui-bulletproof-eb4fb14dfe28
原文作者: Scripting Soul
侵删~
React 给予了你极大的自由。但自由也会带来不一致、面条代码和团队协作的混乱 —— 除非你使用可扩展的设计模式。
本文将深入介绍 9 种经过打磨和验证的架构模式,它们会改变你设计组件的方式。这些不是 hack,而是基本功。无论你在构建仪表盘、设计系统还是数据密集型应用,它们都适用。
这些模式将会:
让你的组件更易维护
鼓励在不过度设计的前提下实现复用
简化大型团队中的协作
定义
将组件拆分为两类:
Container:处理数据获取、state 和逻辑。
Presenter:只负责展示。
为什么有效
它能让逻辑与 UI 清晰分离。Presenter 是“哑组件”(无副作用、无 state),因此它可复用、可测试、可预测。Container 可以独立演进,而不会影响 UI 的样式或结构。
// UserContainer.jsx
function UserContainer() {
const { data, isLoading } = useQuery('user', fetchUser);
return <UserProfile user={data} loading={isLoading} />;
}
// UserProfile.jsx
function UserProfile({ user, loading }) {
if (loading) return <Skeleton />;
return <div>{user.name}</div>;
}
实际收益
当你一致性地使用该模式时,组件会更具可组合性。你不再需要纠结 API 调用放在哪里,也能避免在条件渲染时写出嵌套很深的三元表达式。每个部分只做一件事 —— 并且做好。
定义
Controlled 组件:父组件通过 props 管理 value。
Uncontrolled 组件:内部通过 ref 自行管理 state。
为什么有效
有时你需要灵活性。Controlled 组件适合集成到表单中,而 Uncontrolled 组件渲染更快,且更易上手。
// Controlled
e => setValue(e.target.value)} />
// Uncontrolled
<input defaultValue="Hello" ref={inputRef} />
实际收益
清楚何时使用哪种方式能提升性能并简化表单。Controlled 组件功能强大,但会带来更多渲染。混合方式能让大型表单更顺畅。
定义
通过共享 state 和 context 设计父子组件关系。父组件管理逻辑,并通过 context 而不是 props 向子组件传递控制权。
为什么有效
它避免了 prop drilling,并为用户提供了一种更声明式的 UI 组合方式。
<Tabs>
<Tabs.List>
<Tabs.Trigger value="posts">Posts</Tabs.Trigger>
<Tabs.Trigger value="comments">Comments</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="posts">Post content</Tabs.Panel>
<Tabs.Panel value="comments">Comment content</Tabs.Panel>
</Tabs>
实际收益
Compound 组件让 API 更加优雅。消费者无需传递 ID 和 handler,只需组合接口即可。而在内部,你能保持 state 管理的集中化。它不仅改善功能,也提升了开发体验。
定义
不再传递静态 JSX 子元素,而是传递一个返回 JSX 的函数 —— 这样可以访问内部逻辑。
为什么有效
它能暴露功能而不控制展示。可以把它看作是 hooks 的前身。
<Toggle>
{({ on, toggle }) => (
<button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>
)}
</Toggle>
实际收益
在 class 组件或共享 UI 逻辑场景下依然有用。虽然现在 hooks 更流行,但 Render Props 依然在嵌套或动态 UI 中提供了强大的组合模式。
定义
将行为与展示分离。组件提供 state 和逻辑,但将渲染交给使用者。
为什么有效
这样你可以完全掌控标记、样式和布局,同时还能利用经过验证的逻辑,例如可访问性、ARIA role 和键盘交互。
<Dropdown>
{({ open, toggle }) => (
<button onClick={toggle}>
{open ? 'Close' : 'Open'}
</button>
)}
</Dropdown>
实际收益
Headless 组件能帮助你构建灵活且框架无关的 UI 库。它们不会限制界面外观,而是让团队按需渲染,同时保持逻辑的集中与复用。
定义
允许用户覆盖内部的 state 转换逻辑。组件内部使用 reducer,同时允许消费者注入自定义逻辑。
为什么有效
消费者无需 fork 或复制组件即可获得控制权。这类似于 state 更新的控制反转。
function useToggle({ reducer = defaultReducer } = {}) {
const [state, dispatch] = useReducer(reducer, { on: false });
const toggle = () => dispatch({ type: 'toggle' });
return [state.on, toggle];
}
实际收益
非常适合设计系统和可复用 hooks。默认情况下提供灵活性,并让高级场景能插入自定义行为。这是“可复用”与“可配置”的差别。
定义
一种特定的角色划分方式:
Smart 组件:处理协调、数据获取、mutation、state 管理。
Dumb 组件:无状态,只负责展示和布局。
为什么有效
这种明确的角色区分避免了所有组件“啥都干”。它与软件工程中的单一职责原则完美契合。
// Smart
function TodoListContainer() {
const todos = useTodos();
return <TodoList todos={todos} />;
}
// Dumb
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
);
}
实际收益
当 Dumb 组件纯粹且解耦时,它们可以直接放进 Storybook,单独测试,并跨功能复用。你会开始发现每个功能都能拆分为可复用的积木,代码库也会更轻量、更模块化。
定义
将 state 和逻辑尽可能靠近使用它们的组件,而不是过早抽象。
为什么有效
避免不必要的间接性。让组件自包含,更容易删除、重构或迁移。
与其这样:
// useFormState.js
export function useFormState() {
...
}
// Form.jsx
const { fields, errors } = useFormState();
不如先这样:
function Form() {
const [fields, setFields] = useState({});
const [errors, setErrors] = useState({});
...
}
只有在确实需要复用时,再提取到 hook。
实际收益
过早的抽象会导致代码脆弱、过度设计。就近放置鼓励清晰与意图。
定义
使用 props 配置行为。
使用 children 组合 UI。
为什么有效
保持 API 一致性。帮助他人理解哪些是可配置的,哪些是可组合的。
<Card variant="elevated">
<Card.Title>Settings</Card.Title>
<Card.Body>
<SettingsForm />
</Card.Body>
</Card>
variant 是配置 prop。
children 是插入布局的嵌套组件。
实际收益
这让组件像迷你 DSL(领域特定语言)。它以简洁、声明式的方式赋能使用者。组件变得直观且可组合。
React 不会强制规则。这既是它的优势,也是它的陷阱。
我们不需要从零开始造轮子,也不必追随所有潮流。但当我们带着意图来设计组件 —— 使用这些架构模式时,就能构建出经得起时间考验的系统。
一个以清晰为基础的 UI 更容易测试、扩展,并帮助新成员快速上手。这些模式不是 hack,而是经过 React 化的永恒原则。
架构无需恐惧。我们可以理解它、掌握它,并在需要时用好它。
🔥号外~号外~
最近我们推出了大厂的一手面经模块,都是刚面完的小伙伴们热乎乎分享的:
这些面经都是花了不少心思整理的,比网上那些过时的八股文靠谱多了。
有需要的小伙伴可以点击这里👉前端面试题宝典(打开小程序,首页即可直接领取【大厂真实面经】),也可直接联系小助理咨询。
毕竟信息差就是竞争力,早点了解面试套路,早点拿到心仪offer!
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。