
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) => {// 设置三个异步逻辑相关的 stateconst [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 为 falsesetDatas(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可以做到:
- 抽取业务逻辑。
- 封装通用逻辑。
- 监听浏览器状态。
- 拆分复杂组件。
做到以上,项目会在很大程度上,更易于维护与理解。
