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 中获取的数据