大家好,我是在学习 React 的前端西瓜哥。
我们在写 React 函数组件时,如果想要复用组件的部分逻辑,可以考虑写自定义 Hook。本文会教大家如何写自定义 React Hook。
Hook 的规则
在此之前,我们先了解一下 Hook 的使用规则。
首先 Hook 只能在函数组件的顶层使用,不能在循环、条件、嵌套函数中执行。
这和 React Hook 的实现原理有关。当第一次执行函数组件时,React 会分配一个对象,然后一个个调用 Hook 时,将传入的参数或得到的结果依次放入到有序的表中缓存起来,然后保存在该对象中。
之后再次执行函数组件时,必须要保证这些 Hook 的执行顺序相同,才能做依赖项的对比,以及和前一次渲染的状态的对比等对比逻辑。
如果能 hook 可以出现循环、条件、嵌套函数中,就不能保证 Hook 执行顺序不变。
此外Hook 只能在函数组件内工作,不要在非组件函数中使用 Hook。
如果你在普通函数内使用了 Hook,React 会报错。
当然如果是自定义 Hook(一个使用了 React 内置 Hook 的普通函数),然后放到函数组件内,那也是合法的。
为防止开发者不小心写岔,React 官方还写了一个名为 eslint-plugin-react-hooks 的 ESLint 插件,用于检测不合法的 Hook 调用位置,实在是太贴心了。强烈建议使用。
写自定义 Hook
自定义 Hook,就是使用了内置 Hook 的普通函数。它是对函数组件可复用的部分逻辑的做了一层封装,然后再被函数组件使用。
自定义的 Hook 必须使用 use 开头。因为 React 的官方 ESLint 插件认为 use 开头是自定义 Hook。
如果你不用 use 开头,ESLint 插件就会认为这是一个普通函数,调用时不符合 Hook 规则时不会报错,你就可能写出有问题的代码。
另外,自定义 Hook 下使用的 Hook,也必须位于顶层,这也是为了保证 hook 的顺序多次执行能保持一致。
下面我们来写几个常用的自定义 Hook。
useMount / useUnmount
我们希望实现类似类函数 componentDidMount 的效果。
const useMount = fn => { useEffect(() => { fn(); }, []); }
使用方式:
function App = () => { // 自定义 Hook useEffect(() => { console.log('挂载'); }); // 原来的写法 useEffect(() => { console.log('挂载'); }, []); }
相比原来的 useEffect 的实现,做了封装后的 useMount 更语义化一些。此外也不需要写多余的依赖项,并将组件销毁的回调函数也隔离出去了。
类似类函数 componentWillUnmount 的 useUnmount Hook 实现如下:
const useUnmount = fn => { useEffect(() => fn, []); } // 用法 useUnmount(() => { console.log('销毁'); })
useUpdateEffect
下面我们再实现一个 useEffect 的剔除掉挂载那一次的版本,对标类函数的 componentDidUpdate。
const useUpdateEffect = (fn, deps) => { const isMount = useRef(true); useEffect(() => { if (isMount.current) { isMount.current = false; return; } return fn(); }, deps); } // 用法 useUpdateEffect(() => { console.log('更新'); })
思路其实很简单,就是多使用一个默认值为 true 的布尔值变量。我们用它来记录当前是否为第一次执行,如果是,就不执行传入的函数,然后将布尔值设置为 false。
false 代表之后都是第二次第三次的执行,每次都执行传入的函数。
这里我们没有用 useState,因为通过 setState 方法会重新渲染组件。所以我们使用了 useRef,修改它的值不会触发组件的更新。
结尾
自定义 Hook,是一种比组件更小粒度的可复用逻辑组织方式,这也是 React 函数组件带给我们最大的惊喜。为此,我们有必要学习好自定义 Hook。
我是前端西瓜哥,欢迎关注我。