從頭實現一個簡易版React(二)

寫在開頭

從頭實現一個簡易版React(一)地址:https://segmentfault.com/a/11...
上一節,咱們詳細介紹了實現一個簡易React的思路以及總體的結構,可是對於渲染和更新的原理,卻尚未說起,所以,本節咱們將重點放在vDom的渲染上。javascript

進入正題

咱們把React元素分爲text,basic,custom三種,並分別封裝了三種vDom的ReactComponent,用來處理各自的渲染和更新,在這裏,咱們將重心放在各自ReactComponet的mount方法上。html

ReactTextComponent

ReactTextComponent用來處理文本節點,爲了標識方便,在返回的內容上加了span標籤。java

// 用來表示文本節點在渲染,更新,刪除時應該作的事情
class ReactTextComponent extends ReactComponent {
  // 渲染
  mountComponent(rootId) {
    this._rootNodeId = rootId
    return `<span data-reactid="${rootId}">${this._vDom}</span>`
  }
}
//代碼地址:src/react/component/ReactTextComponent.js

ReactTextComponent的mount方法很是簡單,打上標識符,將內容插入標籤內,並把標籤內容返回就能夠了。react

ReactDomComponent

這個類用來處理原生節點的vDom,在將vDom渲染爲原生DOM時,要考慮3點:git

  1. 元素類型
  2. 拼湊屬性,包含普通屬性及事件的處理
  3. 子節點的遞歸渲染

代碼以下:github

// 用來表示原生節點在渲染,更新,刪除時應該作的事情
class ReactDomComponent extends ReactComponent {
  constructor(vDom) {
    super(vDom)
    this._renderedChildComponents = null
  }

  // 渲染
  mountComponent(rootId) {
    this._rootNodeId = rootId

    const { props, type, props: { children = [] } } = this._vDom,
      childComponents = []

    // 設置tag,加上標識
    let tagOpen = `${type} data-reactid=${this._rootNodeId}`,
      tagClose = `/${type}`,
      content = ''

    // 拼湊屬性
    for (let propKey in props) {
      // 事件
      if (/^on[A-Za-z]/.test(propKey)) {
        const eventType = propKey.replace('on', '')
        $(document).delegate(`[data-reactid="${this._rootNodeId}"]`, `${eventType}.${this._rootNodeId}`, props[propKey])
      }

      // 普通屬性,排除children與事件
      if (props[propKey] && propKey !== 'children' && !/^on[A-Za-z]/.test(propKey)) {
        tagOpen += ` ${propKey}=${props[propKey]}`
      }
    }

    // 獲取子節點渲染出的內容
    children.forEach((item, index) => {
      // 再次使用工廠方法實例化子節點的component,拼接好返回
      const childComponent = instantiateReactComponent(item)
      childComponent._mountIndex = index

      childComponents.push(childComponent)

      // 子節點的rootId是父節點的rootId加上索引拼接的值
      const curRootId = `${this._rootNodeId}.${index}`
      // 獲得子節點的渲染內容
      const childMarkup = childComponent.mountComponent(curRootId)
      // 拼接
      content += childMarkup

      // 保存全部子節點的component
      this._renderedChildComponents = childComponents
    })

    return `<${tagOpen}>${content}<${tagClose}>`
  }
}
//代碼地址:src/react/component/ReactDomComponent.js

在React的官方實現中,本身實現了一套事件系統,這裏用了jQuery的事件代替。
在樣式上,須要基於傳入的style對象建立樣式,這裏也暫時忽略了。算法

ReactCompositComponent

在建立自定義組件時,一般會這樣建立segmentfault

import React from 'react'

class App extends React.Component {
  render() {
    return (
       
    )
  }
}

因此,第一步,咱們先實現Component這個父類dom

// 全部自定義組件的父類
class Component {
  constructor(props) {
    this.props = props
  }

  setState(newState) {
    this._reactInternalInstance.updateComponent(null, newState)
  }
}
//代碼地址:src/react/Component.js

Component類上咱們主要實現了setState方法,至於有什麼用,咱們放在更新裏說。
在自定義組件的vDom中,type保存的是咱們建立的Component的引用,因此在ReactCompositeComponent的mount方法中。咱們首先根據vDom的type建立組件的實例,在以此調用它初始渲染的生命週期方法,render方法。
在render方法中,返回了組件渲染內容的vDom,咱們根據這個vDom建立它的ReactComponent並調用mount(),就獲得了真實的渲染內容。
貼代碼:this

export default class extends ReactComponent {
  constructor(element) {
    super(element)
    // 存放對應的組件實例
    this._instance = null
    this._renderedComponent = null
  }

  // 渲染
  mountComponent(rootId) {
    this._rootNodeId = rootId
    const { type: Component, props } = this._vDom

    // 獲取自定義組件的實例
    const inst = new Component(props)
    this._instance = inst

    // 保留對當前component的引用,下面更新時會用到
    inst._reactInternalInstance = this

    inst.componentWillMount && inst.componentWillMount()

    // 調用自定義組件的render方法,返回一個Vdom
    const renderedVdom = inst.render()

    // 獲取renderedComponent的component
    const renderedComponent = instantiateReactComponent(renderedVdom)
    this._renderedComponent = renderedComponent

    // 獲得渲染以後的內容
    const renderMarkup = renderedComponent.mountComponent(this._rootNodeId)

    // 在React.render方法最後觸發了mountReady事件,所在在這裏監聽,在渲染完成後觸發
    $(document).on('mountReady', () => {
      inst.componentDidMount && inst.componentDidMount()
    })

    return renderMarkup
  }
}
// 代碼地址:src/react/component/ReactCompositeComponent.js

從這裏能夠看出,自定義組件的mount方法並不負責具體的渲染,這些都交給了它的render,它把重心放在了建立對象和調用生命週期上。

總結

文章到這,咱們的簡易版react已經初步實現了虛擬DOM的建立,生命週期的調用,虛擬DOM的遞歸渲染和事件處理。
總結一下,每個vDom都有ReactComponent相對應,遞歸渲染的本質無非就是獲取每一個vDom的ReactComponent,並調用它的mount方法。
以上就是整個渲染的思路,下節咱們將實現它的diff算法以及更新。
下一節地址:https://segmentfault.com/a/11...

參考資料,感謝幾位前輩的分享:
https://www.cnblogs.com/sven3...
https://github.com/purplebamb...陳屹 《深刻React技術棧》

相關文章
相關標籤/搜索