持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情
VirtualDom介绍
virtualDom实际就是个object类型的字典{},字典中包含type,props,children,textContent等key来描述的是dom元素的。
如下所示真是的dom
<div className={'container'}>
<div>React</div>
</div>
描述dom的VirtualDom,则为
{
type: "div",
props: { className: "container" },
children: [
{
type: "div",
props: null,
children: [
{
type: "text",
props: {
textContent: "React"
}
}
]
}
]
}
VirtualDom生成过程
<div name= '33'>
<div>{funcDoc}</div>
</div>
function fucDoc() {
return <div>function component</div>
}
可以看到上述代码经过babel转换后会调用React.createElement api
"use strict";
/*#__PURE__*/
React.createElement("div", {
name: "33"
}, /*#__PURE__*/React.createElement("div", null, funcDoc));
function fucDoc() {
return /*#__PURE__*/React.createElement("div", null, "function component");
}
而调用React.createElement 的api后返回的刚好是我们想要的VirtualDOm
如下代码所示返回的刚好是VirtualDOm对象。
/**
*
* @param type 标签类型
* @param props 标签的属性
* @param children 子元素
* @returns {{children: *[], type, props}}
*/
export default function createElement (type, props, ...children) {
const childElements = ...省略处理逻辑...
return {
type,
props,
childElements
}
}
实现tinyReact
环境配置
统一配置
我们需要告诉babel使用我们自己写的TinyReact,需要配置babel
在.babelrc文件中将React改为TinyReact
{
"presets": [
"@babel/preset-env",
[
"@babel/preset-react",
{
"pragma": "TinyReact.createElement" //React改为TinyReact
}
]
]
}
通过以上配置告诉babel调用TinyReact.createElement来转换jsx文件
单个文件配置
除了统一配置外也可以在单个文件中添加
/** @jsx TinyReact.createElement */注释,来实现单个文件配置
生成VirtualDom
上一步操作会让babel调用我们自己写的createElement方法
//index.js文件
const virtualDOM = (
<div className="container">
<h1 test = "text">我是标题一</h1>
<h2 test = "text">我是标题二</h2>
<div>
div嵌套一下
<div>里面的div</div>
</div>
{shouldHide && <div>条件渲染1</div>}
{!shouldHide && <div>隐藏我</div>}
<button onClick={() => alert("你好")}>点我</button>
2, 3
<input type="text" value="13" />
</div>
)
console.log(virtualDOM)
如果createElement直接返回打印结果如下
可以看到文本被放在children中直接返回了,显然不对的,另外条件渲染!shouldHide为false被直接返回了,我们想要的是隐藏标签的效果
文本节点对应virtualDom为
{
type: 'text',
props: {
textContent: '我是文本'
}
}
/**
*
* @param type 标签类型
* @param props 标签的属性
* @param children 子元素
* @returns {{children: *[], type, props}}
*/
export default function createElement (type, props, ...children) {
const childElements = [].concat(...children).reduce((result,child) => {
if (child !== false && child !== true && child !== null) {
if (child instanceof Object) {
result.push(child)
} else {
// 文本节点 手动调用 createElement
result.push(createElement("text", {textContent: child}))
}
}
return result
}, [])
return {
type,
props: Object.assign({ children: childElements }, props),
children: childElements
}
}
实现render方法渲染UI
//index.js
......
TinyReact.render(<virtualDOM />, root)
//TinyReact/index.js
import createElement from "./createElement";
import render from "./render";
export default {
render,
createElement
}
//TinyReact/render.js
import mountElement from "./mountElement";
export default function render(virtualDOM, container, oldDom) {
mountElement(virtualDOM, container)
}
//TinyReact/mountElement
import {isFunction} from "./utils";
import mountComponent from "./mountComponent";
import mountNativeElement from "./mountNativeElement";
export default function mountElement(virtualDOM, container) {
if (isFunction(virtualDOM)) {
// 判断是否为组件,组件的逻辑稍后实现
// mountComponent(virtualDOM, container, oldDOM)
} else {
mountNativeElement(virtualDOM, container, oldDOM)
}
}
//TinyReact/mountNativeElement
import createDOMElement from "./createDOMElement"
export default function mountNativeElement(virtualDOM, container) {
const newElement = createDOMElement(virtualDOM)
container.appendChild(newElement)
}
//TinyReact/createDOMElement
export default function updateElementNode(element, virtualDOM) {
const newProps = virtualDOM.props
Object.keys(newProps).forEach(propName => {
const newPropsValue = newProps[propName]
if (propName.slice(0, 2) === "on") {
const eventName = propName.toLowerCase().slice(2)
element.addEventListener(eventName, newPropsValue)
} else if (propName === "value" || propName === "checked") {
element[propName] = newPropsValue
} else if (propName !== "children") {
if (propName === "className") {
element.setAttribute("class", newPropsValue)
} else {
element.setAttribute(propName, newPropsValue)
}
}
})
}