實現一個react系列二:渲染組件


前言

本文主要參考了從零開始實現一個React從 0 到 1 實現React
在上一節JSX和虛擬DOM中,我們了解了react中的JSX到虛擬dom,以及如何將虛擬dom渲染成真實的dom。在這一節中,我們將會了解react中組件是如何渲染的。

組件

react中,組件有兩種使用方法:

import React from 'react'
// 類定義的組件
class Hello extends React.Component {
  render() {
    return <div>hello</div>
  }
}
// 無狀態組件,通過函數來定義
const World = () => {
  return <h1>world!</h1>
}
ReactDom.render(<Hello />, document.getElementById('#root'))

通過類定義組件時,是需要繼承React.component的,我們第一步就從React.Component的實現開始。
類實現的組件有自己私有的state,同時可以通過this.props來獲取傳進來的參數。

function Component(props) {
  this.props = props
  this.state = {}
}
  • setState

我們知道在react中,我們可以通過setState來改變組件state的值,而且當state改變后,組件對應的也會重新渲染。

  • 改變state的值:我們可以使用Object.assign來實現。
  • 重新渲染組件:我們可以在改變state值后,調用render函數,重新渲染。異步的setState在后面的章節會實現。
Component.prototype.setState = function (updateState) {
  // 更新 state
  this.state = Object.assign({}, this.state, updateState)
  // 重新渲染組件
  render(this)
}

渲染

在上一節中,我們知道ReactDom.render(),會將其第一個參數轉成React.createElement()形式,而組件也會被轉為React.createElement(Hello, null)這種形式。


所以react中組件在渲染時會被當成函數渲染的。所以我們在render函數中需要判斷虛擬dom的標簽屬性(此處用tag表示的)是函數還是原生dom。如果是函數的話,我們只需要拿到組件的jsx轉換后對應的虛擬dom, 然后在進行渲染。

const render = (vdom, root) => {
  if (typeof vdom.tag === 'function') {
    let component
    if (vdom.tag.prototype.render) {
      // 類定義的組件, vdom.attrs 是傳入的 props
      component = new vdom.tag(vdom.attrs)
    } else {
      // 函數定義組件
      component = vdom.tag(vdom.attrs)
    }
    return _render(component, root)
  }
  _render(vdom, root)
}

對應的_render():

const _render = (vdom, root) => {
  // 類組件的話,需要從 render 函數中拿到 jsx 轉換后的虛擬 dom
  const vdomNode = vdom.render ? vdom.render() : vdom
  if (typeof vdomNode === "string" || typeof vdomNode === "number") {
    root.innerText += vdomNode
    return
  }
  const dom = document.createElement(vdomNode.tag)
  if (vdomNode.attrs) {
    for (let attr in vdomNode.attrs) {
      const value = vdomNode.attrs[attr]
      setAttribute(dom, attr, value)
    }
  }
  // 遍歷子節點, 渲染子節點
  vdomNode.childs && vdomNode.childs.forEach(child => render(child, dom))
  // 將父節點 root 掛到 vdom 上,當再次渲染組件時,跟據 vdom.root 直接渲染 dom
  if (vdom.root) {
    vdom.root.innerText = ''
    vdom.root.appendChild(dom)
    return
  }
  vdom.root = root
  // 將子元素掛載到其真實 DOM 的父元素上
  root.appendChild(dom)
}

試一試,剛出鍋的代碼效果如何。

import React from "./react"
import ReactDom from "./reactDom"

const World = props => {
  return (
    <h1>
      world!<p>{props.world}</p>
    </h1>
  )
}

class Hello extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
  }
  addCount() {
    const { count } = this.state
    this.setState({
      count: count + 1
    })
  }
  render() {
    return (
      <div ha="lou">
        hello
        <World world="function props" />
        <span>{this.props.initProps}</span>
        <div>{this.state.count}</div>
        <button onClick={this.addCount.bind(this)}> + </button>
      </div>
    )
  }
}

ReactDom.render(
  <Hello initProps="this is props" />,
  document.getElementById("root")
)

小結

react在渲染組件時,組件會被babel轉為React.createElement(fn, null)這種形式,第一參數是函數,所以我們需要從fn中獲取由組件的jsx轉換后的虛擬dom,然后在將虛擬dom渲染成真實dom
setState:在調用setState時,先用Object.assign更新state的值,然后重新渲染組件。
附上本文代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM