前言

其实React Hooks已经推出来一段时间了,直到前一阵子才去尝试了下,看到的一些博客都是以API的使用居多,还有一些是对于原理的解析。而我这篇文章想写的是关于React Hooks使用中的作用域问题,希望可以帮助到曾经有过困惑的你。

useEffect基础使用

在讲作用域之前,首先帮助你熟悉或者复习一下useEffect的使用,useEffect的基本使用如下:

useEffect(() => {// do somethingreturn () => {// release something};
}, [value1, value2...])
复制代码

useEffect接受两个参数:一个函数和一个值数组,第二个参数是指在下次render的时候,如果这个数组中的任意一个值发生变化,那么这个effect的函数(第一个参数)会重新执行。

这么讲可能比较抽象,我们以下面的一个例子来说明:

如图,页面中有1个按钮,当点击 "+" 按钮时count要加1,computed始终要为count + 1(实际业务中,这个计算往往不会是这么简单的),现在我们就用useEffect来计算computed:

import React, { useState, useEffect } from 'react';export default () => {const [count, setCount] = useState(0);const [computed, setComputed] = useState(0);useEffect(() => {setComputed(count + 1);// return () => {};}, [count]);return View代码略;
};复制代码

代码很简单,useEffect的第二个参数为[count],表示当count变化时,函数需要执行,在这个函数里面我们去设置computed为count+1,这样就完成了我们的需求。

下面我们深入讲解下useEffect的执行流程。

useEffect执行流程

我们利用console.log来帮助大家理解执行流程,上面代码改为:

export default () => {const [count, setCount] = useState(0);const [computed, setComputed] = useState(0);console.log('render before useEffect', count, computed);useEffect(() => {console.log('in useEffect', count, computed);setComputed(count + 1);return () => {console.log('just log release')};}, [count]);console.log('render after useEffect', count, computed);return View代码略;
};复制代码

首次刷新时,打印日志为:

我们来看发生了什么事情:

1、第一次render执行的时候,useEffect的函数是异步执行的,是在render后执行的,准确的说,在第一个render的时候是在DOM生成后执行的,相当于类组件的componentDidMount和componentDidUpdate。

2、render后开始执行useEffect的函数,这时候我们执行了setComputed函数,触发state的修改,触发重新render。

3、第二次render的时候,useEffect的函数本来应该是要异步执行的,但是这时候注意了,useEffect是有第二个参数的,第二次render的时候,count不变,所以useEffect的函数不执行。

我们点击下 "+" 按钮,再看下打印日志:

1、setCount触发render,首先执行render

2、检测useEffect第二个参数,发现count已经变化,所以这个effect要重新执行,执行effect之前,会去看前一次effect执行时是否返回了函数,如果返回了函数,那么会首先执行这个函数(主要让我们释放副作用)。

3、执行完release函数后,开始执行effect函数,这时候执行setComputed

4、setComputed再次触发render,这次的render,useEffect检测到count没有发生变化,所以不会重新再执行effect。

如果你没看懂这其中render、effect函数、release函数的执行顺序,那么对于后续的一些作用域问题你可能无法理解,麻烦多看几遍这个日志打印的例子。

作用域问题

首先我们看段代码:

import React, { useState, useEffect } from 'react';export default () => {const [state, setState] = useState({count: 0,computed: 1,});useEffect(() => {const buttonNode = document.getElementById('button');function handler() {console.log('in handler', state.count, state.computed);setState({count: state.count + 1,computed: state.count + 2,});}buttonNode.addEventListener('click', handler);return () => buttonNode.removeEventListener('click', handler);}, []);console.log('render', state.count, state.computed);return (<div className="app"><p>count: {state.count}, computed: {state.computed}</p><button id="button"> + </button></div>);
};
复制代码

我们把之前的例子改造了下,把button的点击事件改成了在useEffect里面绑定,useEffect的第二个参数传入空数组[],表示这个effect函数只在componentDidMount的时候执行。我们不断点击 "+" 按钮,期待的结果应该是和上面的例子一样,count不断增加,computed始终为count + 1,我们看下打印日志:

你猜对结果了吗?我们期待的count并没有不断增加,而handler里获取到的state.count居然始终为0。

按照我们的习惯,handler里面用到了state,在handler这个函数作用域里面没有这个变量,那么应该去render这个函数里面找,在第二次点击按钮的时候,state.count应该已经是1了,但是为什么拿到的还是0呢?

如果你看到这个结果没有一刻的困惑,那么你应该是个基础异常扎实的人,很不容易。

这个问题的答案要用作用域来解释。

静态作用域

关于作用域的详细解释大家自己去google,好文章很多,这里不展开讲太多,简单看段代码:

function foo() {console.log(a);
}function bar() {var a = 3;foo();
}var a = 2;bar();
复制代码

这段代码执行打印结果为:2

为什么呢?因为JS的函数会创建一个作用域,这个作用域是在函数被定义的时候就定好的,在上面的代码中,foo函数定义的时候,它的外层作用域是global,global里面a变量是2,所以打印出来的结果是2,如果是动态作用域,那么打印出来的就是3。

记住了吗?

模拟useEffect的作用域问题

由于React Hooks的内部原理需要去看源码才能知道,这里我们用原生JS来模拟,这样你就可以更纯粹地理解。

let init = true;const value = {count: 0};function render() {let count = value.count;if (init) {function handler() {console.log(count);value.count = count + 1;render();}document.addEventListener('click', handler);init = false;}
}render();
复制代码

这段代码定义了一个函数render,render里面绑定了document点击事件,回调函数里面执行了value.count为count + 1,然后触发render,模拟修改state后触发render行为。

这里handler的count也是始终为0,为什么呢?

我们把上面说过的作用域概念引入就很好解释了,当第一次执行render的时候,render函数创建了一个作用域,这个作用域中count = value.count,也就是0,这时init为true,所以handler被定义,词法作用域被创建,它的上层作用域就是刚才执行render的创建的作用域。

根据静态作用域的特性,handler里面的count在它被定义的时候就决定是0了,所以它始终是0.

理解吗?

如果理解了,那么我们返回来看useEffect的作用域。

useEffect作用域问题

仍然是这段代码:

import React, { useState, useEffect } from 'react';export default () => {const [state, setState] = useState({count: 0,computed: 1,});useEffect(() => {const buttonNode = document.getElementById('button');function handler() {setState({count: state.count + 1,computed: state.count + 2,});}buttonNode.addEventListener('click', handler);return () => buttonNode.removeEventListener('click', handler);}, []);return View省略;
};
复制代码

1、在第一次render的时候,执行到useEffect函数的时候,可以想象成React内部是类似下面的代码:

const fnArray = [];
const consArray = [];function useEffect(callback, conditions) {const index = <该useEffect对应的index>;if (<首次render>) {fnArray.push(callback);consArray.push(conditions);} else if (<根据conditions判定需要重新执行effect>) {fnArray[index] = callback;consArray[index] = conditions;}
}
复制代码

源码肯定不是这样的,但是可以这么理解,是用数组在维护hooks,所以useEffect的函数的作用域在执行useEffect的时候就定好了,当你传入的conditions(第二个参数)判定不需要重新执行时,effect函数的作用域的外层为前面某个render创建的作用域,这次render中,conditions发生了变化,判定需要重新执行effect,

普通的useEffect,也就是第二个参数不传,每次都update的effect,这样的effect在每次render执行后,都会更新最新的effect函数,因此可以拿到最新的state

useEffect(() => {// do something
})
复制代码

一个技巧

利用effect执行时机来记录前一个render的值

export function usePrevious(value) {const ref = useRef();useEffect(() => {ref.current = value;});return ref.current;
}复制代码

然后你在你的组件中就可以这么用:

const Component = () => {const [count, setCount] = useState(0);const prevCount = usePrevious(count); // 获取上一次render的countreturn (View代码);
}
复制代码

转载于:https://juejin.im/post/5ca0eec8f265da30c347841f

使用React Hooks你可能会忽视的作用域问题相关推荐

  1. 【译】什么是React Hooks

    原文:What are React Hooks? 作者:Robin Wieruch 译者:博轩 React Hooks 于 2018年10月的React Conf 中引入,作为在 React 函数组件 ...

  2. React Hooks 完全使用指南

    大家好,我是若川.最近组织了源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步.同时极力推荐订阅我写的<学习源码整体架构系列> ...

  3. [译] React Hooks: 没有魔法,只是数组

    [译] React Hooks: 没有魔法,只是数组 原文链接: medium.com/@ryardley/r- 我是 React 新特性 Hooks 的粉丝.但是,在你使用 React Hooks的 ...

  4. 使用React Hooks 时要避免的5个错误!

    作者:Shadeed 译者:前端小智 来源:dmitripavlutin 点赞再看,微信搜索**[大迁世界],B站关注[前端小智]**这个没有大厂背景,但有着一股向上积极心态人.本文 GitHub h ...

  5. React Hooks核心原理与实战

    React Hooks核心原理与实战 一.Hooks的优点 1.1 Hooks的含义 1.2 优点 二.常用的Hooks 2.1 useState 2.2 useEffect 2.3 useCallb ...

  6. React Hooks 使用详解

    本文对 16.8 版本之后 React 发布的新特性 Hooks 进行了详细讲解,并对一些常用的 Hooks 进行代码演示,希望可以对需要的朋友提供点帮助. 一.Hooks 简介 Hooks 是 Re ...

  7. 探React Hooks

    前言 众所周知,hooks在 React@16.8 中已经正式发布了.而下周周会,我们团队有个同学将会仔细介绍分享一下hooks.最近网上呢有不少hooks的文章,这不免激起了我自己的好奇心,想先行探 ...

  8. 通过 React Hooks 声明式地使用 setInterval

    2019独角兽企业重金招聘Python工程师标准>>> 本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: se ...

  9. mounty不可重新挂载因为先前没有完全卸载_【译】React Hooks测试完全指南

    原文地址:https://www.toptal.com/react/testing-react-hooks-tutorial 2018年底,React在16.8版本中引入了Hooks.它们(译注:指R ...

最新文章

  1. STM32固件库文件树及构成详解
  2. pandas修改数据类型_如何正确在pandas里使用inplace参数
  3. SpringBoot 基础拦截器
  4. SyntaxError: Non-UTF-8 code starting with '\xba' in file 错误的解决方法!!
  5. Leetcode--215. 数组中第K个最大元素
  6. 特征工程系列之降维:用PCA压缩数据
  7. 构造器2(Java)
  8. 【“达观杯”冠军分享】预训练模型彻底改变了NLP,但也不能忽略传统方法带来的提升...
  9. 42.数据库 SQL 操作
  10. android手机如何拥有苹果表情包,怎样让安卓emoji显示iPhone的emoji样式
  11. 2021软考软件设计师真题
  12. linux cgroup 学习的一些总结
  13. 服务器机箱 改造 桌面,你的显卡站起来了吗?——桌面改造分享装机篇
  14. 【转】图片热点链接使用方法
  15. 大数据技术学习带来的思考
  16. RNN(三) 在SLU中的应用
  17. 美食杰 login的实现效果
  18. 设备树文件里的aliases和chosen
  19. 小米4 miui6 android,小米4怎么刷miui6?小米4刷miui6三种方法详解
  20. 大数定律与蒙特卡罗法

热门文章

  1. 掌握shell应用,Linux任你行走
  2. java性能调优03
  3. rabbitMQ第一篇:rabbitMQ的安装和配置
  4. Java程序内存分析
  5. FreeBSD控制台分辨率调整
  6. 自动生成用户名,密码的方法
  7. ASP.Net中MD5和SHA1加密的几种方法
  8. 《软件设计精要与模式》前言
  9. Java学习_day008面向对象(OOP):对象和类
  10. jquery实现截取pc图片_jquery 上传图片自由截取