当我刚接手 React 项目的时候,就对整体项目代码看了一遍,其中就有一个命名为 customer-hooks,打开一看,全都是命名为 usexxx 的 jsx 文件,后面了解到这是大佬们封装的自定义 hook。

于是,今天就自己来总结一下对于 Custom React Hooks 一些思考。

自定义 Hook

以下来自 React 官网 关于自定义 Hook 的介绍:

与 React 组件不同的是,自定义 Hook 不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么(如果需要的话)。换句话说,它就像一个正常的函数。但是它的名字应该始终以 use 开头,这样可以一眼看出其符合 Hook 的规则。

而对于 Hook 的规则:

  • 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。
  • 只在 React 函数中调用 Hook

自定义 Hook 封装了一些逻辑代码,并能够把数据向下传递到渲染树的操作。

启动一个 react 项目

可以通过如下命令快速创建简单 react 应用,详细步骤可以参考官网。

npx create-react-app react-demo

简单的例子

下面编写我们自定义的 Hook,本人还是比较菜鸡,所以就拿阿里的 ahooks 官方文档 学习了,在 src 文件目录下创建如下目录结构,用来存放我们的自定义 hook。


代码是来自于 ahooks github 仓库地址

PS:这里就不粘贴源码了,大家可以在上述地址拿到。

我们的父组件代码如下:

import SubApp from './subApp';
import useBoolean from './custom-hooks/useBoolean/index';

function App() {
const [state, { toggle, setTrue, setFalse }] = useBoolean(true);
return (
<div>
<p>Effects:{JSON.stringify(state)}</p>
<p>
<button type="button" onClick={() => toggle()}>
Toggle
</button>
<button type="button" onClick={setFalse} style={{ margin: '0 16px' }}>
Set false
</button>
<button type="button" onClick={setTrue}>
Set true
</button>
</p>
<SubApp />
</div>
)
}

export default App;

通过 npm start 启动项目,可以看到如下界面:

可以看到,使用了自定义 Hook,我们会省去很多重复性的逻辑代码,在父子组件我们都可以使用自定义的 hook。

思考一

但是,我就有问题了,虽然自定义 hook 很香,但发现没有,当我们重新渲染的时候,这一整块逻辑代码也会跟着运行,如果它的运行可以忽略不计倒还好,但还是要考虑一下万一情况,比如你运行了很多个 hook 呢?

想了想,既然这个 useBoolean 是依赖于返回的 state,那么是不是可以通过 useMemo 来解决问题。

查看 ahooks 源码,果然,就有通过 useMemo 来处理的:

const actions = useMemo(() => {
const reverseValueOrigin = (reverseValue === undefined ? !defaultValue : reverseValue) as D | R;

// 切换返回值
const toggle = (value?: D | R) => {
// 强制返回状态值,适用于点击操作
if (value !== undefined) {
setState(value);
return;
}
setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
};
// 设置默认值
const setLeft = () => setState(defaultValue);
// 设置取反值
const setRight = () => setState(reverseValueOrigin);
return {
toggle,
setLeft,
setRight,
};
}, [defaultValue, reverseValue]);

思考二

上述我是有父子组件,它们都调用了自定义 Hook ,那么我想的是它们会共享状态嘛,毕竟都是返回 state,那么假设我改了父组件的 state,那么子组件也会跟着变吗?

带着好奇心,我操作了一下界面,将父组件调用一下 toggle 方法,结果如下:


在此基础上,我再调用一下父组件的 setTrue 方法试试,结果如下:


发现,并不会因为多个组件调用同一个自定义 Hook 而共享状态。

来自 React 官网 的表述:

自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是 完全隔离 的。

那么自定义 Hook 如何获取独立的 state?

每次调用 Hook,它都会获取独立的 state。由于我们直接调用了 useBoolean,从 React 的角度来看,我们的组件只是调用了 useState 和 useMemo。

我们可以在一个组件中多次调用 useState 和 useEffect等,它们是完全独立的。

那么,结合官方的说法,理解起来就容易多了,其实我们可以这样想,每次调用会返回一个新的实例,如下述我们使用 useBoolean 代码:

const [state, { toggle, setTrue, setFalse }] = useBoolean(true);

其实可以想象成返回新的实例,每个实例都会开一个新的空间存放,那么每个组件引用的那个实例都是完全独立开的,所以就不存在状态共享了,放心使用。

思考三

既然状态隔离了,我在想有啥办法能够共享状态呢?

我想有时候也得需要在组件之间共享状态的。

于是,我又翻了翻公司代码以及问了我导师,我发现居然使用了 context,那么,就得研究一下 context 的一些用法。

结尾

为了让文章少点废话,让大家在简短的时间内能够吸收知识,本篇内容就先到此了,下一篇我们将讲解 React Context 的一些使用方法,来解决上文的思考。

关于本文内容,也欢迎和大家交流讨论。

我是【一百个Chocolate】,如果喜欢的话,可以给本文点个赞,我们下期再见啦~