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

调用方法:1、类组件中的调用可以利用React.createRef()、ref的函数式声明或props自定义onRef属性来实现;2、函数组件、Hook组件中的调用可以利用useImperativeHandle或forwardRef抛出子组件ref来实现。

怎么调试React源码?下面本篇文章带大家聊聊多种工具下的调试React源码的方法,介绍一下在贡献者、create-react-app、vite项目中如何debugger React的真实源码,希望对大家有所帮助!

React 自定义 Hook 是一种将组件逻辑封装在可重用函数中的方式,它们提供了一种在不编写类的情况下复用状态逻辑的方式。本文将详细介绍如何自定义封装 hook。

React为什么不将Vite作为构建应用的首选?下面本篇文章就来带大家聊聊React不将Vite作为默认推荐的原因,希望对大家有所帮助!

react设置div高度的方法:1、通过css方式实现div高度;2、在state中声明一个对象C,并在该对象中存放更换按钮的样式,然后获取A并重新设置C中的“marginTop”即可。

我时常会听到人们谈起React函数组件,提到函数组件会不可避免的变得体积更大,逻辑更复杂。毕竟,我们把组件写在了“一个函数”里,因此你不得不接受组件会膨胀导致这个函数会不断膨胀。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SublimeText3漢化版
中文版,非常好用

Dreamweaver Mac版
視覺化網頁開發工具

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。