10 useCallback基础用法
useCallback概念解释
我们第五个要学习的Hook(钩子函数)是useCallback,他的作用是“勾住”组件属性中某些处理函数,创建这些函数对应在react原型链上的变量引用。useCallback第2个参数是处理函数中的依赖变量,只有当依赖变量发生改变时才会重新修改并创建新的一份处理函数。
react原型链?
我对react原型链也不太懂,你可以简单得把 react原型链 理解成 “react定义的一块内存”。我们使用某些 hook 定义的“变量或函数”都存放在这块内存里。这块内存里保存的变量或函数,并不会因为组件重新渲染而消失。
1、当我们需要使用时可以“对象的引用名”从该内存里获取,例如useContext
2、当希望更改某些变量时,可以通过特定的函数来修改该内存中变量的值,例如useState中的setXxxx()
3、当某些函数依赖变量发生改变时,react可以重新生成、并修改该内存中对应的函数,例如useReducer、useCallback
此处更新与2020年10月13日
今天学习了一下 JS 原型链:每一个对象或者说由 function 创建的对象,他们都有一个属性__proto__
,该属性值为创建该对象的构造函数的原型对象,又称 隐式原型,而这一层的隐式原型也有__proto__
属性,即__proto__.__proto__
属性值为 Object.prototype,还可以继续再往下深入__proto__.__proto__.__proto__
为了避免死循环,最终到此,即Object.prototype.__proto__
为 null。作为构造函数对象,有属性 prototype,属性值为该函数的显示原型对象。constructor 则表示原型对象的构造函数本身。const arr = [1, 2, 3]
console.log(arr.__proto__ === Array.prototype) // true
console.log(arr.__proto__.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__ === null) // true
function MyFun() { this.name = 'puxiao' }
const myFun = new MyFun()
console.log(myFun.__proto__ === MyFun.prototype) // true
console.log(MyFun.__proto__ === Function.prototype) // true
console.log(myFun.__proto__.__proto__ === Object.prototype) // true
console.log(myFun.__proto__.__proto__.__proto__) // null
console.log(Object.prototype.__proto__) // null
要想更加容易理解上面代码,就需要明白,所谓 object 是指 ,而 Object 其实是 JS 内置的对象函数。同理 所谓 array 是指 [],而 Array 其实是 JS 内置的 数组函数
正是 JS 中
__prototype__(隐式原型)
prototype(显式原型)
constructor(原型对象所在的构造函数本身)
这 3个概念,最终组合成了 庞大的 JS 功能。我们平时定义的任何 类、对象、函数 都出在这种 链条 中,以及对 这个链条中某个环节属性功能的扩展,这种组织形式就叫 JS 原型链。
JS 原型还有一个原则就是可以无限得去扩展自身属性,当前级别的原型扩展属性之后,下层级别的对象自动就拥有该属性。
那么我们可以大概推理出来,React就是巧妙利用了这种 JS 原型链的原则,将底层模块需要用到的处理函数提升到更高(或者说更加原始)的级别中。这样即使底层中发生了变化,但是他依然拥有高层中定义好的函数引用。
请重点留意“修改”这个词,因为“修改”牵扯到react最为隐秘却极其重要的一层概念。
“修改”有3种情况:
1、用完全不一样的新值去替换之前的旧值 ——> 这会触发react重新渲染 ——> 例如{age:34}
去替换{age:18}
2、用和旧值看似一模一样的新值去替换之前的旧值 ——> 这依然会触发react重新渲染,因为react底层对新旧值做对比时使用的是 Object.is判断,字面上看似一模一样没有用,react依然会认为这是2个对象,依然会触发react重新渲染 ——> 例如{age:18}
去替换{age:18}
3、用旧值的引用去替换旧值 ——> 这次就不会触发重新渲染 ——> 例如let obj={age:18}; let obj2=obj
,用obj2去替换obj
为了提高react性能,就需要用旧值的引用去替换旧值,从而阻止本次无谓的渲染。
问题的关键就在于“如何获取旧值的引用”?
答:对于函数来说可以使用useCallback。
在本章或以后的章节中,我依然会使用 react原型链 这个词,你都按照我刚才说的“react定义的一块内存”概念去理解就好了。
懵圈了没?我已经尽量总结得让你容易理解了,如果你似懂非懂没有关系,本文后面会通过示例代码会让你明白如何使用。
让我们先忘掉useCallback,先来学习一下以下2个知识点。
第1个知识点:React.memo() 的用法
首先我们知道,默认情况下如果父组件重新渲染,那么该父组件下的所有子组件都会随着父级的重新渲染而重新渲染。
1、无论子组件是类组件或是函数组件。
2、无论子组件在本次渲染过程中,子组件是否有任何相关的数据变化。
举例,假设某父组件中有3个子组件:子组件A、子组件B、子组件C。若因为子组件A发生了某些操作,引发父组件重新渲染,这时即使子组件B和子组件C没有任何需要更改的地方,但是默认他们两个也会重新被渲染一次。
为了减少这个不必要的重新渲染,如果是类组件,可以在组件shouldComponentUpdate(准备要开始更新前)生命周期函数中,通过比较props和state中前后两次的值,如果完全相等则跳过本次渲染,改为直接使用上一次渲染结果,以此提高性能提升。
伪代码如下:
shouldComponentUpdate(nextProps,nextStates){
//判断xxx值是否相同,如果相同则不进行重新渲染
return (nextProps.xxx !== this.props.xxx); //注意是 !== 而不是 !=
}
为了简化我们这一步操作,可以将类组件由默认继承自React.Component改为React.PureComponent。React.PureComponent默认会帮我们完成上面的浅层对比,以跳过本次重新渲染。
请注意:React.PureComponent会对props上所有可枚举属性做一遍浅层对比。而不像 shouldComponentUpdate 中可以有针对性的只对某属性做对比。
上面讲的都是类组件,与之对应的是React.memo(),这个是针对函数组件的,作用和React.PureComponent完全相同。
React.memo()的使用方法很简单,就是把要导出的函数组件包裹在React.memo中即可。
伪代码如下:
import React from 'react'
function Xxxx() {
return <div>xx</div>;
}
export default React.memo(Xxxx); //使用React.memo包裹住要导出的函数组件