中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React的核心原理和用法

發布時間:2021-09-16 20:31:08 來源:億速云 閱讀:181 作者:chen 欄目:web開發

本篇內容介紹了“React的核心原理和用法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

 1. 項目基本準備工作

1.1 創建項目

利用npx create-react-app my_react命令創建項目

1.2 項目結構

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

React的核心原理和用法

此時的index.js

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

2.創建react.js和react-dom.js文件

React的核心原理和用法

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

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

3.完成react-dom

我們在index.js文件中

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

以這樣的方式使用ReactDOM,說明他有render這個方法。

所以我們可以這樣實現react-dom

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

我們看下運行結果

React的核心原理和用法 

可喜可賀!萬里長城邁出了第一步

好了,現在我們給每一個 元素打上 一個標記 ,這樣的話 就可以通過這個標記 辨別出與其他 元素的關系,也可以直接通過這標記找到該元素了。

就像下面這張圖一樣,是不是就直接看出0.0和0.1的父節點就是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

如代碼所示,我們給每一個元素添加了一個標記data-reactid

運行,發現確實標記成功了,哈哈哈

React的核心原理和用法

4. 重構render方法

我們前面的render方法

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

默認傳入的element為字符串, 但是實際情況是有可能是 文本節點,也有可能是DOM節點,也有可能是  自定義組件。所以我們實現一個createUnit方法,將element傳入,讓它來判斷element是什么類型的節點,。然后再返回一個被判斷為某種類型,并且添加了對應的方法和屬性的對象  。例如,我們的element是字符串類型,那么就返回一個字符串類型的對象,而這個對象自身有element  屬性和getMarkUp方法,這個getMarkUp方法,將element轉化成真實的dom其實你也可以簡單地認為 createUnit 方法 就是 為  element 對象添加 一個getMarkUp方法

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

如代碼所示,將element傳入createUnit方法,獲得的unit是一個對象

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

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

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

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

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

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

那怎么寫這個createUnit呢?

5. 實現createUnit方法

我們創建一個新的文件叫做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是字符串時就 new  一個TextUnit的對象,然后返回出去,這個也就是我們上面講到的unit對象了。

為什么要 TextUnit 繼承 于 Unit呢?

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

6. 實現Unitnew

Unit 得到的對象應當是這樣的

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

也就是說,這是所有的 種類都有的屬性,所以我們可以這樣實現 Unit

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

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

學過 java或其他語言的同學應該秒懂,這是因為getMarkUp希望是被子類重寫的方法,因為每個子類執行這個方法返回的結果是不一樣的。

7. 實現TextUnit

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

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

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

// Unit.js class Unit{     constructor(element){         this._currentElement = element     }     getMarkUp(){         throw Error("此方法應該被重寫,不能直接被使用")     } } 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 }

我們在index.js引入 unit測試下

  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);// 用來返回HTML標記     $(container).html(markUp) }  export default ReactDOM

React的核心原理和用法

在這里插入圖片描述

意料之內的成功!哈哈哈啊

8.  理解React.creacteElement方法

在第一次學習react的時候,我總會帶著許多疑問。比如看到下面的代碼就會想:為什么我們只是引入了React,但是并沒有明顯的看到我們在其他地方用,這時我就會想著既然沒有用到,那如果刪除之后會不會受到影響呢?答案當然是不行的。

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'));

當我們帶著這個問題去研究的時候會發現其實在渲染element的時候調了React.createElement(),所以上面的問題就在這里找到了答案。

如下面代碼所示,這就是從jsx語法到React.createElement的轉化

<h2 id="title" className="bg" style={{color: 'red'}}>         hello         <span>world</span> </h2>  //上面的這段代碼很簡單,但是我們都知道react是所謂的虛擬dom,當然不可能就是我們看到的這樣。當我們將上面的代碼經過babel轉譯后,我們再看看  React.createElement("h2", {   id: "title",   className: "bg",   style: {     color: 'red'   } }, "hello", React.createElement("span", null, "world"));

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

var reactElement = ReactElement.createElement(    ... // 標簽名稱字符串/ReactClass,    ... // [元素的屬性值對對象],    ... // [元素的子節點] )

1、參數:

1)第一個參數:可以是一個html標簽名稱字符串,也可以是一個ReactClass(必須);

2)第二個參數:元素的屬性值對對象(可選),這些屬性可以通過this.props.*來調用;

3)第三個參數開始:元素的子節點(可選)。

2、返回值:

