资讯专栏INFORMATION COLUMN

从渲染原理到性能优化(一)

heartFollower / 2545人阅读

摘要:有人会说,会用就行了,知道渲染原理有必要么其实渲染原理决定着性能优化的方法,只有在了解原理之后,才能完全理解为什么这样做可以优化性能。性能优化结合渲染原理,通过实际例子,看看如何优化组件。

前言
以下,是我在2018 React Conf 的分享内容,希望对大家有所帮助。可以先在官网下载我的ppt对照看,效果更佳哦~。

很多人都使用过React,但是很少人能说出它内部的渲染原理。有人会说,会用就行了,知道渲染原理有必要么?其实渲染原理决定着性能优化的方法,只有在了解原理之后,才能完全理解为什么这样做可以优化性能。正所谓:知其然,然后知其所以然。
废话不多说,下面我们就开始吧~

本篇文章,将会分为四部分介绍:

JSX如何生成element
当我们写下一段JSX代码的时候,react是如何根据我们的JSX代码来生成虚拟DOM的组成元素element的。

element如何生成真实DOM节点
再生成elment之后,react又如何将其转成浏览器的真实节点。这里会通过介绍首次渲染以及更新渲染的流程来帮助大家理解这个渲染流程。

性能优化
结合渲染原理,通过实际例子,看看如何优化组件。

React 16异步渲染方案
到目前为止,这些优化组件的方法还不能解决什么问题,所以我们需要引入异步渲染,以及异步渲染的原理是什么。

一、JSX如何生成element

这里是一段写在render里的jsx代码。

return (
    
Hello, This is React
Start to learn right now!
Right Reserve.
)

首先,它会经过babel编译成React.createElement的表达式。

return (
    React.createElement(
        "div",
        { className: "cn" },
        React.createElement(
            Header,
            null,
            "Hello, This is React"
        ),
        React.createElement(
            "div",
            null,
            "Start to learn right now!"
        ),
        "Right Reserve"
    )
)

这个createElement方法是做什么的呢?

其实从它的名字就可以看出,这是用来生成element的。element在React里,其实就是组成虚拟DOM 树的节点,它用来描述你想要在浏览器上看到什么。
它的参数有三个:
1、type -> 标签
2、attributes -> 标签属性,没有的话,可以为null
3、children -> 标签的子节点

这个React.createElement的表达式会在render函数被调用的时候执行,换句话说,当render函数被调用的时候,会返回一个element
说了那么久element,这个element究竟长什么样呢?
其实,它就是一个对象,如下:

{
  type: "div",
    props: {
      className: "cn",
        children: [
          {
            type: function Header,
            props: {
                children: "Hello, This is React"
            }
          },
          {
            type: "div",
            props: {
                children: "start to learn right now!"
            }
          },
          "Right Reserve"
      ]
  }
}

我们来观察一下这个对象的children,现在有三种类型:
1、string
2、原生DOM节点
3、React Component - 自定义组件

除了这三种,还有两种类型:
4、fale ,null, undefined,number
5、数组 - 使用map方法的时候

这里需要记住一个点:element不一定是Object类型。

二、element如何生成真实节点

顺利得到element之后,我们再来看看React是如何把element转化成真实DOM节点的。
首先,需要去初始化element,初始化的规则如下:
以第一条为例:先判断是否为Object类型,是的话,看它的type是否是原生DOM标签,是的话,给它创建ReactDOMComponent的实例对象,其他同理。

这时候有的人可能会有所疑问:这些个ReactDOMComponent, ReactCompositeComponentWrapper怎么开发的时候都没有见过?

其实这些都是React的私有类,React自己使用,不会暴露给用户的。它们的常用方法有:mountComponent,updateComponent等。其中mountComponent 用于创建组件,而updateComponent用于用户更新组件。而我们自定义组件的生命周期函数以及render函数都是在这些私有类的方法里被调用的。
既然这些私有类的方法那么重要我们就先来简单了解一下吧~

ReactDOMComponent

首先是ReactMComponent的mountComponent方法,这个方法的作用是:将element转成真实DOM节点,并且插入到相应的container里,然后返回markup(realDOM)。
由此可知ReactDOMComponent的mountComponent是element生成真实节点的关键
下面看个栗子它是怎么做到的吧。

假设有这样一个type类型是原生DOM的element:

{
  type: "div",
    props: {
    className: "cn",
      children: "Hello world",
    }
}

简单mountComponent的实现:

mountComponent(container) {
  const domElement = document.createElement(this._currentElement.type);
  const textNode = document.createTextNode(this._currentElement.props.children);

  domElement.appendChild(textNode);
  container.appendChild(domElement);
  return domElement;
}

其实实现的过程很简单,就是根据type生成domElement,再将子节点append进来返回。当然,真实的mountComponent没有那么简单,感兴趣的可以自己去看源码啦。
这里需要记住的一个点是:这个类的mountComponent方法会自己操作浏览器DOM元素
讲完ReactDOMComponent,再来看看ReactCompositeComponentWrapper。

ReactCompositeComponentWrapper

