React自定义Hook
本文适合觉得React自定义hook难、对自定义hook有兴趣的小伙伴阅读。
欢迎关注前端早茶,与广东靓仔携手共同进阶~
一、前言
React官方文档:
https://zh-hans.reactjs.org/docs/hooks-custom.html#gatsby-focus-wrapper
二、什么是自定义Hook
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
以上是React官方的定义,我们可以很明确,自定义一个Hook就是开发一个函数,这个函数只是一个普通的函数,不是什么构造函数、自执行函数之类的。
那要开发一个什么样的函数?一个函数的组成有函数名、参数、函数内部、返回值,我们先结合React官方文档中对自定义Hook的要求,对其每个部分分析一下。
函数名:以 “
use
” 开头,比如useState。为什么要以 “use
” 开头。这是因为,如果不以 “use
” 开头的话,无法判断这个函数是否包含对其内部 Hook 的调用,React 将无法自动检查这个自定义Hook是否违反了 Hook 的规则。参数:并没有特殊要求,只是React官网中有提到过,可以接收另一个Hook的返回值作为参数,实现在多个 Hook 之间传递信息的功能。
函数内部:在函数内部可以调用其他的Hook,但是要遵循两个规则:
只在最顶层使用 Hook ,不要在循环,条件或嵌套函数中调用 Hook,这是因为 React 是靠 Hook 调用的顺序来知道哪个 state 对于哪个
useState
的。具有看官网这里的解释。不要在普通的 JavaScript 函数中调用 Hook,只能在 React 函数中调用 Hook。这一点很好理解吧,Hook 本身就是由 React 提供的。
返回值:没有限制一定要返回什么,当然一个函数默认返回一个
undefined
。
三、自定义Hook的场景
四、自定义一个最简单的Hook
const Demo = () =>{
useEffect(() => {
console.log('组件首次渲染')
},[]);
}
export default Demo;
useEffect
的第二参数接收一个空数组表示只在组件首次渲染时执行作为第一参数传入useEffect
的方法。那我们把这个通用逻辑通过自定义一个名叫useMount
的 Hook 提取出来,实现如下所示:import { useEffect } from 'react';
const useMount = (fn: () => void) => {
useEffect(() => {
fn();
}, []);
};
export default useMount;
useMount
这个自定义Hook接收函数fn
作为参数,在组件首次渲染时执行函数fn
。五、如何使用自定义Hook
export
导出,故用import
引入使用:import useMount from '@/hooks/useMount';
const Demo = () =>{
useMount(() =>{
console.log('组件首次渲染')
})
return(
<div>demodiv>
)
}
export default Demo;
六、自定义Hook的内部
React提供了10个内部Hook,在自定义Hook的内部,一般都是利用这10个内部Hook,进行组装和扩展,来自定义各种功能的Hook。
初学者在这里往往有个疑问,比如在一个自定义Hook useMyState
中使用useState
定义了一个a
变量。
import { useState } from 'react';
const useMyState = () => {
const [a,setA] = useState();
return [a,setA]
};
export default useMyState;
useMy
的组件中又使用useState
定义了一个a
变量,这样会不会引起冲突。import useMyState from '@/hooks/useMount';
const Demo = () =>{
const [a,setA] = useState();
const [b,setB] = useMyState();
return(
<div>demodiv>
)
}
export default Demo;
useState
和useEffect
,它们是完全独立的。而且Hook也是个函数,有函数作用域在兜底。const Demo = () =>{
const [current , setCurrent ] = useState(1);
const [previous , setPrevious ]= useState(0);
const updata = () => {
setCurrent(value =>{
setPrevious(value);
return value+1;
})
}
return (
<div>
<div>{current}div>
<div>{previous}div>
<div onClick={updata}>改变div>
div>
);
}
export default Demo
其中使用两个useState
创建了当前current
和之前previous
的两个变量,在改变current
时,把current
之前的值赋值给previous
。虽然这么实现也可以,但是有点不优雅,假如还要对current
之前的值进行一些判断再赋值给
previous
,那是不是在改变current
的setCurrent
方法中要写很多不相干的东西,有点违背单一原则。
usePrevious
。import { useRef } from 'react';
const usePrevious = (state, compare) =>{
const prevRef = useRef();
const curRef = useRef();
const needUpdate = typeof compare === 'function' ? compare(curRef.current, state) : true;
if (needUpdate) {
prevRef.current = curRef.current;
curRef.current = state;
}
return prevRef.current;
}
export default usePrevious;
useRef
来保存一个值的旧值和新值,比之前用useState
来保存好。原因在于useRef
返回一个可变的 ref
对象,其current
属性被初始化为传入的参数。返回的ref
对象在组件的整个生命周期内保持不变。usePrevious
呢,示例如下所示:import usePrevious from '@/hooks/usePrevious';
const Demo = () =>{
const [current , setCurrent ] = useState(1);
const compare = (oldValue,newValue) =>{
if(oldValue !== newValue){
return true;
}
}
const previous = usePrevious(current,compare);
const updata = () => {
setCurrent(value =>{
value = value + 1;
return value;
})
}
return (
<div>
<div>{current}div>
<div>{previous}div>
<div onClick={updata}>改变div>
div>
);
}
export default Demo
七、自定义Hook和React内部Hook有必然关系吗
到这里,我们再思考一个问题,一定要用React内部Hook开发自定义Hook吗?
答案当然是不一定了。为什么呢?我们再来看一下usePrevious
的使用,usePrevious
并没有像useState
提供更新值的方法,那为什么usePrevious
的返回值previous
会实时更新。
这就要说到函数式组件的特性了,函数式组件的函数体就是类组件中的render()
,当组件的state或props改变时,组件会重新渲染,函数式组件的函数体会重新执行一遍。
那么当current
改变时,Demo
这个函数会重新执行一遍,执行到const previous = usePrevious(current,compare);
,usePrevious
接收到新的current
,自然返回新的previous
,这样就实时更新了。
好那么现在自定义一个Hook useMyCount
如下所示:
const useMyCount = (state) =>{
return state + 1;
}
export default useMyCount;
import useMyCount from '@/hooks/usePrevious';
const Demo = () =>{
const [num , setNum ] = useState(1);
const count = useMyCount(num);
const updata = () => {
setNum(value =>{
value = value + 1;
return value;
})
}
return (
<div>
<div>{num}div>
<div>{count}div>
<div onClick={updata}>加一div>
div>
);
}
export default Demo
会发现Demo组件中count
也会实时更新,而在自定义Hook useMyCount
中,有没有使用React内部Hook没有丝毫关系。
再比如我自定义的一个获取当前时间戳的Hook:
const useTime = () =>{
return new Date().getTime();
}
export default useTime;
import * as Api from '@/api'
const useTask = async () =>{
const res = await Api.getTask();
return res;
}
export default useTask;
这些自定义Hook内部都没有用到React的内部Hook,所以说自定义Hook不难。
八、关于自定义Hook一些要求
自定义Hook相当写一个函数,如果其内部要使用React的内部Hook,要遵循Hook的使用规则外。
此外还要符合书写函数的一些要求,比如对参数的定义,对返回值结构的要求。
最后要注意最重要的一点,一个Hook只负责一件事情,即遵循单一原则。
参数方面的要求
无参数
允许 Hooks 无参数。
const time = useTime();
单参数
单参数无论是否必填直接输入。
const a = useA(parame);
多必选参数
必选参数小于 2 个,应平级输入。
const a = useA(parame1, parame2);
如果多于 2 个,应以 object 形式输入。
const a = useA( {parame1:1, parame2:2, parame3:3 } );
多非必选参数
多非必选参数以 object 形式输入。
const a = useA({parame1?, parame2?, parame3?, parame4?});
必选参数 + 非必选参数
必选参数在前,非必选参数在后。
const a = useA(parame,{parame1?, parame2?, parame3?, parame4?});
返回值结构的要求
无输出
允许 Hooks 无输出,一般常见于生命周期类 Hooks。
useMount(() => {});
value 型
Hooks 输出仅有一个值。
const a = useA();
value setValue 型
输出值为 value 和 setValue 类型的,结构为 [value, setValue] 。
const [state, setState] = useA(a);
value actions 型
其中actions为操作数据的方法。
输出值为单 value 与多 actions 类型的,结构为 [value, actions] 。
const [value, { actions1, actions2, actions3}] = useA(...);
values 型
输出值为多 value 类型的,结构为 {...values}
const {value1, value2, value3} = useA();
values actions 型
输出值为多 value 与多 actions 类型的,结构为 {...values, ...actions} 。
const {value1, value2, actions1, actions2} = useA(...);
本文来自作者@红尘炼心
https://juejin.cn/post/7022777747722207269
九、总结
关注我,一起携手进阶
欢迎关注前端早茶,与广东靓仔携手共同进阶~