前言
React支持jsx语法,我们可以直接将HTML代码写到JS中间,然后渲染到页面上,我们写的HTML如果有更新的话,React还有虚拟DOM的对比,只更新变化的部分,而不重新渲染整个页面,大大提高渲染效率。那么JSX是如何一步步渲染到浏览器页面中呢?下面我们一步步来解析。
通过createElement转换成vdom
我们写JSX代码可以通过babel转换工具转换成React.createElement的形式,如下面的代码
let ary = [
{ name: "xiaomi", age: 12 },
{ name: "xiaoming", age: 120 },
{ name: "xiaohong", age: 121 },
{ name: "xiaohe", age: 122 },
];
function App() {
return (
<ul>
{ary.map(function (item, index) {
return (
<li key={index}>
姓名是:{item.name},年龄是:{item.age}
</li>
);
})}
</ul>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
<h1>你好!</h1>
<p>OK</p>
</React.StrictMode>,
document.querySelector("#root")
);
babel转换如下:
从转换后的代码我们可以看出
React.createElement支持多个参数:
type:判断元素节点类型(string/function)config:该元素节点的属性,例如refchildren:从第三个参数开始就全部是children也就是子元素了,子元素可以有多个,类型可以是简单的文本,也可以还是React.createElement,如果有多个,children是个数组。React.createElement的返回值是个对象{type:xxx,props:xxx}
自己实现一个createElement函数
const createElement = function createElement(type, config, children) {
let props
if (config) {
delete config.__source, delete config.__self
}
props = { ...config }
if (arguments.length > 3) {
children = Array.prototype.slice.call(arguments, 2)
}
props.children = children
return {
type,
props,
}
}
通过 render 转换成真实 dom
- 把虚拟DOM变成真实DOM
- 把虚拟DOM上的属性更新或者同步到真实DOM上
- 把虚拟DOM的儿子们也变成真实DOM挂载到自己的dom上
- 把自己挂载到容器上
/**
* 1.把虚拟dom转换成真实DOM
* 2.把虚拟dom上的属性更新或者同步到dom上
* 3.把此虚拟dom的儿子也都变成真实dom挂载到自己的dom上
* 4.把自己挂载到容器上
* @param {*} vdom 要渲染的虚拟DOM
* @param {*} container 把虚拟dom转换成真实DOM 并且插入到容器中
*/
function render(vdom, container) {
const dom = createDOM(vdom)
container.appendChild(dom)
}
/**
* 把虚拟dom变成真实dom
* @param {*} vdom 虚拟dom
*/
function createDOM(vdom) {
//如果是字符串或者数字 直接返回一个真实的文本节点
if (typeof vdom === 'string' || typeof vdom === 'number') {
return document.createTextNode(vdom)
}
let { type, props } = vdom
//创建dom元素
let dom = document.createElement(type)
//使用虚拟dom属性更新刚刚创建出来的真实DOM属性
updateProps(dom, props)
//在这里处理children
if (typeof props.children == 'string' || typeof props.children === 'number') {
dom.textContent = props.children
//如果只有一个儿子并且是个虚拟dom元素
} else if (typeof props.children === 'object' && props.children.type) {
//把此虚拟dom的儿子也都变成真实dom挂载到自己的dom上
render(props.children, dom)
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom)
} else {
document.textContent = props.children ? props.children.toString() : ''
}
//把真实dom放在虚拟dom属性上,为以后更新做准备
vdom.dom = dom
return dom
}
/**
* 使用虚拟dom属性更新刚刚创建出来的真实DOM属性
* @param {*} dom 真实DOM
* @param {*} props 新属性对象
*/
function updateProps(dom, props) {
for (let key in props) {
if (key === 'children') continue //单独处理
if (key === 'style') {
let styleObj = props[key]
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr]
}
} else {
dom[key] = porps[key]
}
}
}
/**
* @param {*} childrenVdom 儿子们的虚拟dom
* @param {*} parentDOM 父亲的真实dom
*/
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
let childVdom = childrenVdom[i]
render(childVdom, parentDOM)
}
}