代码能跑就行?聊聊如何写出“让同事会爱上你”的React组件

538 阅读5分钟

我们都听过那个段子:每个程序员在接手一个老项目时,内心都会循环播放一句话:“这TM是谁写的破代码!” 然后,当他翻看git提交记录时,赫然发现既然是半年前的自己。

这个段子很真实。在日常开发中,我们常常为了快速实现功能,写下一些“能跑就行”的代码。当时觉得没什么,但随着项目迭代,这些代码逐渐变成了难以维护、难以理解、难以扩展的“技术负债”。

一个React项目,就是由无数个组件构成的。组件的质量,直接决定了整个项目的健康度。那么,到底什么样的组件,才算是“好组件”?

今天不讲什么高深的算法或架构,而是几个简单、朴素但极其重要的原则。遵循它们,你写出的组件,将不再是让未来自己和同事头疼的“负债”,而是人人都想复用、人人都愿意维护的“优质组件”。


1. 单一职责原则:一个组件,只做一件事

这是最重要,也最容易被忽略的原则。一个“好”的组件,应该像一把“好”的瑞士军刀里的小刀,功能明确,而不是一把什么都想干的“万用工具”。

一个UserProfile组件,既负责获取用户数据,又负责展示用户信息,还包含了编辑用户信息的表单和提交逻辑。这个组件会变得异常庞大和复杂。

// Bad Example: 一个臃肿的"上帝组件"
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [isEditing, setIsEditing] = useState(false);
  const [formData, setFormData] = useState({});

  useEffect(() => {
    // 1. 负责获取数据
    api.fetchUser(userId).then(data => {
      setUser(data);
      setFormData(data);
    });
  }, [userId]);

  const handleEdit = () => setIsEditing(true);
  const handleSave = () => {
    // 2. 负责更新数据
    api.updateUser(userId, formData).then(() => setIsEditing(false));
  };

  if (!user) return <div>Loading...</div>;

  // 3. 负责两种UI状态的展示
  return isEditing ? (
    <div>
      <input value={formData.name} onChange={...} />
      <button onClick={handleSave}>保存</button>
    </div>
  ) : (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button onClick={handleEdit}>编辑</button>
    </div>
  );
}

我们将它拆分成三个小组件:

  1. useUser (自定义Hook): 专门负责获取和管理用户数据。
  2. UserProfileView: 专门负责展示用户信息。
  3. UserProfileEdit: 专门负责编辑用户信息。
// 1. Hook for data logic
function useUser(userId) {
  const [user, setUser] = useState(null);
  // ... fetching logic here ...
  return user;
}

// 2. Component for display
function UserProfileView({ user, onEdit }) {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button onClick={onEdit}>编辑</button>
    </div>
  );
}

// 3. The container component that orchestrates everything
function UserProfile({ userId }) {
  const user = useUser(userId);
  const [isEditing, setIsEditing] = useState(false);

  if (!user) return <div>Loading...</div>;

  return isEditing ? (
    <UserProfileEdit user={user} onSave={() => setIsEditing(false)} />
  ) : (
    <UserProfileView user={user} onEdit={() => setIsEditing(true)} />
  );
}

当同事想修改展示逻辑时,他知道只需要看UserProfileView,而不用在一大堆状态和逻辑里懵晕。


2. 状态最小化与Props设计:清晰的数据流

一个组件应该拥有尽可能少的状态(state)。很多时候,一个数据能通过props传递,或者能通过已有状态计算出来,就不应该再额外创建一个新的state。

从props接收一个数组,然后把它存到state里,仅仅是为了计算它的长度。

function UserList({ users }) {
  const [userCount, setUserCount] = useState(0);

  useEffect(() => {
    // 完全不必要的 state 和 effect
    setUserCount(users.length);
  }, [users]);

  return <div>共有 {userCount} 名用户</div>;
}

派生状态直接计算即可。

function UserList({ users }) {
  const userCount = users.length; // 直接计算
  return <div>共有 {userCount} 名用户</div>;
}

关于Props设计的建议:

  • 命名清晰onUserClickhandleClick 好一万倍。
  • 布尔值<Button type="submit" disabled={true} /><Button submit disabled /> 可读性更好,后者容易让人困惑。
  • 对象传递:将相关的props组织成一个对象传递,如 user={user}userName={user.name} userAvatar={user.avatar} 更简洁。

3. 利用组合,而非继承:拥抱 children

React的核心思想之一就是组合。props.children 是你最强大的工具,它能让你的组件变得极其灵活和可复用。

为不同类型的卡片,创建不同的卡片组件,如 ImageCard, VideoCard, TextCard。它们大部分样式和逻辑都是重复的。

创建一个通用的Card组件,把变化的部分作为 children 传入。

function Card({ title, children }) {
  return (
    <div className="card">
      <h2 className="card-title">{title}</h2>
      <div className="card-content">
        {children}
      </div>
    </div>
  );
}

// 现在,你可以自由组合了
function App() {
  return (
    <>
      <Card title="图片卡片">
        <img src="..." alt="..." />
      </Card>
      <Card title="用户信息卡片">
        <UserProfileView user={...} />
      </Card>
    </>
  );
}

同事不需要去学习你那N个不同Card组件的API,只需要一个通用的Card,就能构建出任何他想要的卡片样式。


4. 类型安全:用TypeScript为组件保驾护航

在2025年的今天,为React项目配备TypeScript几乎是“标配”。它不是束缚,而是你和同事之间最清晰的“文档”和“契约”。

一个没有类型定义的组件,当同事在使用时,只能靠猜,或者去读你的源码,才能知道该传什么props。

function Avatar({ src, size, isRound }) {
  // ...
}
type AvatarProps = {
  /** 图片链接 */
  src: string;
  /** 尺寸大小,单位px */
  size?: number;
  /** 是否为圆形 */
  isRound?: boolean;
};

function Avatar({ src, size = 50, isRound = true }: AvatarProps) {
  // ...
}

所以当同事输入<Avatar时,IDE会自动提示所有可用的props、它们的类型和注释。


5. 可读性:写代码,是在与人交流

最后,但同样重要的是,让你的代码易于阅读。

  • 提前返回 (Early Return) :避免深层次的 if/else 嵌套。

    // Bad
    function Component({ user }) {
      if (user) {
        if (user.isActive) {
          return <Dashboard />;
        } else {
          return <InactiveMessage />;
        }
      } else {
        return <Login />;
      }
    }
    
    // Good
    function Component({ user }) {
      if (!user) {
        return <Login />;
      }
      if (!user.isActive) {
        return <InactiveMessage />;
      }
      return <Dashboard />;
    }
    
  • 有意义的命名:变量名、函数名、组件名,都应该清晰地表达其意图。

  • 避免复杂的JSX:如果JSX中的条件渲染逻辑变得复杂,就应该把它抽离成一个新的小组件或一个返回JSX的函数。


写出“能跑”的代码,只是工程师的起点。而写出清晰、健壮、易于维护的代码,让我们成为“体面”的、受人尊敬的程序猿🐒。

以上这些原则,没有一个需要高深的技巧,它们更多的是一种意识,一种对代码质量的追求,一种对团队协作的责任心。让你或你的同事,免于一次“深夜加班😄”。 你们怎么来看?

📌 你可以继续看我的系列文章