cover_image

React 开发使用单一职责原则

ikoofe KooFE前端团队
2025年02月12日 23:08

本文翻译自 Single Responsibility Principle in React: The Art of Component Focus,主要讨论单一职责原则在 React 中的应用。

单一职责原则指出,一个类应该只有一个引起变化的原因。

多职责的问题

以下是一个常见的反模式:

// 不要这样做const UserProfile = () => {  const [user, setUser] = useState<User | null>(null);  const [loading, setLoading] = useState(true);  const [error, setError] = useState<Error | null>(null);  useEffect(() => {    fetchUser();  }, []);  const fetchUser = async () => {    try {      const response = await fetch("/api/user");      const data = await response.json();      setUser(data);    } catch (e) {      setError(e as Error);    } finally {      setLoading(false);    }  };  const handleUpdateProfile = async (data: Partial<User>) => {    try {      await fetch("/api/user", {        method: "PUT",        body: JSON.stringify(data),      });      fetchUser(); // 刷新数据    } catch (e) {      setError(e as Error);    }  };  if (loading) return <div>Loading...</div>;  if (error) return <div>Error: {error.message}</div>;  if (!user) return <div>No user found</div>;  return (    <div>      <h1>{user.name}</h1>      <form onSubmit={/* form logic */}>{/* 复杂的表单字段 */}</form>      <UserStats userId={user.id} />      <UserPosts userId={user.id} />    </div>  );};

这个组件违反了 SRP,因为它负责了以下内容:

  • 数据获取
  • 错误处理
  • 加载状态
  • 表单处理
  • 布局和展示

更好的方式:关注点分离

让我们将其拆分为专注的组件:

// 数据获取钩子const useUser = (userId: string) => {  const [user, setUser] = useState<User | null>(null);  const [loading, setLoading] = useState(true);  const [error, setError] = useState<Error | null>(null);  useEffect(() => {    fetchUser();  }, [userId]);  const fetchUser = async () => {    try {      const response = await fetch(`/api/user/${userId}`);      const data = await response.json();      setUser(data);    } catch (e) {      setError(e as Error);    } finally {      setLoading(false);    }  };  return { user, loading, error, refetch: fetchUser };};// 展示组件const UserProfileView = ({  user,  onUpdate,}: {  user: User;  onUpdate: (data: Partial<User>) => void;}) => (  <div>    <h1>{user.name}</h1>    <UserProfileForm user={user} onSubmit={onUpdate} />    <UserStats userId={user.id} />    <UserPosts userId={user.id} />  </div>);// 容器组件const UserProfileContainer = ({ userId }: { userId: string }) => {  const { user, loading, error, refetch } = useUser(userId);  const handleUpdate = async (data: Partial<User>) => {    try {      await fetch(`/api/user/${userId}`, {        method: "PUT",        body: JSON.stringify(data),      });      refetch();    } catch (e) {      // 错误处理    }  };  if (loading) return <LoadingSpinner />;  if (error) return <ErrorMessage error={error} />;  if (!user) return <NotFound message="User not found" />;  return <UserProfileView user={user} onUpdate={handleUpdate} />;};

关键要点

  • 分离数据和展示 —— 使用钩子处理数据,组件处理 UI
  • 创建专注的组件 —— 每个组件应该做好一件事
  • 使用组合 —— 从简单部分构建复杂功能
  • 逻辑复用 —— 将可复用的逻辑提取到自定义钩子中
  • 分层思考 —— 数据层、业务逻辑层、展示层

结论

当每个组件都有一个明确且单一的职责时,整个应用程序将变得更易于维护、测试和扩展。

正如 Bob 大叔在《Clean Architecture》中强调的那样,关键在于“只有一个引起变化的原因”。这种细微的区分至关重要:

  • 一个组件可能做几件相关的事情,但如果它们都因相同的原因而改变(例如更新用户资料 UI),那么它们可能应该放在一起。

  • 相反,两个看似简单的操作如果因不同的原因而改变(例如用户偏好与认证逻辑),则可能需要分开。

当你发现自己用“和”来描述一个组件的功能时,它可能违反了 SRP。拆分开它!但也要考虑这些部分为何需要改变,以及谁会请求这些改变。




继续滑动看下一个
KooFE前端团队
向上滑动看下一个