一個給定類型的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 最終的打印 期待結果

React的核心原理和用法 

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

9.  實現React.createElement方法

經過上面的講解,我們大概已經知道React.createElement方法的作用了,現在就來看看是怎么實現的

React的核心原理和用法

我們創建了一個新的文件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 }

我們 定義了一個 Element 類 ,然后在createElement方法里創建了這個類的對象, 并且return出去了

沒錯,這個對象就是上面所說的給定類型的ReactElement元素,也就是下面這張圖所顯示的

React的核心原理和用法 

我們應當是這樣React.createElement()調用這個方法的,所以我們要把這個方法掛載到react身上。

我們前面還沒有實現react.js

其實,很簡單,就是返回一個React對象,這個對象有createElement方法

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

10. 實現NativeUnit

上面實現了 createElement返回 給定類型的ReactElement元素  后,就將改元素傳入,render方法,因此 就會經過 createUnit方法, createUnit方法判斷是屬于什么類型的 元素,如下面代碼

// Unit.js import {Element} from "./element" // 新增代碼 class Unit{     constructor(element){         this._currentElement = element     }     getMarkUp(){         throw Error("此方法應該被重寫,不能直接被使用")     } } 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 }

好了,現在我們來實現NativeUnit類,其實主要就是實現NativeUnit的getMarkUp方法

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

要明確的一點是,NativeUnit 的getMarkUp方法,是要把

React的核心原理和用法

這樣一個element 對象轉化為 真實的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'){ // 如果是一個樣式對象              }else if(propName === 'className'){ // 如果是一個類名              }else if(propName === 'children'){ // 如果是子元素              }else { // 其他 自定義的屬性 例如 reactid                 tagStart += (` ${propName}=${props[propName]} `)             }         }         return tagStart+'>' + childString +tagEnd     } }

這只是 大體上的 一個實現 ,其實就是 把標簽 和屬性 以及 子元素 拼接成 字符串,然后返回出去。

我們測試下,現在有沒有 把ul 渲染出來

// 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的核心原理和用法

發現確實成功渲染出來了,但是 屬性和 子元素還沒有,這是因為我們 還沒實現 具體 的功能。

現在我們來實現事件綁定 功能

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'){ // 如果是一個樣式對象                             }else if(propName === 'className'){ // 如果是一個類名                              }else if(propName === 'children'){ // 如果是子元素                             }else { // 其他 自定義的屬性 例如 reactid                              }         }         return tagStart+'>' + childString +tagEnd     } }

在這里,我們是用了事件代理的模式,之所以用事件代理,是因為這些標簽元素還沒被渲染到頁面上,但我們又必須提前綁定事件,所以需要用到事件代理

接下來,實現 樣式對象的綁定

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'){ // 如果是一個樣式對象                 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'){ // 如果是一個類名                              }else if(propName === 'children'){ // 如果是子元素                             }else { // 其他 自定義的屬性 例如 reactid                            }         }         return tagStart+'>' + childString +tagEnd     } }

這里 其實就是把

{style:{backgroundColor:"red"}}

對象中的 style這個對象 屬性拿出來,

然后把backgroundColor 通過正則 變化成background-color,

然后再拼接到tagStart中。

接下來再實現className,發現這個也太簡單了吧

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') { // 如果是一個樣式對象                 ...             } else if (propName === 'className') { // 如果是一個類名                 tagStart += (` class="${props[propName]}"`)             } else if (propName === 'children') { // 如果是子元素                ...             } else { // 其他 自定義的屬性 例如 reactid                 ...             }         }         return tagStart + '>' + childString + tagEnd     } }

為什么這么簡單呢?因為只需要把

className: 'list'

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

接下來,是時候實現子元素的拼接了哈

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') { // 如果是一個樣式對象                 ...             } else if (propName === 'className') { // 如果是一個類名                 ...             } else if (propName === 'children') { // 如果是子元素                 let children = props[propName];                 children.forEach((child, index) => {                     let childUnit = createUnit(child); // 可能是字符串 ,也可能是原生標簽,也可能是自定義屬性                     let childMarkUp = childUnit.getMarkUp(`${this._reactid}.${index}`)                     childString += childMarkUp;                 })             } else { // 其他 自定義的屬性 例如 reactid                              }         }         return tagStart + '>' + childString + tagEnd     } }

發現子元素 ,其實只要進行遞歸操作,也就是將子元素傳進createUnit,把返回的childUnit 通過childMarkUp 方法變成  真實動,再拼接到childString 就好了。其實想想也挺簡單,就類似深拷貝的操作。

好了,接下來就是 其他屬性了

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') { // 如果是一個樣式對象                ...             } else if (propName === 'className') { // 如果是一個類名                ...             } else if (propName === 'children') { // 如果是子元素                 ...             } else { // 其他 自定義的屬性 例如 reactid                 tagStart += (` ${propName}=${props[propName]} `)             }         }         return tagStart + '>' + childString + tagEnd     } }

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

好了。現在我們已經完成了NativeUini的getMarkUp方法。我們來測試一下是否成功了沒有吧!

React的核心原理和用法

害,不出所料地成功了。

11. 完成React.Component

接下來我們看看自定義組件是怎么被渲染的,例如下面的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:"計時器"}) ReactDOM.render(element,document.getElementById('root'))

