摘要:接收一個(gè)屬性,這個(gè)組件會(huì)讓后代組件統(tǒng)一提供這個(gè)變量值。因此對(duì)于同一個(gè)對(duì)象而言,一定是后代元素。解決方法就是把內(nèi)聯(lián)函數(shù)提取出來,如下講了這么多,我們還沒有講到其實(shí)我們已經(jīng)講完了的工作原理了。
本節(jié)主要講解以下幾個(gè)新的特性:
Context
ContextType
lazy
Suspense
錯(cuò)誤邊界(Error boundaries)
memo
想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你!
Context定義:Context 提供了一種方式,能夠讓數(shù)據(jù)在組件樹中傳遞而不必一級(jí)一級(jí)手動(dòng)傳遞。
這定義讀的有點(diǎn)晦澀,來看張圖:
假設(shè)有如上的組件層級(jí)關(guān)系,如果最底層的 Item 組件,需要最頂層的 Window 組件中的變量,那我們只能一層一層的傳遞下去。非常的繁瑣,最重要的是中間層可能不需要這些變量。
有了 Context 之后,我們傳遞變量的方式是這樣的:
Item 可以直接從 Window 中獲取變量值。
當(dāng)然這種方式會(huì)讓組件失去獨(dú)立性,復(fù)用起來更困難。不過存在即合理,一定有 Context 適用場(chǎng)景。那 Context 是如何工作的呢。
首先要有一個(gè) Context 實(shí)例對(duì)象,這個(gè)對(duì)象可以派生出兩個(gè) React 組件,分別是 Provier 和 Consumer。
Provider 接收一個(gè) value 屬性,這個(gè)組件會(huì)讓后代組件統(tǒng)一提供這個(gè)變量值。當(dāng)然后代組件不能直接獲取這個(gè)變量,因?yàn)闆]有途徑。所以就衍生出 Consumer 組件,用來接收 Provier 提供的值。
一個(gè) Provider 可以和多個(gè)消費(fèi)組件有對(duì)應(yīng)關(guān)系。多個(gè) Consumer 也可以嵌套使用,里層的會(huì)覆蓋外層的數(shù)據(jù)。
因此對(duì)于同一個(gè) Context 對(duì)象而言,Consumer 一定是 Provier 后代元素。
創(chuàng)建 Contect 方式如下:
const MyContext = React.createContext(defaultValue?);
來個(gè)實(shí)例:
import React, {createContext, Component} from "react"; const BatteryContext = createContext(); class Leaf extends Component { render() { return ({ battery => ); } } // 為了體現(xiàn)層級(jí)多的關(guān)系,增加一層 Middle 組件 class Middle extends Component { render() { returnBattery: {battery}
}} } class App extends Component { render () { return ( ) } } export default App;
上述,首先創(chuàng)建一個(gè) Context 對(duì)象 BatteryContext, 在 BatteryContext.Provider 組件中渲染 Middle 組件,為了說明一開始我們所說的多層組件關(guān)系,所以我們?cè)?Middle 組件內(nèi)不直接使用 BatteryContext.Consumer。而是在 其內(nèi)部在渲染 Leaf 組件,在 Leaf 組件內(nèi)使用 BatteryContext.Consumer 獲取BatteryContext.Provider 傳遞過來的 value 值。
運(yùn)行結(jié)果:
當(dāng) Provider 的 value 值發(fā)生變化時(shí),它內(nèi)部的所有消費(fèi)組件都會(huì)重新渲染。Provider 及其內(nèi)部 consumer 組件都不受制于 shouldComponentUpdate 函數(shù),因此當(dāng) consumer 組件在其祖先組件退出更新的情況下也能更新。
來個(gè)實(shí)例:
... class App extends Component { state = { battery: 60 } render () { const {battery} = this.state; return () } } ...
首先在 App 中的 state 內(nèi)聲明一個(gè) battery 并將其傳遞給 BatteryContext.Provider 組件,通過 button 的點(diǎn)擊事件進(jìn)減少 一 操作。
運(yùn)行效果 :
同樣,一個(gè)組件可能會(huì)消費(fèi)多個(gè) context,來演示一下:
import React, {createContext, Component} from "react"; const BatteryContext = createContext(); const OnlineContext = createContext(); class Leaf extends Component { render() { return ({ battery => ( ); } } // 為了體現(xiàn)層級(jí)多的關(guān)系,增加一層 Middle 組件 class Middle extends Component { render() { return{ online => ) }Battery: {battery}, Online: {String(online)}
}} } class App extends Component { state = { online: false, battery: 60 } render () { const {battery, online} = this.state; console.log("render") return ( ) } } export default App;
同 BatteryContext 一樣,我們?cè)诼暶饕粋€(gè) OnlineContext,并在 App state 中聲明一個(gè) online 變量,在 render 中解析出 online。如果有多個(gè) Context 的話,只要把對(duì)應(yīng)的 Provier 嵌套進(jìn)來即可,順序并不重要。同樣也加個(gè) button 來切換 online 的值。
接著就是使用 Consumer,與 Provier 一樣嵌套即可,順序一樣不重要,由于 Consumer 需要聲明函數(shù),語法稍微復(fù)雜些。
運(yùn)行結(jié)果:
接下來在 App 中注釋掉
//
在看運(yùn)行效果:
可以看出,并沒有報(bào)錯(cuò),只是 battery 取不到值。這時(shí)候 createContext() 的默認(rèn)值就派上用場(chǎng)了,用以下方式創(chuàng)建:
const BatteryContext = createContext(90);
這個(gè)默認(rèn)值的使用場(chǎng)景就是在 Consumer 找不到 Provier 的時(shí)候。當(dāng)然一般業(yè)務(wù)是不會(huì)有這種場(chǎng)景的。
ContextType... class Leaf extends Component { render() { return ({ battery => ); } } ...Battery: {battery}
}
回到一開始的實(shí)例,我們?cè)诳聪?Consuer 里面的實(shí)現(xiàn)。由于 Consumer 特性,里面的 JSX 必須是該 Consumer 的回返值。這樣的代碼就顯得有點(diǎn)復(fù)雜。我們希望在整個(gè) JSX 渲染之前就能獲取 battery 的值。所以 ContextType 就派上用場(chǎng)了。這是一個(gè)靜態(tài)變量,如下:
... class Leaf extends Component { static contextType = BatteryContext; render() { const battery = this.context; return (Battery: {battery}
); } } ...
掛載在 class 上的 contextType 屬性會(huì)被重賦值為一個(gè)由 React.createContext() 創(chuàng)建的 Context 對(duì)象。這能讓你使用 this.context 來消費(fèi)最近 Context 上的那個(gè)值。你可以在任何生命周期中訪問到它,包括 render 函數(shù)中。
你只通過該 API 訂閱單一 context。如果你想訂閱多個(gè),就只能用較復(fù)雜的寫法了。lazy 和 Supense 的使用
React.lazy 函數(shù)能讓你像渲染常規(guī)組件一樣處理動(dòng)態(tài)引入(的組件)。
首先聲明一個(gè) About 組件
import React, {Component} from "react" export default class About extends Component { render () { returnAbout} }
然后在 APP 中使用 lazy 動(dòng)態(tài)導(dǎo)入 About 組件:
import React, {Component, lazy, Suspense} from "react" const About = lazy(() => import(/*webpackChunkName: "about" */"./About.jsx")) class App extends Component { render() { return (); } } export default App;
運(yùn)行后會(huì)發(fā)現(xiàn):
因?yàn)?App 渲染完成后,包含 About 的模塊還沒有被加載完成,React 不知道當(dāng)前的 About 該顯示什么。我們可以使用加載指示器為此組件做優(yōu)雅降級(jí)。這里我們使用 Suspense 組件來解決。
只需將異步組件 About 包裹起來即可。
...Loading...
fallback 屬性接受任何在組件加載過程中你想展示的 React 元素。你可以將 Suspense 組件置于懶加載組件之上的任何位置。你甚至可以用一個(gè) Suspense 組件包裹多個(gè)異步組件。
那如果 about 組件加載失敗會(huì)發(fā)生什么呢?
上面我們使用 webpackChunkName 導(dǎo)入的名加載的時(shí)候取個(gè)一個(gè)名字 about,我們看下網(wǎng)絡(luò)請(qǐng)求,右鍵點(diǎn)擊 Block Request URL
重新加載頁面后,會(huì)發(fā)現(xiàn)整個(gè)頁面都報(bào)錯(cuò)了:
在實(shí)際業(yè)務(wù)開發(fā)中,我們肯定不能忽略這種場(chǎng)景,怎么辦呢?
錯(cuò)誤邊界(Error boundaries)如果模塊加載失敗(如網(wǎng)絡(luò)問題),它會(huì)觸發(fā)一個(gè)錯(cuò)誤。你可以通過錯(cuò)誤邊界技術(shù)來處理這些情況,以顯示良好的用戶體驗(yàn)并管理恢復(fù)事宜。
如果一個(gè) class 組件中定義了 static getDerivedStateFromError() 或 componentDidCatch() 這兩個(gè)生命周期方法中的任意一個(gè)(或兩個(gè))時(shí),那么它就變成一個(gè)錯(cuò)誤邊界。當(dāng)拋出錯(cuò)誤后,請(qǐng)使用 static getDerivedStateFromError() 渲染備用 UI ,使用 componentDidCatch() 打印錯(cuò)誤信息。
接著,借用錯(cuò)誤邊界,我們來優(yōu)化以上當(dāng)異步組件加載失敗的情況:
class App extends Component { state = { hasError: false, } static getDerivedStateFromError(e) { return { hasError: true }; } render() { if (this.state.hasError) { returnerror} return (}>Loading...
運(yùn)行效果:
memo先來看個(gè)例子:
class Foo extends Component { render () { console.log("Foo render"); return null; } } class App extends Component { state = { count: 0 } render() { return (); } }
例子很簡(jiǎn)單聲明一個(gè) Foo 組件,并在 APP 的 state 中聲明一個(gè)變量 count ,然后通過按鈕更改 count 的值。
運(yùn)行結(jié)果:
可以看出 count 值每變化一次, Foo 組件都會(huì)重新渲染一次,即使它沒有必要重新渲染,這個(gè)是我們的可以優(yōu)化點(diǎn)。
React 中提供了一個(gè) shouldComponentUpdate,如果這個(gè)函數(shù)返回 false,就不會(huì)重新渲染。在 Foo 組件中,這里判斷只要傳入的 name 屬性沒有變化,就表示不用重新渲染。
class Foo extends Component { ... shouldComponentUpdate (nextProps, nextState) { if (nextProps.name === this.props.name) { return false } return true } ... }
運(yùn)行效果:
Foo 組件不會(huì)重新渲染了。但如果我們傳入數(shù)據(jù)有好多個(gè)層級(jí),我們得一個(gè)一個(gè)的對(duì)比,顯然就會(huì)很繁瑣且冗長(zhǎng)。 其實(shí) React 已經(jīng)幫我們提供了現(xiàn)層的對(duì)比邏輯就是 PureComponent 組件。我們讓 Foo 組件繼承 PureComponent
... class Foo extends PureComponent { render () { console.log("Foo render"); return null; } } ...
運(yùn)行效果同上。但它的實(shí)現(xiàn)還是有局限性的,只有傳入屬性本身的對(duì)比,屬性的內(nèi)部發(fā)生了變化,它就搞不定了。來個(gè)粟子:
class Foo extends PureComponent { render () { console.log("Foo render"); return{this.props.person.age}; } } class App extends Component { state = { person: { count: 0, age: 1 } } render() { const {person} = this.state; return (); } }
在 App 中聲明一個(gè) person,通過點(diǎn)擊按鈕更改 person 中的age屬性,并把 person 傳遞給 Foo 組件,在 Foo 組件中顯示 age。
運(yùn)行效果:
點(diǎn)擊按鍵后,本應(yīng)該重新渲染的 Foo 組件,卻沒有重新渲染。就是因?yàn)?PureComponent 提供的 shouldComponentUpdate 發(fā)現(xiàn)的 person 本身沒有變化,才拒絕重新渲染。
所以一定要注意 PureComponent 使用的場(chǎng)景。只有傳入的 props 第一級(jí)發(fā)生變化,才會(huì)觸發(fā)重新渲染。所以要注意這種關(guān)系,不然容易發(fā)生視圖不渲染的 bug。
PureComponent 還有一個(gè)陷阱,修改一下上面的例子,把 age 的修改換成對(duì) count,然后在 Foo 組件上加一個(gè)回調(diào)函數(shù):
... return (); ...{}}/>
運(yùn)行效果:
可以看到 Foo 組件每次都會(huì)重新渲染,雖然 person 本身沒有變化,但是傳入的內(nèi)聯(lián)函數(shù)每次都是新的。
解決方法就是把內(nèi)聯(lián)函數(shù)提取出來,如下:
... callBack = () => {}...
講了這么多,我們還沒有講到 memo,其實(shí)我們已經(jīng)講完了 memo 的工作原理了。
React.memo 為高階組件。它與 React.PureComponent 非常相似,但它適用于函數(shù)組件,但不適用于 class 組件。
我們 Foo 組件并沒有相關(guān)的狀態(tài),所以可以用函數(shù)組件來表示。
... function Foo (props) { console.log("Foo render"); return{props.person.age}; } ...
接著使用 memo 來優(yōu)化 Foo 組件
... const Foo = memo(function Foo (props) { console.log("Foo render"); return{props.person.age}; }) ...
運(yùn)行效果
最后,如果你喜歡這個(gè)系列的,肯請(qǐng)大家給個(gè)贊的,我將會(huì)更有的動(dòng)力堅(jiān)持寫下去。
參考React 官方文檔
《React勁爆新特性Hooks 重構(gòu)去哪兒網(wǎng)》
交流干貨系列文章匯總?cè)缦拢X得不錯(cuò)點(diǎn)個(gè)Star,歡迎 加群 互相學(xué)習(xí)。
https://github.com/qq44924588...
我是小智,公眾號(hào)「大遷世界」作者,對(duì)前端技術(shù)保持學(xué)習(xí)愛好者。我會(huì)經(jīng)常分享自己所學(xué)所看的干貨,在進(jìn)階的路上,共勉!
關(guān)注公眾號(hào),后臺(tái)回復(fù)福利,即可看到福利,你懂的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/104610.html
摘要:粟例說明一下獲取子組件或者節(jié)點(diǎn)的句柄指向已掛載到上的文本輸入元素本質(zhì)上,就像是可以在其屬性中保存一個(gè)可變值的盒子。粟例說明一下渲染周期之間的共享數(shù)據(jù)的存儲(chǔ)上述使用聲明兩個(gè)副作用,第一個(gè)每隔一秒對(duì)加,因?yàn)橹恍鑸?zhí)行一次,所以每二個(gè)參為空數(shù)組。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! React 新特性講解及實(shí)例(一) React 新特性 Hooks 講解及實(shí)...
摘要:來個(gè)使用類形式的例子以上就不說解釋了,第一篇已經(jīng)講過了,接著將改成用的形式接著使用形式接收一個(gè)對(duì)象的返回值并返回該的當(dāng)前值。如果的返回值是函數(shù)的話,那么就可以簡(jiǎn)寫成的方式,只是簡(jiǎn)寫而已,實(shí)際并沒有區(qū)別。 本文是 React 系列的第三篇 React 新特性講解及實(shí)例(一) React 新特性 Hooks 講解及實(shí)例(二) 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著...
摘要:還可以返回另一個(gè)回調(diào)函數(shù),這個(gè)函數(shù)的執(zhí)行時(shí)機(jī)很重要。對(duì)于第二個(gè)我們可以通過返回一個(gè)回調(diào)函數(shù)來注銷事件的注冊(cè)。回調(diào)函數(shù)在視圖被銷毀之前觸發(fā),銷毀的原因有兩種重新渲染和組件卸載。 本文是 React 系列的第二篇 React 新特性講解及實(shí)例(一) 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 什么是 Hooks Hook 是 React 16.8 的新增特性。它可...
摘要:屬性是一個(gè)組件的外部輸入。只會(huì)在開發(fā)模式下進(jìn)行屬性類型檢查,當(dāng)代碼進(jìn)行生產(chǎn)發(fā)布后,為了減少額外的性能開銷,類型檢查將會(huì)被略過。某個(gè)類的實(shí)例枚舉,屬性值必須為其中的某一個(gè)值。屬性為一個(gè)數(shù)組,且數(shù)組中的元素必須符合指定類型。 在第二篇文章 《新型前端開發(fā)方式》 中有說到 React 有很爽的一點(diǎn)就是給我們一種創(chuàng)造 HTML 標(biāo)簽的能力,那么今天這篇文章就詳細(xì)講解下 React 是如何提供這...
摘要:屬性是一個(gè)組件的外部輸入。只會(huì)在開發(fā)模式下進(jìn)行屬性類型檢查,當(dāng)代碼進(jìn)行生產(chǎn)發(fā)布后,為了減少額外的性能開銷,類型檢查將會(huì)被略過。某個(gè)類的實(shí)例枚舉,屬性值必須為其中的某一個(gè)值。屬性為一個(gè)數(shù)組,且數(shù)組中的元素必須符合指定類型。 在第二篇文章 《新型前端開發(fā)方式》 中有說到 React 有很爽的一點(diǎn)就是給我們一種創(chuàng)造 HTML 標(biāo)簽的能力,那么今天這篇文章就詳細(xì)講解下 React 是如何提供這...
閱讀 1625·2021-11-17 09:33
閱讀 1201·2021-11-12 10:36
閱讀 2481·2019-08-30 15:54
閱讀 2487·2019-08-30 13:14
閱讀 2979·2019-08-26 14:05
閱讀 3345·2019-08-26 11:32
閱讀 3074·2019-08-26 10:09
閱讀 3066·2019-08-26 10:09