【D1N910】一線大廠React實踐寶典 React 與 DOM(進度 4/9)
正常操作,正常分析,大家好,我是D1N910。
在大約兩年前,我購買了?騰訊課堂【NEXT】一線大廠 React 實踐寶典 這門課。?
因為我一直基本上是使用Vue來進行前端頁面的開發(fā),但是一直沒有時間去實踐看完。 兩年沒去看了,真的很慚愧,時間,唉,過得真快啊。
為了在這門課過期之前看完,所以我要抓緊時間學(xué)完了。
本系列的專欄記錄了我學(xué)習(xí)?一線大廠React實踐寶典? 的筆記。?
下面的內(nèi)容版權(quán)歸屬是??騰訊課堂【NEXT】一線大廠 React 實踐寶典 這門課。
當(dāng)然,如果我微不足道的筆記也算是我自己的版權(quán),那么也是有我的小小一份。
你可以用來學(xué)習(xí) React。
但是如果你用來商業(yè)化盈利,請先獲得??騰訊課堂【NEXT】一線大廠 React 實踐寶典 制作方的許可。
BTW,作為挖坑狂魔,如果這篇專欄沒有更新,那么當(dāng)我沒說過。
這是一個系列的文章,如果你沒有看過前面的內(nèi)容,那么建議你先看一看


這是一個自己的約定,五一,沖沖沖!
現(xiàn)在,拿下 《React?組件》這個戰(zhàn)壕,能達到今天進度的 1/3,疲憊不堪的戰(zhàn)士喲,加油,加油!

虛擬DOM
小劇場
求知男:React很方便,視圖更新不用書寫操作 DOM 的代碼,它是怎么做到的呢。
聰明男:這個就涉及到了虛擬 DOM?的概念了,我們對?React 組件狀態(tài)的更新,可以通過虛擬 DOM 反映到實際的 DOM中。所以在 React 當(dāng)中,我們只需要維護 React 組件的狀態(tài)即可。
求知男:由面向 DOM 編程轉(zhuǎn)向面向狀態(tài)編程,虛擬 DOM 真是個好東西。
聰明男:Yep。虛擬 DOM 為了我們減少了很多復(fù)雜的 DOM 操作,而且還能夠提高性能。
概念介紹
虛擬 DOM 是提高性能的核心部分。
React 把 DOM 抽象為了 JavaScript 的對象,我們稱之為虛擬 DOM。
虛擬 DOM 本質(zhì)是一個 JavaScript 對象
React 組件在發(fā)生變換時,都會自動同步到這個虛擬 DOM 中。
修改組件 -> 反映到虛擬 DOM -> 對比、批處理等 -> 同步到實際 DOM
為什么使用虛擬 DOM
(1)性能
頻繁操作 DOM?很消耗頁面性能的。
虛擬 DOM 優(yōu)化了操作 DOM 的方式,減少了操作 DOM 的次數(shù),提高了性能。
(2)開發(fā)
直接操作 DOM 的開發(fā)方式,開發(fā)體驗不是很好;不利于項目的維護。對于每一種操作,都要寫特殊的操作 DOM 的方式,對于日后需求變更,不利于維護。
虛擬 DOM 讓開發(fā)人員避免直接操作 DOM。它提供了通用的方案,開發(fā)同學(xué)只需要維護狀態(tài)即可。
虛擬 DOM 與 DOM 的性能對比
首先界面上分別用 react 和原生 JS 渲染出兩條各有一千條數(shù)據(jù)的列表,點擊 sort 按鈕能夠重排序,且最終能夠在控制臺中打印出執(zhí)行時間
(用 console.time 和 console.timeEnd 實現(xiàn)執(zhí)行時間的記錄)

左側(cè) react 渲染的寫法

右側(cè)原生渲染的寫法

分別執(zhí)行后發(fā)現(xiàn),看起來 React?的執(zhí)行效率遠遠大于 原生JS 渲染的嘛

但其實原生 js 這邊的排序方法里的渲染方法可以優(yōu)化的。比如可以等全部執(zhí)行完了以后再進行url的賦值


這時候看起來原生 JS 進行的 DOM 操作效率就要高一些了。