这个类的mountComponent方法作用是:实例化自定义组件,最后是通过递归调用到ReactDOMComponent的mountComponent方法来得到真实DOM。
注意:也就是说他自己是不直接生成DOM节点的。
那这个递归是一个怎样的过程呢?我们通过首次渲染来看下。

首次渲染

假设我们有一个Example的组件,它返回

hello world
这样一个标签。
首次渲染的过程如下:

首先从React.render开始,由于我们刚刚说,render函数被调用的时候会返回一个element,所以此时返回给我们的element是:

{
  type: function Example,
  props: {
    children: null
  }
}

由于这个type是一个自定义组件类,此时要初始化的类是ReactCompositeComponentWrapper,接着调用它的mountComponent方法。这里面会做四件事情,详情可以看上图。其中,第二步的render的得到的element为:

{
  type: "div",
    props: {
    children: "Hello World"
  }
}

由于这个type是一个原生DOM标签,此时要初始化的类是ReactDOMComponent。接下来它的mountComponent方法就可以帮我们生成对应的DOM节点放在浏览器里啦。
这时候有人可能会有疑问,如果第二步render出来的element 类型也是自定义组件呢?
这时候它就会去调用ReactCompositeComponentWrapper的mountComponent方法,从而形成了一个递归。不管你的自定义组件嵌套多少层,最后总会生成原生dom类型的element,所以最后一定能调用到ReactDOMComponent的mountComponent方法。
感兴趣的可以自己在打断点看下这个递归的过程。
由我打的断点图可以看出在ReactCompositeComponent的mountComponent被调用多次之后,最后调用到了ReactDOMComponent的mountComponent方法。

到这里,首次渲染的过程就基本讲完了:-D。
但是还有一个问题:前面我们说自定义组件的生命周期跟render函数都是在私有类的方法里被调用的,现在只看到render函数被调用了,那么首次渲染时候生命周期函数 componentWillMount 跟 componentDidMount 在哪被调用呢?

由图可知,在第一步得到instance对象之后,就会去看instance.componentWillMount是否有被定义,有的话调用,而在整个渲染过程结束之后调用componentDidMount。
以上,就是渲染原理的部分,让我们来总结以下:

JSX代码经过babel编译之后变成React.createElement的表达式,这个表达式在render函数被调用的时候执行生成一个element。

在首次渲染的时候,先去按照规则初始化element,接着ReactComponentComponentWrapper通过递归,最终调用ReactDOMComponent的mountComponent方法来帮助生成真实DOM节点。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97541.html

相关文章

  • 前端性能优化之--页面渲染优化全面解析

    摘要:下面我们撇开网络方面的优化,只分析静态资源方面的优化。不过,也会阻止的构建和延缓网页渲染。未优化正常加载优化后异步加载根据上面的分析,我们可以清楚的认识到,非必要优先加载的,选择异步加载是最优选择。 为什么做优化 经典问题:白屏时间过长,用户体验差产生的原因:网络问题、关键渲染路径(CRP)问题 怎么做优化 如何做好优化呢,网上随便一搜,就有很多优化总结,无非就是网络优化、静态资源(h...

    MadPecker 评论0 收藏0
  • 前端性能优化之--页面渲染优化全面解析

    摘要:下面我们撇开网络方面的优化,只分析静态资源方面的优化。不过,也会阻止的构建和延缓网页渲染。未优化正常加载优化后异步加载根据上面的分析,我们可以清楚的认识到,非必要优先加载的,选择异步加载是最优选择。 为什么做优化 经典问题:白屏时间过长,用户体验差产生的原因:网络问题、关键渲染路径(CRP)问题 怎么做优化 如何做好优化呢,网上随便一搜,就有很多优化总结,无非就是网络优化、静态资源(h...

    gghyoo 评论0 收藏0
  • 前端性能优化之--页面渲染优化全面解析

    摘要:下面我们撇开网络方面的优化,只分析静态资源方面的优化。不过,也会阻止的构建和延缓网页渲染。未优化正常加载优化后异步加载根据上面的分析,我们可以清楚的认识到,非必要优先加载的,选择异步加载是最优选择。 为什么做优化 经典问题:白屏时间过长,用户体验差产生的原因:网络问题、关键渲染路径(CRP)问题 怎么做优化 如何做好优化呢,网上随便一搜,就有很多优化总结,无非就是网络优化、静态资源(h...

    gaomysion 评论0 收藏0
  • 前端性能优化

    摘要:端优谈谈关于前端的缓存的问题我们都知道对页面进行缓存能够有利于减少请求发送,从而达到对页面的优化。而作为一名有追求的前端,势必要力所能及地优化我们前端页面的性能。这种方式主要解决了浅谈前端中的过早优化问题过早优化是万恶之源。 优化向:单页应用多路由预渲染指南 Ajax 技术的出现,让我们的 Web 应用能够在不刷新的状态下显示不同页面的内容,这就是单页应用。在一个单页应用中,往往只有一...

    Dean 评论0 收藏0

发表评论

0条评论

heartFollower

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<