前一段时间一直在学习React相关的东西,然后找了一个很火,但又不是很难的切入点。那就是vdom。
首先介绍一下什么是vdom
自己的理解
我觉得vdom就是一个普通的js对象,只不过它是通过一定的结构来约束。就是把一个真实的dom给抽象化。 我觉得它给我们带来的好处是:
- 我们不再需要操作dom而是直接修改这个js对象,就可以映射到真实的dom上面。
- 它是与环境无关的,因为它只是一个对象或者说一个数据表示,所以在其他的地方也是可以复用的,而不像dom这样只能在浏览器上面,一个跨平台的能力。
- 还有一个是为了diff,我们只需要diff这个对象,然后把diff的结果patch到真实的dom上面。
下面我们一步一步的来实现
- 为了我们后续的使用,我们先自己模拟一个React
// 我们一般开发类组件都是继承自这个类(class components)
class Component {
render() {}
}
window.React = {
createElement: createElement,
Component: Component,
}
window.ReactDOM = {
render: render
}
- 下面实现createElement函数
function createElement(type, props, children){
return new Element(type, props, children)
}
- 实现Element类,就是一个组件或者元素
class Element {
constructor(type, props, children){
this.type = type; // 是什么元素,比如:div、<Component>
this.props = props; // 里面的属性,比如:className、id、一些事件等
this.children = children // 子元素
}
}
- 上面创建的Element有了,下面我们就可以创建整个dom了
function createDom(dom){
let pNode = null
if(typeof dom === 'string' || typeof dom === 'number' ){
return document.createTextNode(dom)
}
if(typeof dom.type === 'string'){
// 创建对应的element
const ele = createElement(dom.type, dom.props, dom.children)
// 创建真实的dom元素
pNode = document.createElement(ele.type)
// 设置元素的属性
setProps(pNode, ele.props)
// 遍历子元素
if(ele.children instanceof Array){
ele.children.map(createDom).forEach((node)=>{
node && pNode.appendChild(node)
})
}
} else if(dom.type.prototype.__proto__ === React.Component.prototype){
// 如果是React常见的组件
const Element = new dom.type()
pNode = createDom(Element.render())
}
return pNode
}
- 下面就到了设置属性props的时候了
function setProps(dom, props){
let replProp = ''
// 这里用于匹配React的事件,因为React的事件都是onClick、onInput这样的
const eventReg = /on[A-Z]/
for (let prop in props) {
// 遍历每一个props
if (props.hasOwnProperty(prop)) {
if(prop === 'className'){
// 将className转换为class
replProp = 'class'
}
if(eventReg.test(prop)){
// onClick => Click => click
// 这里跟React的事件机制不一样,直接绑在了这个元素上面,可以了解一下React的合成事件机制
dom.addEventListener(prop.slice(2).toLocaleLowerCase(), props[prop], false)
}else {
dom.setAttribute(replProp || prop, props[prop])
replProp = ''
}
}
}
}
- 为了让我们的dom能够挂载到真实的页面上面
function render(dom, ele){
if(typeof ele === 'string'){
ele = document.querySelector(ele)
}
ele && ele.appendChild(dom)
}
- render
index.html页面
<div id="root"></div>
index.js页面
ReactDOM.render(createDom(vdom), document.getElementById('root'))
- 下面我们准备一些假的数据
// 模拟一个React的组件
class List extends React.Component {
render() {
return React.createElement(
"ul",
null,
[
React.createElement("ul", { 'className': 'aew'}, [
"1",
React.createElement("li", null, ["12"]),
React.createElement("li", null, ["13"]),
React.createElement("li", null, ["14"])
]),
React.createElement("li", null, ["2"]),
React.createElement("li", null, ["3"]),
React.createElement("li", null, ["4"]),
]
);
}
}
// 要渲染的dom
const vdom = {
type: 'div',
props: {
'className': 'aedaw',
'id': 'gtihh',
'onClick': fun,
},
children: [
'hello, world',
{
type: 'div',
props: {
'className': 'aew',
'id': 'gtihh',
},
children:[
'1',
]
},
{
type: 'input',
props: {
'onFocus': fun,
'onBlur': fun,
'placeholder': 'djkrngrdgrd'
},
children: [
'2',
]
},
{
type: List,
props: {
},
children:[
]
}
]
}
// 事件处理函数
function def(e){
console.log(e.target, e.type)
}
- 下面我们来看一下效果
页面显示效果


总结
这只是我自己的一个理解的实现,遗憾的是没有查看过react是怎么实现的,其实只是实现了一个大概,很多细节都没有补充呢。比如:ref、key等等。