容器组件是提供、创建和持有数据并服务于子组件的组件,其唯一的工作是处理数据,而将展示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>
);
}