閱讀原始碼一個痛處是會陷進理不順主幹的困局中,本系列文章在實現一個(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 } }
至此,我們完成了元件的處理邏輯。
在上個小節元件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') )
效果圖如下:
至此,我們實作了 props 和 state 部分的邏輯。
元件即函數;當JSX 中是自訂元件時,經過babel 轉換後的React.createElement(fn, ..) 後中的第一個參數變為了函數,除此之外其它邏輯與JSX 中為html 元素的時候相同;
此外我們將state/props/setState 等api 封裝進了父類React.Component 中,從而在子類中能調用這些屬性和方法。
以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP中文網!
相關推薦:
以上是對於 React 元件和state|props的解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!