系列文章目录
第一章:React从入门到进阶之初识React
第二章:React从入门到进阶之JSX简介
第三章:React从入门到进阶之元素渲染
第四章:React从入门到进阶之JSX虚拟DOM渲染为真实DOM的原理和步骤
第五章:React从入门到进阶之组件化开发及Props属性传值
第六章:React从入门到进阶之state及组件的生命周期
第七章:React从入门到进阶之React事件处理
一、分析与思考
前面我们已经学习了JSX的一些基本语法,通过前面的学习我们知道:在js文件中通过调用ReactDOM.render()方法,并传入一堆JSX代码(可以理解为虚拟DOM)。当程序运行时这些JSX代码就会被转换为真实的DOM元素然后呈现在页面上;那么这些JSX虚拟DOM是如何被转换为真实DOM的呢,原理又是怎样的呢?
当我们用react脚手架创建好一个项目后,会生成一个package.json文件,在这个文件的最下面有这样一段代码
"babel" :{ presets:["react-app"] }
了解babel的人应该都知道,babel主要是用来将ES6或更高版本的js代码转换为ES5的代码,从而提高兼容性。上面这段代码的配置同样也是如此,它可以将JSX虚拟DOM转换成React可识别的React DOM对象,如下图:
二、JSX虚拟DOM渲染为真实DOM的原理和步骤
接下来我们就来分析一下JSX虚拟DOM渲染为真实DOM的原理和步骤
- 基于babel-preset-react-app把JSX语法变为React.createElement的模式
- 只要遇到元素标签(或组件)都要调用createElement
- createElement的前两个参数是固定的:标签名(组件名)、属性,第三个及以后的参数是子元素
- 如果传递了属性,第二个参数是一个对象(包含了各属性的信息),没有传递属性则第二个参数为null
- 基于React.createElement方法执行创建出虚拟DOM对象(JSX对象)
- 首先创建一个对象
- type属性存储的是标签名或组件
- props属性:如果没有传递任何属性,也没有任何子元素,则为空对象;把传递的createElement的属性,都赋值给props;如果有子元素则新增一个children属性,可能是一个值也可能是一个数组
- 基于ReactDOM.render把创建的虚拟DOM对象渲染到页面指定的容器中
- ReactDOM.render([jsxObj],[container],[callback]),render接收三个参数:jsx对象,页面指定的容器和回调函数(可不传)
- callback渲染触发的回调函数,着这里可以获取到真实DOM
三、基于渲染原理重写createElement和render
React.createElement = function(type, props, ...children){
let jsxObj = {
tyupe,
props:{
},
}
//传递了属性,把传递的属性都放在jsxObj的props中
if(props !== null){
//基于es6实现
jsxObj.props = {
...props};
//或者有es5的语法用for循环
}
//如果传递了子元素,还需要给jsxObj的props设置children属性
if(children.length > 0){
jsxObj.props.children = children;
//如果传递的子元素只有一项,则直接把第一项的值赋值给jsxObj.props.children即可
if(children.length === 1){
jsxObj.props.children = children[0];
}
}
return jsxObj;
}
ReactDOM.render = function render(jsxObj, container, callback){
let {
type, props} = jsxObj;
//创建DOM元素
if(typeof type === "string"){
//创建真实DOM元素对象
let element = document.createElement(type);
//给创建的DOM设置属性
for(let key in props){
if(!props.hasOwnProperty(key)) break;
//样式类和行内样式特殊处理
if(key === "className"){
element.setAttribute('class', props[key]);
continue;
}
if(key === "style"){
let styObj = props[key];
for(let attr in styObj){
if(!styObj.hasOwnProperty(attr)) break;
element.style[attr] = styObj[attr];
}
continue;
}
//关于子元素处理
if(key === "children"){
let children = props[key];
if(!Array.isArray(children)){
children = [children];
}
//循环子元素
children.forEach(item=>{
//如果是文本,则直接创建文本节点赋值给element,如果是新的虚拟DOM对象,则需要重复调用render方法,把新创建的DOM对象增加给element(递归)
if(typeof item === "string"){
element.appendChild(document.createTextNode(item));
return;
}
render(item, element);
});
}
element.setAttribute(key, props[key]);
}
container.appendChild(element);
callback && callback();
}
}