My Little World

应对复杂条件渲染场景

容器模式:实现按条件执行 Hooks

把条件判断的结果放到两个组件之中,确保真正 render UI 的组件收到的所有属性都是有值的。

1
2
3
4
5
6
7
8
9
10
11

// 定义一个容器组件用于封装真正的 UserInfoModal
export default function UserInfoModalWrapper({
visible,
...rest, // 使用 rest 获取除了 visible 之外的属性
}) {
// 如果对话框不显示,则不 render 任何内容
if (!visible) return null;
// 否则真正执行对话框的组件逻辑
return <UserInfoModal visible {...rest} />;
}

在容器模式中可以看到,条件的隔离对象是多个子组件,这就意味着它通常用于一些比较大块逻辑的隔离。
所以对于一些比较细节的控制,其实还有一种做法,就是把判断条件放到 Hooks 中去。

1
2
3
4
5
6
7
8
9
10
11

function useUser(id) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
// 当 id 不存在,直接返回,不发送请求
if (!id) return
// 获取用户信息的逻辑
});
}

通过这样一个容器模式,我们把原来需要条件运行的 Hooks 拆分成子组件,
然后通过一个容器组件来进行实际的条件判断,从而渲染不同的组件,实现按条件渲染的目的。
这在一些复杂的场景之下,也能达到拆分复杂度,让每个组件更加精简的目的.

使用 render props 模式重用 UI 逻辑

render props 就是把一个 render 函数作为属性传递给某个组件,
由这个组件去执行这个函数从而 render 实际的内容

Hooks 是逻辑重用的第一选择。
不过在如今的函数组件情况下,Hooks 有一个局限,那就是只能用作数据逻辑的重用,
而一旦涉及 UI 表现逻辑的重用,就有些力不从心了,
而这正是 render props 擅长的地方。
所以,即使有了 Hooks,我们也要掌握 render props 这个设计模式的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

import { Popover } from "antd";

function ListWithMore({ renderItem, data = [], max }) {
const elements = data.map((item, index) => renderItem(item, index, data));
const show = elements.slice(0, max);
const hide = elements.slice(max);
return (
<span className="exp-10-list-with-more">
{show}
{hide.length > 0 && (
<Popover content={<div style={{ maxWidth: 500 }}>{hide}</div>}>
<span className="more-items-wrapper">
and{" "}
<span className="more-items-trigger"> {hide.length} more...</span>
</span>
</Popover>
)}
</span>
);
}

// 这里用一个示例数据
import data from './data';

function ListWithMoreExample () => {
return (
<div className="exp-10-list-with-more">
<h1>User Names</h1>
<div className="user-names">
Liked by:{" "}
<ListWithMore
renderItem={(user) => {
return <span className="user-name">{user.name}</span>;
}}
data={data}
max={3}
/>
</div>
<br />
<br />
<h1>User List</h1>
<div className="user-list">
<div className="user-list-row user-list-row-head">
<span className="user-name-cell">Name</span>
<span>City</span>
<span>Job Title</span>
</div>
<ListWithMore
renderItem={(user) => {
return (
<div className="user-list-row">
<span className="user-name-cell">{user.name}</span>
<span>{user.city}</span>
<span>{user.job}</span>
</div>
);
}}
data={data}
max={5}
/>
</div>
</div>
);
};