这篇文章有点晦涩难懂,并且我做了很多的铺垫,就是希望,大家能够用这一篇文章彻底搞懂 useCallBack。所以,希望大家能够静下心来,仔细品,同时,把代码也执行一下。否则,根本看不懂,或者可以多看几遍。
一、抛出问题
以前写类组件时,经常会写以下(有隐含性能的)代码
export default class Parent extends React.Component {
tempfn(v){
}
render = () => (
<div>
<Son person={{name:"张三疯"}} onMyClick={(val)=>this.tempFn(val)} />
</div>
)
}
以上代码存在的问题,当父组件(Parent)重新渲染引起子组件Son的渲染。那么就会重新定义对象:{name:张三疯} ,也还会重新定义函数(val)=>this.tempFn(val)。diff算法在比较虚拟dom时,发现有变化,就会渲染的更多。耗费cpu性能和内存。如果说这两个对象在渲染前后,没有变化,那么,就会节约性能。
解决办法:把它们抽取成变量。如下:
export default class Parent extends React.Component {
constructor(props){
super(props);
this.state={
person:{name:"张三疯"}
}
this.tempFn = this.tempFn.bind(this);
}
tempFn(val){
}
render = () => (
<div>
<Son person={this.state.person} onMyClick={this.tempFn} />
</div>
)
}
但是,在函数式组件里,就没有办法了,因为,类组件里可以用this存储这个函数,而在函数式组件了,没法存储这个函数。因为,函数式组件,每次重新渲染时,会把函数里的代码全部执行,而类组件只执行render函数里的代码(你可以认为函数式组件就是类组件中的render方法) ??难道对象和函数不能定义在函数式组件的外面吗??,答案是不行(你可以试试)。
二、解决问题:
1、初步认识useCallBack
useCallBack能干什么?
1)、useCallback会返回一个函数的memoized(记忆的)值
2)、在依赖不变的情况下,多次定义(如:函数)的时候,返回的值是相同的
格式:
let 新的函数 = useCallback(曾经的函数, [依赖的值])
示例代码:
import {useState,useCallback} from "react"
export default ()=>{
//1、 定义一个状态(基本类型):count
const [count,setCount] = useState(0);
let changecount = useCallback(() => {
setCount(count + 1)
}, [count]) //依赖了count,所以,只要count有变化,就会执行 setCount(count + 1)
let changecount = useCallback(() => {
setCount(count + 1)
}, [])//这种写法,没有依赖值,那么不会执行 setCount(count + 1)
return (
<div>
<input type="button" value="修改count2" onClick={changecount} />
<p>count:{count}</p>
</div>
)
}
2、那么,useCallBack到底在何处提高了性能?
答: 通常在将一个组件中的函数,传递给子元素进行回调使用时,使用useCallback对函数进行处理
示例代码:
1)、首先需要知道,父组件更新时,子组件也会更新
const MySon = (props)=>{
console.log("props.name",props.name)
return <input type="button" value={props.name} />
}
export default function Myparent() {
const [p,setP] = useState(0)
console.log("父组件更新了");
return (
<div>
<MySon name="btn1" />
<input type="button" value="加一" onClick={e=>setP(p+1)} />
</div>
)
}
2)、如果父组件更新时,不希望子组件更新,使用memo( React.memo()
是一个高阶函数,它与 React.PureComponent类似, React.memo()
用在函数式组件里,防止无效的更新)
import {useState,memo} from "react"
const MySon = memo((props)=>{
console.log("props.name",props.name)
return <input type="button" value={props.name} />
})
3)、当然,如果出现了,父组件修改的数据影响了子组件的数据(props),那么子组件肯定是要更新的
const MySon = memo((props)=>{
console.log("props.name",props.name)
return <input type="button" value={props.name} />
})
export default function Myparent() {
const [p,setP] = useState(0);
const [name,setName]=useState("hi");
console.log("父组件更新了");
return (
<div>
<MySon name={name} />
{/*点击这个按钮,子组件不更新*,因为,p的值并没有在子组件中使用/}
<input type="button" value="加一" onClick={e=>setP(p+1)} />
{/*点击这个按钮,子组件会更新,因为,name是传入子组件的值*/}
<input type="button" value="修改Name" onClick={e=>setName("haha")} />
</div>
)
}
4)、但是:子组件使用父组件的函数(父组件给子组件传递函数)时,虽然,函数体没有变化,但是父组件更新依然会更新。这不是我们希望的。
解释:在父组件里定义的函数,每次的值(函数体)是不变了的。由于,父组件更新时,重新定义函数(new Function()),那么函数的值(地址)会发生变化。所以,引起子组件的更新了
const MySon = memo((props)=>{
console.log("props.name",props.name)
return <input type="button" value={props.name} onClick={props.increament} />
})
export default function Myparent() {
const [p,setP] = useState(0);
const [name,setName]=useState("hi");
const increament1 = () => {
console.log('执行increament1')
}
console.log("父组件更新了");
return (
<div>
<MySon name={name} increament={increament1} />
{/*点击下面按钮时,看上去并没有修改子组件所使用的的数据name和increment。
但是,实际上,increment1的值发生变化了。
因为:
1、父组件更新时,重新定义了函数(即:重新new Function()),即:变量increment1的值(存储的地址)发生变化了
2、对于memo来说,只做了了浅比较,只要变量的值(对于对象来说,在内存中存储的就是地址)发生了变化,那么,就会引起组件的更新。
*/}
<input type="button" value="加一" onClick={e=>setP(p+1)} />
</div>
)
}
5)、useCallBack出场了
首先,假定 increament1函数里依赖着某个数据(如:count),即:当count发生变化时,increament1函数才会重新定义。
其次,把increment1函数用useCallBack进行包裹。这样就不会出现无效的刷新(只要count的值不变,函数increament1就不会重新定义,子组件就不会重新刷新)
const increament1 = useCallback(() => {
console.log('执行increament1')
},[count])
最终的示例代码:
import {useState,useCallback,memo} from "react"
const MySon = memo((props)=>{
console.log("props.name",props.name)
return <input type="button" value={props.name} onClick={props.increament} />
})
export default function Myparent() {
const [p,setP] = useState(0);
const [name,setName]=useState("hi");
const [count,setCount] = useState(0);
//increment1函数是否要重新定义,取决于count的值是否发生变化
const increament1 = useCallback(() => {
console.log('执行increament1')
},[count])
console.log("父组件更新了");
return (
<div>
<MySon name={name} increament={increament1} />
{/*点击下面这个按钮,子组件不会刷新,因为,并没有修改count的值*/}
<input type="button" value="p加一" onClick={e=>setP(p+1)} />
{/*点击下面这个按钮,子组件会刷新,因为,increment1函数是否要重新定义,取决于count的值是否发生变化,下面这个按钮修改了count的值*/}
<input type="button" value="count加1" onClick={e=>setCount(count+1)} />
</div>
)
}