React渲染原理
一些概念
**React 元素:**React Element,通过 React.createElement
创建,平常我们写的 jsx是语法糖,最终还是会被 Babel 编译成 React.createElement
。
例如:
1 | function App() { |
经过 Babel 编译之后的代码为:
1 | function App() { |
我们可以打印一下 React 元素:
1 | console.log(<div><h1>hello world</h1></div>); |
**React 节点:**React 节点是专门用于渲染到 UI 界面的对象,并且是由 ReactDOM
负责创建并渲染,该对象可以通过 React 元素/字符串数字等创建。平常所说的 jsx 创建的对象是虚拟 DOM,其实并不严谨,这玩意才是真正的虚拟 DOM。
实际上在 ReactDOM 中React节点的源码表示应该是
ReactComponent
,但是翻译成组件会造成误解,就约定称之为React节点吧。
节点类型:
- React DOM 节点:该节点由
type
为字符串的React元素创建,例如前面的div
- React 组件节点:该节点由
type
为函数 / 类的React元素创建,例如前面的App
- React 文本节点:由字符串、数字所创建。
- React 空节点:由
null
、undefined
、false
、true
所创建,但是页面没有显示。 - React 数组节点:由一个数组创建。
React DOM 节点的源码级表示:
ReactDOMComponent
React 组件节点的源码级表示:ReactCompositeComponent
React 文本节点的源码级表示:ReactDOMTextComponent
React 空节点的源码级表示:ReactEmptyComponent
**需要注意的是:**在 ReactDOM 16版本以上有些找不到了,应该是有些东西做了调整,这些类型仅在 15 版本可找得到,但是渲染的核心应该没有变。
如何验证说的对不对呢,将这些东西直接扔到 ReactDOM.render
函数中,能渲染就可行。
1 | ReactDOM.render('hello world', document.getElementById('root')); |
如果扔进去一个普通对象会直接报错:
1 | ReactDOM.render( |
首次渲染
我们先来看看首次渲染,也就是从零到一渲染页面的过程。
大致过程简单来说就是:
- 根据参数创建节点(
ReactDOM.render
函数) - 根据不同的节点做不同的操作
- 生成虚拟 DOM 树,并保存该虚拟 DOM 树
- 将之前生成的真实的DOM对象(在过程2)加入到容器中(
getElemetById('root')
),也就是实际渲染到页面中。
可以看到实际上核心就是步骤2,我们也详细看看步骤2的操作。
步骤2的大致过程:
- 文本节点:通过
document.createTextNode
创建真实的文本节点 - 空节点:什么都不做
- 数组节点:遍历数组,对于数组每一项进行递归操作(回到第1步,直到遍历结束)
- DOM 节点:通过
document.createElement
创建真实的DOM对象,并设置相应的一些属性,然后遍历对应 React元素的children
属性,递归操作 - 组件节点
- 函数组件:调用函数,将该函数的返回结果进行递归操作
- 类组件:
- 创建该类的实例
- 调用对象的生命周期方法:
static getDerivedStateFromProps
- 调用
render
方法,将函数的返回结果进行递归操作 - 将该组件的
componentDidMount
加入一个执行队列,当页面渲染完毕执行该队列中的函数
这其中生成的那些真实 DOM 对象会在这过程中形成DOM树,最后统一将根节点加入到 getElementById('root')
中。
来看一个例子吧:
App.jsx
1 | import React, { Component } from 'react' |
index.jsx
1 | import React from 'react'; |
生成的虚拟DOM树大致是这样: