溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

React的核心原理和用法

發(fā)布時(shí)間:2021-09-16 20:31:08 來(lái)源:億速云 閱讀:180 作者:chen 欄目:web開(kāi)發(fā)

本篇內(nèi)容介紹了“React的核心原理和用法”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

 1. 項(xiàng)目基本準(zhǔn)備工作

1.1 創(chuàng)建項(xiàng)目

利用npx create-react-app my_react命令創(chuàng)建項(xiàng)目

1.2 項(xiàng)目結(jié)構(gòu)

將一些用不到的文件刪除后,目錄變成這樣

React的核心原理和用法

此時(shí)的index.js

import React from 'react'; import ReactDOM from 'react-dom';  ReactDOM.render(   "sunny",   document.getElementById('root') );

2.創(chuàng)建react.js和react-dom.js文件

React的核心原理和用法

我們就可以把需要引入react和react-dom的改成自己創(chuàng)建的文件啦

import React from './react'; import ReactDOM from './react-dom';  ReactDOM.render(   "sunny",   document.getElementById('root') );

3.完成react-dom

我們?cè)趇ndex.js文件中

ReactDOM.render(   "sunny",   document.getElementById('root') );

以這樣的方式使用ReactDOM,說(shuō)明他有render這個(gè)方法。

所以我們可以這樣實(shí)現(xiàn)react-dom

// react-dom.js let ReactDOM = {     render } function render(element,container){     container.innerHTML = `<span>${element}</span>`      }  export default ReactDOM

我們看下運(yùn)行結(jié)果

React的核心原理和用法 

可喜可賀!萬(wàn)里長(zhǎng)城邁出了第一步

好了,現(xiàn)在我們給每一個(gè) 元素打上 一個(gè)標(biāo)記 ,這樣的話 就可以通過(guò)這個(gè)標(biāo)記 辨別出與其他 元素的關(guān)系,也可以直接通過(guò)這標(biāo)記找到該元素了。

就像下面這張圖一樣,是不是就直接看出0.0和0.1的父節(jié)點(diǎn)就是0了呢?

React的核心原理和用法
// react-dom.js let ReactDOM = {     render,     rootIndex:0 } function render(element,container){     container.innerHTML = `<span data-reactid=${ReactDOM.rootIndex}>${element}</span>` }  export default ReactDOM

如代碼所示,我們給每一個(gè)元素添加了一個(gè)標(biāo)記data-reactid

運(yùn)行,發(fā)現(xiàn)確實(shí)標(biāo)記成功了,哈哈哈

React的核心原理和用法

4. 重構(gòu)render方法

我們前面的render方法

function render(element,container){     container.innerHTML = `<span data-reactid=${ReactDOM.rootIndex}>${element}</span>` }

默認(rèn)傳入的element為字符串, 但是實(shí)際情況是有可能是 文本節(jié)點(diǎn),也有可能是DOM節(jié)點(diǎn),也有可能是  自定義組件。所以我們實(shí)現(xiàn)一個(gè)createUnit方法,將element傳入,讓它來(lái)判斷element是什么類型的節(jié)點(diǎn),。然后再返回一個(gè)被判斷為某種類型,并且添加了對(duì)應(yīng)的方法和屬性的對(duì)象  。例如,我們的element是字符串類型,那么就返回一個(gè)字符串類型的對(duì)象,而這個(gè)對(duì)象自身有element  屬性和getMarkUp方法,這個(gè)getMarkUp方法,將element轉(zhuǎn)化成真實(shí)的dom其實(shí)你也可以簡(jiǎn)單地認(rèn)為 createUnit 方法 就是 為  element 對(duì)象添加 一個(gè)getMarkUp方法

// react-dom.js import $ from "jquery" let ReactDOM = {     render,     rootIndex:0 } function render(element,container){     let unit = createUnit(element)     let markUp = unit.getMarkUp();// 用來(lái)返回HTML標(biāo)記     $(container).html(markUp) }  export default ReactDOM

如代碼所示,將element傳入createUnit方法,獲得的unit是一個(gè)對(duì)象

{   _currentElement:element,   getMarkUp(){     ...   } }

再執(zhí)行 unit的getMarkUp方法,獲得到 真實(shí)的dom,然后就可以掛載到container上去啦!

注意,如果傳入render的element是字符串"sunny", 即

import React from './react'; import ReactDOM from './react-dom';  ReactDOM.render(   "sunny",   document.getElementById('root') );

也就是說(shuō)傳入createUnit的element是字符串"sunny",那么返回的unit是

{  _currentElement:"sunny",  getMarkUp(){     } }

那怎么寫這個(gè)createUnit呢?

5. 實(shí)現(xiàn)createUnit方法

我們創(chuàng)建一個(gè)新的文件叫做unit.js

React的核心原理和用法

在這里插入圖片描述

// Unit.js class Unit{     } class TextUnit extends Unit{      }  function createUnit(element){     if(typeof element === 'string' || typeof element === "number"){         return new TextUnit(element)     } }  export {     createUnit }

如代碼所示,createUnit判斷element是字符串時(shí)就 new  一個(gè)TextUnit的對(duì)象,然后返回出去,這個(gè)也就是我們上面講到的unit對(duì)象了。

為什么要 TextUnit 繼承 于 Unit呢?

這是因?yàn)?element除了字符串 ,也有可能是 原生的標(biāo)簽,列如div,span等,也有可能是我們自定義的組件,所以我們先寫 了一個(gè)  unit類,這個(gè)類實(shí)現(xiàn) 這幾種element 所共有的屬性。然后 具體的 類 ,例如 TextUnit 直接繼承 Unit ,再實(shí)現(xiàn)自有的 屬性就好了。

6. 實(shí)現(xiàn)Unitnew

