因为,babel会将jsx代码转换
import React, { Component } from 'react';
class Process extends Component {
render() {
return (哈哈哈)
}
}
import React, { Component } from 'react';
class Process extends Component {
render() {
// 用到了React.createElement方法
return React.createElement(
'div',
null,
'\u54C8\u54C8\u54C8'
);
}
}
组件通过
React.createElement(type,props,children)
import React from "react";
const App = () => (
Hello World!!!
);
export default App;
var App = function App() {
return React.createElement(
"div",
{className:'test'}
"Hello World!!!"
);
};
import React from 'react'
ReactDOM.render(
,
document.getElementById('root')
);
Hello.jsx
class Hello extends React.Component{
render(){
return (
father
child
child1
)
}
}
Hello.jsx被babel编译后会变成
React.createElement(
"div",
{
id: "father"
},
"father",
React.createElement("span", null, "child"), React.createElement(
"a",
{
href: "/bar"
},
"child1"
)
);
// 注意这里经过babel编译后用到了React.createElement
// 这就解释了为什么每个组件里面都要import React却发现并没有调用他
const React = {
createElement(type, props, children) {
const element = {
type,
props: props || {}
};
if (children) {
element.props.children = children;
}
return element;
},
}
// 可以发现,createElemtn(type, props, children)接受三个参数
// 最终这个方法返回一个element形式的对象
// 可是上面的jsx代码通过babel转换后不只三个参数?
// 排除调type和config两个属性
// 将第二参数后的所有属性看作子元素赋给prop.children
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
// 如果不止一个child,合成一个数组后赋值给prop.children
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
....
props.children = childArray;
}
const element = {
type: "div",
props: {
id: "container",
children: [
{
type: "span",
props: {
children: [
{
type: "TEXT ELEMENT",
props: { nodeValue: "Foo" }
}
]
}
},
{
type: "a",
props: {
href: "/bar", children: [
{
type: "TEXT ELEMENT",
props: { nodeValue: "bar" }
}
]
},
}
]
}
};
所以,最后组件都会在经过Babel转换和React.createElement后变成一个 Element对象
element = {
type:'div',
props:{
id:'xx',
children:[
{
....
},
{
....
},
]
}
}
最后在在index.jsx中
ReactDOM.render(element,container)
通过render 方法将element转换成我们的实际DOM
下面内容基本都是参考如下博客内容:
https://foio.github.io/react-implement/
//表示根节点的instance
let rootInstance = null;
function render(element, container) {
const prevInstance = rootInstance; //虚拟DOM的根节点
const nextInstance = reconcile(container, prevInstance, element); //对比DOM diff,并更新真实DOM
rootInstance = nextInstance; // 新的虚拟DOM根节点
}
上面render方法主要作用就是调用reconcile方法(这里是diff算法的具体实现地方)
function reconcile(parentDom, instance, element) {
if (instance == null) {
//虚拟的根节点为空时,使用当前React元素,创建新的虚拟DOM
const newInstance = instantiate(element);
//将真实DOM插入容器
parentDom.appendChild(newInstance.dom);
return newInstance;
}else if(element == null){
//删除DOM
parentDom.removeChild(instance.dom);
return null;
} if (instance.element.type === element.type) {
//原有虚拟DOM节点类型与要创建的DOM节点类型一致时,可以重用dom以提升性能,只需要更新dom节点属性
updateDomProperties(instance.dom, instance.element.props, element.props);
//对instance子节点进行对比,以保证尽可能的重用DOM
instance.childInstances = reconcileChildren(instance, element);
instance.element = element;
return instance;
} else {
//使用当前React元素,创建新的虚拟DOM
const newInstance = instantiate(element);
//将真实DOM替换容器中的原有DOM
parentDom.replaceChild(newInstance.dom, instance.dom);
return newInstance;
}
}
这里的instance就是所谓的虚拟DOM,每个真实的DOM都有一个虚拟DOM(instance)与之匹配
这样在操作DOM变化的时候,对比新老element的变化,得到最终需要改变的DOM
再去跟新真实的DOM,达到最小化改变DOM
function reconcileChildren(instance, element) {
// instance 旧
// element 新
const dom = instance.dom;
const childInstances = instance.childInstances;
const nextChildElements = element.props.children || [];
const newChildInstances = []; // 新的孩子数组
//选取新旧子节点数据组中最大的值
const count = Math.max(childInstances.length, nextChildElements.length);
for (let i = 0; i < count; i++) {
const childInstance = childInstances[i];
const childElement = nextChildElements[i];
//调用reconcile创建子节点的虚拟DOM
/*这里存在三种情况:
* (1). childInstance和childElement都存在,则调用reconcile进行diff操作
* (2). childInstance为空而childElement存在,调用调用reconcile创建新的instance
* (3). childInstance存在而childElement为空,则调用reconcile进行删除操作,此时会返回null
*/
const newChildInstance = reconcile(dom, childInstance, childElement);
newChildInstances.push(newChildInstance);
}
return newChildInstances.filter(instance => instance != null); //过滤null
}
function instantiate(element) {
const { type, props } = element;
const isTextElement = type === "TEXT ELEMENT";
const dom = isTextElement
? document.createTextNode("")
: document.createElement(type);
updateDomProperties(dom, [], props); //更新DOM节点的属性、绑定事件
//递归地调用instantiate函数,创建虚拟DOM的子节点
const childElements = props.children || [];
const childInstances = childElements.map(instantiate);
const childDoms = childInstances.map(childInstance => childInstance.dom);
childDoms.forEach(childDom => dom.appendChild(childDom));
const instance = { dom, element, childInstances };
return instance;
}
function updateDomProperties(dom, prevProps, nextProps) {
//判断是否为事件属性
const isEvent = name => name.startsWith("on");
//判断是否为普通属性
const isAttribute = name => !isEvent(name) && name != "children";
//移除原有DOM节点上绑定的事件
Object.keys(prevProps).filter(isEvent).forEach(name => {
const eventType = name.toLowerCase().substring(2);
dom.removeEventListener(eventType, prevProps[name]);
});
//移除原有DOM节点的普通属性
Object.keys(prevProps).filter(isAttribute).forEach(name => {
dom[name] = null;
});
//添加新属性
Object.keys(nextProps).filter(isAttribute).forEach(name => {
dom[name] = nextProps[name];
});
//添加新事件
Object.keys(nextProps).filter(isEvent).forEach(name => {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, nextProps[name]);
});
}
element = {
type:xx,
props:{
id:xx,
child:{
type:xx
props:xx
}
}
}
这里涉及到:
// render主要主要就是嗲用reconcile
render: reconcile
// reconcile主要通过diff算法减少更新dom
reconcile: instantiate,reconcileChildren
// instantiate将element变成真正的DOM
instantiate: updateDomProperties
// updateDomProperties更新DOM属性,绑定事件
上面我们实现了基本的虚拟DOM比较,能够在一定程度上复用DOM,但仍然有如下问题
(1)每次更改都会触发整棵虚拟DOM树的比较
(2)需要显式的调用render函数,来渲染最新状态
(3)不支持自定义组件
为了解决上面缺点,于是有了Component,于是我们将原来的instance的三个属性新增了publicInstance,表示对Component实例的引用;
而Component实例内部又有一个对它自身Instance的引用:internalInstance。
internalInstance: 作用是为了在组件内部拿到子虚拟DOM的根节点,从而进行子树的diff操作,避免整个虚拟DOM进行diff操作。
instance = {
element: {}, //React元素的引用
dom: {}, //真实浏览器DOM的引用
childInstances: [] //子节点的Instance引用
}
instance = {
element: {}, //React元素的引用
dom: {}, //真实浏览器DOM的引用
childInstances: [], //子节点的Instance引用
publicInstance: { //表示Component的引用
//对自身instance的引用
// 用于在setState时通过组件this拿到instance以触发更新
__internalInstance: {}
}
}
引入Component后, React的element可能是一个自定义的组件, 我们需要一个用于创建自定义组件Component实例(publicInstance)的方法:
function createPublicInstance(element, internalInstance) {
// 当元素进到这里来, 说明type是个自定义element的构造函数
const { type, props } = element;
// 调用组件的构造函数,创建组件实例
const publicInstance = new type(props);
// 自定义组件对应的Instance引用, 用于在实例中通过this.__internalInstance获取组件对应的instance,以更新组件
publicInstance.__internalInstance = internalInstance;
return publicInstance;
}
关键: 组件内部获取该组件实例,然后调用reconcile方法
所有的React组件都需要继承一个基类Component,该基类有一个构造函数和setState方法,其中setState方法会触发组件的更新
class Component {
constructor(props) {
this.props = props;
this.state = this.state || {};
}
//用于更新组件的内部状态
setState(partialState) {
this.state = Object.assign({}, this.state, partialState);
// 通过this.__internalInstance获取Component对应的instance,并通过updateInstance进行更新
updateInstance(this.__internalInstance);
}
}
function updateInstance(internalInstance) {
//internalInstance是自定义组件对应的instance {element, dom, childInstances}
const parentDom = internalInstance.dom.parentNode;
const element = internalInstance.element;
//调用reconile函数,进行虚拟DOM比较,并更新DOM树
reconcile(parentDom, internalInstance, element);
}
unction reconcile(parentDom, instance, element) {
if (instance == null) {
//虚拟的根节点为空时,使用当前React元素,创建新的虚拟DOM
const newInstance = instantiate(element);
//将真实DOM插入容器
parentDom.appendChild(newInstance.dom);
return newInstance;
}else if(element == null){
//删除DOM
parentDom.removeChild(instance.dom);
return null;
} else if(instance.element.type !== element.type){
//使用当前React元素,创建新的虚拟DOM
const newInstance = instantiate(element);
//将真实DOM替换容器中的原有DOM
parentDom.replaceChild(newInstance.dom, instance.dom);
return newInstance;
} else if (typeof element.type === 'string') {
//原有虚拟DOM节点类型与要创建的DOM节点类型一致且为原生类型(非自定义类型),可以重用dom以提升性能,只需要更新dom节点属性
updateDomProperties(instance.dom, instance.element.props, element.props);
//对instance子节点进行对比,以保证尽可能的重用DOM
instance.childInstances = reconcileChildren(instance, element);
instance.element = element;
return instance;
} else {
//逻辑到之有两个条件
//(1)原有虚拟DOM节点类型与要创建的DOM节点类型一致
//(2)element type为自定义类型,其中publicInstance是自定义组件的实例
//更新自定义组件的属性
instance.publicInstance.props = element.props;
//原有孩子节点instance数组
const oldChildInstance = instance.childInstance;
//调用自定义的render函数,创建自定义组件的孩子节点element
const childElement = instance.publicInstance.render(); // 组件的render函数
//对比自定义组件的虚拟DOM,更新DOM
const childInstance = reconcile(parentDom, oldChildInstance, childElement);
//更新instance引用
instance.dom = childInstance.dom;
instance.childInstance = childInstance;
instance.element = element;
return instance;
}
}
nstantiate函数的作用是将element转成为instance结构,它也需要额外的修改以支持自定义组件。在instantiate函数中,对于DOM类型的组件,我们需要调用document.createElement,而 对于自定义的组件,则需要调用createPublicInstance。
function instantiate(element) {
const { type, props } = element;
const isDomElement = typeof type === 'string';
if(isDomElement) {
const isTextElement = type === "TEXT ELEMENT";
const dom = isTextElement
? document.createTextNode("")
: document.createElement(type);
updateDomProperties(dom, [], props); //更新DOM节点的属性、绑定事件
//递归地调用instantiate函数,创建虚拟DOM的子节点
const childElements = props.children || [];
const childInstances = childElements.map(instantiate);
const childDoms = childInstances.map(childInstance => childInstance.dom);
childDoms.forEach(childDom => dom.appendChild(childDom));
const instance = { dom, element, childInstances };
return instance;
}else {
const instance = {};
//对于自定义类组件,创建对应的publicInstance
const publicInstance = createPublicInstance(element, instance);
//调用自定义组件的render方法,获取child element
const childElement = publicInstance.render();
//创建child element的instance
const childInstance = instantiate(childElement); // 递归 孩子拿到 { dom, element, childInstances }
const dom = childInstance.dom;
//返回自定义类型组件的instance,其中publicInstance为自定义组件的实例
//自定义组件的instance有几个特殊的地方:
//(1) childInstance不是数组,而是自定义组件的根节点对应的instance
//(2) dom是自定义组件的根节点对应的DOM
//(3) publicInstance是自定义组件类实例,内部维护着__internalInstance指向instance
Object.assign(instance, { dom, element, childInstance, publicInstance });
return instance;
}
}