誒?那這樣 React 的虛擬 DOM 有啥用哦。
首先這邊原生 JS 更快,是因為少了虛擬 DOM 的一些操作。
React 的虛擬 DOM 并不意味著就一定比原生 JS 要來得更快,它是提供了一種通用的優(yōu)化方法來幫助我們對 DOM 的進行操作。
其實我們能夠注意到,我們方才對原生 JS 的優(yōu)化,是需要每次都要專門人工去寫特殊的優(yōu)化。
如果我們某些時刻忘記了這些優(yōu)化,就會無意間導(dǎo)致性能大大降低。
而且像剛剛我們做的原生 JS 的優(yōu)化,把 innerHTML 專門抽離出來,只做一次更新,不是常見的操作。
一般這種數(shù)據(jù)更改,是類似上面的單獨操作的,比如用戶在一個購物車列表里單獨對于某幾個商品不斷地點擊添加按鈕,這樣,他們的每一次操作都會觸發(fā)頁面重繪。
這時候性能的差距就體現(xiàn)出來了。
就是前面的941ms的結(jié)果了。
頻繁直接操作 DOM,消耗會很大。但是頻繁操作虛擬 DOM,一個 JavaScript 對象,它的開銷是非常低的。
React 為我們提高了一種非常好的途徑去提高頁面的性能。
虛擬 DOM 的工作流程
在傳統(tǒng) APP下:
傳統(tǒng) App -> (構(gòu)建 / 修改) -> DOM
DOM-> (事件) -> DOM
在 React App 下:
React App?-> (構(gòu)建 / 修改) -> 虛擬?DOM?-> (比較 / 批處理,構(gòu)建 / 修改) ->? DOM
DOM -> (事件) -> 虛擬?DOM?-> (事件) ->?React App
對于React App來說,它只面對虛擬 DOM 進行操作,只需要考慮組件本身數(shù)據(jù)、狀態(tài)即可。
React 狀態(tài)變更 ->
觸發(fā) Render 方法 ->?
產(chǎn)生新的虛擬 DOM 樹 ->?
計算新舊 DOM 樹的差異(Diff 算法)->?
將差異更新到真實DOM中
Diff 算法
視頻里沒有講?Diff 算法具體怎么跑。只是說 Diff 算法從虛擬 DOM 的根結(jié)點開始遍歷,查看具體變化(新增、修改、刪除節(jié)點),最后將這個變化反饋到真實 DOM 上。視頻里強調(diào)的是 Diff 算法的作用(好處),并沒有說 Diff 算法在 React 中具體怎么實現(xiàn)的。
總結(jié)
1、Diff 提供了一種通用的方式來執(zhí)行各種操作;
2、Diff 找到了一種最小的改變方式,來避免直接改變實際?DOM
這里做個小計劃,后面會安排查看 Diff 算法的具體實現(xiàn)。
本小節(jié)沒有 【資料】Diff 算法,真的好遺憾。
但是后面的源碼講解里有這個部分的內(nèi)容,期待!
本小節(jié)完畢,代碼如下
https://github.com/D1N910/learn-react/tree/master/Chapter3/demo-10-virtual-dom

?React 與 DOM
小劇場 - React 與 DOM 的聯(lián)系
求知男:我們現(xiàn)在都是通過狀態(tài)來控制視圖,但在某些場景下,希望能夠直接操作 DOM 節(jié)點,應(yīng)該怎么做的?
聰明男:OK,你說的是什么場景呢?
求知男:例如我這里需要聚焦一個文本框,直接操作 DOM 會比較簡單。
聰明男:u1s1,確實,當(dāng)然 React 已經(jīng)想到過這些問題了,下面我們就來學(xué)習(xí)這塊內(nèi)容吧。
Refs & DOM
Refs 的概念
Refs 提供了一種方式,用于訪問在 render 方法中創(chuàng)建的 DOM 節(jié)點或者 React 元素。
父組件可以通過傳遞 Props 來操作子組件。組件本身可以通過 Refs 獲取相關(guān) DOM 節(jié)點的實例,通過 Refs 可以直接操作 DOM 來更新組件。
常見的場景舉例
(1)處理焦點
比如頁面出現(xiàn) input 輸入框的時候需要自動聚焦。
(2)文本選擇
比如選擇頁面上某些元素的文本內(nèi)容
(3)媒體控制
視頻或者音頻的播放器,使用 audio 、video 的 api。
(4)觸發(fā)強制動畫
使用實際的 dom 操作才可以進行動畫。
Refs的使用:通過Refs訪問 DOM 節(jié)點
方法1、使用 React.createRef & ref.current 來定義并獲取 Refs
1、創(chuàng)建 Ref
在構(gòu)建函數(shù)中,使用 React.createRef() 來創(chuàng)建 Ref

