今天,让我们一起深入探究 React Hook 的实现方法,以便更好的理解它。但是,它的各种神奇特性的不足是,一旦出现问题,调试非常困难,这是由于它的背后是由复杂的堆栈追踪(stack trace)支持的。因此,通过深入学习 React 的新特性:hook 系统,我们就能比较快地解决遇到的问题,甚至可以直接杜绝问题的发生。
在开始讲解之前,我先声明我不是 React 的开发者或者维护者,所以我的理解可能也并不是完全正确。我确实非常深入地研究过了 React 的 hook 系统的实现,但是无论如何我仍无法保证这就是 React 实际的工作方式。话虽如此,我还是会用 React 源代码中的证据和引用来支持我的文章,使我的论点尽可能坚实。
React hook 系统概要示意图
我们先来了解 hook 的运行机制,并要确保它一定在 React 的作用域内使用,因为如果 hook 不在正确的上下文中被调用,它就是毫无意义的,这一点你或许已经知道了。
Dispatcher
dispatcher 是一个包含了 hook 函数的共享对象。基于 ReactDOM 的渲染状态,它将会被动态的分配或者清理,并且它能够确保用户不可在 React 组件之外获取 hook(详见源码)。
在切换到正确的 Dispatcher 以渲染根组件之前,我们通过一个名为 enableHooks
的标志来启用/禁用 hook。在技术上来说,这就意味着我们可以在运行时开启或关闭 hook。React 16.6.X 版本中也有对此的实验性实现,但它实际上处于禁用状态(详见源码)
当我们完成渲染工作后,我们将 dispatcher 置空并禁止用户在 ReactDOM 的渲染周期之外使用 hook。这个机制能够保证用户不会做什么蠢事(详见源码)。
dispatcher 在每次 hook 的调用中都会被函数 resolveDispatcher()
解析。正如我之前所说,在 React 的渲染周期之外,这些都无意义了,React 将会打印出警告信息:“hook 只能在函数组件内部调用”(详见源码)。
let currentDispatcher
const dispatcherWithoutHooks = {
/* ... */ }
const dispatcherWithHooks = {
/* ... */ }
function resolveDispatcher() {
if (currentDispatcher) return currentDispatcher throw Error("Hooks can't be called")}function useXXX(...args) {
const dispatcher = resolveDispatcher()
return dispatcher.useXXX(...args)
}
function renderRoot() {
currentDispatcher = enableHooks ? dispatcherWithHooks : dispatcherWithoutHooks performWork() currentDispatcher = null
}
dispatcher 实现方式概览。
现在我们简单了解了 dispatcher 的封装机制,下面继续回到本文的核心 —— hook。下面我想先给你介绍一个新的概念: