React前端渲染优化--父组件导致子组件重复渲染的问题

 更新时间:2022年08月04日 15:22:08   作者:DominicElvira  
本篇文章是针对父组件导致子组件重复渲染的优化方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

React前端渲染优化--父组件导致子组件重复渲染

说明

目前我们所使用 react 版本一般会有以下四种方式触发渲染 render,而其中通过父组件 render 会直接通知子组件也进行 render。

一般的优化方式

鉴于此种情况,如果完全不做控制下,父组件 render, 那么子组件一定会 render。真实 dom 的渲染 react 会在 diff 算法之后合计出最小改动,进行操作。但对于结构复杂页面,自顶向下,只是单纯 diff 也要花费很长的时间来处理 js 任务。再加上我们每个组件的 render 中也会写很多业务、数据处理。

js 为单线程执行,显然,不必要的子组件的 render 会浪费 js 线程资源,复杂任务还会长时间占用线程导致假死状态,也就是页面卡顿,react 底层有 Fiber 来优化任务队列,但无法优化业务代码上的问题。

一般子组件可以通过确认 props 是否发生变化来控制自身是否进行 render,比如 react-mobx 中的 observer 高阶方法或者 React.PureComponet 就是用来做浅层比较进行控制处理。

项目中常见会导致重复渲染的写法以及改进方法

函数导致的渲染重复

箭头函数 props.fn = () => {} 或者 绑定方法 props.fn = this.xxx.bind(this)

这样的写法每次父组件 render 都会新声明一个 function 传递给子组件,会导致 observer 失去比对作用,父组件每次 render 都会使这个组件 render,严重影响性能!

import React from 'react';
import { observer } from 'mobx-react';
// 我们开发中常见的一个被观测组件,例如 ObserverComponent
@observer
class ObserverComponent extends React.Component {
    render() {
        return (<div>ObserverComponent</div>)
    }
}
// 例如在父组件 Parent 使用被观测的子组件 ObserverComponent
// 请不要给子组件 ObserverComponent 的 props 设置 箭头函数 () => {} 或者 fn.bind(this) 方法
@observer
class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this); // 【正确】
    }
    handleChange() {}
    doSomething = () => {}
    render() {
        return (
            <ObserverComponent
                onChange={() => {}}                      // 【错误】
                onChange={this.handleChange.bind(this)}  // 【错误】
                onChange={this.handleChange}             // 【正确】
                todo={this.doSomething}                  // 【正确】
            />
        )
    }
}

字面量写法导致的渲染重复

由于字面量的写法{} 和 { pageSizeOptions: ['10'] },每次都会字面量声明一个新的对象传递给列表组件,导致页面重新 render。

toJS() 方法每次也会返回新对象,会导致页面重新渲染

组件重复渲染问题(pureComponent, React.memo, useMemo, useCallback)

在一个组件中, 其state变化会引起render的重新执行, 函数式组件中, 使用setHook更新state也会引起render的重新执行

render执行会带来两个方面的影响

  • 1.当前组件需要重新渲染, 除了那些状态和生命周期初始化被保留的,其余正常的都会重新执行。
  • 2.子组件会重新渲染, 即使其是一个无状态组件

针对上述问题, react给出来解决方案:

  • pureComponent
  • React.memo
  • useMemo
  • useCallback

下面将具体说明这几个都使用场景和解决的问题

  • useMemo设计的初衷就是避免重复进行大规模的计算, 它的理想作用对象是当前组件

具体是将当前组件中一个经过很复杂的计算得到的值缓存起来, 当其依赖项不变的时候, 即使组件重新渲染, 也不会重新计算。

通过上述描述也能理解出其缓存的是一个具体的数据(可以和接下来的useCallback区分开)

/*
缓存了一个对象, 只有当count变化时才会重新返回该对象
*/
const useInfo = useMemo(
    () => ({
        count: count,
        name: "name"
    }),
    [count]
)
  • 针对第二点, 分别有三个解决方案

