MiniReact系列之实现一个简易createElement(一)
昨天实现了一个简易版本的createElement,只是虚拟DOM貌似没啥用,今天尝试着将他转化成真实DOM,咋搞呢(⊙_⊙)??????走一步看一步吧。。。毕竟我是个菜逼(菜逼要有菜逼的样子( ̄. ̄))
完整代码git地址:github.com/nocodig/min…
一、开整
文档上 说在提供的container中渲染一个React元素,返回对该组件的一个引用,如果之前已经渲染过React元素,那么会对其进行更新操作。
呃。。好像有点难。。。。和昨天一样我先搞一个乞丐版的,先只考虑渲染新节点的情况吧,并且不考虑元素节点是React组件的情况。
二、乞丐版
render方法
render方法我们传入三个参数
virtualDOM:虚拟DOM节点container:挂载的节点容器oldDOM: 上一次渲染的节点数据
oldDOM参数什么时候用,这篇文章中我们先不考虑,先假设不存在。并不会影响理解render方法
render方法。根据官方文档渲染虚拟节点到指定容器上,而且里面会进行虚拟DOM比对
// render.js
import diff from "./diff";
export default function render(virtualDOM, container, oldDOM) {
// 暴露出外部调用方法,需要考虑两种情况
// 1. 没有旧的节点,直接调用,挂载新的节点
// 2. 原来已经挂载节点,需要对新就节点进行比对,然后进行DOM更新
diff(virtualDOM, container, oldDOM);
}
接下来写下diff方法
diff方法
与render方法一样,接收三个参数
virtualDOM:虚拟DOM节点container:挂载的节点容器oldDOM: 上一次渲染的节点数据
由于我是个菜逼,咱们先不考虑存在旧节点的情况,只考虑新节点挂载,所以此时需要在函数判断一下,没有旧节点时,直接调用的mountElement用来挂载新的元素节点
// diff.js
import mountElement from "./mountElement";
export default function diff(virtualDOM, container, oldDOM) {
if (!oldDOM) {
// 原来没有旧节点,直接挂载新的节点即可
mountElement(virtualDOM, container)
}
// 存在旧节点,需要对新旧节点进行比对更新,新空着
}
mountElement方法
这个方法主要作用就是解析virtualDOM,但是解析时节点可能是React元素或者是浏览器普通元素,需要做些特殊处理,用来区分是React组件还是普通元素。
mountElement方法接收两个参数
virtualDOM:虚拟DOM节点container:挂载的节点容器
React组件我们下次在进行处理,这次先把普通元素解析解决
// mountElement.js
import mountNativeElement from "./mountNativeElement";
export default function mountElement(virtualDOM, container) {
// 存在两种情况,考虑是nativeDOM,还是ComponentDOM,
mountNativeElement(virtualDOM, container);
}
mountNativeElement方法
这个方法用来将浏览器普通元素进行解析。该方法接收两个参数:
virtualDOM:虚拟DOM节点container:挂载的节点容器
virtualDOM节点类型分为:
- 文本节点
- 元素节点
如果是文本节点时,我们要使用createTextNode创建一个文节点,如果是元素节点,使用createElement创建一个元素节点。
virtualDOM上存储了节点所有信息。比如type,props,children使用virtualDOM.type === 'text'即可判断是否是文本节点,值存储在virtualDOM.props.textContent,元素节点类似,但是元素节点上可能有子节点,需要对virtualDOM.children进行遍历,需要递归调用mountElement,将子节点也生成DOM节点。
为什么需要调用mountElement而不是mountNativeElement,是因为子节点下面可能存在普通的元素节点,也有可能存在React元素
// mountNativeElement.js
import mountElement from './mountElement.js
export default function mountNativeElement(virtualDOM, container) {
// 考虑此时节点存在文本节点和普通节点
let newElement = null
if (virtualDOM.type === "text") {
newElement = document.createTextNode(virtualDOM.props.textContent);
} else {
newElement = document.createElement(virtualDOM.type);
}
virtualDOM.children.forEach((child) => {
// 此时调用mountElement由于不知子几点是否存在reactDOM
mountElement(child, newElement);
});
container.appendChild(newElement);
}
基本解析需要的方法都写完了,看下页面效果
看起来都解析出来了,但是此时点击按钮并不会有任何反应,那是因为我们只是创建了元素,但是没有将事件绑定上去,属性也没有绑定上去,我们还需要给元素节点上把属性加上去
三、乞丐升级版
updateNodeElement方法
这个方法主要是将想DOM节点上增加属性,绑定事件,接收两个参数:
newElement:属性、事件绑定的节点virtualDOM:虚拟DOM节点,我们需要的属性都是props属性上
我们这个方法在哪里调用呢??????在mountNativeElement方法中调用就行,我对他改造下:
import mountElement from './mountElement.js
export default function mountNativeElement(virtualDOM, container) {
// 考虑此时节点存在文本节点和普通节点
let newElement = null
if (virtualDOM.type === "text") {
newElement = document.createTextNode(virtualDOM.props.textContent);
} else {
newElement = document.createElement(virtualDOM.type);
// 为元素设置属性🔽就是这里
updateNodeElement(newElement, virtualDOM)
}
....
}
由于virtualDOM.props属性是对象,我们需要用Object.keys(props)取出属性,并且遍历,遍历要做下面事情:
- 判断是否是事件,需要使用
newElement.addEvenLister进行事件绑定- 如何判断是事件,截取前两个字符是否是
on
- 如何判断是事件,截取前两个字符是否是
- 判断属性是否
value或者checked,此时不能用setAttribute设置属性,需要用newElement.value = xxx方式赋值 children属性不需要设置成属性className需要转化成class
updateNodeElement代码如下:
export default function updateNodeElement(newElement, virtualDOM) {
const newProps = virtualDOM.props;
Object.keys(newProps).forEach(propName => {
const propValue = newProps[propName]
if (propName.slice(0, 2) === 'on') {
// 为元素添加事件,事件函数前面都是通on开头
const eventName = propName.toLowerCase().slice(2)
newElement.addEventListener(eventName, propValue)
} else if (propName === 'value' || propValue === 'checked') {
// 为元素添加value、checked属性,这两个属性不能setAttribute设置
newElement[propName] = propValue
} else if (propName !== 'children') {
// children 不作为属性存在要进行过滤
if (propName === 'className') {
// className需要转换成class
newElement.setAttribute('class', propValue)
} else {
newElement.setAttribute(propName, propValue)
}
}
})
}
让我们看下效果,发现样式变了,输入框中也有值了。
点击下按钮,出现了弹窗:
说明乞丐升级版是OK的
结束
简单的实现了一下最简单的render方法,缺失的功能后面博客中还需继续更新这部分的实现,今天实在太困了,写不动了,,,,,身体重要,拒绝内卷。。