容器组件是提供、创建和持有数据并服务于子组件的组件,其唯一的工作是处理数据,而将展示 UI 的工作交给展示组件。而处理数据的工作的逻辑不止能直接写在容器组件中,其实也可以被封装成一个 hooks,再被容器组件调用,从而使这部分逻辑抽离、语义化并易于维护。

以下是一个容器组件的例子。它通过 useEffect 获取文章数据,期间会维护isLoadingerrorposts三个状态变量,再通过这三个变量控制展示组件的渲染。

// src/components/PostContainer.tsx
import { useEffect, useState } from "react";
import { ISinglePost } from "./types";
import Posts from "./Posts";

export default function PostContainer() {
  const [posts, setPosts] = useState<ISinglePost[] | null>(null);
  const [isLoading, setIsLoading] = useState<Boolean>(false);
  const [error, setError] = useState<unknown>();

  useEffect(() => {
    (async () => {
      try {
        setIsLoading(true);
        const resp = await fetch("https://jsonplaceholder.typicode.com/posts");
        const data = await resp.json();
        setPosts(data.filter((post: ISinglePost) => post.userId === 1));
        setIsLoading(false);
      } catch (err) {
        setError(err);
        setIsLoading(false);
      }
    })();
  }, []);

  return isLoading ? (
    <span>Loading... </span>
  ) : posts ? (
    <Posts posts={posts} />
  ) : (
    <span>{JSON.stringify(error)}</span>
  );
}

除了能直接在组件里获取文章数据,其实我们也可以将获取文章数据这部分的逻辑抽离为一个 hooks。同样维护isLoadingerrorposts三个状态变量,但是最后是将这些变量作为函数的返回值,利用函数闭包特性,将三个变量暴露给容器组件使用。

// src/hooks/usePosts.ts
import { useEffect, useState } from "react";
import { ISinglePost } from "./types";

export default function usePosts() {
  const [posts, setPosts] = useState<ISinglePost[] | null>(null);
  const [isLoading, setIsLoading] = useState<Boolean>(false);
  const [error, setError] = useState<unknown>();

  useEffect(() => {
    (async () => {
      try {
        setIsLoading(true);
        const resp = await fetch("https://jsonplaceholder.typicode.com/posts");
        const data = await resp.json();
        setPosts(data.filter((post: ISinglePost) => post.userId === 1));
        setIsLoading(false);
      } catch (err) {
        setError(err);
        setIsLoading(false);
      }
    })();
  }, []);

  return {
    isLoading,
    posts,
    error,
  };
}

容器组件使用usePostshooks 的示例如下

// src/components/PostsContainer.tsx
import { ISinglePost } from "./types";
import usePosts from "@/hooks/usePosts";
import SinglePost from "./SinglePost";

export default function Posts(props: { posts: ISinglePost[] }) {
  const { isLoading, posts, error } = usePosts();

  return (
    <ul
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
      }}
    >
      {isLoading ? (
        <span>Loading...</span>
      ) : posts ? (
        posts.map((post: ISinglePost) => <SinglePost {...post} />)
      ) : (
        <span>{JSON.stringify(error)}</span>
      )}
    </ul>
  );
}

参考
React 中的关注点分离——如何使用容器组件和展示组件