Open Lirx-Xin opened 3 years ago
React组件代码中的优化思路主要是减少render渲染函数的重复运行,因为React中父组件更新,所有子组件也会重新渲染。 在类组件中可通过在shouldComponentUpdate生命周期中判断更新后的prop或state与更新前的prop或state是否有更新来决定是否重新渲染,返回false不重新渲染。也可以通过继承PureComponent组件来使该组件自身拥有判断prop(或state)决定是否重新渲染的能力,不过该方式只提供一个浅比较的方法(即数组与对象类型的prop只比较其在内存中的引用地址,即便其内部值改变了,也不会触发更新),该方式也会导致其子组件在触发更新时,浅比较下该更新的不更新,即使其子组件是继承自Component类。
shouldComponentUpdate
PureComponent
Component
那么在函数式组件中没有生命周期也没有类,那么该如何进行这方面的优化呢
该API是对标类组件中的PureComponent;
//app组件 import React,{useState} from 'react' import Child from './child' export default function App(){ let [name,setName] = useState('nihao') return ( <div> <div>{name}</div> <button onClick={() => setName('name被修改')}>修改名字</button> <Child name="不变"></Child> </div> ) } // child组件 export default function Child(props){ console.log(props.name) return ( <div>{props.name}</div> ) }
首次渲染时,控制台会打印不变,说明child组件已经渲染。当我们点击修改按钮时,name改变,此时控制台会再次打印不变,说明Child函数再次执行,该组件再次渲染,但是该组件并未有状态的更改,此次的执行渲染自然是额外的性能开销,那要怎样才能不让他渲染呢,这里就可以使用React.memo; React.memo其实是一个高阶组件,它会将我们的组件进行一次包装,返回一个可缓存状态的组件。
不变
React.memo
// 使用React.memo的Child组件 function Child(props){ console.log(props.name) return ( <div>{props.name}</div> ) } export default React.memo(Child)
此时再点击修改名字按钮,就会发现控制台不会再打印不变,因为Child组件没有更新就不会重新渲染。 但是这样的React.memo作用与PureComponent的作用类似,他们只进行一个浅比较的方法。不过React.memo还提供了一个回调,让我们自定义其状态属性的比较方法。
// Child组件 function Child(props){ console.log(props.name) return ( <div>{props.name}</div> ) } function areEquel(prevProps,nextProps){ // 对props对象进行比较,相等返回true,不等返回false,与shouldComponentUpdate返回结果相反 } export default React.memo(Child,areEquel)
将上方的示例代码进行更改:
//app组件 import React,{useState} from 'react' import Child from './child' export default function App(){ let [name,setName] = useState('nihao') const cb = () => { setName('自己修改name') } return ( <div> <div>{name}</div> <button onClick={() => setName('name被修改')}>修改名字</button> <Child onClick={cb} name="不变"></Child> </div> ) } // child组件 function Child(props){ console.log(props.name) return ( <div> <button onClick={props.onClick}>自己修改</button> <div>{props.name}</div> </div> ) } export default React.memo(Child)
此时当我们点击修改名字时,控制台又会打印出不变,这是为什么呢,传递给Child组件的props看起来是没有改变,但其实当App组件进行重新渲染时,该函数组件会再次执行,也就是再次创建了一个cb函数,与其更新之前的cb函数,在内存中的引用地址其实是不一样的,所以Child组件触发了刷新。 此时,我们就可以使用useCallback:
App
cb
useCallback
//app组件 import React,{useState,useCallback} from 'react' import Child from './child' export default function App(){ let [name,setName] = useState('nihao') const cb = () => { setName('自己修改name') } const memoziedcb = useCallback(cb,[]) return ( <div> <div>{name}</div> <button onClick={() => setName('name被修改')}>修改名字</button> <Child onClick={memoziedcb} name="不变"></Child> </div> ) } // child组件 function Child(props){ console.log(props.name) return ( <div> <button onClick={props.onClick}>自己修改</button> <div>{props.name}</div> </div> ) } export default React.memo(Child)
此时再次触发修改,Child组件不会再次重新渲染。 将函数及依赖项作为参数传递给useCallback,useCallback会返回该函数的memoized,返回的这个函数,只有在依赖项改变时,才会更新。 如果我们的 callback 传递了参数,当参数变化的时候需要让它重新添加一个缓存,可以将参数放在 useCallback 第二个参数的数组中,作为依赖的形式,使用方式跟 useEffect 类似。
memoized
callback
useEffect
useMemo的作用与React.memo及useCallback的作用不一样,useMemo主要是为了减少计算量:
useMemo
function App(){ let [count,setCount] = useState(0) function expensiveFn{ // 复杂计算结果并返回 console.log(num) return num } const num = expensiveFn() return ( <div> <h5>{count}</h5> <button onClick={() => setCount(count+num)}>增加</button> </div> ) }
上面实例中,每次点击按钮都会对函数expensiveFn进行计算,而expensiveFn返回的值是没有改变的,所以这样无疑是多余的性能开销。此时我们就可以使用useMemo来进行优化:
function App(){ let [count,setCount] = useState(0) function expensiveFn{ // 复杂计算结果并返回 console.log(num) return num } const num = useMemo(expensiveFn,[]) return ( <div> <h5>{count}</h5> <button onClick={() => setCount(count+num)}>增加</button> </div> ) }
此时,我们再点击按钮时,我们会发现,打印的num值只有第一次会执行; 注意:如果在使用useMemo时,我们没有传递依赖值,函数会每次都执行。对于一些计算量较少的计算,可以不用useMemo,也不会有什么太大收益。
React函数式组件&&React Hook开发中的性能优化
React组件代码中的优化思路主要是减少render渲染函数的重复运行,因为React中父组件更新,所有子组件也会重新渲染。 在类组件中可通过在
shouldComponentUpdate
生命周期中判断更新后的prop或state与更新前的prop或state是否有更新来决定是否重新渲染,返回false不重新渲染。也可以通过继承PureComponent
组件来使该组件自身拥有判断prop(或state)决定是否重新渲染的能力,不过该方式只提供一个浅比较的方法(即数组与对象类型的prop只比较其在内存中的引用地址,即便其内部值改变了,也不会触发更新),该方式也会导致其子组件在触发更新时,浅比较下该更新的不更新,即使其子组件是继承自Component
类。那么在函数式组件中没有生命周期也没有类,那么该如何进行这方面的优化呢
React.memo
该API是对标类组件中的
PureComponent
;首次渲染时,控制台会打印
不变
,说明child组件已经渲染。当我们点击修改按钮时,name改变,此时控制台会再次打印不变
,说明Child函数再次执行,该组件再次渲染,但是该组件并未有状态的更改,此次的执行渲染自然是额外的性能开销,那要怎样才能不让他渲染呢,这里就可以使用React.memo
;React.memo
其实是一个高阶组件,它会将我们的组件进行一次包装,返回一个可缓存状态的组件。此时再点击修改名字按钮,就会发现控制台不会再打印
不变
,因为Child组件没有更新就不会重新渲染。 但是这样的React.memo
作用与PureComponent
的作用类似,他们只进行一个浅比较的方法。不过React.memo
还提供了一个回调,让我们自定义其状态属性的比较方法。useCallback
将上方的示例代码进行更改:
此时当我们点击修改名字时,控制台又会打印出
不变
,这是为什么呢,传递给Child组件的props看起来是没有改变,但其实当App
组件进行重新渲染时,该函数组件会再次执行,也就是再次创建了一个cb
函数,与其更新之前的cb
函数,在内存中的引用地址其实是不一样的,所以Child组件触发了刷新。 此时,我们就可以使用useCallback
:此时再次触发修改,Child组件不会再次重新渲染。 将函数及依赖项作为参数传递给
useCallback
,useCallback
会返回该函数的memoized
,返回的这个函数,只有在依赖项改变时,才会更新。 如果我们的callback
传递了参数,当参数变化的时候需要让它重新添加一个缓存,可以将参数放在useCallback
第二个参数的数组中,作为依赖的形式,使用方式跟useEffect
类似。useMemo
useMemo
的作用与React.memo
及useCallback
的作用不一样,useMemo
主要是为了减少计算量:上面实例中,每次点击按钮都会对函数expensiveFn进行计算,而expensiveFn返回的值是没有改变的,所以这样无疑是多余的性能开销。此时我们就可以使用
useMemo
来进行优化:此时,我们再点击按钮时,我们会发现,打印的num值只有第一次会执行; 注意:如果在使用
useMemo
时,我们没有传递依赖值,函数会每次都执行。对于一些计算量较少的计算,可以不用useMemo,也不会有什么太大收益。