useCallback
我们在定义函数组件的时候时常在函数体内定义一些内嵌函数,这些内嵌函数会在组件每次重新渲染的时候被重新定义。以及如果它们作为props传递给子组件的话,即使其它props的值没有发生变化,它都会使子组件重新渲染因为只要父组件被重新渲染了就会在父组件中又被重新创建,而且子组件也会被重新运行。而无用的组件重渲染可能会产生一些性能问题。
每次重新生成新的内嵌函数还有另外一个问题就是当我们把内嵌函数作为依赖项传进useEffect的dependencies数组的话,因为该函数频繁被重新生成,所以useEffect里面的副作用函数就会频繁被调用。为了解决上述问题,React允许我们使用useCallback来记住当前定义的函数,并在下次组件渲染的时候返回之前定义的函数而不是使用新定义的函数。
简单来说就是useCallback返回一个函数,只有在依赖项发生变化的时候才会更新然后返回一个新的函数。这样当组件重新渲染时还是使用之前的函数,就不会被重新定义一次。
useCallback产生的原因
import { useState,useEffect } from 'react'
const Home = function (props) {
let [count,setCount]=useState(0)
let collect=()=>{
console.log('收藏成功');
setCount(1);
};
useEffect(()=>{
console.log('props属性改变,状态state改变,或者函数改变都会运行');
},[collect]);
return (
<div>
<p>{props.info.title}</p>
<p>{props.info.singer}</p>
<p>{count}</p>
<button onClick={collect}>收藏</button>
</div>
)
}
export default function App() {
let [arr, setArr] = useState([{ title: 'makeup', singer: 'hyomin' }, { title: 'city zoo', singer: 'G.E.M.' }])
return (
<div>
<h1>App组件</h1>
<h2>Home组件</h2>
{
arr.map((el, index) => {
return <Home info={el} key={index}></Home>
})
}
</div>
)
}
collect函数实际上修改的状态是count,而useEffect监听的依赖项是collect函数。当页面初次加载后执行collect函数本应该不会改变collect函数,副作用函数不会运行。但是运行了代表在内存中创建了多个collect函数执行相同的业务就会影响性能。因为执行useState返回的修改函数来修改状态就会让函数组件所有代码重新运行,这样每一次重新运行就会生成独立的作用域,重新创建新的collect函数。
解决方法就是使用useCallback来把我们在函数组件内部定义的函数保存起来,当组件重新渲染时还是使用之前的就不会被重新再一次被创建。
useCallback的使用
import {useCallback} from "react"
const memoizedCallback = useCallback(callback, dependencies)
useCallback接收两个参数,第一个参数是需要被缓存的函数,第二个参数是这个函数的依赖项,只有依赖项数组里面的元素的值发生变化时useCallback才会返回新定义的函数,否则useCallback都会返回之前定义的函数。依赖项也可以是任何数据,它都会被监听。
import { useState,useEffect,useCallback } from 'react'
const Home = function (props) {
let [count,setCount]=useState(0);
let collect=useCallback(()=>{
console.log('收藏成功');
setCount(1);
},[])
useEffect(()=>{
console.log('props属性改变,状态state改变,或者函数改变都会运行');
},[collect]);
return (
<div>
<p>{props.info.title}</p>
<p>{props.info.singer}</p>
<p>{count}</p>
<button onClick={collect}>收藏</button>
</div>
)
}
export default function App() {
let [arr, setArr] = useState([{ title: 'makeup', singer: 'hyomin' }, { title: 'city zoo', singer: 'G.E.M.' }])
return (
<div>
<h1>App组件</h1>
<h2>Home组件</h2>
{
arr.map((el, index) => {
return <Home info={el} key={index}></Home>
})
}
</div>
)
}
依赖项是一个空数组,返回的函数只会在组件初次被渲染时会生成,在useEffect函数中也不会再次被调用,因为函数没有被重新创建。
任何优化都会有代价,useCallback也是一样的。当我们在函数组件里面调用useCallback函数的时候,React背后要做一系列计算才能保证当依赖项不发生变化的时候,我们拿到的是同一个函数,因此如果我们滥用useCallback的话,并不会带来想象中的性能优化,反而会影响到我们的性能。
在当前组件中没有可能发生更新模板或者属性改变的情况下,组件内部定义的函数没必要使用useCallback。