Hooks有两大优点,一是方便进行逻辑复用,二是更好的关注分离。因此,我们可以在工作上常用的业务逻辑,抽离出来,写出属于我们自己的hooks。
如何定义?
只需要在创建一个普通函数,然后函数名是 useXXX
,然后这个函数内部一定要使用其他Hooks,不然只是名字加了use并不算是自定义Hooks,只能是普通函数,如果用了,这个就不但是普通函数,还是一个Hooks。
所以,自定义Hooks会有两个特点:
- 函数名开头加了use的函数,React,才能够知道这个函数是一个Hooks。
- 函数内部使用了Hooks,这个Hooks可以是内置的Hooks,也可以是其他自定义的Hooks。
实际应用
封装通用逻辑
在开发项目中,通常都会有多个常用的通用逻辑,只需要封装成一个Hooks,就可以在多个地方使用这个通用逻辑。例如 请求逻辑。
例子:一个项目里,我们只需要向服务器请求数据即可,除了需要展示的数据外,还需要如何处理请求错误,请求的过程以及是否有正确的返回。
如下:
import { useState } from "react";
const UserText = () => {
//分别为: 保存用户句子、loading状态、 错误状态
const [users, setUsers] = useState({});
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
//定义获取用户的回调函数
const fetchUsers = async () => {
setLoading(true);
try {
//请求数据
const res = await fetch("https://api.gmit.vip/Api/YiYan");
const json = await res.json();
//请求成功后
setUsers(json.data);
} catch (err) {
//请求失败
setError(err);
}
setLoading(false);
};
return (
<div>
<button onClick={fetchUsers} disabled={loading}>
{loading ? "Loading..." : "Show Users"}
</button>
{error && <div style={{ color: "red" }}>Failed: {String(error)}</div>}
<br />
<div>{users.text}</div>
</div>
);
};
export default UserText
一般是这样请求数据的,但是,当其他页面也需要请求数据的时候,会重复写如上类似的请求逻辑。为了避免这样的情况,就需要对上面代码中的请求逻辑进行封装。
上面的逻辑是这样的过程:
- 首先创建三个state;
- 请求发出后,设置 loabding state 为ture;
- 请求成功后,将返回的数据放到某个state中,并将loading state 设为 false
- 请求失败后,将 error state 为true,并将loading state 设为false。
- 最后,把这些状态返回给调用的方法。
通过这样的过程,下面定义个一名为useAsync的hooks来封装逻辑:
import { useState, useCallback } from "react";
const useAsync = (asyncFunction) => {
// 设置三个异步逻辑相关的 state
const [datas, setDatas] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// 定义一个 callback 用于执行异步逻辑
const execute = useCallback(() => {
// 请求开始时,设置 loading 为 true,清除已有数据和 error 状态
setLoading(true);
setDatas(null);
setError(null);
return asyncFunction()
.then((response) => {
// 请求成功时,将数据写进 state,设置 loading 为 false
setDatas(response);
setLoading(false);
})
.catch((error) => {
// 请求失败时,设置 loading 为 false,并设置错误状态
setError(error);
setLoading(false);
});
}, [asyncFunction]);
return { execute, loading, datas, error };
};
export default useAsync
使用这个Hooks:
import useAsync from "./useAsync";
const UserList = () => {
const { execute, loading, datas, error } = useAsync(async () => {
const res = await fetch("https://api.gmit.vip/Api/YiYan");
const json = await res.json();
return json.data;
});
return (
<div className="user-list">
<button onClick={execute} disabled={loading}>
{loading ? "Loading..." : "Show Users"}
</button>
{error && <div style={{ color: "red" }}>Failed: {String(error)}</div>}
<br />
<div>{datas && datas.text}</div>
</div>
);
};
export default UserList;
当然,除了请求逻辑,还有关于浏览器的一些参数,例如我想获取浏览器的滚动条参数,也是可以这样封装的,这样就可以实现监听浏览器状态。
拆分复杂组件
一般来说,代码行数超过100行或200行以上 的时候,就需要考虑拆分组件,不然会变得难以维护。例如说接手某项目,一看某组件居然达到500多行,就完全没有要去看的感觉。所以,要“保持每个函数的短小”这个建议,就会很好理解且维护。
拆分很简单,只需要把这个组件的逻辑拆分成一个单独的Hooks,然后调用这个Hooks即可。拆分并成Hooks并不一定是为了重用 ,而是为了让代码看起来更好理解,让不相关的逻辑分离开来。
例如如下是已经拆分好的代码:
const useArticles = () => {
//文章列表请求
//使用刚刚在上面已经封装好的 useAsync()来请求 文章列表
//....这里是请求数据的代码
return { articles, articlesError }
}
const useCategories = () => {
//分类列表请求
//使用刚刚在上面已经封装好的 useAsync()来请求 分类列表
//....这里是请求数据的代码
return { categories, categoriesError }
}
const useCombinedArticles = (useArticles, useCategories) => {
//把文章列表和分类列表这两个数据组合到一起
//返回组合后的数据
}
const useFiltereArticles = (useArticles) => {
//按照分类来过滤
//返回过滤后的数据
}
const BlogList = () => {
const [selectedCategory, setSelectedCategory] = useState(null);
//获取文章列表
const { articles, articlesError } = useArticles();
//获取分类列表
const { categories, categoriesError } = useCategories();
//组合数据
const combined = useCombinedArticles(articles, categories);
//实现过滤
const result = useFilteredArticles(combined, selectedCategory);
//一大堆的业务逻辑
return (
//UI渲染
)
}
export default BlogList;
总结
自定义Hooks可以做到:
- 抽取业务逻辑。
- 封装通用逻辑。
- 监听浏览器状态。
- 拆分复杂组件。
做到以上,项目会在很大程度上,更易于维护与理解。