单一指责原则

就一个类而言,应该仅有一个引起它变化的原因。在 JavaScript 中,需要用到类的场景并不太多,单一职责原则更多地是被运用在对象或者方法级别上,因此本节我们的讨论大多基于对象和方法。

因此,SRP 原则体现为:一个对象(方法)只做一件事情。

SRP 原则在很多设计模式中都有着广泛的运用,例如代理模式迭代器模式单例模式装饰者模式

优缺点

SRP 原则的优点是降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。

但 SRP 原则也有一些缺点,最明显的是会增加编写代码的复杂度。当我们按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。

React中的单一职责原则

常见的错误案例

这里有一个常见的反模式:

// DON'T DO THIS
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(); // Refresh data
    } 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 */}>{/* Complex form fields */}</form>
      <UserStats userId={user.id} />
      <UserPosts userId={user.id} />
    </div>
  );
};

更好的方式:关注点分离

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

// 数据获取钩子
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} />;
};

关键要点

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