我們發現自定義組件好像需要繼承React.Component。這是為什么呢?

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

知道這個原因后,我們就可以先簡單地實現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一樣,先通過createUnit判斷element是屬于什么類型,如果是自定義組件就 return  CompositeUnit

// Unit.js import { Element } from "./element" // 新增代碼 import $ from "jquery" class Unit {     constructor(element) {         this._currentElement = element     }     getMarkUp() {         throw Error("此方法應該被重寫,不能直接被使用")     } } 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'來判斷 呢?因為Counter是  一個類,而類在js中的本質就是function

好了,接下來實現一下CompositeUnit類

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

咦,好簡短 啊,不過 沒那么 簡單,但是讓 我的三寸不爛之舌來講解一下,包懂

此時的_currentElement 是:

{  type:Counter,  props:{} }

let {type:Component,props} = this._currentElement// 實際上,在例子中type就是Counternew  Component(props);其實就是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:"計時器"}) ReactDOM.render(element,document.getElementById('root'))

可想而知 ,通過new Counter就獲得了Counter的實例

也就是componentInstance  ,而每一個Counter的實例都會有render方法,所以執行componentInstance.render()

就獲得一個給定類型的ReactElement元素(好熟悉的一句話,對,我們在上面講到過)。

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

其實,仔細想想,就會發現,在

let renderUnit = createUnit(renderElement);

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

而到了

let renderUnit = createUnit(renderElement);

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

好了,測試一下

React的核心原理和用法

發現確實成功了。

12. 實現 componentWillMount

我們在之前的例子上添加個componentWillMount 生命周期函數吧

// 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("陽光你好,我是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:"計時器"}) ReactDOM.render(element,document.getElementById('root'))

我們知道componentWillMount 實在組件渲染前執行的,所以我們可以在render之前執行這個生命周期函數

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

可能聰明的小伙伴會問,不是說componentWillMount是在組件重新渲染前執行的嗎?那組件沒掛到頁面上應該都是渲染前,所以componentWillMount也可以在return  renderUnit.getMarkUp(this._reactid)前執行啊。

其實要回答這個問題,倒不如回答另一個問題:

父組件的componentWillMount和子組件的componentWillMount哪個先執行。

答案是父組件先執行。

這是因為在父組件中會先執行 父組件的componentWillMount  ,然后執行componentInstance.render();的時候,會解析子組件,然后又進入子組件的getMarkUp。又執行子組件的componentWillMount  。

若要回答 為什么componentWillMount 要在 render函數執行前執行,只能說,react就是這么設計的哈哈哈

13. 實現componentDidMount

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

所以,我們可以在返回組件的真實dom之前 就監聽 一個mounted事件,這個事件執行componentDidMount方法。

lass CompositeUnit extends Unit{     getMarkUp(reactid){       this._reactid = reactid       let {type:Component,props} = this._currentElement // 實際上,在例子中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掛載到 頁面上后再觸發這個 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);// 用來返回HTML標記     $(container).html(markUp)     $(document).trigger("mounted") }  export default ReactDOM

由此依賴,就實現了,componentDidMount 生命周期函數,哈哈哈。

測試一下,成功了沒有哈

React的核心原理和用法

啊,一如既往的成功,可能好奇的你問我為什么每次測試都成功,那是因為,不成功也被我調試到成功了。

為了下面 實現 setState 功能,我們 修改一下 CompositeUnit 的getMarkUp方法。

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

我們為這個 CompositeUnit 的實例添加了

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

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

  3. _renderUnit:當前組件的render方法返回的react元素對應的unit._currentElement

另外,我們也通過

componentInstance._currentUnit = this // 把 unit 掛到 實例componentInstance

把當前 的unit 掛載到了 組件實例componentInstance身上。

可見 組件的實例保存了 當前 unit,當前的unit也保存了組件實例

14. 實現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("陽光你好,我是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:"計時器"}) ReactDOM.render(element,document.getElementById('root'))

前面說到,setState方法是從Component組件繼承過來的。所以我們給Component組件添加setState方法

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

我們發現原來是在setState方法里調用了當前實例的對應的unit的update方法,它傳進去了 部分state的值。

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

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

我們首先 更換了_currentElement的值,這里為什么會有 有或者沒有nextElement的情況呢?

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

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

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

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

let nextProps = this._currentElement.props

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

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

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

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

接下來就可以進行比較這兩個元素了

我們首先會判斷要不要進行深度比較。

如果不是進行深度比較就非常簡單

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

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

我們先簡單地寫一下shouldDeepCompare方法,直接return false,來測試一下 非深度比較,是否能夠正確執行

function shouldDeepCompare(){     return false } class CompositeUnit extends Unit{     update(nextElement,partialState){         // 有傳新元素的話就更新currentElement為新的元素         this._currentElement = nextElement || this._currentElement;          // 獲取新的狀態,并且更新組件的state         let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);         // 新的屬性對象         let nextProps = this._currentElement.props         // 下面要進行比較更新         // 先得到上次渲染的unit         let preRenderedUnitInstance = this._renderUnit;         // 通過上次渲染的unit得到上次渲染的元素         let preRenderElement = preRenderedUnitInstance._currentElement         // 得到最新的渲染元素         let nextRenderElement = this._componentInstance.render()         // 如果新舊兩個元素類型一樣,則可以進行深度比較,如果不一樣,直接干掉老的元素,新建新的         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的核心原理和用法

在這里插入圖片描述

發現確實成功了。

如果可以進行深度比較呢?

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

如果可以深度,就執行

preRenderedUnitInstance.update(nextRenderElement)

這是什么意思?

我們當前是在執行渲染Counter的話,那preRenderedUnitInstance 是什么呢?

沒錯!它是Counter組件 執行render方法 ,再執行createUnit獲得的

React的核心原理和用法

在這里插入圖片描述

這個字符串的 unit

然后調用了這個 unit的 update方法

注意,這里 的unit是字符串的 unit,也就是說是 TextUnit

所以我們需要實現 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方法非常簡單,先判斷 渲染內容有沒有變化,有的話就 替換點字符串的內容

并把當前unit 的_currentElement 替換成最新的nextElement

我們簡單的把shouldDeepCompare 改成 return true,測試一下深度比較

function shouldDeepCompare(){     return true }

 React的核心原理和用法

一如既往成功

15. 實現shouldComponentUpdate方法

我們知道有個shouldComponentUpdate,用來決定要不要 重渲染 該組件的

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

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

而在 還是上面,實現 update的過程中,我們已經得到了nextState 和nextProps

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

所以,我們可以在update里執行shouldComponentUpdate方法,來確定要不要重新渲染組件

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

16. 實現componentDidUpdate生命周期函數

so Easy。

只要在更新后觸發這個事件就好了

class CompositeUnit extends Unit{     update(nextElement,partialState){                  if(this._componentInstance.shouldComponentUpdate && !this._componentInstance.shouldComponentUpdate(nextProps,nextState)){             return;         }             if(shouldDeepCompare(preRenderElement,nextRenderElement)){             // 如果可以進行深度比較,則把更新的工作交給上次渲染出來的那個Element元素對應的unit來處理             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. 實現shouDeepCompare

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

接著判斷 oldElement 和newElement 是否 都是 Element類型,不是的話就return false,是的 再判斷  type是否相同(即判斷是否是同個組件,是的話 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的核心原理和用法”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

荆州市| 含山县| 班玛县| 临江市| 伊春市| 新建县| 新民市| 改则县| 扶绥县| 墨江| 武威市| 丁青县| 辽阳市| 玉屏| 磴口县| 广德县| 彰化县| 贵溪市| 龙泉市| 安泽县| 彭泽县| 瑞丽市| 峨山| 五台县| 青铜峡市| 康保县| 贡觉县| 台南县| 遵义市| 延川县| 马龙县| 祁阳县| 化德县| 安岳县| 绩溪县| 尉犁县| 临安市| 谢通门县| 会昌县| 宁强县| 密山市|