首页 / React 19 入门教程 / 组件设计原则

React 19 入门教程

组件设计原则

本教程共 50 篇 · 第 48 篇 · 更新于 2026-07-29 · 约 8 分钟阅读

React组件设计设计原则组合单一职责最佳实践

48. 组件设计原则

本节目标:掌握组件拆分的基本原则,写出可复用、可维护的 React 组件。

单一职责原则

一个组件只做一件事。它干得好好的,你一看名字就知道它干啥。

反例:

// ❌ 一个组件干了四件事:请求、列表、搜索、分页
function UserPage() {
  const [users, setUsers] = useState([]);
  const [search, setSearch] = useState('');
  const [page, setPage] = useState(1);

  useEffect(() => { /* 请求数据 */ }, [search, page]);

  return (
    <div>
      <input value={search} onChange={...} />
      <ul>{users.map(...)}</ul>
      <button onClick={() => setPage(page + 1)}>下一页</button>
    </div>
  );
}

拆分后:

// ✅ 各司其职
function UserPage() {
  return (
    <div>
      <SearchInput />
      <UserList />
      <Pagination />
    </div>
  );
}
Tip

判断标准:如果组件的注释里要写”和”字,说明职责不单一。

组合优于继承

React 不推荐用继承来复用代码。用组合(components as props)。

// ❌ 继承模式——React 不支持也不推荐
class BaseModal extends React.Component {
  render() { return <div className="modal">{this.renderContent()}</div>; }
}

class ConfirmModal extends BaseModal {
  renderContent() { return <button>确认</button>; }
}

// ✅ 组合模式——用 children
function Modal({ title, children, onClose }) {
  return (
    <div className="modal">
      <h2>{title}</h2>
      {children}
      <button onClick={onClose}>关闭</button>
    </div>
  );
}

function ConfirmDialog() {
  return (
    <Modal title="确认删除?">
      <p>此操作不可撤销</p>
      <button>确认删除</button>
    </Modal>
  );
}

children prop 是最基础的组合手段。更复杂的场景用 render prop 或插槽模式。

单向数据流

React 的数据流是单向的:从父到子,通过 props。

function Parent() {
  const [name, setName] = useState('Alice');

  return (
    <>
      <DisplayUser name={name} />
      <EditUser name={name} onNameChange={setName} />
    </>
  );
}

function DisplayUser({ name }) {
  return <p>当前用户:{name}</p>;
}

function EditUser({ name, onNameChange }) {
  return <input value={name} onChange={e => onNameChange(e.target.value)} />;
}

子组件不直接改父组件的 state。通过回调函数”通知”父组件去改。

Note

单向数据流让数据流向清晰。你永远知道 state 在哪里、谁改了它。

状态提升

当多个组件需要共享同一个 state,把它移到最近的共同祖先。

// ❌ 每个组件各管各的
function TemperatureInput() {
  const [celsius, setCelsius] = useState(0);
  return <input value={celsius} onChange={...} />;
}

function FahrenheitInput() {
  const [fahrenheit, setFahrenheit] = useState(32);
  return <input value={fahrenheit} onChange={...} />;
}

// ✅ 状态提升到共同父组件
function TemperatureConverter() {
  const [celsius, setCelsius] = useState(0);

  const fahrenheit = celsius * 9 / 5 + 32;

  return (
    <>
      <input value={celsius} onChange={e => setCelsius(e.target.value)} />
      <input value={fahrenheit} readOnly />
    </>
  );
}

什么时候提升:如果 state 只在一个组件内部用,就不用提。

受控 vs 非受控

表单组件有两种写法。

受控组件:value 由 React state 管理。

function ControlledInput() {
  const [value, setValue] = useState('');

  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}

优点:输入值完全由 React 控制,校验、格式化都方便。

缺点:每次按键都触发重渲染。

非受控组件:value 由 DOM 自己管理。

import { useRef } from 'react';

function UncontrolledInput() {
  const inputRef = useRef(null);

  function handleSubmit() {
    console.log(inputRef.current.value);
  }

  return (
    <>
      <input ref={inputRef} defaultValue="" />
      <button onClick={handleSubmit}>提交</button>
    </>
  );
}

优点:性能好(无重渲染)。

缺点:不能实时校验、不能条件禁用提交按钮。

Tip

大多数场景用受控组件。只在文件上传、大量输入框(>100)时考虑非受控。

组件拆分粒度

组件不是越小越好,也不是越大越好。

信号——该拆了:

  • 组件超过 200 行
  • 有重复的 JSX 片段
  • 注释里出现”和”字
  • 一个 prop 影响了组件的两种不同行为

信号——不该拆:

  • 拆出来只有一个地方用
  • 拆出来没有独立语义
  • 拆出来只是为了减少行数
// 折中:内部提取 render 函数
function SearchResults({ results, isLoading }) {
  if (isLoading) return <Spinner />;

  return (
    <ul>
      {results.map(item => (
        <li key={item.id}>
          <h3>{item.title}</h3>
          <p>{item.description}</p>
        </li>
      ))}
    </ul>
  );
}

// 等 item 变复杂了再拆
function SearchResultItem({ item }) {
  return (
    <li>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
    </li>
  );
}

props 设计原则

props 要保持扁平

// ❌ 嵌套对象
<UserCard user={{ name: 'Alice', age: 25, address: { city: 'Beijing' } }} />

// ✅ 扁平 props
<UserCard name="Alice" age={25} city="Beijing" />

布尔 prop 用 is/has 前缀

<Button isLoading disabled />
<Modal hasFooter isClosable />

回调 prop 用 on + 动词

<Form onSubmit={handleSubmit} />
<Button onClick={handleClick} />
<Input onChangeText={handleChange} />

自定义 Hook 抽逻辑

组件里出现重复的”状态 + effect”模式,抽成自定义 Hook。

// ❌ 每个组件都写一遍
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/users').then(r => r.json()).then(data => {
      setUsers(data);
      setLoading(false);
    });
  }, []);
  // ...
}

// ✅ 抽成 Hook
function useUsers() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/users').then(r => r.json()).then(data => {
      setUsers(data);
      setLoading(false);
    });
  }, []);

  return { users, loading };
}

function UserList() {
  const { users, loading } = useUsers();
  // ...
}
Note

自定义 Hook 是 React 逻辑复用的主要手段。组件是 UI 复用的手段。两者配合使用。

小结

  • 一个组件只做一件事
  • 用组合(children/render props)代替继承
  • 数据单向流动,state 变化通过回调上传
  • 共享状态提升到最近共同祖先
  • 表单优先用受控组件
  • props 扁平、命名规范
  • 重复逻辑抽成自定义 Hook