热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

DeepintoReactHooks

前言在React16.7的版本中,Hooks诞生了,截止到目前,也有五六个月了,想必大家也也慢慢熟悉了这个新名词。我也一样,对着这个新特性充满了好奇,也写了几个demo体验一下,这

前言

在React 16.7 的版本中,Hooks 诞生了,截止到目前, 也有五六个月了, 想必大家也也慢慢熟悉了这个新名词。

我也一样, 对着这个新特性充满了好奇, 也写了几个demo 体验一下, 这个特性使得我们可以在一个函数组件中实现管理状态, 可以说是十分的神奇。 楼主最近也看了一些这方面的文章, 在这里总结分享一下, 希望对大家有所启发。

Hooks 系统总览

《Deep into React Hooks》

首先, 我们需要知道的是, 只有在 React scope 内调用的 Hooks 才是有效的,那 React 用什么机制来保证 Hooks 是在正确的上下文被调用的呢?

Dispatcher

dispatcher 是一个包含了诸多 Hook functions 的共享对象,在 render phase,它会被自动的分配或者销毁,它也保证 Hooks 不会在React component 之外被调用。

Hooks 功能的开启和关闭由一个flag 控制,这意味着, 在运行时之中, 可以动态的开启,关闭 Hooks相关功能。

React 16.6.X 也有一些试验性的功能是通过这种方式控制的, 具体实现参考:

对应源码

if (enableHooks) {
ReactCurrentOwner.currentDispatcher = Dispatcher;
} else {
ReactCurrentOwner.currentDispatcher = DispatcherWithoutHooks;
}

render 执行完毕之后,就销毁dispatcher, 这样也能组织在 react 渲染周期之外意外的调用Hooks.

对应源码:

// We're done performing work. Time to clean up.
isWorking = false;
ReactCurrentOwner.currentDispatcher = null;
resetContextDependences();
resetHooks();
// Yield back to main thread.

Hooks 的执行是由一个叫resolveDispatcher 的函数来决定的。 就像之前提到的, 在React 渲染周期之外 调用Hooks 是无效的, 这时候, React 也会跑出错误:

'Hooks can only be called inside the body of a function component.'

源码如下:

function resolveDispatcher() {
const dispatcher = ReactCurrentOwner.currentDispatcher;
invariant(
dispatcher !== null,
'Hooks can only be called inside the body of a function component.',
);
return dispatcher;
}

以上我们了解了Hooks的基础机制, 下面我们再看几个核心概念。

Hooks 队列

我们都知道, Hooks 的调用顺序十分重要。

React 假设当你多次调用 useState 的时候,你能保证每次渲染时它们的调用顺序是不变的。

Hooks 不是独立的,就好比是根据调用顺序被串起来的一系列结点。

在了解这个机制之前,我们需要了解几个概念:

  • 在初次渲染的时候, Hooks会被赋予一个初始值。
  • 这个值在运行时会被更新。
  • React 会记住Hooks的状态。
  • React 给根据调用顺序给你提供正确的state。
  • React 会知道每个Hook具体属于哪个Fiber。

用一个例子来解释吧, 假设, 我们有一个状态集:

{
foo: 'foo',
bar: 'bar',
baz: 'baz',
}

处理Hooks的时候,会被处理成一个队列, 每一个结点都是一个 state 的 model :

{
memoizedState: 'foo',
next: {
memoizedState: 'bar',
next: {
memoizedState: 'bar',
next: null
}
}
}

此处源码:

function createHook(): Hook {
return {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
}

在一个function Component 被渲染之前, 一个名为 prepareHooks 的方法会被调用, 在这个方法里, 当前的Fiber 和 Hooks 队列重的第一个结点会被储存到一个全局变量里, 这样, 下次调用 useXXX 的时候, React 就知道改运行哪个context了。

对应源码:

let currentlyRenderingFiber
let workInProgressQueue
let currentHook
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:123
function prepareHooks(recentFiber) {
currentlyRenderingFiber = workInProgressFiber
currentHook = recentFiber.memoizedState
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:148
function finishHooks() {
currentlyRenderingFiber.memoizedState = workInProgressHook
currentlyRenderingFiber = null
workInProgressHook = null
currentHook = null
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:115
function resolveCurrentlyRenderingFiber() {
if (currentlyRenderingFiber) return currentlyRenderingFiber
throw Error("Hooks can't be called")
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:267
function createWorkInProgressHook() {
workInProgressHook = currentHook ? cloneHook(currentHook) : createNewHook()
currentHook = currentHook.next
workInProgressHook
}
function useXXX() {
const fiber = resolveCurrentlyRenderingFiber()
const hook = createWorkInProgressHook()
// ...
}
function updateFunctionComponent(recentFiber, workInProgressFiber, Component, props) {
prepareHooks(recentFiber, workInProgressFiber)
Component(props)
finishHooks()
}

更新结束后, 一个名为 finishHooks 的方法会被调用, Hooks 队列中第一个结点的引用会被记录在 memoizedState 变量里, 这个变量是全局的, 意味着可以在外部去访问, 比如:

const ChildCompOnent= () => {
useState('foo')
useState('bar')
useState('baz')
return null
}
const ParentCompOnent= () => {
const childFiberRef = useRef()
useEffect(() => {
let hookNode = childFiberRef.current.memoizedState
assert(hookNode.memoizedState, 'foo')
hookNode = hooksNode.next
assert(hookNode.memoizedState, 'bar')
hookNode = hooksNode.next
assert(hookNode.memoizedState, 'baz')
})
return (

)
}

下面我们就拿最常见的Hook来具体分析。

State Hooks

比如:


const [count, setCount] = useState(0);

其实, useState 的背后,是 useReducer, 它提供一个一个简单的预先定义的 reducer handler。 源码实现

也就意味着, 我们通过 useState拿到的两个值, 其实分别是一个 reducer 的 state, 和 一个 action 的 dispatcher.

此处源码:

function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}

如代码所示, 我们可以直接提供一个 state 和对应的 action dispatcher。 但是与此同时, 我们也可以直接传递一个包含action 的dispatcher 进去, 接收一个旧的state, 返回新的state.

这意味着我们可以把一个state的setter当作一个参数传递给Component, 然后在父组件里修改state, 而不用传递一个新的prop进去。

简单示例:

const ParentCompOnent= () => {
const [name, setName] = useState()
return (

)
}
const ChildCompOnent= (props) => {
useEffect(() => {
props.toUpperCase((state) => state.toUpperCase())
}, [true])
return null
}

官网中也有类似的例子:

function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}




);
}

说完了State, 我们再看一下Effect。

Effect Hooks

Efftect 稍微有些不同, 它增加了额外的逻辑层。 在深入具体的实现之前, 我们需要事先了解几点概念:

  • Effect Hooks 在 render 的时候被创建, 在 painting 之后被执行, 在下一次painting 之前被销毁。
  • Effect Hooks 按照定义的顺序执行。

需要注意的一点是, paintingrender 还是有所区别的,render method 只是创建了一个Fiber node, 还没开始 paint.

// 脑坑疼, 休息一下再补充,未完待续&#8230;

《Deep into React Hooks》


推荐阅读
  • Android性能优化检测App卡顿
    在移动APP性能评测-流畅度评测中,我们介绍了如何准确客观评价APP的流畅度,最终采用SM指标来评价应用的流畅度,在知道如何评价流畅度之后 ... [详细]
  • UILabel的混合显示动画效果
    UILabel的混合显示动画效果 ... [详细]
  • IOSUITableView解析(一)
    UITableView的作用由于Iphone的大小有限,所以UITableView的作用是巨大的。比如QQ,微博等应用都用到了该控件。UITableVi ... [详细]
  • 1、dispatch_barrier_asyncdispatch_barrier_async用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执 ... [详细]
  • P1144 最短路计数· BFS/dijkstra
    题解其实题目很简单不写了,这里总结一下从这道题目里学到的知识:当最短路的边权都是1时,dijkstraspfa就是BFS如果使用优先队列,内部结构是pair时 ... [详细]
  • diskmark使用教程
    首先说明一下软件各个参数的意义。1~9测试次数;50MB~4000MB测试规模;C,D,E,F选择测试对象;ALL测试以下所有;第一行代表你硬盘的读写速度。第二行代表你硬盘4K文件 ... [详细]
  • RabbitMQ之队列与消息持久化
    队列持久化在之前的例子中,我们所用的队列都是临时队列,当服务重启后之前创建的队列就都没有了。队列的持久化是在定义队列时的第二个参数决定的(false为队列不用持久化)channel.queueDecl ... [详细]
  • 一、腐烂的橘子1、题目描 ... [详细]
  • 开发笔记:googletest安装与使用
    本文由编程笔记#小编为大家整理,主要介绍了googletest安装与使用相关的知识,希望对你有一定的参考价值。简介googletest是Google公司 ... [详细]
  • 服务器性能优化之网络性能优化
    hi,大家好,今天分享一篇后台服务器性能优 ... [详细]
  • 题目链接:杭电多校7-VirtualJudgevjudge上题目显示的有问题,我下面附上官方题目:样例输入:32201 ... [详细]
  • 我正在使用数组列表通过构建一个交互式菜单供用户选择来存储来自用户输入的值。到目前为止,我的两个选择是为用户提供向列表输入数据和读取列表的全部内容。到目前为止,我创建的代码由两个类组成。 ... [详细]
  • IDEA实用插件Lombok
    LombokLombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。通常,我们所定义的对象和b ... [详细]
  • Proof (of knowledge) of exponentiation
    1.ProofofexponentiationProofofexponentiation是基于adaptiverootassumption(充分必要条件࿰ ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
author-avatar
爱的甜蜜日记2010
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有