React 组件设计:9 种让你的 UI 坚不可摧的架构模式

99 阅读3分钟

React 组件设计:9 种让你的 UI 坚不可摧的架构模式

React Component Design: 9 Architecture Patterns That Make Your UI Bulletproof

React 给予了你极大的自由。但自由也会带来不一致、面条代码和团队协作的混乱。

这些模式将会:

  • 让你的组件更易维护
  • 鼓励在不过度设计的前提下实现复用
  • 简化大型团队中的协作

Presentational + Container 模式

将组件拆分为两类:

  • Container:处理数据获取、state 和逻辑。
  • Presenter:只负责展示。
// 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 vs Uncontrolled 模式

  • Controlled 组件:父组件通过 props 管理 value。
  • Uncontrolled 组件:内部通过 ref 自行管理 state。
// Controlled
<input value={value} onChange={e => setValue(e.target.value)} />

// Uncontrolled
<input defaultValue="Hello" ref={inputRef} />

清楚何时使用哪种方式能提升性能并简化表单。Controlled 组件功能强大,但会带来更多渲染。混合方式能让大型表单更顺畅。

Compound Component 模式

通过共享 state 和 context 设计父子组件关系。父组件管理逻辑,并通过 context 而不是 props 向子组件传递控制权

<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 管理的集中化。它不仅改善功能,也提升了开发体验。

Render Props

不再传递静态 JSX 子元素,而是传递一个返回 JSX 的函数 —— 这样可以访问内部逻辑。

<Toggle>
  {({ on, toggle }) => (
    <button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>
  )}
</Toggle>

在嵌套或动态 UI 中提供了强大的组合模式

Headless Component 模式

将行为与展示分离。组件提供 state 和逻辑,但将渲染交给使用者。这样你可以完全掌控标记、样式和布局,同时还能利用经过验证的逻辑,例如可访问性、ARIA role 和键盘交互。

<Dropdown>
  {({ open, toggle }) => (
    <button onClick={toggle}>
      {open ? 'Close' : 'Open'}
    </button>
  )}
</Dropdown>

Headless 组件能帮助你构建灵活且框架无关的 UI 库。它们不会限制界面外观,而是让团队按需渲染,同时保持逻辑的集中与复用。

State Reducer 模式

允许用户覆盖内部的 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-Dumb Component 配对

  • 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,单独测试,并跨功能复用。你会开始发现每个功能都能拆分为可复用的积木,代码库也会更轻量、更模块化。

逻辑与 UI 的就近放置

将 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 组合

  • 使用 props 配置行为。
  • 使用 children 组合 UI。

保持 API 一致性。帮助他人理解哪些是可配置的,哪些是可组合的。

// variant 是配置 prop
// children 是插入布局的嵌套组件
<Card variant="elevated">
  <Card.Title>Settings</Card.Title>
  <Card.Body>
    <SettingsForm />
  </Card.Body>
</Card>

这让组件像迷你 DSL(领域特定语言)。它以简洁、声明式的方式赋能使用者。组件变得直观且可组合。