我们都听过那个段子:每个程序员在接手一个老项目时,内心都会循环播放一句话:“这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>
);
}
我们将它拆分成三个小组件:
useUser(自定义Hook): 专门负责获取和管理用户数据。UserProfileView: 专门负责展示用户信息。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设计的建议:
- 命名清晰:
onUserClick比handleClick好一万倍。 - 布尔值:
<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的函数。
写出“能跑”的代码,只是工程师的起点。而写出清晰、健壮、易于维护的代码,让我们成为“体面”的、受人尊敬的程序猿🐒。
以上这些原则,没有一个需要高深的技巧,它们更多的是一种意识,一种对代码质量的追求,一种对团队协作的责任心。让你或你的同事,免于一次“深夜加班😄”。 你们怎么来看?