React 应用SOLID原则01

React 应用 SOLID 原则 01

在面向对象编程中,SOLID 原则是设计模式的基础,每个字母分别对应如下:

  • 单一职责原则(SRP)
  • 开放封闭原则(OCP)
  • 里氏替换原则(LSP)
  • 接口隔离原则(ISP)
  • 依赖倒置原则(DIP)

下面看一下每个原则在 React 中的应用

单一职责原则(SRP)

定义:每个应用应该只有一个职责,也就是只做一件事,可以简单的理解为每个功能/模块/组件都应该只做一件事

为了确保每个组件只做一件事

  • 将功能较多的大型组件拆分为较小的组件
  • 将与功能无关的代码提取到单独的函数中
  • 将有联系的功能提取到自定义 hooks 中

代码演示:

显示活跃用户列表的组件

该组件:获取数据,过滤数据,渲染数据

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
const ActiveUsersList = () => {
const [users, setUsers] = useState([]);

useEffect(() => {
const loadUsers = async () => {
const response = await fetch("/some-api");
const data = await response.json();
setUsers(data);
};

loadUsers();
}, []);

const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);

return (
<ul>
{users
.filter((user) => !user.isBanned && user.lastActivityAt >= weekAgo)
.map((user) => (
<li key={user.id}>
<img src={user.avatarUrl} />
<p>{user.fullName}</p>
<small>{user.role}</small>
</li>
))}
</ul>
);
};

同时使用了 useState 和 useEffect,我没可以将他提取到自定义 hook 中

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
const useUsers = () => {
const [users, setUsers] = useState([]);

useEffect(() => {
const loadUsers = async () => {
const response = await fetch("/some-api");
const data = await response.json();
setUsers(data);
};

loadUsers();
}, []);

return { users };
};

const ActiveUsersList = () => {
const { users } = useUsers();

const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);

return (
<ul>
{users
.filter((user) => !user.isBanned && user.lastActivityAt >= weekAgo)
.map((user) => (
<li key={user.id}>
<img src={user.avatarUrl} />
<p>{user.fullName}</p>
<small>{user.role}</small>
</li>
))}
</ul>
);
};

现在,useUser hook 只关心一件事:从 api 获取用户,它使我们的组件更具有可读性

接下来看组件渲染 jsx,我没对对象数组进行遍历,要注意每个数组项生成的 jsx 的复杂性

  • 如果他是一个没有附加任何事件处理函数的单行代码,那么保持内联
  • 对于更复杂的 jsx,建议将其提取为单独的组件
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
const UserItem = ({ user }) => {
return (
<li>
<img src={user.avatarUrl} />
<p>{user.fullName}</p>
<small>{user.role}</small>
</li>
);
};

const ActiveUsersList = () => {
const { users } = useUsers();

const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);

return (
<ul>
{users
.filter((user) => !user.isBanned && user.lastActivityAt >= weekAgo)
.map((user) => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
};

我们将用于呈现用户信息的逻辑提取到了单独的组件中,从而我们的组件更小,更可读

最后对于从 api 获取到的用户列表中所过滤出的非活跃用户的逻辑是相对独立的,可以在其他部分重用,所以可以提取到一个公共函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const getOnlyActive = (users) => {
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);

return users.filter(
(user) => !user.isBanned && user.lastActivityAt >= weekAgo
);
};

const ActiveUsersList = () => {
const { users } = useUsers();

return (
<ul>
{getOnlyActive(users).map((user) => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
};

通过上面几个步骤,组件已经变得非常简单,但是还有优化空间。理论上我们只需要获取数据并渲染他,而不需要任何额外的操作,所以可以将 获取数据,过滤数据 这个操作封装到一个新的自定义 hooks 中

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
// 获取数据
const useUsers = () => {
const [users, setUsers] = useState([]);

useEffect(() => {
const loadUsers = async () => {
const response = await fetch("/some-api");
const data = await response.json();
setUsers(data);
};

loadUsers();
}, []);

return { users };
};

// 列表渲染
const UserItem = ({ user }) => {
return (
<li>
<img src={user.avatarUrl} />
<p>{user.fullName}</p>
<small>{user.role}</small>
</li>
);
};

// 列表过滤
const getOnlyActive = (users) => {
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);

return users.filter(
(user) => !user.isBanned && user.lastActivityAt >= weekAgo
);
};

const useActiveUsers = () => {
const { users } = useUsers();

const activeUsers = useMemo(() => {
return getOnlyActive(users);
}, [users]);

return { activeUsers };
};

const ActiveUsersList = () => {
const { activeUsers } = useActiveUsers();

return (
<ul>
{activeUsers.map((user) => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
};

在这里我们创建了 useActiveUsers hook 来处理获取数据和过滤数据,这样组件只做了一件事:渲染他从 hook 中获取的数据