容器组件是提供、创建和持有数据并服务于子组件的组件,其唯一的工作是处理数据,而将展示 UI 的工作交给展示组件。而处理数据的工作的逻辑不止能直接写在容器组件中,其实也可以被封装成一个 hooks,再被容器组件调用,从而使这部分逻辑抽离、语义化并易于维护。
以下是一个容器组件的例子。它通过 useEffect 获取文章数据,期间会维护isLoading
、error
、posts
三个状态变量,再通过这三个变量控制展示组件的渲染。
// 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。同样维护isLoading
、error
、posts
三个状态变量,但是最后是将这些变量作为函数的返回值,利用函数闭包特性,将三个变量暴露给容器组件使用。
// 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,
};
}
容器组件使用usePosts
hooks 的示例如下
// 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>
);
}