time_alone3_hires.jpg

Hooks有两大优点,一是方便进行逻辑复用,二是更好的关注分离。因此,我们可以在工作上常用的业务逻辑,抽离出来,写出属于我们自己的hooks。

如何定义?

只需要在创建一个普通函数,然后函数名是 useXXX,然后这个函数内部一定要使用其他Hooks,不然只是名字加了use并不算是自定义Hooks,只能是普通函数,如果用了,这个就不但是普通函数,还是一个Hooks。

所以,自定义Hooks会有两个特点:

  • 函数名开头加了use的函数,React,才能够知道这个函数是一个Hooks。
  • 函数内部使用了Hooks,这个Hooks可以是内置的Hooks,也可以是其他自定义的Hooks。

实际应用

封装通用逻辑

在开发项目中,通常都会有多个常用的通用逻辑,只需要封装成一个Hooks,就可以在多个地方使用这个通用逻辑。例如 请求逻辑。

例子:一个项目里,我们只需要向服务器请求数据即可,除了需要展示的数据外,还需要如何处理请求错误,请求的过程以及是否有正确的返回。

如下:

  1. import { useState } from "react";
  2. const UserText = () => {
  3. //分别为: 保存用户句子、loading状态、 错误状态
  4. const [users, setUsers] = useState({});
  5. const [loading, setLoading] = useState(false);
  6. const [error, setError] = useState(null);
  7. //定义获取用户的回调函数
  8. const fetchUsers = async () => {
  9. setLoading(true);
  10. try {
  11. //请求数据
  12. const res = await fetch("https://api.gmit.vip/Api/YiYan");
  13. const json = await res.json();
  14. //请求成功后
  15. setUsers(json.data);
  16. } catch (err) {
  17. //请求失败
  18. setError(err);
  19. }
  20. setLoading(false);
  21. };
  22. return (
  23. <div>
  24. <button onClick={fetchUsers} disabled={loading}>
  25. {loading ? "Loading..." : "Show Users"}
  26. </button>
  27. {error && <div style={{ color: "red" }}>Failed: {String(error)}</div>}
  28. <br />
  29. <div>{users.text}</div>
  30. </div>
  31. );
  32. };
  33. export default UserText

一般是这样请求数据的,但是,当其他页面也需要请求数据的时候,会重复写如上类似的请求逻辑。为了避免这样的情况,就需要对上面代码中的请求逻辑进行封装。

上面的逻辑是这样的过程:

  1. 首先创建三个state;
  2. 请求发出后,设置 loabding state 为ture;
  3. 请求成功后,将返回的数据放到某个state中,并将loading state 设为 false
  4. 请求失败后,将 error state 为true,并将loading state 设为false。
  5. 最后,把这些状态返回给调用的方法。

通过这样的过程,下面定义个一名为useAsync的hooks来封装逻辑:

  1. import { useState, useCallback } from "react";
  2. const useAsync = (asyncFunction) => {
  3. // 设置三个异步逻辑相关的 state
  4. const [datas, setDatas] = useState(null);
  5. const [loading, setLoading] = useState(false);
  6. const [error, setError] = useState(null);
  7. // 定义一个 callback 用于执行异步逻辑
  8. const execute = useCallback(() => {
  9. // 请求开始时,设置 loading 为 true,清除已有数据和 error 状态
  10. setLoading(true);
  11. setDatas(null);
  12. setError(null);
  13. return asyncFunction()
  14. .then((response) => {
  15. // 请求成功时,将数据写进 state,设置 loading 为 false
  16. setDatas(response);
  17. setLoading(false);
  18. })
  19. .catch((error) => {
  20. // 请求失败时,设置 loading 为 false,并设置错误状态
  21. setError(error);
  22. setLoading(false);
  23. });
  24. }, [asyncFunction]);
  25. return { execute, loading, datas, error };
  26. };
  27. export default useAsync

使用这个Hooks:

  1. import useAsync from "./useAsync";
  2. const UserList = () => {
  3. const { execute, loading, datas, error } = useAsync(async () => {
  4. const res = await fetch("https://api.gmit.vip/Api/YiYan");
  5. const json = await res.json();
  6. return json.data;
  7. });
  8. return (
  9. <div className="user-list">
  10. <button onClick={execute} disabled={loading}>
  11. {loading ? "Loading..." : "Show Users"}
  12. </button>
  13. {error && <div style={{ color: "red" }}>Failed: {String(error)}</div>}
  14. <br />
  15. <div>{datas && datas.text}</div>
  16. </div>
  17. );
  18. };
  19. export default UserList;

当然,除了请求逻辑,还有关于浏览器的一些参数,例如我想获取浏览器的滚动条参数,也是可以这样封装的,这样就可以实现监听浏览器状态。

拆分复杂组件

一般来说,代码行数超过100行或200行以上 的时候,就需要考虑拆分组件,不然会变得难以维护。例如说接手某项目,一看某组件居然达到500多行,就完全没有要去看的感觉。所以,要“保持每个函数的短小”这个建议,就会很好理解且维护。

拆分很简单,只需要把这个组件的逻辑拆分成一个单独的Hooks,然后调用这个Hooks即可。拆分并成Hooks并不一定是为了重用 ,而是为了让代码看起来更好理解,让不相关的逻辑分离开来。

例如如下是已经拆分好的代码:

  1. const useArticles = () => {
  2. //文章列表请求
  3. //使用刚刚在上面已经封装好的 useAsync()来请求 文章列表
  4. //....这里是请求数据的代码
  5. return { articles, articlesError }
  6. }
  7. const useCategories = () => {
  8. //分类列表请求
  9. //使用刚刚在上面已经封装好的 useAsync()来请求 分类列表
  10. //....这里是请求数据的代码
  11. return { categories, categoriesError }
  12. }
  13. const useCombinedArticles = (useArticles, useCategories) => {
  14. //把文章列表和分类列表这两个数据组合到一起
  15. //返回组合后的数据
  16. }
  17. const useFiltereArticles = (useArticles) => {
  18. //按照分类来过滤
  19. //返回过滤后的数据
  20. }
  21. const BlogList = () => {
  22. const [selectedCategory, setSelectedCategory] = useState(null);
  23. //获取文章列表
  24. const { articles, articlesError } = useArticles();
  25. //获取分类列表
  26. const { categories, categoriesError } = useCategories();
  27. //组合数据
  28. const combined = useCombinedArticles(articles, categories);
  29. //实现过滤
  30. const result = useFilteredArticles(combined, selectedCategory);
  31. //一大堆的业务逻辑
  32. return (
  33. //UI渲染
  34. )
  35. }
  36. export default BlogList;

总结

自定义Hooks可以做到:

  1. 抽取业务逻辑。
  2. 封装通用逻辑。
  3. 监听浏览器状态。
  4. 拆分复杂组件。

做到以上,项目会在很大程度上,更易于维护与理解。