摘要:他們的應(yīng)用是比較復(fù)雜的,組件樹也是非常龐大,假設(shè)有一千個組件要渲染,每個耗費(fèi)一千個就是由于是單線程的,這里都在努力的干活,一旦開始,中間就不會停。
悄悄的, React v16.7 發(fā)布了。 React v16.7: No, This Is Not The One With Hooks.
最近我也一直在關(guān)注這兩個功能,就花些時間就整理了一下資料, 在此分享給大家, 希望對大家有所幫助。
引子為什么不推薦在 componentwillmount 里最獲取數(shù)據(jù)的操作呢?
這個問題被過問很多遍了, 前幾天又討論到這個問題, 就以這個作為切入點(diǎn)吧。
有些朋友可能會想, 數(shù)據(jù)早點(diǎn)獲取回來,頁面就能快點(diǎn)渲染出來呀, 提升用戶體驗(yàn), 何樂而為不為?
這個問題, 簡單回答起來就是, 因?yàn)槭强赡軙?b>調(diào)用多次。
要深入回答這個問題, 就不得不提到一個React 的核心概念: React Fiber.
一些必須要先了解的背景 React FiberReact Fiber 是在 v16 的時候引入的一個全新架構(gòu), 旨在解決異步渲染問題。
新的架構(gòu)使得使得 React 用異步渲染成為可能,但要注意,這個改變只是讓異步渲染成為可能。
但是React 卻并沒有在 v16 發(fā)布的時候立刻開啟,也就是說,React 在 v16 發(fā)布之后依然使用的是同步渲染。
不過,雖然異步渲染沒有立刻采用,F(xiàn)iber 架構(gòu)還是打開了通向新世界的大門,React v16 一系列新功能幾乎都是基于 Fiber 架構(gòu)。
說到這, 也要說一下 同步渲染 和 異步渲染.
同步渲染 和 異步渲染 同步渲染我們都知道React 是facebook 推出的, 他們內(nèi)部也在大量使用這個框架,(個人感覺是很良心了, 內(nèi)部推動, 而不是丟出去拿用戶當(dāng)小白鼠), 然后就發(fā)現(xiàn)了很多問題, 比較突出的就是渲染問題。
他們的應(yīng)用是比較復(fù)雜的, 組件樹也是非常龐大, 假設(shè)有一千個組件要渲染, 每個耗費(fèi)1ms, 一千個就是1000ms, 由于javascript 是單線程的, 這 1000ms 里 CPU 都在努力的干活, 一旦開始,中間就不會停。 如果這時候用戶去操作, 比如輸入, 點(diǎn)擊按鈕, 此時頁面是沒有響應(yīng)的。 等更新完了, 你之前的那些輸入就會啪啪啪一下子出來了。
這就是我們說的頁面卡頓, 用起來很不爽, 體驗(yàn)不好。
這個問題和設(shè)備性能沒有多大關(guān)系, 歸根結(jié)底還是同步渲染機(jī)制的問題。
目前的React 版本(v16.7), 當(dāng)組件樹很大的時候,也會出現(xiàn)這個問題, 逐層渲染, 逐漸深入,不更新完就不會停。
函數(shù)調(diào)用棧如圖所示:
因?yàn)镴avaScript單線程的特點(diǎn),每個同步任務(wù)不能耗時太長,不然就會讓程序不會對其他輸入作出相應(yīng),React的更新過程就是犯了這個禁忌,而React Fiber就是要改變現(xiàn)狀。
異步渲染Fiber 的做法是:分片。
把一個很耗時的任務(wù)分成很多小片,每一個小片的運(yùn)行時間很短,雖然總時間依然很長,但是在每個小片執(zhí)行完之后,都給其他任務(wù)一個執(zhí)行的機(jī)會,這樣唯一的線程就不會被獨(dú)占,其他任務(wù)依然有運(yùn)行的機(jī)會。 而維護(hù)每一個分片的數(shù)據(jù)結(jié)構(gòu), 就是Fiber。
用一張圖來展示Fiber 的碎片化更新過程:
中間每一個波谷代表深入某個分片的執(zhí)行過程,每個波峰就是一個分片執(zhí)行結(jié)束交還控制權(quán)的時機(jī)。
更詳細(xì)的信息可以看: Lin Clark - A Cartoon Intro to Fiber - React Conf 2017
在React Fiber中,一次更新過程會分成多個分片完成,所以完全有可能一個更新任務(wù)還沒有完成,就被另一個更高優(yōu)先級的更新過程打斷,這時候,優(yōu)先級高的更新任務(wù)會優(yōu)先處理完,而低優(yōu)先級更新任務(wù)所做的工作則會完全作廢,然后等待機(jī)會重頭再來。
因?yàn)橐粋€更新過程可能被打斷,所以React Fiber一個更新過程被分為兩個階段: render phase and commit phase.
兩個重要概念: render phase and commit phase有了Fiber 之后, react 的渲染過程不再是一旦開始就不能終止的模式了, 而是劃分成為了兩個過程: 第一階段和第二階段, 也就是官網(wǎng)所謂的 render phase and commit phase。
在 Render phase 中, React Fiber會找出需要更新哪些DOM,這個階段是可以被打斷的, 而到了第二階段commit phase, 就一鼓作氣把DOM更新完,絕不會被打斷。
兩個階段的分界點(diǎn)這兩個階段, 分界點(diǎn)是什么呢?
其實(shí)是 render 函數(shù)。 而且, render 函數(shù) 也是屬于 第一階段 render phase 的。
那這兩個 phase 包含的的生命周期函數(shù)有哪些呢?
render phase:
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
commit phase:
componentDidMount
componentDidUpdate
componentWillUnmount
因?yàn)榈谝浑A段的過程會被打斷而且“重頭再來”,就會造成意想不到的情況。
比如說,一個低優(yōu)先級的任務(wù)A正在執(zhí)行,已經(jīng)調(diào)用了某個組件的componentWillUpdate函數(shù),接下來發(fā)現(xiàn)自己的時間分片已經(jīng)用完了,于是冒出水面,看看有沒有緊急任務(wù),哎呀,真的有一個緊急任務(wù)B,接下來React Fiber就會去執(zhí)行這個緊急任務(wù)B,任務(wù)A雖然進(jìn)行了一半,但是沒辦法,只能完全放棄,等到任務(wù)B全搞定之后,任務(wù)A重頭來一遍,注意,是重頭來一遍,不是從剛才中段的部分開始,也就是說,componentWillUpdate函數(shù)會被再調(diào)用一次。
在現(xiàn)有的React中,每個生命周期函數(shù)在一個加載或者更新過程中絕對只會被調(diào)用一次;在React Fiber中,不再是這樣了,第一階段中的生命周期函數(shù)在一次加載和更新過程中可能會被多次調(diào)用!。
這里也可以回答文行開頭的那個問題了, 當(dāng)然, 在異步渲染模式?jīng)]有開啟之前, 你可以在 willMount 里做ajax (不建議)。 首先,一個組件的 componentWillMount 比 componentDidMount 也早調(diào)用不了幾微秒,性能沒啥提高,而且如果開啟了異步渲染, 這就難受了。 React 官方也意識到了這個問題,覺得有必要去勸告(威脅, 阻止)開發(fā)者不要在render phase 里寫有副作用的代碼了(副作用:簡單說就是做本函數(shù)之外的事情,比如改一個全局變量, ajax之類)。
static getDerivedStateFromProps(nextProps, prevState) { //根據(jù)nextProps和prevState計算出預(yù)期的狀態(tài)改變,返回結(jié)果會被送給setState }新的靜態(tài)方法
為了減少(避免?)一些開發(fā)者的騷操作,React v16.3,干脆引入了一個新的生命周期函數(shù) getDerivedStateFromProps, 這個函數(shù)是一個 static 函數(shù),也是一個純函數(shù),里面不能通過 this 訪問到當(dāng)前組件(強(qiáng)制避免一些有副作用的操作),輸入只能通過參數(shù),對組件渲染的影響只能通過返回值。目的大概也是讓開發(fā)者逐步去適應(yīng)異步渲染。
我們再看一下 React v16.3 之前的的生命周期函數(shù) 示意圖:
再看看16.3的示意圖:
上圖中并包含全部React生命周期函數(shù),另外在React v16發(fā)布時,還增加了一個componentDidCatch,當(dāng)異常發(fā)生時,一個可以捕捉到異常的componentDidCatch就排上用場了。不過,很快React覺著這還不夠,在v16.6.0又推出了一個新的捕捉異常的生命周期函數(shù)getDerivedStateFromError。
如果異常發(fā)生在render階段,React就會調(diào)用getDerivedStateFromError,如果異常發(fā)生在第commit階段,React會調(diào)用componentDidCatch。 這個異常可以是任何類型的異常, 捕捉到這個異常之后呢, 可以做一些補(bǔ)救之類的事情。
componentDidCatch 和 getDerivedStateFromError 的 區(qū)別componentDidCatch 和 getDerivedStateFromError 都是能捕捉異常的,那他們有什么區(qū)別呢?
我們之前說了兩個階段, render phase 和 commit phase.
render phase 里產(chǎn)生異常的時候, 會調(diào)用 getDerivedStateFromError;
在 commit phase 里產(chǎn)生異常大的時候, 會調(diào)用 componentDidCatch。
嚴(yán)格來說, 其實(shí)還有一點(diǎn)區(qū)別:
componentDidCatch 是不會在服務(wù)器端渲染的時候被調(diào)用的 而 getDerivedStateFromError 會。
背景小結(jié)啰里八嗦一大堆, 關(guān)于背景的東西就說到這, 大家只需要了解什么是Fiber: ‘ 哦, 這個這個東西是支持異步渲染的, 雖然這個東西還沒開啟’。
然后就是渲染的兩個階段:renderphase 和 commit phase.
render phase 可以被打斷, 大家不要在此階段做一些有副作用的操作,可以放心在commit phase 里做。
然后就是生命周期的調(diào)整, react 把你有可能在render phase 里做的有副作用的函數(shù)都改成了static 函數(shù), 強(qiáng)迫開發(fā)者做一些純函數(shù)的操作。
現(xiàn)在我們進(jìn)入正題: Suspense 和 Hooks。
正題 suspenseSuspense要解決的兩個問題:
代碼分片;
異步獲取數(shù)據(jù)。
剛開始的時候, React 覺得自己只是管視圖的, 代碼打包的事不歸我管, 怎么拿數(shù)據(jù)也不歸我管。 代碼都打到一起, 比如十幾M, 下載就要半天,體驗(yàn)顯然不會好到哪里去。
可是后來呢,這兩個事情越來越重要, React 又覺得, 嗯,還是要摻和一下,是時候站出來展現(xiàn)真正的技術(shù)了。
Suspense 在v16.6的時候 已經(jīng)解決了代碼分片的問題,異步獲取數(shù)據(jù)還沒有正式發(fā)布。
先看一個簡單的例子:
import React from "react"; import moment from "moment"; const Clock = () =>{moment().format("MMMM Do YYYY, h:mm:ss a")}
; export default Clock;
假設(shè)我們有一個組件, 是看當(dāng)前時間的, 它用了一個很大的第三方插件, 而我想只在用的時候再加載資源,不打在總包里。
再看一段代碼:
// Usage of Clock const Clock = React.lazy(() => { console.log("start importing Clock"); return import("./Clock"); });
這里我們使用了React.lazy, 這樣就能實(shí)現(xiàn)代碼的懶加載。 React.lazy 的參數(shù)是一個function, 返回的是一個promise. 這里返回的是一個import 函數(shù), webpack build 的時候, 看到這個東西, 就知道這是個分界點(diǎn)。 import 里面的東西可以打包到另外一個包里。
真正要用的話, 代碼大概是這個樣子的:
}> { showClock ? : null}
showClock 為 true, 就嘗試render clock, 這時候, 就觸發(fā)另一個事件: 去加載clock.js 和它里面的 lib momment。
看到這你可能覺得奇怪, 怎么還需要用個
哎嗨, 不包還真是不行。 為什么呢?
前面我們說到, 目前react 的渲染模式還是同步的, 一口氣走到黑, 那我現(xiàn)在畫到clock 這里, 但是這clock 在另外一個文件里, 服務(wù)器就需要去下載, 什么時候能下載完呢, 不知道。 假設(shè)你要花十分鐘去下載, 那這十分鐘你讓react 去干啥, 總不能一直等你吧。 Suspens 就是來解決這個問題的, 你要畫clock, 現(xiàn)在沒有,那就會拋一個異常出來,我們之前說
componentDidCatch 和 getDerivedStateFromProps, 這兩個函數(shù)就是來抓子組件 或者 子子組件拋出的異常的。
子組件有異常的時候就會往上拋,直到某個組件的 getDerivedStateFromProps 抓住這個異常,抓住之后干嘛呢, 還能干嘛呀, 忍著。 下載資源的時候會拋出一個promise, 會有地方(這里是suspense)捕捉這個promise, suspense 實(shí)現(xiàn)了getDerivedStateFromProps, getDerivedStateFromProps 捕獲到異常的時候, 一看, 哎, 小老弟,你來啦,還是個promise, 然后就等這個promise resole, resolve 完成之后呢,它會嘗試重新畫一下子組件。這時候資源已經(jīng)到本地了, 也就能畫成功了。
用偽代碼 大致實(shí)現(xiàn)一下:
getDerivedStateFromError(error) { if (isPromise(error)) { error.then(reRender); } }
以上大概就是Suspense 的原理, 其實(shí)也不是很復(fù)雜,就是利用了 componentDidCatch 和 getDerivedStateFromError, 其實(shí)剛開始在v16的時候, 是要用componentDidCatch 的, 但它畢竟是commit phase 里的東西, 還是分出來吧, 所以又加了個getDerivedStateFromError來實(shí)現(xiàn) Suspense 的功能。
這里需要注意的是 reRender 會渲染suspense 下面的所有子組件。
異步渲染什么時候開啟呢, 根據(jù)介紹說是在19年的第二個季度隨著一個小版本的升級開啟, 讓我們提前做好準(zhǔn)備。
做些什么準(zhǔn)備呢?
render 函數(shù)之前的代碼都檢查一邊, 避免一些有副作用的操作
到這, 我們說完了Suspense 的一半功能, 還有另一半: 異步獲取數(shù)據(jù)。
目前這一部分功能還沒正式發(fā)布。 那我們獲取數(shù)據(jù)還是只能在commit phase 做, 也就是在componentDidMount 里 或者 didUpdate 里做。
就目前來說, 如果一個組件要自己獲取數(shù)據(jù), 就必須實(shí)現(xiàn)為一個類組件, 而且會畫兩次, 第一次沒有數(shù)據(jù), 是空的, 你可以畫個loading, didMount 之后發(fā)請求, 數(shù)據(jù)回來之后, 把數(shù)據(jù)setState 到組件里, 這時候有數(shù)據(jù)了, 再畫一次,就畫出來了。
雖然是一個很簡答的功能, 我就想請求個數(shù)據(jù), 還要寫一堆東西, 很麻煩, 但在目前的正式版里, 不得不這么做。
但以后這種情況會得到改善, 看一段示例:
import {unstable_createResource as createResource} from "react-cache"; const resource = createResource(fetchDataApi); const Foo = () => { const result = resource.read(); return ({result}); // ...};
代碼里我們看不到任何譬如 async await 之類的操作, 看起來完全是同步的操作, 這是什么原理呢。
上面的例子里, 有個 resource.read(), 這里就會調(diào)api, 返回一個promise, 上面會有suspense 抓住, 等resolve 的時候,再畫一下, 就達(dá)到目的了。
到這,細(xì)心的同學(xué)可能就發(fā)現(xiàn)了一個問題, resource.read(); 明顯是一個有副作用的操作, 而且 render 函數(shù)又屬于render phase, 之前又說, 不建議在 render phase 里做有副作用的操作, 這么矛盾, 不是自己打臉了嗎。
這里也能看出來React 團(tuán)隊(duì)現(xiàn)在還沒完全想好, 目前放出來測試api 也是以unstable_開頭的, 不用用意還是跟明顯的: 讓大家不要寫class的組件,Suspense 能很好的支持函數(shù)式組件。
hooksReact v16.7.0-alpha 中第一次引入了 Hooks 的概念, 為什么要引入這個東西呢?
有兩個原因:
React 官方覺得 class組件太難以理解,OO(面向?qū)ο螅┨y懂了
React 官方覺得 , React 生命周期太難理解。
最終目的就是, 開發(fā)者不用去理解class, 也不用操心生命周期方法。
但是React 官方又說, Hooks的目的并不是消滅類組件。此處應(yīng)手動滑稽。
回歸正題, 我們繼續(xù)看Hooks, 首先看一下官方的API:
乍一看還是挺多的, 其實(shí)有很多的Hook 還處在實(shí)驗(yàn)階段,很可能有一部分要被砍掉, 目前大家只需要熟悉的, 三個就夠了:
useState
useEffect
useContext
useState舉個例子來看下, 一個簡單的counter :
// 有狀態(tài)類組件 class Counter extends React.Component { state = { count: 0 } increment = () => { this.setState({count: this.state.count + 1}); } minus = () => { this.setState({count: this.state.count - 1}); } render() { return (); } }{this.state.count}
// 使用useState Hook const Counter = () => { const [count, setCount] = useState(0); const increment = () => setCount(count + 1); return (); };{count}
這里的Counter 不是一個類了, 而是一個函數(shù)。
進(jìn)去就調(diào)用了useState, 傳入 0,對state 進(jìn)行初始化,此時count 就是0, 返回一個數(shù)組, 第一個元素就是 state 的值,第二個元素是更新 state 的函數(shù)。
// 下面代碼等同于: const [count, setCount] = useState(0); const result = useState(0); const count = result[0]; const setCount = result[1];
利用 count 可以讀取到這個 state,利用 setCount 可以更新這個 state,而且我們完全可以控制這兩個變量的命名,只要高興,你完全可以這么寫:
const [theCount, updateCount] = useState(0);
因?yàn)?useState 在 Counter 這個函數(shù)體中,每次 Counter 被渲染的時候,這個 useState 調(diào)用都會被執(zhí)行,useState 自己肯定不是一個純函數(shù),因?yàn)樗獏^(qū)分第一次調(diào)用(組件被 mount 時)和后續(xù)調(diào)用(重復(fù)渲染時),只有第一次才用得上參數(shù)的初始值,而后續(xù)的調(diào)用就返回“記住”的 state 值。
讀者看到這里,心里可能會有這樣的疑問:如果組件中多次使用 useState 怎么辦?React 如何“記住”哪個狀態(tài)對應(yīng)哪個變量?
React 是完全根據(jù) useState 的調(diào)用順序來“記住”狀態(tài)歸屬的,假設(shè)組件代碼如下:
const Counter = () => { const [count, setCount] = useState(0); const [foo, updateFoo] = useState("foo"); // ... }
每一次 Counter 被渲染,都是第一次 useState 調(diào)用獲得 count 和 setCount,第二次 useState 調(diào)用獲得 foo 和 updateFoo(這里我故意讓命名不用 set 前綴,可見函數(shù)名可以隨意)。
React 是渲染過程中的“上帝”,每一次渲染 Counter 都要由 React 發(fā)起,所以它有機(jī)會準(zhǔn)備好一個內(nèi)存記錄,當(dāng)開始執(zhí)行的時候,每一次 useState 調(diào)用對應(yīng)內(nèi)存記錄上一個位置,而且是按照順序來記錄的。React 不知道你把 useState 等 Hooks API 返回的結(jié)果賦值給什么變量,但是它也不需要知道,它只需要按照 useState 調(diào)用順序記錄就好了。
你可以理解為會有一個槽去記錄狀態(tài)。
正因?yàn)檫@個原因,Hooks,千萬不要在 if 語句或者 for 循環(huán)語句中使用!
像下面的代碼,肯定會出亂子的:
const Counter = () => { const [count, setCount] = useState(0); if (count % 2 === 0) { const [foo, updateFoo] = useState("foo"); } const [bar, updateBar] = useState("bar"); // ... }
因?yàn)闂l件判斷,讓每次渲染中 useState 的調(diào)用次序不一致了,于是 React 就錯亂了。
useEffect除了 useState,React 還提供 useEffect,用于支持組件中增加副作用的支持。
在 React 組件生命周期中如果要做有副作用的操作,代碼放在哪里?
當(dāng)然是放在 componentDidMount 或者 componentDidUpdate 里,但是這意味著組件必須是一個 class。
在 Counter 組件,如果我們想要在用戶點(diǎn)擊“+”或者“-”按鈕之后把計數(shù)值體現(xiàn)在網(wǎng)頁標(biāo)題上,這就是一個修改 DOM 的副作用操作,所以必須把 Counter 寫成 class,而且添加下面的代碼:
componentDidMount() { document.title = `Count: ${this.state.count}`; } componentDidUpdate() { document.title = `Count: ${this.state.count}`; }
而有了 useEffect,我們就不用寫一個 class 了,對應(yīng)代碼如下:
import { useState, useEffect } from "react"; const Counter = () => { const [count, setCount] = useState(0); useEffect(() => { document.title = `Count: ${this.state.count}`; }); return (); };{count}
useEffect 的參數(shù)是一個函數(shù),組件每次渲染之后,都會調(diào)用這個函數(shù)參數(shù),這樣就達(dá)到了 componentDidMount 和 componentDidUpdate 一樣的效果。
雖然本質(zhì)上,依然是 componentDidMount 和 componentDidUpdate 兩個生命周期被調(diào)用,但是現(xiàn)在我們關(guān)心的不是 mount 或者 update 過程,而是“after render”事件,useEffect 就是告訴組件在“渲染完”之后做點(diǎn)什么事。
讀者可能會問,現(xiàn)在把 componentDidMount 和 componentDidUpdate 混在了一起,那假如某個場景下我只在 mount 時做事但 update 不做事,用 useEffect 不就不行了嗎?
其實(shí),用一點(diǎn)小技巧就可以解決。useEffect 還支持第二個可選參數(shù),只有同一 useEffect 的兩次調(diào)用第二個參數(shù)不同時,第一個函數(shù)參數(shù)才會被調(diào)用. 所以,如果想模擬 componentDidMount,只需要這樣寫:
useEffect(() => { // 這里只有mount時才被調(diào)用,相當(dāng)于componentDidMount }, [123]);
在上面的代碼中,useEffect 的第二個參數(shù)是 [123],其實(shí)也可以是任何一個常數(shù),因?yàn)樗肋h(yuǎn)不變,所以 useEffect 只在 mount 時調(diào)用第一個函數(shù)參數(shù)一次,達(dá)到了 componentDidMount 一樣的效果。
useContext在前面介紹“提供者模式”章節(jié)我們介紹過 React 新的 Context API,這個 API 不是完美的,在多個 Context 嵌套的時候尤其麻煩。
比如,一段 JSX 如果既依賴于 ThemeContext 又依賴于 LanguageContext,那么按照 React Context API 應(yīng)該這么寫:
{ theme => ( language => { //可以使用theme和lanugage了 } ) }
因?yàn)?Context API 要用 render props,所以用兩個 Context 就要用兩次 render props,也就用了兩個函數(shù)嵌套,這樣的縮格看起來也的確過分了一點(diǎn)點(diǎn)。
使用 Hooks 的 useContext,上面的代碼可以縮略為下面這樣:
const theme = useContext(ThemeContext); const language = useContext(LanguageContext); // 這里就可以用theme和language了
這個useContext把一個需要很費(fèi)勁才能理解的 Context API 使用大大簡化,不需要理解render props,直接一個函數(shù)調(diào)用就搞定。
但是,useContext也并不是完美的,它會造成意想不到的重新渲染,我們看一個完整的使用useContext的組件。
const ThemedPage = () => { const theme = useContext(ThemeContext); return (); };
因?yàn)檫@個組件ThemedPage使用了useContext,它很自然成為了Context的一個消費(fèi)者,所以,只要Context的值發(fā)生了變化,ThemedPage就會被重新渲染,這很自然,因?yàn)椴恢匦落秩疽簿蜎]辦法重新獲得theme值,但現(xiàn)在有一個大問題,對于ThemedPage來說,實(shí)際上只依賴于theme中的color屬性,如果只是theme中的size發(fā)生了變化但是color屬性沒有變化,ThemedPage依然會被重新渲染,當(dāng)然,我們通過給Header、Content和Footer這些組件添加shouldComponentUpdate實(shí)現(xiàn)可以減少沒有必要的重新渲染,但是上一層的ThemedPage中的JSX重新渲染是躲不過去了。
說到底,useContext 需要一種表達(dá)方式告訴React:“我沒有改變,重用上次內(nèi)容好了。”
希望Hooks正式發(fā)布的時候能夠彌補(bǔ)這一缺陷。
Hooks 帶來的代碼模式改變上面我們介紹了 useState、useEffect 和 useContext 三個最基本的 Hooks,可以感受到,Hooks 將大大簡化使用 React 的代碼。
首先我們可能不再需要 class了,雖然 React 官方表示 class 類型的組件將繼續(xù)支持,但是,業(yè)界已經(jīng)普遍表示會遷移到 Hooks 寫法上,也就是放棄 class,只用函數(shù)形式來編寫組件。
對于 useContext,它并沒有為消除 class 做貢獻(xiàn),卻為消除 render props 模式做了貢獻(xiàn)。很長一段時間,高階組件和 render props 是組件之間共享邏輯的兩個武器,但如同我前面章節(jié)介紹的那樣,這兩個武器都不是十全十美的,現(xiàn)在 Hooks 的出現(xiàn),也預(yù)示著高階組件和 render props 可能要被逐步取代。
但讀者朋友,不要覺得之前學(xué)習(xí)高階組件和 render props 是浪費(fèi)時間,相反,你只有明白 React 的使用歷史,才能更好地理解 Hooks 的意義。
可以預(yù)測,在 Hooks 興起之后,共享代碼之間邏輯會用函數(shù)形式,而且這些函數(shù)會以 use- 前綴為約定,重用這些邏輯的方式,就是在函數(shù)形式組件中調(diào)用這些 useXXX 函數(shù)。
例如,我們可以寫這樣一個共享 Hook useMountLog,用于在 mount 時記錄一個日志,代碼如下:
const useMountLog = (name) => { useEffect(() => { console.log(`${name} mounted`); }, [123]); }
任何一個函數(shù)形式組件都可以直接調(diào)用這個 useMountLog 獲得這個功能,如下:
const Counter = () => { useMountLog("Counter"); ... }
對了,所有的 Hooks API 都只能在函數(shù)類型組件中調(diào)用,class 類型的組件不能用,從這點(diǎn)看,很顯然,class 類型組件將會走向消亡。
如何用Hooks 模擬舊版本的生命周期函數(shù)Hooks 未來正式發(fā)布后, 我們自然而然的會遇到這個問題, 如何把寫在舊生命周期內(nèi)的邏輯遷移到Hooks里面來。下面我們就簡單說一下,
模擬整個生命周期中只運(yùn)行一次的方法useMemo(() => { // execute only once }, []);
我們可以看到useMemo 接收兩個參數(shù), 第一個參數(shù)是一個函數(shù), 第二個參數(shù)是一個數(shù)組。
這里有個地方要注意, 就是, 第二個參數(shù)的數(shù)組里的元素和上一次執(zhí)行useMemo的第二個參數(shù)的數(shù)組的元素 完全一樣的話,那就表示沒有變化, 就不用執(zhí)行第一個參數(shù)里的函數(shù)了。 如果有不同, 說明有變化, 就執(zhí)行。
上面的例子里, 我們只傳入了一個空數(shù)組, 不會有變化, 也就是只會執(zhí)行一次。
模擬shouldComponentUpdateconst areEqual = (prevProps, nextProps) => { // 返回結(jié)果和shouldComponentUpdate正好相反 // 訪問不了state }; React.memo(Foo, areEqual);模擬componentDidMount
useEffect(() => { // 這里在mount時執(zhí)行一次 }, []);模擬componentDidUpdate
const mounted = useRef(); useEffect(() => { if (!mounted.current) { mounted.current = true; } else { // 這里只在update是執(zhí)行 } });模擬componentDidUnmount
useEffect(() => { // 這里在mount時執(zhí)行一次 return () => { // 這里在unmount時執(zhí)行一次 } }, []);未來的代碼形勢
Hooks 未來發(fā)布之后, 我們的代碼會寫成什么樣子呢? 簡單設(shè)想一下:
// Hooks之后的組件邏輯重用形態(tài) const XXXX = () => { const [xx, xxx, xxxx] = useX(); useY(); const {a, b} = useZ(); return ( <> //JSX > ); };
內(nèi)部可能用各種Hooks, 也可能包含第三方的Hooks。 分享Hooks 就是實(shí)現(xiàn)代碼重用的一種形勢。 其實(shí)現(xiàn)在已經(jīng)有人在做這方面的工作了: useHooks.com, 有興趣的朋友可以去看下。
Suspense 和 Hooks 帶來的改變Suspense 和 Hooks 發(fā)布后, 會帶來什么樣的改變呢? 毫無疑問, 未來的組件, 更多的將會是函數(shù)式組件。
原因很簡單, 以后大家分享出來的都是Hooks,這東西只能在函數(shù)組件里用啊, 其他地方用不了,后面就會自然而然的發(fā)生了。
但函數(shù)式組件和函數(shù)式編程還不是同一個概念。 函數(shù)式編程必須是純的, 沒有副作用的, 函數(shù)式組件里, 不能保證, 比如那個resource.read(), 明顯是有副作用的。
關(guān)于好壞既然這兩個東西是趨勢, 那這兩個東西到底好不好呢 ?
個人理解, 任何東西都不是十全十美。 既然大勢所趨, 我們就努力去了解它,學(xué)會它, 努力用它好的地方, 避免用不好的地方。
React 發(fā)布路線圖最新的消息: https://reactjs.org/blog/2018...
React 16.6 with Suspense for Code Splitting (already shipped)
A minor 16.x release with React Hooks (~Q1 2019)
A minor 16.x release with Concurrent Mode (~Q2 2019)
A minor 16.x release with Suspense for Data Fetching (~mid 2019)
明顯能夠看到資源在往 Suspense 和 Hooks 傾斜。
結(jié)語看到這, 相信大家都Suspense 和 Hooks 都有了一個大概的了解了。
收集各種資料花費(fèi)了挺長時間,大概用了兩三天寫出來,中間參考了很多資料, 一部分是摘錄到了上面的內(nèi)容里。
在這里整理分享一下, 希望對大家有所幫助。
才疏學(xué)淺, 難免會有紕漏, 歡迎指正:)。
參考資料https://reactjs.org/docs/hook...
https://zhuanlan.zhihu.com/ad...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/100322.html
摘要:更新日志應(yīng)對添加以編程方式收集性能測量。在和在將棄用為常見攻擊面。添加對事件的支持。在從調(diào)用時發(fā)出警告,創(chuàng)建循環(huán)。在和從錯誤的渲染器使用時發(fā)出警告。 2019年8月8日,我們發(fā)布了React 16.9。它包含幾個新功能,錯誤修正和新的棄用警告,以幫助準(zhǔn)備未來的主要版本。 showImg(https://segmentfault.com/img/bVbwoB5?w=1728&h=666)...
摘要:更新日志應(yīng)對添加以編程方式收集性能測量。在和在將棄用為常見攻擊面。添加對事件的支持。在從調(diào)用時發(fā)出警告,創(chuàng)建循環(huán)。在和從錯誤的渲染器使用時發(fā)出警告。 2019年8月8日,我們發(fā)布了React 16.9。它包含幾個新功能,錯誤修正和新的棄用警告,以幫助準(zhǔn)備未來的主要版本。 showImg(https://segmentfault.com/img/bVbwoB5?w=1728&h=666)...
摘要:在前端開發(fā)過程中,源碼解讀是必不可少的一個環(huán)節(jié),我們直接進(jìn)入主題,注意當(dāng)前版本號。注意包文件僅僅是的必要的功能性的定義,它必須要結(jié)合一起使用下是,原生環(huán)境下是。 在前端開發(fā)過程中,源碼解讀是必不可少的一個環(huán)節(jié),我們直接進(jìn)入主題,注意當(dāng)前 React 版本號 16.8.6。 注意:react 包文件僅僅是 React components 的必要的、功能性的定義,它必須要結(jié)合 React...
摘要:前言自推出之后,收到了不少追捧,很多問題也隨之而來。在出現(xiàn)之前,可以使用保存狀態(tài)和更新狀態(tài)用以應(yīng)對這種情況。為了在這個用例上追趕的腳步,的需要提供副作用隔離功能。提供了一個,可以用它接入你的風(fēng)格的。 showImg(https://segmentfault.com/img/remote/1460000019913697?w=1280&h=853); 前言 React Hooks 自推出...
摘要:引言于發(fā)布版本,時至今日已更新到,且引入了大量的令人振奮的新特性,本文章將帶領(lǐng)大家根據(jù)更新的時間脈絡(luò)了解的新特性。其作用是根據(jù)傳遞的來更新。新增等指針事件。 1 引言 于 2017.09.26 Facebook 發(fā)布 React v16.0 版本,時至今日已更新到 React v16.6,且引入了大量的令人振奮的新特性,本文章將帶領(lǐng)大家根據(jù) React 更新的時間脈絡(luò)了解 React1...
閱讀 649·2021-11-25 09:43
閱讀 1668·2021-11-18 10:02
閱讀 1041·2021-10-15 09:39
閱讀 1890·2021-10-12 10:18
閱讀 2122·2021-09-22 15:43
閱讀 773·2021-09-22 15:10
閱讀 2088·2019-08-30 15:53
閱讀 988·2019-08-30 13:00