網(wǎng)站怎么做彈幕播放器最好的免費(fèi)建站網(wǎng)站
概述
- react 在渲染過程中要做很多事情,所以不可能直接通過初始元素直接渲染
- 還需要一個(gè)東西,就是虛擬節(jié)點(diǎn),暫不涉及React Fiber的概念,將vDom樹和Fiber 樹統(tǒng)稱為虛擬節(jié)點(diǎn)
- 有了初始元素后,React 就會(huì)根據(jù)初始元素和其他可以生成虛擬節(jié)點(diǎn)的東西生成虛擬節(jié)點(diǎn)
- React一定是通過虛擬節(jié)點(diǎn)來進(jìn)行渲染的
常用節(jié)點(diǎn)類型
- 除了初始元素能生成虛擬節(jié)點(diǎn)以外,還有哪些可能生成虛擬節(jié)點(diǎn)?總共有多少節(jié)點(diǎn)類型?
1. Dom節(jié)點(diǎn) (ReactDomComponent)
- 此dom非彼dom, 這里的dom指的是虛擬dom節(jié)點(diǎn),當(dāng)初始化元素的type屬性為字符串的時(shí)候
- React 就會(huì)創(chuàng)建虛擬dom節(jié)點(diǎn),例如,前面使用 jsx 直接書寫的
const B = <div></div>
- 它的屬性就是div, 可以打印出來
{ type: 'div' }
2. 組件節(jié)點(diǎn) (ReactComposite)
- class組件和函數(shù)式組件
- type 有兩類:class App 或 f Test() 這種舉例
3. 文本節(jié)點(diǎn) (ReactTextNode)
- 直接書寫字符串或數(shù)字,React 會(huì)創(chuàng)建為文本節(jié)點(diǎn)
- 比如,我們可以直接用 ReactDOM.render 方法直接渲染字符串或數(shù)字
import ReactDOM from 'react-dom/client';const root = ReactDOM.createRoot(document.getElementById('root'));// root.render('一頭豬') // 創(chuàng)建文本節(jié)點(diǎn) root.render(1111); // 創(chuàng)建文本節(jié)點(diǎn)
4. 空節(jié)點(diǎn)(ReactEmpty)
- 我們平時(shí)寫 React 代碼的時(shí)候,經(jīng)常會(huì)寫三目表達(dá)式
{this.state.xxx ? <App/> : false}
- 用來進(jìn)行條件渲染,只知道為 false 就不會(huì)渲染,到底是怎么一回事?
- 其實(shí),遇到字面量 null, false, true, undefined 在 React 中均會(huì)被創(chuàng)建一個(gè)空節(jié)點(diǎn)
- 在渲染過程中,如果遇到空節(jié)點(diǎn),那么它將什么都不會(huì)做
import ReactDOM from 'react-dom/client'const root = ReactDOM.createRoot(document.getElementById('root')); // root.render(flase); // 創(chuàng)建空節(jié)點(diǎn) // root.render(true); // 創(chuàng)建空節(jié)點(diǎn) root.render(null) root.render(undefined) // 創(chuàng)建空節(jié)點(diǎn)
5. 數(shù)組節(jié)點(diǎn)(ReactArrayNode)
- 不是渲染數(shù)組本身,當(dāng)React遇到數(shù)組時(shí),會(huì)創(chuàng)建數(shù)組節(jié)點(diǎn),但是不會(huì)直接進(jìn)行渲染
- 而是將數(shù)組里的每一項(xiàng)按出來,根據(jù)不同節(jié)點(diǎn)類型去做相應(yīng)的事情
- 所以,數(shù)組里的每一項(xiàng)只能是這里提到的五個(gè)節(jié)點(diǎn)類型
渲染過程
- 通過 document.createElement 創(chuàng)建的元素就是真實(shí)的dom
- React 的工作是通過初始元素或可以生成虛擬節(jié)點(diǎn)的東西生成虛擬節(jié)點(diǎn)然后針對(duì)不同的節(jié)點(diǎn)類型去做不同的事情最終生成真實(shí)dom掛載到頁面上
- 渲染原理
- 初始元素和可以生成虛擬節(jié)點(diǎn)的東西
- 虛擬節(jié)點(diǎn):根據(jù)不同的節(jié)點(diǎn)去做不同的事情
- 掛載到界面(UI)
首次渲染階段
-
React 會(huì)根據(jù)初始元素先生成虛擬節(jié)點(diǎn),然后做了一系列操作后最終渲染成真實(shí)的UI
-
根據(jù)不同的虛擬節(jié)點(diǎn)來看它到底做了些什么處理?
-
1 )初始元素-dom節(jié)點(diǎn)
- 對(duì)于初始元素的 type 屬性為字符串時(shí),React會(huì)通過 document.createElement 來創(chuàng)建真實(shí)DOM
- 因?yàn)?#xff0c;初始元素的 type 為字符串,所以直接會(huì)根據(jù) type 屬性創(chuàng)建不同的真實(shí)DOM
- 創(chuàng)建完真實(shí)DOM后立即設(shè)置該真實(shí)dom的所有屬性,比如,直接在jsx中可以直接書寫的 className, style 等都會(huì)作用到真實(shí)dom上
// jsx 語法: React初始元素 const B = <div class='wrapper' style={{color: 'red'}}><p className='text'>123</p> </div>
- 當(dāng)然 html 結(jié)構(gòu)肯定不止一層,所以,在設(shè)置完屬性后React會(huì)根據(jù)children屬性進(jìn)行遞歸遍歷
- 根據(jù)不同的 節(jié)點(diǎn)類型 去做不同的事情,同樣的,如果 children 是初始元素,創(chuàng)建真實(shí)dom、設(shè)置屬性
- 然后檢查是否有子元素,重復(fù)次步驟,移植到最后一個(gè)元素位置,遇到其他節(jié)點(diǎn)類型會(huì)做以下事情
-
2 )初始元素-組件節(jié)點(diǎn)
- 如果初始元素的 type 屬性是一個(gè) class 類 或 function 函數(shù)時(shí)
- 那么會(huì)創(chuàng)建一個(gè)組件節(jié)點(diǎn),所以,針對(duì)類或函數(shù)組件, 它的處理是不同的
- 函數(shù)組件
- 對(duì)于函數(shù)組件會(huì)直接調(diào)用函數(shù),將函數(shù)的返回值進(jìn)行遞歸處理
- 看看是什么節(jié)點(diǎn)類型,然后去做對(duì)應(yīng)的事情,所以一定要返回能生成虛擬節(jié)點(diǎn)的東西
- 最終生成一棵vDOM樹
- 類組件
- 對(duì)于類組件而言,會(huì)相對(duì)麻煩一些
- a. 首先創(chuàng)建類的實(shí)例(調(diào)用constructor)
- b. 調(diào)用生命周期方法 static getDerivedStateFromProps
- c. 調(diào)用生命周期方法 render, 根據(jù)返回值遞歸處理,跟函數(shù)組件處理返回值一樣,最終生成一棵 vDom樹
- d. 將該組件的生命周期方法 componentDidMount 加入到執(zhí)行隊(duì)列中等待真實(shí)dom掛載到頁面后執(zhí)行
- 注意
- 前面說了 render 是一個(gè)遞歸處理,所以如果一個(gè)組件存在 父子關(guān)系的時(shí)候
- 那么肯定要等子組件渲染完
- 父組件才能走出 render, 所以,子組件的 componentDidMount 一定是比父組件
- 先入隊(duì)列的,肯定先運(yùn)行
- 對(duì)于類組件而言,會(huì)相對(duì)麻煩一些
-
3 )文本節(jié)點(diǎn)
- 針對(duì)文本節(jié)點(diǎn),會(huì)直接通過 document.createTextNode 創(chuàng)建真實(shí)的文本節(jié)點(diǎn)
-
4 )空節(jié)點(diǎn)
- 如果生成的是 空節(jié)點(diǎn),那么它將什么都不會(huì)做
-
5 )數(shù)組節(jié)點(diǎn)
- 就像前面提到的一樣,React不會(huì)直接渲染數(shù)組,而是將里面的每一項(xiàng)拿出來遍歷
- 根據(jù)不同的節(jié)點(diǎn)類型去做不同的事,直到遞歸處理完數(shù)組里的每一項(xiàng) (這里流一個(gè)問題,為何數(shù)組里要寫 key)
-
注意,嵌套組件渲染時(shí)的大致執(zhí)行順序
- 先執(zhí)行父組件的 constructor, getDerivedStateFromProps, render
- 再執(zhí)行子組件的 constructor, getDerivedStateFromProps, render, componentDidMount
- 最后執(zhí)行父組件的 componentDidMount
更新與卸載
- 掛載完成后組件進(jìn)入活躍狀態(tài),等待數(shù)據(jù)的更新進(jìn)行重新渲染
- 那么到底有幾種場(chǎng)景會(huì)觸發(fā)更新?整個(gè)過程又是怎么樣的,有哪些需要注意的地方?
組件更新(setState)
- 最常見的,我們經(jīng)常用 setState 來重新設(shè)置組件的狀態(tài)進(jìn)行重新渲染
- 使用setState只會(huì)更新調(diào)用此方法的類。不會(huì)涉及到兄弟節(jié)點(diǎn)以及父級(jí)節(jié)點(diǎn)
- 影響范圍僅僅是自己的子節(jié)點(diǎn),步驟如下:
- 1 ) 運(yùn)行當(dāng)前類組件的生命周期靜態(tài)方法static getDerivedStateFromProps,根據(jù)返回值合并當(dāng)前組件的狀態(tài)
- 2 ) 運(yùn)行當(dāng)前類組件的生命周期方法shouldComponentUpdate,如果該方法返回的false,直接終止更新流程
- 3 ) 運(yùn)行當(dāng)前類組件的生命周期方法render,得到一個(gè)新的vDom樹,進(jìn)入新舊兩棵樹的對(duì)比更新
- 4 ) 將當(dāng)前類組件的生命周期方法 getSnapshotBeforeUpdate 加入執(zhí)行隊(duì)列,等待將來執(zhí)行
- 5 ) 將當(dāng)前類組件的生命周期方法 componentDidUpdate 加入執(zhí)行隊(duì)列,等待將來執(zhí)行
- 6 ) 重新生成vDom樹
- 7 ) 執(zhí)行隊(duì)列,此隊(duì)列存放的是更新過程涉及到原本存在的類組件的 生命周期 方法 getSnapshotBeforeUpdate
- 8 ) 根據(jù)vDom樹更新真實(shí)DOM
- 9 ) 執(zhí)行隊(duì)列,此隊(duì)列存放的是更新過程涉及到原本存在的類組件的 生命周期 方法 componentDidUpdate
- 10 ) 執(zhí)行隊(duì)列,此隊(duì)列存放的是更新過程中所有卸載的類組件的 生命周期方法 compoentWillUnmount
根節(jié)點(diǎn)更新(ReactDOM.createRoot().render)
- 在ReactDOM的新版本中,已經(jīng)不是直接使用 ReactDOM.render 進(jìn)行更新了
- 而是通過 createRoot (要控制的DOM區(qū)域)的返回值來調(diào)用 render
import React from 'react'; import ReactDOM from 'react-dom/client'; import'./index.css'; import App from'./App';const root = ReactDOM.createRoot(document.getElementById('root'); root.render(<App/> );
對(duì)比更新過程(diff)
- 知道了兩個(gè)更新的場(chǎng)景以及會(huì)運(yùn)行哪些生命周期方法后,我們來看一下具體的過程到底是怎么樣的。
- 所謂對(duì)比更新就是將新vDom樹跟之前首次渲染過程中保存的老vDom樹對(duì)比發(fā)現(xiàn)差異然后去做一系列操作的過程。
- 那么問題來了,如果我們?cè)谝粋€(gè)類組件中重新渲染了,React怎么知道在產(chǎn)生的新樹中它的層級(jí)呢?
- 難道是給vDom樹全部掛上一個(gè)不同的標(biāo)識(shí)來遍歷尋找更新的哪個(gè)組件嗎?
- 當(dāng)然不是,我們都知道React的diff算法將之前的復(fù)雜度0(n^3)降為了0(n)
- 它做了以下幾個(gè)假設(shè):
- 1.假設(shè)此次更新的節(jié)點(diǎn)層級(jí)不會(huì)發(fā)生移動(dòng)(直接找到舊樹中的位置進(jìn)行對(duì)比)
- 2.兄弟節(jié)點(diǎn)之間通過key進(jìn)行唯一標(biāo)識(shí)
- 3.如果新舊的節(jié)點(diǎn)類型不相同,那么它認(rèn)為就是一個(gè)新的結(jié)構(gòu)
- 比如之前是初始元素div現(xiàn)在變成了初始元素 span那么它會(huì)認(rèn)為整個(gè)結(jié)構(gòu)全部變了,
- 無論嵌套了多深也會(huì)全部丟棄重新創(chuàng)建
key的作用
-
如果列表里面有初始元素,并且沒有給初始元素添加 key那么它會(huì)警告
- Warning: Each child in a list should have a unique “key” prop. 。
-
那么 key值到底是干嘛用的呢?
- 其實(shí)key的作用非常簡單,僅僅是為了通過舊節(jié)點(diǎn)
- 尋找對(duì)應(yīng)的新節(jié)點(diǎn)進(jìn)行對(duì)比提高節(jié)點(diǎn)的復(fù)用率
-
現(xiàn)在來舉個(gè)例子,假如現(xiàn)在有五個(gè)兄弟節(jié)點(diǎn)更新后變成了四個(gè)節(jié)點(diǎn)
-
未添加key

- 添加了key

找到對(duì)比目標(biāo)-節(jié)點(diǎn)類型一致
- 經(jīng)過假設(shè)和一系列的操作找到了需要對(duì)比的目標(biāo)
- 如果發(fā)現(xiàn)節(jié)點(diǎn)類型一致,那么它會(huì)根據(jù)不同的節(jié)點(diǎn)類型做不同的事情
- 初始元素-DOM節(jié)點(diǎn)
- 如果是DOM節(jié)點(diǎn),React會(huì)直接重用之前的真實(shí)DOM
- 將這次變化的屬性記錄下來,等待將來完成更新
- 然后遍歷其子節(jié)點(diǎn)進(jìn)行遞歸對(duì)比更新
- 初始元素-組件節(jié)點(diǎn)
- 函數(shù)組件
- 如果是函數(shù)組件,React僅僅是重新調(diào)用函數(shù)拿到新的vDom樹,然后遞歸進(jìn)行對(duì)比更新
- 類組件
- 針對(duì)類組件,React也會(huì)重用之前的實(shí)例對(duì)象。后續(xù)步驟如下:
- 1.運(yùn)行生命周期靜態(tài)方法static getDerivedStateFromProps。將返回值合并當(dāng)前狀態(tài)
- 2.運(yùn)行生命周期方法shouldComponentUpdate,如果該方法返回false,終止當(dāng)前流程
- 3.運(yùn)行生命周期方法render,得到新的vDom樹,進(jìn)行新舊兩棵樹的遞歸對(duì)比更新
- 4.將生命周期方法getSnapshotBeforeUpdate加入到隊(duì)列等待執(zhí)行
- 5.將生命周期方法componentDidUpdate加入到隊(duì)列等待執(zhí)行
3.文本節(jié)點(diǎn)
- 對(duì)于文本節(jié)點(diǎn),同樣的React也會(huì)重用之前的真實(shí)文本節(jié)點(diǎn)。
- 將新的文本記錄下來,等待將來統(tǒng)一更新(設(shè)置nodeValue)
4.空節(jié)點(diǎn)
- 如果節(jié)點(diǎn)的類型都是空節(jié)點(diǎn),那么React啥都不會(huì)做
5.數(shù)組節(jié)點(diǎn)
- 首次掛載提到的,數(shù)組節(jié)點(diǎn)不會(huì)直接渲染
- 在更新階段也一樣,遍歷每一項(xiàng),進(jìn)行對(duì)比更新,然后去做不同的事
找到對(duì)比目標(biāo)-節(jié)點(diǎn)類型不一致
- 如果找到了對(duì)比目標(biāo),但是發(fā)現(xiàn)節(jié)點(diǎn)類型不一致了,這時(shí)候類型變了,那么你的子節(jié)點(diǎn)肯定也都不一樣了
- 就算一萬個(gè)子節(jié)點(diǎn),并且他們都是沒有變化的,只有最外層的父節(jié)點(diǎn)的節(jié)點(diǎn)類型變了
- 照樣會(huì)全部進(jìn)行卸載重新創(chuàng)建,與其去一個(gè)個(gè)遞歸查看子節(jié)點(diǎn),不如直接全部卸載重新創(chuàng)建
import'./App.css'; import React from 'react';function Count(props) {console.log('Count')return <h1>{props. count}</h1> }class App extends React. Component {constructor() {super()this.state={arr:[1,2,3]}this.update =this.update.bind(this)}update() {this.setState({arr: [1,2,3,4]})}render() {console.log('父親render執(zhí)行')return (<div><button onClick={this.update}>點(diǎn)我更新</button>{ this.state.arr.map((count) => <Count key={count} count={count} />) }</div>)} } export default App;
- 這個(gè)例子,初始化的時(shí)候,Count組件被初始化3次
- 而點(diǎn)擊更新的時(shí)候,Count組件更新了4次
- 這是因?yàn)樗呛瘮?shù)式組件,更新時(shí),僅僅是重新調(diào)用函數(shù),拿到新的vDOM樹
- 在react內(nèi)部加了key,可以復(fù)用的是底層的vDom的樹,而非這個(gè)函數(shù)式組件
- 函數(shù)式組件,每次渲染,都會(huì)重新執(zhí)行這個(gè)函數(shù),這里要分清兩者的區(qū)別
未找到對(duì)比目標(biāo)
- 如果未找到對(duì)比的目標(biāo),跟 節(jié)點(diǎn)類型 不一致的做法類似,
- 那么對(duì)于多出的節(jié)點(diǎn)進(jìn)行掛載流程,對(duì)于舊節(jié)點(diǎn)進(jìn)行卸載直接棄用
- 如果其包含子節(jié)點(diǎn)進(jìn)行遞歸卸載,對(duì)于初始類組件節(jié)點(diǎn)會(huì)多一個(gè)步驟,那就是運(yùn)行生命周期方法componentWillUnmount。
- 注意:
- 盡量保持結(jié)構(gòu)的穩(wěn)定性,如果未添加key的情況下
- 兄弟節(jié)點(diǎn)更新位置前后錯(cuò)位一個(gè)那么后續(xù)全部的比較都會(huì)錯(cuò)位導(dǎo)致找不到對(duì)比目標(biāo)從而進(jìn)行卸載新建流程,對(duì)性能大打折扣
總結(jié)
- 對(duì)于首次掛載階段
- 需要了解React的渲染流程
- 通過書寫的初始元素和一些其他可以生成虛擬節(jié)點(diǎn)的東西來生成虛擬節(jié)點(diǎn)
- 然后針對(duì)不同的節(jié)點(diǎn)類型去做不同的事情,最終將真實(shí)DOM掛載到頁面上
- 然后執(zhí)行渲染期間加入到隊(duì)列的一些生命周期,然后組件進(jìn)入到活躍狀態(tài)
- 對(duì)于更新卸載階段
- 需要注意的是有幾個(gè)更新的場(chǎng)景,以及key的作用到底是什么,有或沒有會(huì)產(chǎn)生多大的影響
- 還有一些小細(xì)節(jié),比如條件渲染時(shí),不要去破壞結(jié)構(gòu),盡量使用空節(jié)點(diǎn)來保持前后結(jié)構(gòu)順序的統(tǒng)一
- 重點(diǎn)是新舊兩棵樹的對(duì)比更新流程
- 找到目標(biāo),節(jié)點(diǎn)類型一致時(shí)針對(duì)不同的節(jié)點(diǎn)類型會(huì)做哪些事,類型不一致時(shí)會(huì)去卸載整個(gè)舊節(jié)點(diǎn)
- 無論有多少子節(jié)點(diǎn),都會(huì)全部遞歸進(jìn)行卸載