Unit 得到的對(duì)象應(yīng)當(dāng)是這樣的

{   _currentElement:element,   getMarkUp(){     ...   } }

也就是說(shuō),這是所有的 種類都有的屬性,所以我們可以這樣實(shí)現(xiàn) Unit

class Unit{     constructor(element){         this._currentElement = element     }     getMarkUp(){         throw Error("此方法應(yīng)該被重寫,不能直接被使用")     } }

為什么getMarkUp 要throw Error("此方法應(yīng)該被重寫,不能直接被使用")呢?

學(xué)過(guò) java或其他語(yǔ)言的同學(xué)應(yīng)該秒懂,這是因?yàn)間etMarkUp希望是被子類重寫的方法,因?yàn)槊總€(gè)子類執(zhí)行這個(gè)方法返回的結(jié)果是不一樣的。

7. 實(shí)現(xiàn)TextUnit

到這一步,我們只要重寫getMarkUp方法就好了,不過(guò)不要忘記,給每一個(gè)元素添加一個(gè)  reactid,至于為什么,已經(jīng)在上面說(shuō)過(guò)了,也放了一張大圖了哈。

class TextUnit extends Unit{     getMarkUp(reactid){         this._reactid = reactid         return `<span data-reactid=${reactid}>${this._currentElement}</span>`     } }

好了,到這里先看下完整的Unit.js長(zhǎng)什么樣子吧

// Unit.js class Unit{     constructor(element){         this._currentElement = element     }     getMarkUp(){         throw Error("此方法應(yīng)該被重寫,不能直接被使用")     } } class TextUnit extends Unit{     getMarkUp(reactid){         this._reactid = reactid         return `<span data-reactid=${reactid}>${this._currentElement}</span>`     } }  function createUnit(element){     if(typeof element === 'string' || typeof element === "number"){         return new TextUnit(element)     } }  export {     createUnit }

我們?cè)趇ndex.js引入 unit測(cè)試下

  1. // index.js 

  2. import React from './react'; 

  3. import ReactDOM from './react-dom'; 

  4.  

  5. ReactDOM.render( 

  6.   "sunny", 

  7.   document.getElementById('root') 

  8. ); 


// react-dom.js import {createUnit} from './unit' import $ from "jquery" let ReactDOM = {     render,     rootIndex:0 } function render(element,container){     let unit = createUnit(element)     let markUp = unit.getMarkUp(ReactDOM.rootIndex);// 用來(lái)返回HTML標(biāo)記     $(container).html(markUp) }  export default ReactDOM

React的核心原理和用法

在這里插入圖片描述

意料之內(nèi)的成功!哈哈哈啊

8.  理解React.creacteElement方法

在第一次學(xué)習(xí)react的時(shí)候,我總會(huì)帶著許多疑問(wèn)。比如看到下面的代碼就會(huì)想:為什么我們只是引入了React,但是并沒(méi)有明顯的看到我們?cè)谄渌胤接?,這時(shí)我就會(huì)想著既然沒(méi)有用到,那如果刪除之后會(huì)不會(huì)受到影響呢?答案當(dāng)然是不行的。

import React from 'react'; import ReactDOM from 'react-dom';  let element = (     <h2 id="title" className="bg" style={{color: 'red'}}>         hello         <span>world</span>     </h2> )  console.log({type: element.type, props:element.props})  ReactDOM.render(element,document.getElementById('root'));

當(dāng)我們帶著這個(gè)問(wèn)題去研究的時(shí)候會(huì)發(fā)現(xiàn)其實(shí)在渲染element的時(shí)候調(diào)了React.createElement(),所以上面的問(wèn)題就在這里找到了答案。

如下面代碼所示,這就是從jsx語(yǔ)法到React.createElement的轉(zhuǎn)化

<h2 id="title" className="bg" style={{color: 'red'}}>         hello         <span>world</span> </h2>  //上面的這段代碼很簡(jiǎn)單,但是我們都知道react是所謂的虛擬dom,當(dāng)然不可能就是我們看到的這樣。當(dāng)我們將上面的代碼經(jīng)過(guò)babel轉(zhuǎn)譯后,我們?cè)倏纯?nbsp; React.createElement("h2", {   id: "title",   className: "bg",   style: {     color: 'red'   } }, "hello", React.createElement("span", null, "world"));

document有createElement()方法,React也有createElement()方法,下面就來(lái)介紹React的createElement()方法。

var reactElement = ReactElement.createElement(    ... // 標(biāo)簽名稱字符串/ReactClass,    ... // [元素的屬性值對(duì)對(duì)象],    ... // [元素的子節(jié)點(diǎn)] )

1、參數(shù):

1)第一個(gè)參數(shù):可以是一個(gè)html標(biāo)簽名稱字符串,也可以是一個(gè)ReactClass(必須);

2)第二個(gè)參數(shù):元素的屬性值對(duì)對(duì)象(可選),這些屬性可以通過(guò)this.props.*來(lái)調(diào)用;

3)第三個(gè)參數(shù)開(kāi)始:元素的子節(jié)點(diǎn)(可選)。

2、返回值:

一個(gè)給定類型的ReactElement元素

我們可以改下我們的index.js

// index.js import React from './react'; import ReactDOM from './react-dom';  var li1 = React.createElement('li', {onClick:()=>{alert("click")}}, 'First'); var li2 = React.createElement('li', {}, 'Second'); var li3 = React.createElement('li', {}, 'Third'); var ul = React.createElement('ul', {className: 'list'}, li1, li2, li3); console.log(ul); ReactDOM.render(ul,document.getElementById('root'))

可以就看下 ul 最終的打印 期待結(jié)果

React的核心原理和用法 

