函数组件在使用Hook时的性能优化

开篇

列出渲染次数多的原因

  1. 回调中有异步操作(减少回调中的异步操作)。

  2. 传递给子组件的props在父组件渲染时被重新定义(使用useCallback、useMemo配合React.memo来减少子组件的渲染)。

使用useCallback时

  1. 当作为props传递给子组件时,配合React.memo用于减少子组件的渲染次数。

  2. 在当前组件中只是减少了重新定义的次数,重新定义的性能损耗其实可以忽略。

  3. 可通过setState回调函数,来减少依赖项,从而减少props被重新定义的次数。

使用useMemo时

  1. 其实只是提供了someRef.current的快捷使用方式,缓存了变量的索引。
  2. 在当前组件使用时,用于缓存通过较大计算量得到的值。
  3. 当作为props传递给子组件时,配合React.memo用于减少子组件的渲染次数。

实例

回调中有异步操作

javascript 复制代码
const Child = ({handleClick})=>{
    console.log('child: 我渲染了')

    return (
        <button onClick={handleClick}>点击我</button>
    )
}
const App = ()=>{
    const [num,setNum] = useState(0)

    const handleClick = ()=>{
        
        setNum(num + 1)
        setNum(num + 1)
        setNum(num + 1)
        console.log('num',num)

        setTimeout(()=>{
            console.log(num)
            setNum((a)=>{
                console.log(a)
                return a + 1
            })
        })
    }
    console.log('父:渲染了')
    return (
        <>
            {num}
            <br/>
            <Child handleClick={handleClick}/>
        </>
    )

}

export default App;

对于setTimeout中console.log(num)输出0的理解:

setTimeout的回调产生了闭包,冻结了num的初始值。

javascript 复制代码
const Child = ({handleClick})=>{
    console.log('child: 我渲染了')

    return (
        <button onClick={handleClick}>点击我</button>
    )
}
const App = ()=>{
    const [num,setNum] = useState(0)

    const handleClick = ()=>{
        
        setNum(num + 1)
        setNum(num + 1)
        setNum(num + 1)
        console.log('num',num)
        // 排除setTimeout影响,研究闭包的形成
        ;(()=>{
            console.log(num)
            setNum((a)=>{
                console.log(a)
                return a + 1
            })
        })()
    }
    console.log('父:渲染了')
    return (
        <>
            {num}
            <br/>
            <Child handleClick={handleClick}/>
        </>
    )

}

export default App;

消除影响,减少次数

如果回调中有异步操作,并且异步操作中也需要改变组件状态,就应该把异步操作外改变状态的语句移到异步操作内执行。

注:实践中发现,此种方式增加的渲染次数无法减少,因为异步中的set是同步执行的。从逻辑讲,确实两个setState的时机是不同的,异步中的setState需要等待,所以优先渲染已准备好的state,不让用户等待,是合理的。

思:一般遇到此种情况,为减少渲染次数,分拆成更小的组件去渲染可以解决问题,提升性能;或在不同时机去执行set操作。

传递给子组件的props在父组件渲染时被重新定义

javascript 复制代码
const Child = ({handleClick})=>{
    console.log('child: 我渲染了')

    return (
        <button onClick={handleClick}>点击我</button>
    )
}

const ChildTwo = React.memo(({ handleClick2 }) => {
    console.log('childtwo: 我渲染了')
    return (
        <button onClick={handleClick2}>点击我2</button>
    )
})

const App = ()=>{
    const [num,setNum] = useState(0)

    const handleClick = ()=>{
        setNum(num+1)
    }
    // const handleClick2 = useCallback(()=>{
    //     setNum(1)
    // },[])
    // const handleClick2 = useCallback(()=>{
    //     setNum(num+1)
    // },[num])
    const handleClick2 = useCallback(()=>{
        setNum(num=>num+1)
    },[])
    const obj = useMemo(()=>{
        return {}
    },[])
    console.log('父:渲染了')

    return (
        <>
            {num}
            <br/>
            <Child handleClick={handleClick}/>
            <ChildTwo handleClick2={handleClick2}/>
            {/* <ChildTwo a={obj}/> */}
        </>
    )

}

export default App;

注:

useCallback和useMemo在使用时的依赖记得要加上,因为他们两个是根据依赖的变化来重新定义的,不然我们在其内部使用num时,是不能拿到num的最新值的,除非你使用setNum(num=>num+1),这也是减少依赖的好方法,不过不适用所有场景。

引申内容

  • 自执行函数前方必须加上分号,否则js解释器会因为无法识别而报错。

  • react没有暴露主动控制组件重新渲染的接口,不过可以自己为组件传入一个props,通过改变这个props来实现组件的重新渲染。

  • 纯展示列表不会出现数组变更的,可以用index作为key;数组可变更的列表就需要使用id等唯一且不变的标识作为key。

后端如何没有传递id,初始的时候自己可对数组格式化��入id。

可设置全局变量,做自增操作设置id,以保证id的唯一性。

相关推荐
ZJ_.8 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营12 分钟前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood38 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端39 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_8543 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235612 小时前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O4 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js