Web前端之路

2020 动手写个 react (2)

2020-05-08  本文已影响0人  zidea
coding

我们写一段 jsx ,有关 jsx 语法这里暂不做过介绍,留下伏笔

const ele = (
    const ele = (
    <div className="box" size="25">
    hello <span>zidea</span>
    </div>
)
)

然后将上面 jsx 通过 babel 转换为 js 如下

"use strict";

const ele = /*#__PURE__*/React.createElement("div", {
  className: "box",
  size: "25"
}, "hello ", /*#__PURE__*/React.createElement("span", null, "zidea"));

有了上面生成代码,我们简单地分析一下

React.createElement(tag,attrs,children)

实现 createElement 方法

const React = {
    createElement
}


// createElement
function createElement(tag,attrs,...children){
    return {
        tag,
        attrs,
        children
    }
}

export default React;

import React from './react'

const ele = (
    <div className='title' title='react source analysis'>
        hello <span>react</span>
    </div>
)

console.log(ele)

实现渲染

我们都知道在 React 中调用 ReactDom 的 render 方法然后传入虚拟节点和要将虚拟节点添加容器 dom 元素就可以实现将虚拟节点渲染到页面。

ReactDOM.render(ele,document.querySelector("#root"))
import React from './react'

const ele = (
    <div className='box' title='hello zidea'>
        hello <span>react</span>
    </div>
)

// console.log(ele)

ReactDOM.render(ele,document.querySelector("#root"))

接下依旧是创建文件夹 react-dom 然后在该文件夹下创建 index.js 文件实现 ReactDOM 的 render 方法

const ReactDOM = {
    render
}

function render(vnode,container){
    //TODO
}

export default ReactDOM;

有关常见一个 js 模块并且将 ReactDOM 对象暴露出来我就不做过多解释了。接下来我们就专注 render 方法的实现。

文本节点实现

function render(vnode,container){
    //TODO
    if(vnode === undefined ) return;
    // vnode is equal string
    if(typeof vnode === 'string'){
        //create textNode
        const textNode = document.createTextNode(vnode)
        return container.appendChild(textNode)
    }
}

看效果

ReactDOM.render('react',document.querySelector("#root"))

虚拟节点

ReactDOM.render(ele,document.querySelector("#root"));
console.log(vnode)
function render(vnode,container){
    ...

    // deconstruct vnode
    const {tag} = vnode;

    //create dom object
    const dom = document.createElement(tag)

    container.appendChild(dom)
}

获取属性

function render(vnode,container){

    ... 
    // deconstruct vnode
    const {tag,attrs} = vnode;

    //create dom object
    const dom = document.createElement(tag)

    if(attrs){
        // property key: className box
        Object.keys(attrs).forEach(key=>{
            const val = attrs[key]
        })
    }
    ...
}

设置属性

function setAttribute(dom,key,value){

}

实现 setAttribute 方法

function setAttribute(dom,key,value){

    // convert className to class
    // 1. event 2.class 3.style etc 

    // class case
    if(key === 'className'){
        key = 'class'
    }

    // event case
    if(/on\w+/.test(key)){
        //to lower case
        key = key.toLowerCase();
        dom[key] = value || ''
    }else if(key === 'style'){
        if(!value || typeof value === 'string'){
            dom.style.cssText = value || '';
        }else if(value && typeof value === 'object'){
            //{width:16}
            for(let k in value){
                if(typeof value[k] === 'number'){
                    dom.style[k] = value[k] + 'px'
                }else{
                    dom.style[k] = value[k]
                }
            }
        }
    }else{
        if(key in dom){
            dom[key] = value || ''
        }
        if(value){
            dom.setAttribute(key,value)
        }else{
            dom.removeAttribute(key)
        }
    }

}

在 dom 上有许多属性,可以通过属性添加样式,添加事件,保存数据等。我们这里通过判断 attrs 中属性键和值类型来一一进行判断

class 属性

如果键为 className 时,我们将键修改 class 虽然添加值

if(key === 'className'){
    key = 'class'
}
事件属性
if(/on\w+/.test(key)){
    //to lower case
    key = key.toLowerCase();
    dom[key] = value || ''
}
样式属性
else if(key === 'style'){
    if(!value || typeof value === 'string'){
        dom.style.cssText = value || '';
    }else if(value && typeof value === 'object'){
        //{width:16}
        for(let k in value){
            if(typeof value[k] === 'number'){
                dom.style[k] = value[k] + 'px'
            }else{
                dom.style[k] = value[k]
            }
        }
    }
}

样式情况相对于其他属性要复杂一些,分两种情况处理可能是字符串或者一个对象

const ele = (
    <div className="box" size="25" style={{width:16}}>
    hello <span>zidea</span>
    </div>
)
...
style: {
    width: 16
  }
...

这里 style 对象中嵌套一个对象,我们通过解析对象来获取每一个样式的名称和样式值。样式值可能是数值也可能是字符串。

for(let k in value){
    if(typeof value[k] === 'number'){
        dom.style[k] = value[k] + 'px'
    }else{
        dom.style[k] = value[k]
    }
}
if(attrs){
    // property key: className box
    Object.keys(attrs).forEach(key=>{
        const val = attrs[key]
        setAttribute(dom,key,val)
    })
}

container.appendChild(dom)

渲染子节点

通过递归形式来实现渲染子节点

function render(vnode,container){
    
    ...

    vnode.children.forEach(child=>render(child,dom))

    container.appendChild(dom)
}

上一篇 下一篇

猜你喜欢

热点阅读