从 0 到 1 实现 React 系列 —— 2.组件和 state|props
看源碼一個痛處是會陷進(jìn)理不順主干的困局中,本系列文章在實現(xiàn)一個 (x)react 的同時理順 React 框架的主干內(nèi)容(JSX/虛擬DOM/組件/生命周期/diff算法/setState/ref/...)
- 從 0 到 1 實現(xiàn) React 系列 —— JSX 和 Virtual DOM
- 從 0 到 1 實現(xiàn) React 系列 —— 組件和 state|props
- 從 0 到 1 實現(xiàn) React 系列 —— 生命周期和 diff 算法
- 從 0 到 1 實現(xiàn) React 系列 —— 優(yōu)化 setState 和 ref 的實現(xiàn)
項目地址
組件即函數(shù)
在上一篇 JSX 和 Virtual DOM 中,解釋了 JSX 渲染到界面的過程并實現(xiàn)了相應(yīng)代碼,代碼調(diào)用如下所示:
import React from 'react' import ReactDOM from 'react-dom'const element = (<div className="title">hello<span className="content">world!</span></div> )ReactDOM.render(element,document.getElementById('root') )本小節(jié),我們接著探究組件渲染到界面的過程。在此我們引入組件的概念,組件本質(zhì)上就是一個函數(shù),如下就是一段標(biāo)準(zhǔn)組件代碼:
import React from 'react'// 寫法 1: class A {render() {return <div>I'm componentA</div>} }// 寫法 2:無狀態(tài)組件 const A = () => <div>I'm componentA</div>ReactDOM.render(<A />, document.body)<A name="componentA" /> 是 JSX 的寫法,和上一篇同理,babel 將其轉(zhuǎn)化為 React.createElement() 的形式,轉(zhuǎn)化結(jié)果如下所示:
React.createElement(A, null)可以看到當(dāng) JSX 中是自定義組件的時候,createElement 后接的第一個參數(shù)變?yōu)榱撕瘮?shù),在 repl 打印 <A name="componentA" />,結(jié)果如下:
{attributes: undefined,children: [],key: undefined,nodeName: ? A() }注意這時返回的 Virtual DOM 中的 nodeName 也變?yōu)榱撕瘮?shù)。根據(jù)這些線索,我們對之前的 render 函數(shù)進(jìn)行改造。
function render(vdom, container) {if (_.isFunction(vdom.nodeName)) { // 如果 JSX 中是自定義組件let component, returnVdomif (vdom.nodeName.prototype.render) {component = new vdom.nodeName()returnVdom = component.render()} else {returnVdom = vdom.nodeName() // 針對無狀態(tài)組件:const A = () => <div>I'm componentsA</div>}render(returnVdom, container)return} }至此,我們完成了對組件的處理邏輯。
props 和 state 的實現(xiàn)
在上個小節(jié)組件 A 中,是沒有引入任何屬性和狀態(tài)的,我們希望組件間能進(jìn)行屬性的傳遞(props)以及組件內(nèi)能進(jìn)行狀態(tài)的記錄(state)。
import React, { Component } from 'react'class A extends Component {render() {return <div>I'm {this.props.name}</div>} }ReactDOM.render(<A name="componentA" />, document.body)在上面這段代碼中,看到 A 函數(shù)繼承自 Component。我們來構(gòu)造這個父類 Component,并在其添加 state、props、setState 等屬性方法,從而讓子類繼承到它們。
function Component(props) {this.props = propsthis.state = this.state || {} }首先,我們將組件外的 props 傳進(jìn)組件內(nèi),修改 render 函數(shù)中以下代碼:
function render(vdom, container) {if (_.isFunction(vdom.nodeName)) {let component, returnVdomif (vdom.nodeName.prototype.render) {component = new vdom.nodeName(vdom.attributes) // 將組件外的 props 傳進(jìn)組件內(nèi)returnVdom = component.render()} else {returnVdom = vdom.nodeName(vdom.attributes) // 處理無狀態(tài)組件:const A = (props) => <div>I'm {props.name}</div>}...}... }實現(xiàn)完組件間 props 的傳遞后,再來聊聊 state,在 react 中是通過 setState 來完成組件狀態(tài)的改變的,后續(xù)章節(jié)會對這個 api(異步)深入探究,這里簡單實現(xiàn)如下:
function Component(props) {this.props = propsthis.state = this.state || {} }Component.prototype.setState = function() {this.state = Object.assign({}, this.state, updateObj) // 這里簡單實現(xiàn),后續(xù)篇章會深入探究const returnVdom = this.render() // 重新渲染document.getElementById('root').innerHTML = nullrender(returnVdom, document.getElementById('root')) }此時雖然已經(jīng)實現(xiàn)了 setState 的功能,但是 document.getElementById('root') 節(jié)點寫死在 setState 中顯然不是我們希望的,我們將 dom 節(jié)點相關(guān)轉(zhuǎn)移到 _render 函數(shù)中:
Component.prototype.setState = function(updateObj) {this.state = Object.assign({}, this.state, updateObj)_render(this) // 重新渲染 }自然地,重構(gòu)與之相關(guān)的 render 函數(shù):
function render(vdom, container) {let componentif (_.isFunction(vdom.nodeName)) {if (vdom.nodeName.prototype.render) {component = new vdom.nodeName(vdom.attributes)} else {component = vdom.nodeName(vdom.attributes) // 處理無狀態(tài)組件:const A = (props) => <div>I'm {props.name}</div>}}component ? _render(component, container) : _render(vdom, container) }在 render 函數(shù)中分離出 _render 函數(shù)的目的是為了讓 setState 函數(shù)中也能調(diào)用 _render 邏輯。完整 _render 函數(shù)如下:
function _render(component, container) {const vdom = component.render ? component.render() : componentif (_.isString(vdom) || _.isNumber(vdom)) {container.innerText = container.innerText + vdomreturn}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) { // 注意:調(diào)用 setState 方法時是進(jìn)入這段邏輯,從而實現(xiàn)我們將 dom 的邏輯與 setState 函數(shù)分離的目標(biāo);知識點: new 出來的同一個實例component.container.innerHTML = nullcomponent.container.appendChild(dom)return}component.container = containercontainer.appendChild(dom) }讓我們用下面這個用例跑下寫好的 react 吧!
class A extends Component {constructor(props) {super(props)this.state = {count: 1}}click() {this.setState({count: ++this.state.count})}render() {return (<div><button onClick={this.click.bind(this)}>Click Me!</button><div>{this.props.name}:{this.state.count}</div></div>)} }ReactDOM.render(<A name="count" />,document.getElementById('root') )效果圖如下:
至此,我們實現(xiàn)了 props 和 state 部分的邏輯。
小結(jié)
組件即函數(shù);當(dāng) JSX 中是自定義組件時,經(jīng)過 babel 轉(zhuǎn)化后的 React.createElement(fn, ..) 后中的第一個參數(shù)變?yōu)榱撕瘮?shù),除此之外其它邏輯與 JSX 中為 html 元素的時候相同;
此外我們將 state/props/setState 等 api 封裝進(jìn)了父類 React.Component 中,從而在子類中能調(diào)用這些屬性和方法。
在下篇,我們會繼續(xù)實現(xiàn)生命周期機(jī)制,如有疏漏,歡迎斧正。
項目地址
轉(zhuǎn)載于:https://www.cnblogs.com/MuYunyun/p/9298089.html
總結(jié)
以上是生活随笔為你收集整理的从 0 到 1 实现 React 系列 —— 2.组件和 state|props的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: django ORM 操作
- 下一篇: c++stl应用入门