本文默认读者对 React Hooks 已经有一定的了解。 因此不再赘述 Hooks API 的使用了, 还未了解的同学可以去官网阅读React Hooks API Reference
React Hooks 基础
使用 React Hooks 就不得不提到 React 函数组件。
Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.
从官网上的介绍来看, 组件就像 JavaScript 的函数, 它接收一些入参(props), 并返回 React 元素。
在我们最初学习使用 React 框架时, 首先了解的是通过Class
书写组件, 这样可以很好地理解并规划类组件
的数据与方法, 而且生命周期
可以让我们更清晰地了解组件的加载以及更新状态的触发时机。
那函数组件
呢? 函数组件
本身解决了从数据到 React 元素的映射, 而Hooks
则在此基础上提供了数据存储以及 Side Effect(副作用)的处理。
此时此刻, 让我们忘记已经熟知的Class
与生命周期
, 来重新认识函数组件
+ Hooks
的 React。
0. 函数组件
函数组件
是用来处理一些简单的 UI 组件, 通过 Props 传入的数据, 抽象封装一些组件。
function Header(props) {
const { title, description, avatar } = props;
return (
<div>
<img src={avatar} />
<h1>{title}</h1>
<p>{description}</p>
</div>
);
}
限制了函数组件使用的原因一方面在于它没有内部数据, 另一方面就是没有生命周期, 因此无法实现具有一定交互与逻辑的功能。
1. 使用 React Hooks 时的数据存储
函数组件内定义的变量不是固化的, 执行完成后, 函数内的变量就会被清理掉了(非闭包情况)。所以, 我们需要通过 Hooks 固化组件需要的一些数据。
上面是 React Hooks 之 useState 的使用例子, 如下是浅尝辄止的理解:
通过
useState
, 我们在某个地方定义了一个对象, 并挂载到某个不会让它消失的地方。useState
方法返回的第一个值就是我们要的数据。useState
方法返回的第二个值是一个函数, 可以设置这个对象的值, 然后会触发函数重新渲染。
上面这坨话说的很模糊, 某个地方
到底是哪个地方, 定义了一个什么样的对象
, 又挂载到了什么地方。设置了新值后又是怎么触发渲染的???
在了解Hook
加载前还是要了解一下 Hook 的定义和存储的。
下面是Hook
的 type 定义
export type Hook = {
// 数据
memoizedState: any,
// 下面这些暂时不需要了解
baseState: any,
baseQueue: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
// 链表下一个节点的指针
next: Hook | null,
};
我们需要了解的是Hook
是通过链表
存储的
- 函数组件第一次执行时
第一个 Hook 会挂载到React Fiber Node
上, 之后在函数执行遇到的 Hook 会依次按顺序挂载到 Hook 节点后。
- 函数组件更新渲染时
函数执行遇到的 Hooks 会按照顺序读取React Fiber Node
上的 Hook 节点。
如上图所示, 函数组件重新执行时是依赖 Hooks 队列的顺序的。如果在条件判断
中使用 Hook 就会让这个队列错乱。
因此也就会有如下官网提示:
Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
2. 使用 React Hooks 处理副作用
副作用(Side Effect), 个人理解, 是在数据->视图
的转化过程中, 出现的一些特别的时机
。我们可以利用这些时机
去处理一些业务逻辑。
以下是使用useEffect
的一个简单的计数 Demo.
使用useEffect
去实现在渲染完成之后要去处理的一些事情。
大家也可以在官网的 API Reference 中找到 React useEffect 说明。
我们可以通过流程图加深理解useEffect
这个 Hook。
以上是useEffect
Hook 的大致执行逻辑, 通过此图会发现useEffect
多了一步数据比对
的过程, 只有当以下条件成立时, SideEffect 才会触发调用。
- useEffect 第一次执行之时
- useEffect 的依赖存在变更之时
我们修改上面计数的例子就可以更好的理解两个 Hook。
function Demo(props) {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
useEffect(() => {
const timer = setTimeout(() => {
setCount(step + count);
}, 1000)
return () => {
clearTimeout(timer);
}
}, [count]);
return (
<div>
<div>Count: {count}</div>
<div>Step: {step}</div>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click
</button>
</div>);
}
在 Gif 图中, 在更新 Step 步长时, 计数第一次仍然是+1
, 随后才会+2
。
- 在
step
更新时,useEffect
的count
没有变化, 所以没有触发更新 useEffect
触发时, 使用的是 Hook 中暂存的数据, 所以步长step
仍然是 1.
我们在 Hook 加载流程上补充上对比的过程。
Hook
中存入的是上一次渲染后的快照值, 所以在执行时, 也会使用上一次渲染留下来的值。
到这里, 我们概括下函数组件的渲染流程
3. 总结
本文只是提到了useState
, useEffect
这两例典型的 React Hooks。
以useState
为例, 我们了解了React Hooks
是如何解决函数组件内无法保留内部数据的问题。
以useEffect
为例, 我们呢了解了React Hooks
如何处理在数据->视图
之间一些时机
的处理
以及很浅显的提到了Hook
中的数据暂存(快照)的原因
React Hooks 还有很多种用法, 本章只是梳理 React Hooks 的一些基本概念。
PS: 下一章会着重于《如何在业务中梳理重组封装自己的 Hooks》