首先是useCallback, 其语法和useMemo基本一致, 但是其使用场景是父组件定义了一个函数并且将这个函数传递给了子组件, 那么当父组件重新渲染时,生成的会是一个新的函数, 这个时候就可以使用useCallback了,如下:

const Page = (props) => {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('Child组件');
    return (
        <>
            <ChildMemo name={name} onClick={ useCallback((newName: string) => setName(newName), []) }/>
            {/* useCallback((newName: string) => setName(newName),[]) */}
            {/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数
        </>
    )
}

上述是一个简写的形式,意思就是将传递给子组件的这个函数缓存了,其第二个参数就是依赖,当该依赖变化时,将会重新缓存该函数

其余useMemo的区别就在于,其缓存的是函数本身,而useMemo缓存的是函数计算后的值,都会在依赖项变化时重新缓存。

注:虽然其可能对于父组件传递给子组件函数时可能很理想,但实际上其带来的性能损耗也是显而易见的,其使用场景不应该是担心本组件的函数因为本组件重新渲染而重新生成,这样反而起到了反效果,当前组件更新,其重新渲染,内部的函数也重新生成,其性能损耗可以忽略不计,如下图。

使用场景应该是父组件更新导致重新生成的函数又传递给了子组件,导致子组件重新渲染。

  • 接着是pureComponent

它是一个类, 组件继承自它后, 其作为子组件时, 每次父组件更新后, 会浅对比传来的props是否变化, 若没变化, 则子组件不更新。

  • React.memo

同上条功能类似, 当其作用于函数式组件并且作为子组件时, 每次父组件更新后, 会浅对比传来的props是否变化, 若没变化, 则子组件不更新。

// 子组件暴露时暴露为处理后的组件
import {memo} from 'react'
const TeacherModal = (props: any) => {
  return <div></div>
}
export default memo(TeacherModal)

上面两个都区别在于, 一个是类, 一个是高阶组件, 前者作用于类后者作用于函数

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • React使用highlight.js Clipboard.js实现代码高亮复制

    React使用highlight.js Clipboard.js实现代码高亮复制

    这篇文章主要为大家介绍了React使用highlight.js Clipboard.js实现代码高亮复制功能示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • React中使用UEditor百度富文本的方法

    React中使用UEditor百度富文本的方法

    这篇文章主要介绍了React中使用UEditor的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • react项目实践之webpack-dev-serve

    react项目实践之webpack-dev-serve

    这篇文章主要介绍了react项目实践之webpack-dev-serve,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • React 错误边界组件的处理

    React 错误边界组件的处理

    这篇文章主要介绍了React 错误边界组件的处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • React Native 脚手架的基本使用详解

    React Native 脚手架的基本使用详解

    这篇文章主要介绍了React Native 脚手架的基本使用详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • 无废话快速上手React路由开发

    无废话快速上手React路由开发

    本文以简洁为目标,帮助快速上手react-router-dom默认你接触过路由相关的开发,通过实例代码讲解的很详细,对React路由相关知识感兴趣的朋友一起看看吧
    2021-05-05
  • useEffect中return函数的作用和执行时机解读

    useEffect中return函数的作用和执行时机解读

    这篇文章主要介绍了useEffect中return函数的作用和执行时机,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • 基于React实现表单数据的添加和删除详解

    基于React实现表单数据的添加和删除详解

    这篇文章主要给大家介绍了基于React实现表单数据的添加和删除的方法,文中给出了详细的示例供大家参考,相信对大家具有一定的参考价值,需要的朋友们下面来一起看看吧。
    2017-03-03
  • 深入理解react-router 路由的实现原理

    深入理解react-router 路由的实现原理

    这篇文章主要介绍了深入理解react-router 路由的实现原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • react实现点击选中的li高亮的示例代码

    react实现点击选中的li高亮的示例代码

    本篇文章主要介绍了react实现选中的li高亮的示例代码,页面上有很多个li,要实现点击到哪个就哪个高亮。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05

最新评论