实现一个简单的不能再简单的React vdom

1,323 阅读3分钟

前一段时间一直在学习React相关的东西,然后找了一个很火,但又不是很难的切入点。那就是vdom。

首先介绍一下什么是vdom

自己的理解

我觉得vdom就是一个普通的js对象,只不过它是通过一定的结构来约束。就是把一个真实的dom给抽象化。 我觉得它给我们带来的好处是:

  1. 我们不再需要操作dom而是直接修改这个js对象,就可以映射到真实的dom上面。
  2. 它是与环境无关的,因为它只是一个对象或者说一个数据表示,所以在其他的地方也是可以复用的,而不像dom这样只能在浏览器上面,一个跨平台的能力。
  3. 还有一个是为了diff,我们只需要diff这个对象,然后把diff的结果patch到真实的dom上面。

下面我们一步一步的来实现

  1. 为了我们后续的使用,我们先自己模拟一个React
// 我们一般开发类组件都是继承自这个类(class components)
class Component {
    render() {}
}
window.React = {
    createElement: createElement,
    Component: Component,
}
window.ReactDOM = {
    render: render
}
  1. 下面实现createElement函数
function createElement(type, props, children){
    return new Element(type, props, children)
}
  1. 实现Element类,就是一个组件或者元素
class Element {
    constructor(type, props, children){
        this.type = type; // 是什么元素,比如:div、<Component>
        this.props = props; // 里面的属性,比如:className、id、一些事件等
        this.children = children // 子元素
    }
}
  1. 上面创建的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
}
  1. 下面就到了设置属性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 = ''
        }
     }
 }
}
  1. 为了让我们的dom能够挂载到真实的页面上面
function render(dom, ele){
    if(typeof ele === 'string'){
        ele = document.querySelector(ele)
    }
    ele && ele.appendChild(dom)
}
  1. render
index.html页面
    <div id="root"></div>
index.js页面
    ReactDOM.render(createDom(vdom), document.getElementById('root'))
  1. 下面我们准备一些假的数据
// 模拟一个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)
}
  1. 下面我们来看一下效果

页面显示效果

页面显示效果
事件效果
事件效果

总结

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