React HOOK:useCallback、useMemo
useCallback
1.我们在定义函数组件的时候时常在函数体内定义一些内嵌函数,这些内嵌函数会在组件每次重新渲染的时候被重新定义,如果它们作为props传递给了子组件的话,即使其它props的值没有发生变化,它都会使子组件重新渲染,而无用的组件重渲染可能会产生一些性能问题。
每次重新生成新的内嵌函数还有另外一个问题就是当我们把内嵌函数作为dependency传进useEffect的dependencies数组的话,因为该函数频繁被重新生成,所以useEffect里面的effect就会频繁被调用。为了解决上述问题,React允许我们使用useCallback来记住当前定义的函数,并在下次组件渲染的时候返回之前定义的函数而不是使用新定义的函数。
简而言之useCallback就是把我们在函数组件内部定义的函数保存起来,当组件重新渲染时还是使用之前的,就不会被重新定义一次
2.语法
import {useCallback} from "react" const memoizedCallback = useCallback(callback, dependencies) //useCallback接收两个参数,第一个参数是需要被记住的函数,第二个参数是这个函数的dependencies,只有dependencies数组里面的元素的值发生变化时useCallback才会返回新定义的函数,否则useCallback都会返回之前定义的函数。
案例:
import React, { useCallback } from 'react' import useSearch from 'hooks/useSearch' import ReactDOM from 'react-dom' //items列表可能包含上千个数据,子组件就会被渲染多次 const HugeList = ({ items, onClick }) => { return ( <div> { items.map((item, index) => ( <div key={index} onClick={()=>onClick(index)}> {item} </div> )) } </div> ) } const MemoizedHugeList = React.memo(HugeList) const SearchApp = ({ searchText }) => { const handleClick = useCallback(item => { console.log(item) }, []) const items = useSearch(searchText) return (<MemoizedHugeList items={items} onClick={handleClick} />) } ReactDOM.render(<SearchApp />, document.getElementById('root')) //定义了一个HugeList组件,由于这个组件需要渲染一个大的列表(items),所以每次重渲染都是十分消耗性能的,因此使用了React.memo函数来让该组件只有在onClick函数和items数组发生变化的时候才被渲染,接着我在SearchApp里面使用MemoizedHugeList,由于要避免该组件的重复渲染,所以我使用了useCallback来记住定义的handleClick函数,这样在组件后面渲染的时候,handleClick变量指向的都是同一个函数,所以MemorizedHugeList只有在items发生变化时才会重新渲染
3.任何优化都会有代价,useCallback也是一样的。当我们在函数组件里面调用useCallback函数的时候,React背后要做一系列计算才能保证当dependencies不发生变化的时候,我们拿到的是同一个函数,因此如果我们滥用useCallback的话,并不会带来想象中的性能优化,反而会影响到我们的性能
错误使用案例:
import React, { useCallback } from 'react' import ReactDOM from 'react-dom' const Mybtn = () => { const handleClick = useCallback(() => { console.log('666') }, []) return ( <button onClick={handleClick}>btn</button> ) } ReactDOM.render(<Mybtn />, document.getElementById('root')) //上面例子使用的useCallback没有起到任何优化代码性能的作用,反而由于hook内部机制的运行,它消耗的计算资源其实比没有优化之前还多,相当于: import React, { useCallback } from 'react' import ReactDOM from 'react-dom' const Mybtn = () => { const inlineClick = () => { console.log('666') } const handleClick = useCallback(inlineClick, []) return ( <button onClick={handleClick}>btn</button> ) } ReactDOM.render(<Mybtn />, document.getElementById('root'))
useMemo
1.useMemo和useCallback的作用十分类似,只不过它允许你记住任何类型的变量(不只是函数)
语法
import {useMemo} from "react" const memoizedValue = useMemo(() => valueNeededToBeMemoized, dependencies) //useMemo接收一个函数,该函数的返回值就是需要被记住的变量,当useMemo的第二个参数dependencies数组里面的元素的值没有发生变化的时候,memoizedValue使用的就是上一次的值。
案例:
import React, { useMemo } from 'react' import ReactDOM from 'react-dom' const RenderPrimes = ({ iterations, multiplier }) => { const primes = React.useMemo(() => calculatePrimes(iterations, multiplier), [ iterations, multiplier ]) return ( <div> Primes! {primes} </div> ) } ReactDOM.render(<RenderPrimes />, document.getElementById('root')) //例子中calculatePrimes是用来计算素数的,因此每次调用它都需要消耗大量的计算资源。为了提高组件渲染的性能,我们可以使用useMemo来记住计算的结果,当iterations和multiplier保持不变的时候,我们就不需要重新执行calculatePrimes函数来重新计算了,直接使用上一次的结果即可。