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

目录
  • React前端渲染优化--父组件导致子组件重复渲染
    • 说明
    • 一般的优化方式
    • 项目中常见会导致重复渲染的写法以及改进方法
  • 组件重复渲染问题(pureComponent, React.memo, useMemo, useCallback)
    • render执行会带来两个方面的影响
    • 下面将具体说明这几个都使用场景和解决的问题

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)

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

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

(0)

相关推荐

  • 浅谈React前后端同构防止重复渲染

    什么叫前后端同构? 为了解决某些问题(比如SEO.提升渲染速度等)react 提供了2个方法在服务端生成一个HTML文本格式的字符串.在得到了这个HTML格式的字符串之后,通常会将其组装成一个页面直接返回给用户的浏览器. 到这里,服务端的活已经干完了,然后就是浏览器这边干活. 浏览器拿到HTML文本后,立刻进行渲染将内容呈现给用户.然后加载页面所需的 .js 文件,然后执行 JavaScript 脚本,然后开始初始化 react 组件---- 到这里问题就来了.react 初始化组件后会执行组件

  • 详解react阻止无效重渲染的多种方式

    在开发React组件的过程中,我们经常会遇到这个问题:什么情况下组件会重新渲染? 当内部data发生改变,state发生改变(通过调用this.setState()) 以及父组件传过来的props发生改变时,会导致组件重新渲染. 以下几个问题同样值得我们思考: setState()函数在任何情况下都会导致组件重渲染吗?如果setState中的state没有发生改变呢? 如果state和从父组件传过来的props都没变化,那他就一定不会发生重渲染吗? 首先,我们来解决这两个问题 没有导致state

  • React 性能优化之非必要的渲染问题解决

    目录 1. 非必要组件渲染 2. 解决方案之 shouldComponentUpdate 3. 解决方案之 PureComponent 4. 解决方案之 React.memo 5. useMemo 和 useCallback 1. 非必要组件渲染 在 React 工程中,在改变 React 状态时,我们希望对整个页面的影响越小越好.然而实际情况是更改掉某些属性之后,除了会导致组件本身的重新渲染,也可能会导致其相关的组件也发生重新渲染.请看下面的例子: 新建一对父子组件 // 父组件: impor

  • React优化子组件render的使用

    在react中,父组件的重新render会引发子组件的重新render,但是一些情况下我们会觉得这样做有些多余,比如: 父组件并未传递props给子组件 新传递的props渲染结果不变 class A extends React.Component { render() { console.log('render') return <div>这是A组件</div> } } class Main extends React.Component { render() { return

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

    目录 React前端渲染优化--父组件导致子组件重复渲染 说明 一般的优化方式 项目中常见会导致重复渲染的写法以及改进方法 组件重复渲染问题(pureComponent, React.memo, useMemo, useCallback) render执行会带来两个方面的影响 下面将具体说明这几个都使用场景和解决的问题 React前端渲染优化--父组件导致子组件重复渲染 说明 目前我们所使用 react 版本一般会有以下四种方式触发渲染 render,而其中通过父组件 render 会直接通知子

  • react 父组件与子组件之间的值传递的方法

    概念上,组件是封闭的环境.React中是单向数据流的设计,也就是是说只有父组件传递资料给子组件这回事.以正确的技术说明,拥有者组件可以设置被拥有者组件中的数据. 那么子组件要如何与父组件沟通这件事,简单的来说,是一种迂回的作法,在父组件中设置了一个方法(函数),然后传递给子组件的props,子组件在事件触发时,直接呼叫这个props所设置的方法(函数).但这中间,有谁(对象)呼叫了函数的设置,也就是this的作用. 父组件到子组件用props设置,子组件到父组件用上面说的方式,这是基本的套路,但

  • react 中父组件与子组件双向绑定问题

    在项目中我们可能会遇到类似这样的场景,也就是父子组件的双向数据绑定 首先,先把在head中引入react.js.react-dom.js和可选择的babel.js(这里需要注意引用的顺序,react.js必须在react-dom.js之前) <head> <script src="react.js"></script> <script src="react-dom.js"></script> <scr

  • 详解React 父组件和子组件的数据传输

    在学习 React 框架组件间数据传输知识点前,我们需要先明确几点使用原则. React的组件间通讯是单向的.数据必须是由父级传到子级或者子级传递给父级层层传递. 如果要给兄弟级的组件传递数据,那么就要先传递给公共的父级而后在传递给你要传递到的组件位置. 这种非父子关系的组件间传递数据,不推荐使用这种层层传递的方式:而是选择使用维护全局状态功能模块(Redux) 一.父组件向子组件传递数据 父组件向子组件传递数据是通过在父组件中引用子组件时,在子组件标签设置传输数据的属性:而子组件中通过 thi

  • React父组件调用子组件中的方法实例详解

    目录 Class组件 1. 自定义事件 2. 使用 React.createRef() 3. 使用回调Refs Function组件 补充:子组件调用父组件方法 总结 文章中涉及 ref 的应用仅为父组件调用子组件场景下的应用方式,并未涵盖 ref 的所有应用方式! Class组件 1. 自定义事件  Parent.js import React, { Component } from 'react'; import Child from './Child'; class Parent exte

  • react父组件调用子组件的方式汇总

    目录 前言 父子组件都为class 父子组件都为hooks 父组件为class,子组件为hooks 父组件为hooks,子组件是class 小结 前言 本文是小结类文章,主要总结一下工作中遇到的父组件调用子组件方法.当然除了用ref之外还有很多其他方式,本文仅仅列举ref的方式.分别介绍父子组件都为class:父子组件都是hooks:父组件是class子组件是hooks:父组件是hooks,子组件是class的各种情况的调用方式. 父子组件都为class 父子组件都是class,父组件调用子组件

  • vue父组件向子组件(props)传递数据的方法

    vue页面结构 在做项目的时候常常有这样的一个情况,这个页面的数据(比如:id号)要带到另一个页面去查询某个数据的详情等,传统的作法不是在url上加参数,cookie或者是现在H5的"sessionStorage"和"localStorage"上赋值,这是页面之间传递的方法. 随着Angularjs,React,Vue的流行组件式的开发方式成为另一种不错的解决方案. 最近就有一些小伙伴问我,vue组件之间是如何传递参数的?其实vue是有三种方式可以组件之间传递数据(

  • 如何通过简单的代码描述Angular父组件、子组件传值

    目录 引言 零.知识铺垫 CSS选择器 一.什么是父子组件 二.父组件调用子组件的方法 三.父组件向子组件传值 子组件使用@input装饰器接收数据 父组件使用方括号[]发送数据 升级:子组件通过set方法监听传入数据变化 另一种升级:子组件通过ngOnChanges()生命周期钩子监听传入数据变化 四.子组件向父组件传值 子组件向父组件弹射事件 父组件监听子组件弹射的事件 五.总结 六.后记 总结 引言 对于稍微接触过Angular组件的同学来说,组件间交互应该没有什么问题. 本文想追求的是用

  • vue父组件调用子组件方法报错问题及解决

    目录 vue父组件调用子组件方法报错 vue父组件调用子组件方法及遇到的问题 vue父组件调用子组件方法报错 在父组件定义了一个tab标签页,每一个标签页下面都调用不同的组件, 如下图所示: 子组件中定义的方法: setup() { const getList = () =>{ const date = moment(new Date()).format('YYYY-MM') loading.value = true apiGetPageList({ salaryDate: date, page

  • Vue实现的父组件向子组件传值功能示例

    本文实例讲述了Vue实现的父组件向子组件传值功能.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue 父组件向子组件传值</title> <script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></scrip

随机推荐