由此 ,我們只知道了,ReactElement.createElement方法將生產(chǎn)一個(gè)給定類型的ReactElement元素,然后這個(gè)對(duì)象被傳入  render方法,然后進(jìn)行了上面講到的 createUnit和getMarkUp操作。

9.  實(shí)現(xiàn)React.createElement方法

經(jīng)過(guò)上面的講解,我們大概已經(jīng)知道React.createElement方法的作用了,現(xiàn)在就來(lái)看看是怎么實(shí)現(xiàn)的

React的核心原理和用法

我們創(chuàng)建了一個(gè)新的文件element.js

// element.js class Element {     constructor(type,props){         this.type = type         this.props = props     }  } function createElement(type,props={},...children){     props.children = children || [];     return new Element(type,props) }  export {     Element,     createElement }

我們 定義了一個(gè) Element 類 ,然后在createElement方法里創(chuàng)建了這個(gè)類的對(duì)象, 并且return出去了

沒(méi)錯(cuò),這個(gè)對(duì)象就是上面所說(shuō)的給定類型的ReactElement元素,也就是下面這張圖所顯示的

React的核心原理和用法 

我們應(yīng)當(dāng)是這樣React.createElement()調(diào)用這個(gè)方法的,所以我們要把這個(gè)方法掛載到react身上。

我們前面還沒(méi)有實(shí)現(xiàn)react.js

其實(shí),很簡(jiǎn)單,就是返回一個(gè)React對(duì)象,這個(gè)對(duì)象有createElement方法

// react.js import {createElement} from "./element" const React = {    createElement } export default React

10. 實(shí)現(xiàn)NativeUnit

上面實(shí)現(xiàn)了 createElement返回 給定類型的ReactElement元素  后,就將改元素傳入,render方法,因此 就會(huì)經(jīng)過(guò) createUnit方法, createUnit方法判斷是屬于什么類型的 元素,如下面代碼

// Unit.js import {Element} from "./element" // 新增代碼 class Unit{     constructor(element){         this._currentElement = element     }     getMarkUp(){         throw Error("此方法應(yīng)該被重寫,不能直接被使用")     } } class TextUnit extends Unit{     getMarkUp(reactid){         this._reactid = reactid         return `<span data-reactid=${reactid}>${this._currentElement}</span>`     } }  function createUnit(element){     if(typeof element === 'string' || typeof element === "number"){         return new TextUnit(element)     }     // 新增代碼     if(element instanceof Element && typeof element.type === "string"){         return new NativeUnit(element)     } }  export {     createUnit }

好了,現(xiàn)在我們來(lái)實(shí)現(xiàn)NativeUnit類,其實(shí)主要就是實(shí)現(xiàn)NativeUnit的getMarkUp方法

class NativeUnit extends Unit{     getMarkUp(reactid){         this._reactid = reactid          let {type,props} = this._currentElement;     } }

要明確的一點(diǎn)是,NativeUnit 的getMarkUp方法,是要把

React的核心原理和用法

這樣一個(gè)element 對(duì)象轉(zhuǎn)化為 真實(shí)的dom的

因此,我們可以這樣完善getMarkUp方法

