首頁  >  文章  >  web前端  >  對於 React 元件和state|props的解析

對於 React 元件和state|props的解析

不言
不言原創
2018-07-13 15:10:241454瀏覽

閱讀原始碼一個痛處是會陷進理不順主幹的困局中,本系列文章在實現一個(x)react 的同時理順React 框架的主幹內容(JSX/虛擬DOM/元件/... )

元件即函數

在上一篇JSX 和Virtual DOM 中,解釋了JSX 渲染到介面的過程並實作了對應程式碼,程式碼呼叫如下所示:

import React from 'react'
import ReactDOM from 'react-dom'

const element = (
  <p>
    hello<span>world!</span>
  </p>
)

ReactDOM.render(
  element,
  document.getElementById('root')
)

本小節,我們接著探究元件渲染到介面的過程。在此我們引入元件的概念,元件本質上就是一個函數,如下就是一段標準元件程式碼:

import React from 'react'

// 写法 1:
class A {
  render() {
    return <p>I'm componentA</p>
  }
}

// 写法 2:无状态组件
const A = () => <p>I'm componentA</p>

ReactDOM.render(<a></a>, document.body)

<a name="componentA"></a> 是JSX 的寫法,和上一篇同理,babel 將其轉化為React.createElement() 的形式,轉化結果如下所示:

React.createElement(A, null)

可以看到當JSX 中是自訂元件的時候,createElement 後接的第一個參數變為了函數,在repl 打印<a name="componentA"></a>,結果如下:

{
  attributes: undefined,
  children: [],
  key: undefined,
  nodeName: ƒ A()
}

注意這時返回的Virtual DOM 中的nodeName 也變成函數。根據這些線索,我們將先前的 render 函數進行改造。

function render(vdom, container) {
  if (_.isFunction(vdom.nodeName)) { // 如果 JSX 中是自定义组件
    let component, returnVdom
    if (vdom.nodeName.prototype.render) {
      component = new vdom.nodeName()
      returnVdom = component.render()
    } else {
      returnVdom = vdom.nodeName() // 针对无状态组件:const A = () => <p>I'm componentsA</p>
    }
    render(returnVdom, container)
    return
  }
}

至此,我們完成了元件的處理邏輯。

props 和state 的實作

在上個小節元件A 中,是沒有引入任何屬性和狀態的,我們希望元件間能進行屬性的傳遞(props)以及元件內能進行狀態的記錄(state)。

import React, { Component } from 'react'

class A extends Component {
  render() {
    return <p>I'm {this.props.name}</p>
  }
}

ReactDOM.render(<a></a>, document.body)

在上面這段程式碼中,看到 A 函數繼承自 Component。我們來建構這個父類別 Component,並在其上加入 state、props、setState 等屬性方法,從而讓子類別繼承到它們。

function Component(props) {
  this.props = props
  this.state = this.state || {}
}

首先,我們將元件外的props 傳進元件內,修改render 函數中以下程式碼:

function render(vdom, container) {
  if (_.isFunction(vdom.nodeName)) {
    let component, returnVdom
    if (vdom.nodeName.prototype.render) {
      component = new vdom.nodeName(vdom.attributes) // 将组件外的 props 传进组件内
      returnVdom = component.render()
    } else {
      returnVdom = vdom.nodeName(vdom.attributes)     // 处理无状态组件:const A = (props) => <p>I'm {props.name}</p>
    }
    ...
  }
  ...
}

實現完元件間props 的傳遞後,再來聊聊state,在react中是透過setState 來完成元件狀態的改變的,後續章節會對這個api(非同步)深入探究,這裡簡單實作如下:

function Component(props) {
  this.props = props
  this.state = this.state || {}
}

Component.prototype.setState = function() {
  this.state = Object.assign({}, this.state, updateObj) // 这里简单实现,后续篇章会深入探究
  const returnVdom = this.render() // 重新渲染
  document.getElementById('root').innerHTML = null
  render(returnVdom, document.getElementById('root'))
}

此時雖然已經實作了setState 的功能,但是 document.getElementById('root') 節點寫死在setState 中顯然不是我們希望的,我們將dom 節點相關轉移到_render 函數中:

Component.prototype.setState = function(updateObj) {
  this.state = Object.assign({}, this.state, updateObj)
  _render(this) // 重新渲染
}

自然地,重構與之相關的render 函數:

function render(vdom, container) {
  let component
  if (_.isFunction(vdom.nodeName)) {
    if (vdom.nodeName.prototype.render) {
      component = new vdom.nodeName(vdom.attributes)
    } else {
      component = vdom.nodeName(vdom.attributes) // 处理无状态组件:const A = (props) => <p>I'm {props.name}</p>
    }
  }
  component ? _render(component, container) : _render(vdom, container)
}

在render 函數中分離出_render 函數的目的是為了讓setState 函數中也能呼叫_render 邏輯。完整 _render 函數如下:

function _render(component, container) {
  const vdom = component.render ? component.render() : component
  if (_.isString(vdom) || _.isNumber(vdom)) {
    container.innerText = container.innerText + vdom
    return
  }
  const dom = document.createElement(vdom.nodeName)
  for (let attr in vdom.attributes) {
    setAttribute(dom, attr, vdom.attributes[attr])
  }
  vdom.children.forEach(vdomChild => render(vdomChild, dom))
  if (component.container) {  // 注意:调用 setState 方法时是进入这段逻辑,从而实现我们将 dom 的逻辑与 setState 函数分离的目标;知识点: new 出来的同一个实例
    component.container.innerHTML = null
    component.container.appendChild(dom)
    return
  }
  component.container = container
  container.appendChild(dom)
}

讓我們用下面這個用例跑下寫好的 react 吧!

class A extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }

  click() {
    this.setState({
      count: ++this.state.count
    })
  }

  render() {
    return (
      <p>
        <button>Click Me!</button>
        </p><p>{this.props.name}:{this.state.count}</p>
      
    )
  }
}

ReactDOM.render(
  <a></a>,
  document.getElementById('root')
)

效果圖如下:

對於 React 元件和state|props的解析

至此,我們實作了 props 和 state 部分的邏輯。

小結

元件即函數;當JSX 中是自訂元件時,經過babel 轉換後的React.createElement(fn, ..) 後中的第一個參數變為了函數,除此之外其它邏輯與JSX 中為html 元素的時候相同;

此外我們將state/props/setState 等api 封裝進了父類React.Component 中,從而在子類中能調用這些屬性和方法。

以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP中文網!

相關推薦:

vue中config/index.js:配置的詳解

以上是對於 React 元件和state|props的解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn