您好,登錄后才能下訂單哦!
本篇內容介紹了“React的核心原理和用法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
1. 項目基本準備工作
1.1 創建項目
利用npx create-react-app my_react命令創建項目
1.2 項目結構
將一些用不到的文件刪除后,目錄變成這樣
此時的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-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
我們看下運行結果
可喜可賀!萬里長城邁出了第一步
好了,現在我們給每一個 元素打上 一個標記 ,這樣的話 就可以通過這個標記 辨別出與其他 元素的關系,也可以直接通過這標記找到該元素了。
就像下面這張圖一樣,是不是就直接看出0.0和0.1的父節點就是0了呢?
// 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
運行,發現確實標記成功了,哈哈哈
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
在這里插入圖片描述
// 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測試下
// index.js
import React from './react';
import ReactDOM from './react-dom';
ReactDOM.render(
"sunny",
document.getElementById('root')
);
// 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
在這里插入圖片描述
意料之內的成功!哈哈哈啊
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 最終的打印 期待結果
由此 ,我們只知道了,ReactElement.createElement方法將生產一個給定類型的ReactElement元素,然后這個對象被傳入 render方法,然后進行了上面講到的 createUnit和getMarkUp操作。
9. 實現React.createElement方法
經過上面的講解,我們大概已經知道React.createElement方法的作用了,現在就來看看是怎么實現的
我們創建了一個新的文件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.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方法,是要把
這樣一個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'))
發現確實成功渲染出來了,但是 屬性和 子元素還沒有,這是因為我們 還沒實現 具體 的功能。
現在我們來實現事件綁定 功能
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方法。我們來測試一下是否成功了沒有吧!
害,不出所料地成功了。
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。(細思極恐。。)
好了,測試一下
發現確實成功了。
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 生命周期函數,哈哈哈。
測試一下,成功了沒有哈
啊,一如既往的成功,可能好奇的你問我為什么每次測試都成功,那是因為,不成功也被我調試到成功了。
為了下面 實現 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 的實例添加了
鴻蒙官方戰略合作共建——HarmonyOS技術社區
_componentInstance :用了表示 當前組件的實例 (我們所寫的Counter組件)
_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){ } }
在這里插入圖片描述
發現確實成功了。
如果可以進行深度比較呢?
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獲得的
在這里插入圖片描述
這個字符串的 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 }
一如既往成功
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的核心原理和用法”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。