class NativeUnit extends Unit{     getMarkUp(reactid){         this._reactid = reactid          let {type,props} = this._currentElement;         let tagStart = `<${type} `         let childString = ''         let tagEnd = `</${type}>`         for(let propName in props){             if(/^on[A-Z]/.test(propName)){ // 添加綁定事件                              }else if(propName === 'style'){ // 如果是一個(gè)樣式對(duì)象              }else if(propName === 'className'){ // 如果是一個(gè)類名              }else if(propName === 'children'){ // 如果是子元素              }else { // 其他 自定義的屬性 例如 reactid                 tagStart += (` ${propName}=${props[propName]} `)             }         }         return tagStart+'>' + childString +tagEnd     } }

這只是 大體上的 一個(gè)實(shí)現(xiàn) ,其實(shí)就是 把標(biāo)簽 和屬性 以及 子元素 拼接成 字符串,然后返回出去。

我們測(cè)試下,現(xiàn)在有沒(méi)有 把ul 渲染出來(lái)

// index.js import React from './react'; import ReactDOM from './react-dom';  var li1 = React.createElement('li', {}, 'First'); var li2 = React.createElement('li', {}, 'Second'); var li3 = React.createElement('li', {}, 'Third'); var ul = React.createElement('ul', {className: 'list'}, li1, li2, li3); console.log(ul); ReactDOM.render(ul,document.getElementById('root'))

React的核心原理和用法

發(fā)現(xiàn)確實(shí)成功渲染出來(lái)了,但是 屬性和 子元素還沒(méi)有,這是因?yàn)槲覀?還沒(méi)實(shí)現(xiàn) 具體 的功能。

現(xiàn)在我們來(lái)實(shí)現(xiàn)事件綁定 功能

class NativeUnit extends Unit{     getMarkUp(reactid){         this._reactid = reactid          let {type,props} = this._currentElement;         let tagStart = `<${type} data-reactid="${this._reactid}"`         let childString = ''         let tagEnd = `</${type}>`         for(let propName in props){          // 新增代碼             if(/^on[A-Z]/.test(propName)){ // 添加綁定事件                 let eventName = propName.slice(2).toLowerCase(); // 獲取click                 $(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,props[propName])             }else if(propName === 'style'){ // 如果是一個(gè)樣式對(duì)象                             }else if(propName === 'className'){ // 如果是一個(gè)類名                              }else if(propName === 'children'){ // 如果是子元素                             }else { // 其他 自定義的屬性 例如 reactid                              }         }         return tagStart+'>' + childString +tagEnd     } }

在這里,我們是用了事件代理的模式,之所以用事件代理,是因?yàn)檫@些標(biāo)簽元素還沒(méi)被渲染到頁(yè)面上,但我們又必須提前綁定事件,所以需要用到事件代理

接下來(lái),實(shí)現(xiàn) 樣式對(duì)象的綁定

class NativeUnit extends Unit{     getMarkUp(reactid){         this._reactid = reactid          let {type,props} = this._currentElement;         let tagStart = `<${type} data-reactid="${this._reactid}"`         let childString = ''         let tagEnd = `</${type}>`         for(let propName in props){             if(/^on[A-Z]/.test(propName)){ // 添加綁定事件                 ...             }else if(propName === 'style'){ // 如果是一個(gè)樣式對(duì)象                 let styleObj = props[propName]                 let styles = Object.entries(styleObj).map(([attr, value]) => {                     return `${attr.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)}:${value}`;                 }).join(';')                 tagStart += (` style="${styles}" `)             }else if(propName === 'className'){ // 如果是一個(gè)類名                              }else if(propName === 'children'){ // 如果是子元素                             }else { // 其他 自定義的屬性 例如 reactid                            }         }         return tagStart+'>' + childString +tagEnd     } }

這里 其實(shí)就是把

{style:{backgroundColor:"red"}}

對(duì)象中的 style這個(gè)對(duì)象 屬性拿出來(lái),

然后把backgroundColor 通過(guò)正則 變化成background-color,

然后再拼接到tagStart中。

接下來(lái)再實(shí)現(xiàn)className,發(fā)現(xiàn)這個(gè)也太簡(jiǎn)單了吧

class NativeUnit extends Unit {     getMarkUp(reactid) {         this._reactid = reactid         let { type, props } = this._currentElement;         let tagStart = `<${type} data-reactid="${this._reactid}"`         let childString = ''         let tagEnd = `</${type}>`         for (let propName in props) {             if (/^on[A-Z]/.test(propName)) { // 添加綁定事件                ...             } else if (propName === 'style') { // 如果是一個(gè)樣式對(duì)象                 ...             } else if (propName === 'className') { // 如果是一個(gè)類名                 tagStart += (` class="${props[propName]}"`)             } else if (propName === 'children') { // 如果是子元素                ...             } else { // 其他 自定義的屬性 例如 reactid                 ...             }         }         return tagStart + '>' + childString + tagEnd     } }

為什么這么簡(jiǎn)單呢?因?yàn)橹恍枰?/p>

className: 'list'

中的className變化成 class就可以了。OMG!!

接下來(lái),是時(shí)候?qū)崿F(xiàn)子元素的拼接了哈

class NativeUnit extends Unit {     getMarkUp(reactid) {         this._reactid = reactid         let { type, props } = this._currentElement;         let tagStart = `<${type} data-reactid="${this._reactid}"`         let childString = ''         let tagEnd = `</${type}>`         for (let propName in props) {             if (/^on[A-Z]/.test(propName)) { // 添加綁定事件                 ...             } else if (propName === 'style') { // 如果是一個(gè)樣式對(duì)象                 ...             } else if (propName === 'className') { // 如果是一個(gè)類名                 ...             } else if (propName === 'children') { // 如果是子元素                 let children = props[propName];                 children.forEach((child, index) => {                     let childUnit = createUnit(child); // 可能是字符串 ,也可能是原生標(biāo)簽,也可能是自定義屬性                     let childMarkUp = childUnit.getMarkUp(`${this._reactid}.${index}`)                     childString += childMarkUp;                 })             } else { // 其他 自定義的屬性 例如 reactid                              }         }         return tagStart + '>' + childString + tagEnd     } }

發(fā)現(xiàn)子元素 ,其實(shí)只要進(jìn)行遞歸操作,也就是將子元素傳進(jìn)createUnit,把返回的childUnit 通過(guò)childMarkUp 方法變成  真實(shí)動(dòng),再拼接到childString 就好了。其實(shí)想想也挺簡(jiǎn)單,就類似深拷貝的操作。

好了,接下來(lái)就是 其他屬性了

class NativeUnit extends Unit {     getMarkUp(reactid) {         this._reactid = reactid         let { type, props } = this._currentElement;         let tagStart = `<${type} data-reactid="${this._reactid}"`         let childString = ''         let tagEnd = `</${type}>`         for (let propName in props) {             if (/^on[A-Z]/.test(propName)) { // 添加綁定事件                ...             } else if (propName === 'style') { // 如果是一個(gè)樣式對(duì)象                ...             } else if (propName === 'className') { // 如果是一個(gè)類名                ...             } else if (propName === 'children') { // 如果是子元素                 ...             } else { // 其他 自定義的屬性 例如 reactid                 tagStart += (` ${propName}=${props[propName]} `)             }         }         return tagStart + '>' + childString + tagEnd     } }

其他屬性直接就拼上去就好了哈哈哈

好了?,F(xiàn)在我們已經(jīng)完成了NativeUini的getMarkUp方法。我們來(lái)測(cè)試一下是否成功了沒(méi)有吧!

React的核心原理和用法

害,不出所料地成功了。

11. 完成React.Component

接下來(lái)我們看看自定義組件是怎么被渲染的,例如下面的Counter組件

// index.js class Counter extends React.Component{     constructor(props){         super(props)         this.state = {number:0};     }     render(){         let p = React.createElement('p',{style:{color:'red'}},this.state.number);         let button = React.createElement('button',{},"+")         return React.createElement('div',{id:'counter'},p,button)     } } let element = React.createElement(Counter,{name:"計(jì)時(shí)器"}) ReactDOM.render(element,document.getElementById('root'))

我們發(fā)現(xiàn)自定義組件好像需要繼承React.Component。這是為什么呢?

我之前一直誤認(rèn)為所有的生命周期都是從Component繼承過(guò)來(lái)的,也許有很多小伙伴都和我一樣有這樣的誤解,直到我看了Component源碼才恍然大悟,原來(lái)我們用的setState和forceUpdate方法是來(lái)源于這里

知道這個(gè)原因后,我們就可以先簡(jiǎn)單地實(shí)現(xiàn)React.Component了

// component.js class Component{     constructor(props){         this.props = props     } }  export {     Component }

然后再引入react中即可

// react.js import {createElement} from "./element" import {Component} from "./component" const React = {    createElement,    Component } export default React

跟 處理NativeUnit一樣,先通過(guò)createUnit判斷element是屬于什么類型,如果是自定義組件就 return  CompositeUnit

// Unit.js import { Element } from "./element" // 新增代碼 import $ from "jquery" class Unit {     constructor(element) {         this._currentElement = element     }     getMarkUp() {         throw Error("此方法應(yīng)該被重寫,不能直接被使用")     } } class TextUnit extends Unit {      }  class NativeUnit extends Unit {     }  function createUnit(element) {     if (typeof element === 'string' || typeof element === "number") {         return new TextUnit(element)     }     if (element instanceof Element && typeof element.type === "string") {         return new NativeUnit(element)     }     // 新增代碼     if(element instanceof Element && typeof element.type === 'function'){         return new CompositeUnit(element)     }  }   export {     createUnit }

為什么是用 typeof element.type === 'function'來(lái)判斷 呢?因?yàn)镃ounter是  一個(gè)類,而類在js中的本質(zhì)就是function

好了,接下來(lái)實(shí)現(xiàn)一下CompositeUnit類

class CompositeUnit extends Unit{     getMarkUp(reactid){       this._reactid = reactid       let {type:Component,props} = this._currentElement // 實(shí)際上,在例子中type === Counter       let componentInstance = new Component(props);       let renderElement = componentInstance.render();       let renderUnit = createUnit(renderElement);       return renderUnit.getMarkUp(this._reactid)     } }

咦,好簡(jiǎn)短 啊,不過(guò) 沒(méi)那么 簡(jiǎn)單,但是讓 我的三寸不爛之舌來(lái)講解一下,包懂

此時(shí)的_currentElement 是:

{  type:Counter,  props:{} }

let {type:Component,props} = this._currentElement// 實(shí)際上,在例子中type就是Counternew  Component(props);其實(shí)就是new Counter。

也就是我們上面例子中寫的

class Counter extends React.Component{     constructor(props){         super(props)         this.state = {number:0};     }     render(){         let p = React.createElement('p',{style:{color:'red'}},this.state.number);         let button = React.createElement('button',{},"+")         return React.createElement('div',{id:'counter'},p,button)     } } let element = React.createElement(Counter,{name:"計(jì)時(shí)器"}) ReactDOM.render(element,document.getElementById('root'))

可想而知 ,通過(guò)new Counter就獲得了Counter的實(shí)例

也就是componentInstance  ,而每一個(gè)Counter的實(shí)例都會(huì)有render方法,所以執(zhí)行componentInstance.render()

就獲得一個(gè)給定類型的ReactElement元素(好熟悉的一句話,對(duì),我們?cè)谏厦嬷v到過(guò))。

然后就把這個(gè)ReactElement元素對(duì)象傳給createUnit,獲得一個(gè)具有g(shù)etMarkUp的renderUnit 對(duì)象,  然后就可以執(zhí)行renderUnit.getMarkUp(this._reactid)獲得真實(shí)dom,就可以返回了。

其實(shí),仔細(xì)想想,就會(huì)發(fā)現(xiàn),在

let renderUnit = createUnit(renderElement);

之前,我們是在處理自定義組件Counter。

而到了

let renderUnit = createUnit(renderElement);

這一步,其實(shí)就是在處理NativeUnit。(細(xì)思極恐。。)

好了,測(cè)試一下

React的核心原理和用法

發(fā)現(xiàn)確實(shí)成功了。

12. 實(shí)現(xiàn) componentWillMount

我們?cè)谥暗睦由咸砑觽€(gè)componentWillMount 生命周期函數(shù)吧

// index.js import React from './react'; import ReactDOM from './react-dom';  class Counter extends React.Component{     constructor(props){         super(props)         this.state = {number:0};     }     componentWillMount(){         console.log("陽(yáng)光你好,我是componentWillMount");     }     render(){         let p = React.createElement('p',{style:{color:'red'}},this.state.number);         let button = React.createElement('button',{},"+")         return React.createElement('div',{id:'counter'},p,button)     } } let element = React.createElement(Counter,{name:"計(jì)時(shí)器"}) ReactDOM.render(element,document.getElementById('root'))

我們知道componentWillMount 實(shí)在組件渲染前執(zhí)行的,所以我們可以在render之前執(zhí)行這個(gè)生命周期函數(shù)

class CompositeUnit extends Unit{     getMarkUp(reactid){       this._reactid = reactid       let {type:Component,props} = this._currentElement // 實(shí)際上,在例子中type === Counter       let componentInstance = new Component(props);       componentInstance.componentWillMount && componentInstance.componentWillMount() // 添加生命周期函數(shù)       let renderElement = componentInstance.render();       let renderUnit = createUnit(renderElement);       return renderUnit.getMarkUp(this._reactid)     }

可能聰明的小伙伴會(huì)問(wèn),不是說(shuō)componentWillMount是在組件重新渲染前執(zhí)行的嗎?那組件沒(méi)掛到頁(yè)面上應(yīng)該都是渲染前,所以componentWillMount也可以在return  renderUnit.getMarkUp(this._reactid)前執(zhí)行啊。

其實(shí)要回答這個(gè)問(wèn)題,倒不如回答另一個(gè)問(wèn)題:

父組件的componentWillMount和子組件的componentWillMount哪個(gè)先執(zhí)行。

答案是父組件先執(zhí)行。

這是因?yàn)樵诟附M件中會(huì)先執(zhí)行 父組件的componentWillMount  ,然后執(zhí)行componentInstance.render();的時(shí)候,會(huì)解析子組件,然后又進(jìn)入子組件的getMarkUp。又執(zhí)行子組件的componentWillMount  。

若要回答 為什么componentWillMount 要在 render函數(shù)執(zhí)行前執(zhí)行,只能說(shuō),react就是這么設(shè)計(jì)的哈哈哈

13. 實(shí)現(xiàn)componentDidMount

眾所周知,componentDidMount是在組件渲染,也就是掛載到頁(yè)面后才執(zhí)行的。

所以,我們可以在返回組件的真實(shí)dom之前 就監(jiān)聽(tīng) 一個(gè)mounted事件,這個(gè)事件執(zhí)行componentDidMount方法。

lass CompositeUnit extends Unit{     getMarkUp(reactid){       this._reactid = reactid       let {type:Component,props} = this._currentElement // 實(shí)際上,在例子中type === Counter       let componentInstance = new Component(props);       componentInstance.componentWillMount && componentInstance.componentWillMount()       let renderElement = componentInstance.render();       let renderUnit = createUnit(renderElement);       $(document).on("mounted",()=>{           componentInstance.componentDidMount &&  componentInstance.componentDidMount()       })       return renderUnit.getMarkUp(this._reactid)     } }

然后 再在 把組件的dom掛載到 頁(yè)面上后再觸發(fā)這個(gè) mounted事件

// react-dom.js import {createUnit} from './unit' import $ from "jquery" let ReactDOM = {     render,     rootIndex:0 } function render(element,container){     let unit = createUnit(element)     let markUp = unit.getMarkUp(ReactDOM.rootIndex);// 用來(lái)返回HTML標(biāo)記     $(container).html(markUp)     $(document).trigger("mounted") }  export default ReactDOM

由此依賴,就實(shí)現(xiàn)了,componentDidMount 生命周期函數(shù),哈哈哈。

測(cè)試一下,成功了沒(méi)有哈

React的核心原理和用法

啊,一如既往的成功,可能好奇的你問(wèn)我為什么每次測(cè)試都成功,那是因?yàn)?,不成功也被我調(diào)試到成功了。

為了下面 實(shí)現(xiàn) setState 功能,我們 修改一下 CompositeUnit 的getMarkUp方法。

class CompositeUnit extends Unit{     getMarkUp(reactid){       this._reactid = reactid       let {type:Component,props} = this._currentElement // 實(shí)際上,在例子中type === Counter       let componentInstance = this._componentInstance = new Component(props); // 把 實(shí)例對(duì)象 保存到這個(gè) 當(dāng)前的 unit       componentInstance._currentUnit = this // 把 unit 掛到 實(shí)例componentInstance        componentInstance.componentWillMount && componentInstance.componentWillMount()       let renderElement = componentInstance.render();       let renderUnit = this._renderUnit = createUnit(renderElement); // 把渲染內(nèi)容對(duì)象也掛載到當(dāng)前 unit       $(document).on("mounted",()=>{           componentInstance.componentDidMount &&  componentInstance.componentDidMount()       })       return renderUnit.getMarkUp(this._reactid)     }

我們?yōu)檫@個(gè) CompositeUnit 的實(shí)例添加了

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. _componentInstance :用了表示 當(dāng)前組件的實(shí)例 (我們所寫的Counter組件)

  3. _renderUnit:當(dāng)前組件的render方法返回的react元素對(duì)應(yīng)的unit._currentElement

另外,我們也通過(guò)

componentInstance._currentUnit = this // 把 unit 掛到 實(shí)例componentInstance

把當(dāng)前 的unit 掛載到了 組件實(shí)例componentInstance身上。

可見(jiàn) 組件的實(shí)例保存了 當(dāng)前 unit,當(dāng)前的unit也保存了組件實(shí)例

14. 實(shí)現(xiàn)setState

我們看下面的例子,每隔一秒鐘就number+1

// index.js import React from './react'; import ReactDOM from './react-dom'; import $ from 'jquery' class Counter extends React.Component{     constructor(props){         super(props)         this.state = {number:0};     }     componentWillMount(){         console.log("陽(yáng)光你好,我是componentWillMount");         $(document).on("mounted",()=>{             console.log(456);                      })     }     componentDidMount(){         setInterval(()=>{             this.setState({number:this.state.number+1})         },1000)     }     render(){                  return this.state.number     } } let element = React.createElement(Counter,{name:"計(jì)時(shí)器"}) ReactDOM.render(element,document.getElementById('root'))

前面說(shuō)到,setState方法是從Component組件繼承過(guò)來(lái)的。所以我們給Component組件添加setState方法

// component.js class Component{     constructor(props){         this.props = props     }     setState(partialState){         // 第一個(gè)參數(shù)是新的元素,第二個(gè)參數(shù)是新的狀態(tài)         this._currentUnit.update(null,partialState)     } }  export {     Component }

我們發(fā)現(xiàn)原來(lái)是在setState方法里調(diào)用了當(dāng)前實(shí)例的對(duì)應(yīng)的unit的update方法,它傳進(jìn)去了 部分state的值。

看到這里,我們就知道了,我們需要回到 CompositeUnit類添加一個(gè)update方法。

class CompositeUnit extends Unit{     update(nextElement,partialState){         // 有傳新元素的話就更新currentElement為新的元素         this._currentElement = nextElement || this._currentElement;          // 獲取新的狀態(tài),并且更新組件的state         let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);         // 新的屬性對(duì)象         let nextProps = this._currentElement.props     }     getMarkUp(reactid){      ...     } }

我們首先 更換了_currentElement的值,這里為什么會(huì)有 有或者沒(méi)有nextElement的情況呢?

(主要就是因?yàn)?,如?_currentElement 是 字符串或者數(shù)字的話,那么它就需要 傳nextElement 來(lái)替換掉舊的  _currentElement 。而如果不是字符串或者數(shù)字的話,是不需要傳的。而CompositeUnit 必定是組件的,所以不用傳nextElement  )。

接著,我們 通過(guò)下面這句代碼獲取了最新的state,并且更新了組件的state

let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);

獲取 最新的 props跟獲取state的方式不一樣,props是跟_currentElement 綁定在一起的,所以獲取最新的props是通過(guò)

let nextProps = this._currentElement.props

接下來(lái),我們要先獲取新舊的渲染元素,然后拿來(lái)比較,怎么獲取呢?

class CompositeUnit extends Unit{     update(nextElement,partialState){         // 有傳新元素的話就更新currentElement為新的元素         this._currentElement = nextElement || this._currentElement;          // 獲取新的狀態(tài),并且更新組件的state         let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);         // 新的屬性對(duì)象         let nextProps = this._currentElement.props         // 下面要進(jìn)行比較更新         // 先得到上次渲染的unit         let preRenderedUnitInstance = this._renderUnit;         // 通過(guò)上次渲染的unit得到上次渲染的元素         let preRenderElement = preRenderedUnitInstance._currentElement         // 得到最新的渲染元素         let nextRenderElement = this._componentInstance.render()      }     getMarkUp(reactid){

我們先得到上次渲染的unit,再通過(guò)上次渲染的unit得到上次渲染的元素preRenderElement ,

再通過(guò)this._componentInstance.render()得到下次渲染的元素nextRenderElement 。

接下來(lái)就可以進(jìn)行比較這兩個(gè)元素了

我們首先會(huì)判斷要不要進(jìn)行深度比較。

如果不是進(jìn)行深度比較就非常簡(jiǎn)單

直接獲取新的渲染unit,然后通過(guò)getMarkUp獲得要渲染的dom,接著就把當(dāng)前的組件里的dom元素替換掉

class CompositeUnit extends Unit{     update(nextElement,partialState){         // 有傳新元素的話就更新currentElement為新的元素         this._currentElement = nextElement || this._currentElement;          // 獲取新的狀態(tài),并且更新組件的state         let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);         // 新的屬性對(duì)象         let nextProps = this._currentElement.props         // 下面要進(jìn)行比較更新         // 先得到上次渲染的unit         let preRenderedUnitInstance = this._renderUnit;         // 通過(guò)上次渲染的unit得到上次渲染的元素         let preRenderElement = preRenderedUnitInstance._currentElement         // 得到最新的渲染元素         let nextRenderElement = this._componentInstance.render()         // 如果新舊兩個(gè)元素類型一樣,則可以進(jìn)行深度比較,如果不一樣,直接干掉老的元素,新建新的         if(shouldDeepCompare(preRenderElement,nextRenderElement)){          }else{             this._renderUnit = createUnit(nextRenderElement)             let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)             $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)         }      }     getMarkUp(reactid){           } }

我們先簡(jiǎn)單地寫一下shouldDeepCompare方法,直接return false,來(lái)測(cè)試一下 非深度比較,是否能夠正確執(zhí)行

function shouldDeepCompare(){     return false } class CompositeUnit extends Unit{     update(nextElement,partialState){         // 有傳新元素的話就更新currentElement為新的元素         this._currentElement = nextElement || this._currentElement;          // 獲取新的狀態(tài),并且更新組件的state         let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);         // 新的屬性對(duì)象         let nextProps = this._currentElement.props         // 下面要進(jìn)行比較更新         // 先得到上次渲染的unit         let preRenderedUnitInstance = this._renderUnit;         // 通過(guò)上次渲染的unit得到上次渲染的元素         let preRenderElement = preRenderedUnitInstance._currentElement         // 得到最新的渲染元素         let nextRenderElement = this._componentInstance.render()         // 如果新舊兩個(gè)元素類型一樣,則可以進(jìn)行深度比較,如果不一樣,直接干掉老的元素,新建新的         if(shouldDeepCompare(preRenderElement,nextRenderElement)){          }else{             this._renderUnit = createUnit(nextRenderElement)             let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)             $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)         }      }     getMarkUp(reactid){           } }

 React的核心原理和用法

在這里插入圖片描述

發(fā)現(xiàn)確實(shí)成功了。

如果可以進(jìn)行深度比較呢?

class CompositeUnit extends Unit{     update(nextElement,partialState){         // 有傳新元素的話就更新currentElement為新的元素         this._currentElement = nextElement || this._currentElement;          // 獲取新的狀態(tài),并且更新組件的state         let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);         // 新的屬性對(duì)象         let nextProps = this._currentElement.props         // 下面要進(jìn)行比較更新         // 先得到上次渲染的unit         let preRenderedUnitInstance = this._renderUnit;         // 通過(guò)上次渲染的unit得到上次渲染的元素         let preRenderElement = preRenderedUnitInstance._currentElement         // 得到最新的渲染元素         let nextRenderElement = this._componentInstance.render()         // 如果新舊兩個(gè)元素類型一樣,則可以進(jìn)行深度比較,如果不一樣,直接干掉老的元素,新建新的         if(shouldDeepCompare(preRenderElement,nextRenderElement)){             // 如果可以進(jìn)行深度比較,則把更新的nextRenderElement傳進(jìn)去             preRenderedUnitInstance.update(nextRenderElement)                      }else{             this._renderUnit = createUnit(nextRenderElement)             let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)             $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)         }      }     getMarkUp(reactid){            } }

如果可以深度,就執(zhí)行

preRenderedUnitInstance.update(nextRenderElement)

這是什么意思?

我們當(dāng)前是在執(zhí)行渲染Counter的話,那preRenderedUnitInstance 是什么呢?

沒(méi)錯(cuò)!它是Counter組件 執(zhí)行render方法 ,再執(zhí)行createUnit獲得的

React的核心原理和用法

在這里插入圖片描述

這個(gè)字符串的 unit

然后調(diào)用了這個(gè) unit的 update方法

注意,這里 的unit是字符串的 unit,也就是說(shuō)是 TextUnit

所以我們需要實(shí)現(xiàn) TextUnit 的update 方法

class TextUnit extends Unit {     getMarkUp(reactid) {         this._reactid = reactid         return `<span data-reactid=${reactid}>${this._currentElement}</span>`     }     update(nextElement){         debugger         if(this._currentElement !== nextElement){             this._currentElement = nextElement              $(`[data-reactid="${this._reactid}"]`).html(nextElement)         }     } }

TextUnit 的update方法非常簡(jiǎn)單,先判斷 渲染內(nèi)容有沒(méi)有變化,有的話就 替換點(diǎn)字符串的內(nèi)容

并把當(dāng)前unit 的_currentElement 替換成最新的nextElement

我們簡(jiǎn)單的把shouldDeepCompare 改成 return true,測(cè)試一下深度比較

function shouldDeepCompare(){     return true }

 React的核心原理和用法

一如既往成功

15. 實(shí)現(xiàn)shouldComponentUpdate方法

我們知道有個(gè)shouldComponentUpdate,用來(lái)決定要不要 重渲染 該組件的

shouldComponentUpdate(nextProps, nextState) {   return nextState.someData !== this.state.someData }

顯然,它要我們傳入 兩個(gè)參數(shù),分別是 組件更新后的nextProps和nextState

而在 還是上面,實(shí)現(xiàn) update的過(guò)程中,我們已經(jīng)得到了nextState 和nextProps

class CompositeUnit extends Unit{     update(nextElement,partialState){         。。。         // 獲取新的狀態(tài),并且更新組件的state         let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);         // 新的屬性對(duì)象         let nextProps = this._currentElement.props         // 下面要進(jìn)行比較更新         。。。      }     getMarkUp(reactid){

所以,我們可以在update里執(zhí)行shouldComponentUpdate方法,來(lái)確定要不要重新渲染組件

class CompositeUnit extends Unit{     update(nextElement,partialState){         // 有傳新元素的話就更新currentElement為新的元素         this._currentElement = nextElement || this._currentElement;          // 獲取新的狀態(tài),并且更新組件的state         let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);         // 新的屬性對(duì)象         let nextProps = this._currentElement.props         if(this._componentInstance.shouldComponentUpdate && !this._componentInstance.shouldComponentUpdate(nextProps,nextState)){             return;         }         // 下面要進(jìn)行比較更新         // 先得到上次渲染的unit         let preRenderedUnitInstance = this._renderUnit;         // 通過(guò)上次渲染的unit得到上次渲染的元素         let preRenderElement = preRenderedUnitInstance._currentElement         // 得到最新的渲染元素         let nextRenderElement = this._componentInstance.render()         // 如果新舊兩個(gè)元素類型一樣,則可以進(jìn)行深度比較,如果不一樣,直接干掉老的元素,新建新的         if(shouldDeepCompare(preRenderElement,nextRenderElement)){             // 如果可以進(jìn)行深度比較,則把更新的工作交給上次渲染出來(lái)的那個(gè)Element元素對(duì)應(yīng)的unit來(lái)處理             preRenderedUnitInstance.update(nextRenderElement)          }else{             this._renderUnit = createUnit(nextRenderElement)             let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)             $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)         }      }     getMarkUp(reactid){           }

16. 實(shí)現(xiàn)componentDidUpdate生命周期函數(shù)

so Easy。

只要在更新后觸發(fā)這個(gè)事件就好了

class CompositeUnit extends Unit{     update(nextElement,partialState){                  if(this._componentInstance.shouldComponentUpdate && !this._componentInstance.shouldComponentUpdate(nextProps,nextState)){             return;         }             if(shouldDeepCompare(preRenderElement,nextRenderElement)){             // 如果可以進(jìn)行深度比較,則把更新的工作交給上次渲染出來(lái)的那個(gè)Element元素對(duì)應(yīng)的unit來(lái)處理             preRenderedUnitInstance.update(nextRenderElement)             this._componentInstance.componentDidUpdate && this._componentInstance.componentDidUpdate()         }else{             this._renderUnit = createUnit(nextRenderElement)             let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)             $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)         }      }     getMarkUp(reactid){           }

17. 實(shí)現(xiàn)shouDeepCompare

判斷是否需要深比較極其簡(jiǎn)單,只需要判斷 oldElement 和newElement 是否  都是字符串或者數(shù)字,這種類型的就走深比較

接著判斷 oldElement 和newElement 是否 都是 Element類型,不是的話就return false,是的 再判斷  type是否相同(即判斷是否是同個(gè)組件,是的話 return true)

其他情況都return false

function shouldDeepCompare(oldElement,newElement){     if(oldElement != null && newElement != null){         let oldType = typeof oldElement         let newType = typeof newElement         if((oldType === 'string' || oldType === "number")&&(newType === "string" || newType === "number")){             return true         }         if(oldElement instanceof Element && newElement instanceof Element){             return oldElement.type === newElement.type         }     }     return false

“React的核心原理和用法”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI