mini-react 實現原理講解 第一講

文章首發於個人博客 https://github.com/mcuking/bl...

相關代碼請查閱 https://github.com/mcuking/bl...css

支持 JSX

能夠經過 babel 實現將 JSX 轉化爲原生 js,例如:html

const hw = <div>Hello World</div>

const hw = React.createElement('div', null, "Hello World")

以上兩行是等效的,因此本項目無需關心 JSX 語法node

virtual-dom

react 中 virtual-dom 的概念,即便用一個 js 對象——vnode 來描述 DOM 節點,而後根據 vnode 進行實際操做 DOM 節點,從而渲染出 DOM 樹。
其中,vnode 對象有 3 個屬性:react

  • nodeName: 多是某個字符串,或 html 標籤,抑或是某個函數
  • props
  • children

如下就是如何經過 createElement 函數,從 JSX 轉化後的代碼中生成咱們所要的 vnode:git

// 負責生成 vnode
export default function createElement(comp, props, ...args) {
    let children = []
    for (let i = 0; i < args.length; i++) {
        if (args[i] instanceof Array) {
            children = children.concat(args[i])
        } else {
            children.push(args[i])
        }
    }
    return {
        nodeName: comp,
        props: props || {},
        children
    }
}

從 virtual-dom 到實際渲染

如下是咱們使用 react 寫的一個組件github

class Animal extends Component {
    render() {
        return (
            <Pet/>
        )
    }
}

class Pet extends Component {
    render() {
        return (
            <Cat/>
        )
    }
}

class Cat extends Component {
    render() {
        return (
            <div>i am a cat</div>
        )
    }
}
render(<Animal/>, document.getElementById('container'))

最終會渲染爲 i am a cat
渲染過程是:渲染 Animal 的 Vnode -> 渲染 Pet 的 Vnode -> 渲染 Cat 的 Vnode
這是一個遞歸的過程:遞歸的終止條件是——渲染 html 標籤:babel

  • 當 nodeName 爲 html 標籤時,直接操做 dom
  • 當 nodeName 爲組件時,經過 遞歸 操做組件執行 render 方法返回的 vnode

代碼以下:app

function render(vnode, parent) {
    let dom
    if(typeof vnode == "string") {
        dom = document.createTextNode(vnode)
        parent.appendChild(dom)
    } else if(typeof vnode.nodeName == "string") {
        dom = document.createElement(vnode.nodeName)
        setAttrs(dom, vnode.props)
        parent.appendChild(dom)

        for(let i = 0; i < vnode.children.length; i++) {
            render(vnode.children[i], dom)
        }
    } else if (typeof vnode.nodeName == "function") {
        let func = vnode.nodeName

        let inst = new func(vnode.props)
        let innerVnode = inst.render()
        render(innerVnode, parent)
    }
}

// 設置 DOM 節點屬性
function setAttrs(dom, props) {
    for (let k in props) {
        // 屬性爲 className 時,改成 class
        if (k === 'className') {
            dom.setAttribute('class', props[k])
            continue
        }

        // 屬性爲 style 時
        if (k === 'style') {
            if (typeof props[k] === 'string') {
                dom.style.cssText = props[k]
            }

            if (typeof props[k] === 'object') {
                for (let v in props[k]) {
                    dom.style[v] = props[k][v]
                }
            }
            continue
        }

        // 屬性爲 on 開頭的綁定的事件
        if (k[0] === 'o' && k[1] === 'n') {
            dom.addEventListener(k.substring(2).toLowerCase(), props[k], false)
            continue
        }


        // 其他屬性直接賦值
        dom.setAttribute(k, props[k])
    }
}

總結一下:dom

  1. createElement —— 負責建立 vnode
  2. render —— 是根據生成的 vnode, 渲染到實際的 dom 的一個遞歸方法函數

    • 當 vnode 是字符串時, 建立 textNode 節點
    • 當 vnode.nodeName 是字符串的時, 建立 dom 節點, 根據 props 設置節點屬性, 遍歷 render children
    • 當 vnode.nodeName 是函數的時, 獲取 render 方法的返回值 vnode, 執行 render(vnode)

相關文章

相關文章
相關標籤/搜索