2、綁定 Ref
在 render 中,通過修改 ref 屬性,綁定上了 Ref

3、訪問 DOM 節(jié)點
在 componentDidMount 這個組件實際掛載到 dom 后執(zhí)行的聲明周期里,通過?
this.myRef.current 使用 DOM 節(jié)點,然后可以調(diào)用 DOM 節(jié)點上的方法。

為了能夠讓效果明顯點,我這邊設(shè)置的是,頁面渲染完畢后,讓 input 自動獲取焦點,且設(shè)置 value 值為成功。
構(gòu)建后查看頁面,發(fā)現(xiàn)方法成功了。

方法2、通過回調(diào) Refs 獲取 DOM 節(jié)點
1、初始化一個變量存儲 Ref

2、傳入回調(diào)函數(shù)直接綁定 DOM 實例
這里的 ref 實際上就是上一個方法的 this.myRef.current

3、訪問 DOM 節(jié)點(本此不需要 current)

效果也一樣,說明這種方法也成功了

【資料】Refs & 函數(shù)式組件
Refs
注:本擴展所講解的 Refs 基于 React 16 以及以后的版本
Refs 提供了一種訪問 React 組件渲染之后對應(yīng)的 DOM 節(jié)點的方式。
一般情況下,我們在以下幾種場景中可能會用到 refs:
管理焦點、文本選擇或媒體播放
觸發(fā)動畫
與第三方 DOM 庫繼承或者交互
一般來說,我們要盡可能地減少直接使用 Refs 的情況,避免將 Refs 用于可以聲明性地完成的任何操作。
創(chuàng)建 Refs
Refs 是使用 React.createRef() 創(chuàng)建的,并通過 ref 屬性附加到 React 元素。在構(gòu)造組件時,通常將 Refs 分配給實例屬性,以便可以在整個組件中引用它們。
* 參考上面的方法一
訪問 Refs
如果一個 ref 已經(jīng)被傳遞到了 render 函數(shù)的一個元素,那么一個該節(jié)點的 DOM 引用可以通過訪問 current 屬性來獲得
const node = this.myRef.current
ref 的值根據(jù)節(jié)點的類型而有所不同:
當(dāng) ref 屬性用于 HTML 元素時,在構(gòu)造函數(shù)中使用 React.createRef() 創(chuàng)建的 ref 將接收底層 DOM 元素作為其當(dāng)前屬性
當(dāng) ref 屬性用于自定義組件的時候,ref 對象將這個自定義組件的已經(jīng)掛載的實例作為其 current 屬性值
你不應(yīng)該把 ref 屬性用于函數(shù)式組件因為他們并沒有實例
函數(shù)式組件
(* 我們之前有實踐過)
React 函數(shù)式組件是相對于 React 基于 Class 類聲明的組件的另一種寫法,其具有以下的特性:
不需要聲明類,可以避免大量的譬如 extends 或者 constructor 這樣的代碼。
不需要顯式聲明 this 關(guān)鍵字,在 ES6 的類聲明中往往需要將函數(shù)的 this 關(guān)鍵字綁定到當(dāng)前作用域,而因為函數(shù)式聲明的特性,我們不需要再強制綁定。
易于理解與測試。
更佳的性能表現(xiàn):因為函數(shù)式組件中并不需要進行生命周期的管理與狀態(tài)管理,因此 React 并不需要進行某些特定的檢查或者內(nèi)存分配,從而保證了更好的性能表現(xiàn)。
下面一個基于 Class 類聲明的組件:

如果變換成函數(shù)式組件寫法,則:

另外,函數(shù)式組件也可以訪問 context,代碼如下。需要注意的是,
1、想要傳遞 context,需要定義childContextTypes
2、因為 babel 轉(zhuǎn)換問題,這種靜態(tài)的寫法是 ES7 的內(nèi)容,所以要在配置 .babelrc 配置 stage-1,這個配置之前我們是沒有的,如果沒有配置的話,可能會導(dǎo)致 babel 提示不能夠轉(zhuǎn)換 "static childContextTypes = {",?? 這個問題我糾結(jié)了 20min 才解決了,我還是太菜了。

本小節(jié)完畢,相關(guān)代碼
https://github.com/D1N910/learn-react/tree/master/Chapter3/demo-11-autofocus

React 事件語法
傳統(tǒng)事件
可以使用 onclick 綁定一個點擊方法
<button onclick="showLog()">按鈕</button>
* 使用小寫寫法
* 接受參數(shù)是字符串
React 事件
<button onClick={this.showLog}>按鈕</button>
*?使用駝峰寫法
* 接受參數(shù)是一個函數(shù)
因為 React 用的是 JSX?語法,會被編譯成 React.createElement ,然后被 React 接管的。為了識別點擊事件,在虛擬 DOM 中和實際 DOM 的點擊事件綁定起來,就需要這種寫法。
React 事件函數(shù)中的 this
需要在函數(shù)中傳入 this 的原因,是因為我們的寫法是ES6。
我們的函數(shù)一般是作為類的方法聲明的。在這樣的聲明中,默認(rèn)不會傳遞 this。
所以我們需要主動傳遞 this ,通過某種方法使得我們的事件函數(shù)的 this 主動綁定到當(dāng)前的組件。否則我們直接在函數(shù)中使用 this,會提示 undefined。
首先,函數(shù)式組件沒有 this,所以不需要額外綁定。
這里我們討論的是類定義組件。
類定義組件綁定 this 的寫法
1、bind 方法
(1)新增一個事件方法

(2)構(gòu)建函數(shù)中為事件方法綁定this

(3)事件綁定


* 也可以直接在事件綁定的時候綁定 this

2、箭頭函數(shù)
(1)新增一個事件方法

(2)添加箭頭綁定方法

?傳遞參數(shù)
1、bind 方式
bind 方式下要傳遞參數(shù),bind只能寫到事件屬性中,寫法是 this.事件函數(shù).bind(this, ...傳遞參數(shù))。
事件函數(shù)接收參數(shù)的時候,這里觸發(fā)事件的?event?永遠會隱式作為最后一個參數(shù)傳入。
例子

2、箭頭函數(shù) 方式
這里的event需要顯示傳入,當(dāng)然這里的位置排列就是我們自定義了~

【資料】React 合成事件
上面我們已經(jīng)學(xué)會了如何在 React 中綁定事件。
以我們剛剛使用的 onClick 為例子,這個 onClick 實際上是 React 的合成事件。React 并不是將 click 事件綁在該 div 的真實 DOM 上,而是類似事件代理的機制,在 document 處監(jiān)聽所有支持的事件,當(dāng)事件發(fā)生并冒泡至 document 處時,React 將事件內(nèi)容封裝并交由真正的處理函數(shù)運行。
相關(guān)流程如下:
div -> 事件冒泡 ->? document -> synthetic event -> event -> handler
其中因為 event 對象是復(fù)用的,事件處理函數(shù)執(zhí)行完后,屬性會被清空,所以 event?的屬性無法被異步訪問。

React 原生事件與合成事件
如果我們想在 React 中使用原生的 dom 事件,我們可以通過 ref 的方式拿到對應(yīng)組件生成的 dom 元素,然后在對應(yīng)的 dom 元素上綁定事件,不過一般,不推薦這種做法。
以下是一個共同使用原生事件與合成事件的 Demo。

能看出先后執(zhí)行順序,驗證了 React 其實是在事件冒泡到 document 時才開始做處理的。
如果阻止事件冒泡,那么事件是不會到達 React 的合成事件回調(diào)函數(shù)中的。

本小節(jié)完畢,相關(guān)代碼如下
https://github.com/D1N910/learn-react/tree/master/Chapter3/demo-12-handle-event

事件系統(tǒng)的運用
我想實現(xiàn)一個登錄框(小劇場)
A:想實現(xiàn)一個表單組件,有什么思路嗎?
B:可以用受控組件或者非受控組件完成
A:這兩種有什么區(qū)別嗎?
B:受控組件通過狀態(tài)控制表單,用戶的操作只會修改狀態(tài)值,如何去修改表單視圖由我們自己控制。
A:Get,那么非受控組件就是原始的表單形式吧?
B:沒錯,這種形式的表單可以通過 Refs 來獲取到,如果我們需要獲得一些值或者操作,直接修改 Dom 節(jié)點即可。
A:如何選擇呢?
B:在一般情況下,我們在使用表單的時候呢,只需要使用非受控組件即可。如果我們需要狀態(tài)同步或者組件更新,則用受控組件好一些。
受控組件
表單是 React 中很重要的領(lǐng)域。
受控組件的概念
輸入的值由 React 控制的,就叫做受控組件
用戶輸入 = (表單變化)=> newState
我們的輸入會被事件函數(shù)托管,產(chǎn)生表單變換,然后設(shè)置新的狀態(tài)去更新我們的表單值。
用戶輸入,表單值發(fā)生變化 -> 觸發(fā) onchange 事件函數(shù) -> 調(diào)用 setState 更新狀態(tài) -> 觸發(fā) render 重新渲染 -> 表單數(shù)據(jù)更新成功
我們稱符合這樣更新數(shù)據(jù)流程的表單組件為受控組件。
受控組件如何使用
1、創(chuàng)建一個表單

特點:value 和 state 相關(guān)聯(lián);onChange 綁定了事件函數(shù)。
2、添加 onChange 事件函數(shù)

通過 e.targe.value 可以獲取最新的表單值。

如果我們不設(shè)置 onChange 方法,那么就是只讀的,要設(shè)置為readyOnly

常用表單組件
textarea 標(biāo)簽
HMTL 寫法 和 React 寫法差不多

select 標(biāo)簽
HTML 寫法
是用?selected 屬性表示被選中
<select>
<option value="cat" selected>cat</option>
<option value="dog">dog</option>
<option value="horse">horse</option>
</select>
React 寫法
用 value 判斷被選中

checkbox?
React? 寫法
用 checked 判斷是否選中

radio?
React? 寫法
用 checked 判斷是否選中

通用的修改受控組件的辦法
利用 name 來進行通用的受控組件的數(shù)據(jù)變換,需要注意的是,在 checkbox 類型的情況下,value 值對應(yīng)的是 checked?來判斷復(fù)選框是否選中。

最后效果如下

非受控組件
React 表單元素 + Refs = 非受控組件
在需要的時候,通過 Refs 來獲取到輸入值。
非受控組件 -> 用戶輸入表單值發(fā)生變化 -> 通過 Refs 訪問表單節(jié)點,獲取最新表單數(shù)據(jù)。
使用方法:
1、創(chuàng)建一個表單

2、獲取表單值


非受控組件的默認(rèn)值
在受控組件中,在state里可以設(shè)置默認(rèn)值。
在非受控組件中有兩種方式
(1)text input / select 表單 / textarea 表單
使用 defaultValue

(2)checkbox / radio
使用 defaultChecked

為什么不能使用 value 綁定默認(rèn)值?
React 中,表單元素上的 value 屬性將會覆蓋 DOM 中的值。value 屬性需要搭配 onChange 屬性一起使用,否則會導(dǎo)致用戶的輸入無法更新到表單上。
在表單 UI 反饋簡單的場景中,我們可以使用非受控組件。
即時驗證的場景,例如驗證表單是否是一個郵箱、手機號碼等,這種場景需要即時反饋輸入錯誤的 ui,使用受控組件是更好的方式。
本小節(jié)結(jié)束,代碼在此:
https://github.com/D1N910/learn-react/tree/master/Chapter3/demo-13-form

Part 3: React 與 DOM?學(xué)習(xí)完畢
總體學(xué)習(xí)進度(4/9)
我的媽媽媽媽媽媽媽媽媽媽媽
本來以為現(xiàn)在這個點鐘 21:13 分
不是看到了 5?就是看到 6了,但是沒想到才剛看完!
估計是腦子不太清醒吧。
我也太摸魚了 QAQ
本次的課后項目寫一下吧
要求自己寫一個
可以對郵箱進行驗證、對詳細(xì)信息進行字?jǐn)?shù)限制、昵稱和標(biāo)題不能為空等限制
的表單驗證組件
上面的內(nèi)容都是學(xué)習(xí)自 騰訊課堂 【NEXT】學(xué)院?一線大廠React實踐寶典?
個人學(xué)習(xí)筆記
繼續(xù)加油加油(無力)
from 蛋糕