react?context優(yōu)化四重奏教程示例
一、前言
我們在使用react的過程中,經(jīng)常會(huì)遇到需要跨層級(jí)傳遞數(shù)據(jù)的情況。props傳遞數(shù)據(jù)應(yīng)用在這種場景下會(huì)極度繁瑣,且不利于維護(hù),于是context應(yīng)運(yùn)而生
官方解釋: Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過組件樹的逐層傳遞 props
二、用法
在正文之前,先簡單介紹一下context的三種消費(fèi)方法:
- 1.通過
Consumer
來消費(fèi)上下文
const globalContext = React.createContext(); class TestUseContextSon1 extends React.Component { render() { return ( <globalContext.Consumer> {(value) => { return <div>{value.num}</div>; }} </globalContext.Consumer> ); } } export default class TestUseContext extends React.Component { constructor(props) { super(props); this.state = { num: 2, }; } render() { return ( <globalContext.Provider value={{ num: this.state.num }}> <TestUseContextSon1 /> </globalContext.Provider> ); } }
- 2.通過靜態(tài)變量
contextType
來消費(fèi)上下文
const globalContext = React.createContext(); class TestUseContextSon2 extends React.Component { static contextType = globalContext; render() { return <div>{this.context.num}</div>; } } export default class TestUseContext extends React.Component { ...省略... render() { return ( <globalContext.Provider value={{ num: this.state.num }}> <TestUseContextSon2 /> </globalContext.Provider> ); } }
- 3.通過hooks
useContext
來消費(fèi)上下文
const globalContext = React.createContext(); const TestUseContextSon3 = (props) => { const con = useContext(globalContext); return <div>{con.num}</div>; }; export default class TestUseContext extends React.Component { ...省略... render() { return ( <globalContext.Provider value={{ num: this.state.num }}> <TestUseContextSon3 /> </globalContext.Provider> ); } }
比較:
Consumer
既可以在類組件中使用,也可以在函數(shù)組件中使用contextType
只能在類組件中使用useContext
只能在函數(shù)組件中使用
三、缺點(diǎn)
這里有一個(gè)例子:
import React, { useState } from "react"; const globalContext = React.createContext(); const Son1 = () => { return <div>Son1</div>; }; const Son2 = () => { const value = useContext(globalContext); return <div>Son2---{value.num}</div>; }; export const Demo = () => { const [value, setValue] = useState({ num: 1 }); return ( <globalContext.Provider value={value}> <Son1 /> <Son2 /> </globalContext.Provider> ); };
當(dāng)我們改變value
值時(shí),會(huì)導(dǎo)致Son1
、Son2
都發(fā)生重渲染,但這與我們的初衷相悖,造成了額外的開銷,我們期望做到的是Son1
不執(zhí)行,Son2
重新渲染。在較長的一段時(shí)間內(nèi),我都認(rèn)為是使用了context
導(dǎo)致Provider
下面的子組件發(fā)生了重渲染。網(wǎng)上也有很多解釋沒有說清楚,容易誤導(dǎo)人。
實(shí)際情況是value
的變化導(dǎo)致了Son1
、Son2
發(fā)生重渲染。如下示例: 即使我們不使用·context
,當(dāng)value發(fā)生變化時(shí),Son1
、Son2
也會(huì)重渲染。
const Son1 = () => { return <div>Son1</div>; }; const Son2 = () => { return <div>Son2</div>; }; export const Demo = () => { const [value, setValue] = useState({ num: 1 }); return ( <Son1 /> <Son2 /> ); };
那么問題來了,我們使用context的時(shí)候,必然要向<globalContext.Provider value={value}>
Provider的value中傳入一個(gè)狀態(tài),但是當(dāng)狀態(tài)改變時(shí)又不可避免的造成Provider
下的所有子組件重新渲染,我們期望只有消費(fèi)了上下文的子組件重新渲染,那么有什么方法能夠避免這種額外的開銷嗎?
四、context優(yōu)化
我們知道,所有消費(fèi)了context的地方,只要Provider
的value
值發(fā)生變化,都會(huì)發(fā)生重渲染.只要我們有什么辦法能夠避開父組件狀態(tài)發(fā)生變化,引起的子組件狀態(tài)發(fā)生變化,那就可以減少很多不必要的開銷。
一重奏--使用PureComponent
const globalContext = React.createContext(); class TestUseContextSon2 extends React.PureComponent { constructor(props) { super(props); this.state = {}; } render() { console.log("TestUseContextSon2----render"); return ( <globalContext.Consumer> {(value) => { console.log("Consumer----handle"); return <div>{value.num}</div>; }} </globalContext.Consumer> ); } } const TestUseContext = () => { const [value, setValue] = useState({ num: 1 }); return ( <globalContext.Provider value={value}> <button onClick={() => setValue({ num: value.num + 1 })}> 點(diǎn)擊 </button> <TestUseContextSon2 /> </globalContext.Provider> ); }
初始化的時(shí)候,兩個(gè)console各執(zhí)行一遍
點(diǎn)擊按鈕之后,TestUseContextSon2----render
沒有打印,Consumer----handle
打印,達(dá)到預(yù)期結(jié)果。
二重奏--使用shouldComponentUpdate
此處由于作者比較任性,省略100字,基本效果其實(shí)和PureComponent
一致,不做過多描述。
三重奏--使用React.memo
React.memo
既可以用于函數(shù)組件,也可以用于類組件
const globalContext = React.createContext(); const TestUseContextSon3 = React.memo(function (props) { console.log("TestUseContextSon3----render"); return ( <globalContext.Consumer> {(value) => { console.log("Consumer----handle"); return <div>{value.num}</div>; }} </globalContext.Consumer> ); }); const TestUseContext = () => { const [value, setValue] = useState({ num: 1 }); return ( <globalContext.Provider value={value}> <button onClick={() => setValue({ num: value.num + 1 })}> 點(diǎn)擊 </button> <TestUseContextSon3 /> </globalContext.Provider> ); }
點(diǎn)擊按鈕之后,TestUseContextSon2----render
沒有打印,Consumer----handle
打印,達(dá)到預(yù)期結(jié)果。 那如果我們使用useContext來消費(fèi)上下文呢?
const TestUseContextSon4 = React.memo(function (props) { const con = useContext(globalContext); console.log("TestUseContextSon4----render"); return <div>{con.num}</div>; });
點(diǎn)擊按鈕之后,TestUseContextSon4----render
打印,也就是說當(dāng)我們使用useContext
來消費(fèi)上下文的時(shí)候,整個(gè)函數(shù)組件會(huì)重新執(zhí)行。而Consumer
僅僅只是局部執(zhí)行,這意味更少的性能消耗。
四重奏--Provider再封裝+props.children
上面所述的三種方法都存在一個(gè)弊端,Provider的直接下級(jí)組件都需要用memo
、PureComponent
、shouldComponentUpdate
處理,才能屏蔽掉父級(jí)狀態(tài)變化帶來的影響,那么有沒有一種更方便的方式呢?
代碼如下:
/** 主題 */ const ThemeContext = React.createContext({ theme: "red" }); const ThemeProvider = (props) => { const [theme, setTheme] = useState({ theme: "red" }); console.log("ThemeProvider-----", theme.theme); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {props.children} </ThemeContext.Provider> ); }; const Son1 = function (props) { const { setTheme } = useContext(ThemeContext); return <button onClick={() => setTheme({ theme: "blue" })}>改變主題</button>; }; const Son2 = function (props) { const { theme } = useContext(ThemeContext); console.log("Son2----", theme.theme); return <div>主題----{theme.theme}</div>; }; const Son4 = function (props) { console.log("Son4---沒有使用上下文"); return <div>沒有使用上下文</div>; }; export default class ContextChildren extends React.Component { render() { return ( <ThemeProvider> <Son1 /> <Son2 /> <Son4 /> </ThemeProvider> ); } }
在上面這段代碼中,<Son1 />
、<Son2 />
、<Son3 />
并沒有直接放到ThemeContext.Provider
組件下面,而是將該組件再次封裝成ThemeProvider
組件,并將狀態(tài)管理也放在ThemeProvider
組件中,然后通過props.children
來引入組件子節(jié)點(diǎn)。
效果如下:
當(dāng)我們點(diǎn)擊按鈕時(shí),打印如下:
點(diǎn)擊按鈕,setTheme
執(zhí)行,狀態(tài)由{ theme: "red" }
變?yōu)?code>{ theme: "blue" },引起ThemeProvider
組件重新執(zhí)行,打印ThemeProvider----- blue
,組件Son2
由于消費(fèi)了上下文,重新執(zhí)行,打印Son2---- blue
那么問題來了,為什么沒有打印Son4
呢?我們沒有使用memo、PureComponent等處理Son4
組件,但是它確實(shí)不會(huì)重新執(zhí)行。
出現(xiàn)這種現(xiàn)象,其實(shí)是props.children
引起的,props.children
指向一個(gè)對(duì)象,這個(gè)對(duì)象中存放著<Son1 />
、<Son2 />
、<Son3 />
執(zhí)行的結(jié)果,ThemeProvider
執(zhí)行的時(shí)候,props.children
指向的對(duì)象沒有發(fā)生變化,只有當(dāng)ContextChildren
組件重新渲染的時(shí)候,<Son1 />
、<Son2 />
、<Son3 />
才會(huì)重新執(zhí)行,由于我們將狀態(tài)放置于ThemeProvider
組件中,所以ContextChildren
組件不會(huì)重新渲染,<Son1 />
、<Son2 />
、<Son3 />
也就不會(huì)重新執(zhí)行,所以Son4---沒有使用上下文
沒有打印。
那如果將ThemeProvider
組件改成這樣呢?
const ThemeProvider = (props) => { const [theme, setTheme] = useState({ theme: "red" }); console.log("ThemeProvider-----", theme.theme); const content = React.Children.map(props.children, (child) => { return child; }); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {content} </ThemeContext.Provider> ); };
Son4
依然沒有執(zhí)行
再改一下:
const ThemeProvider = (props) => { const [theme, setTheme] = useState({ theme: "red" }); console.log("ThemeProvider-----", theme.theme); const content = React.Children.map(props.children, (child) => { return React.cloneElement(child); }); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {content} </ThemeContext.Provider> ); };
我們使用React.cloneElement
api克隆一下child
Son4
執(zhí)行了,我想這是因?yàn)榭寺≈笾赶虬l(fā)生變化,導(dǎo)致組件重新執(zhí)行
總結(jié)
本文簡單介紹了一下context的幾種用法,以及如何來屏蔽父級(jí)狀態(tài)變化(provider的value一般是和父級(jí)組件狀態(tài)掛鉤的)導(dǎo)致未消費(fèi)上下文的子組件重新渲染導(dǎo)致的額外開銷。
以上就是react context優(yōu)化四重奏教程示例的詳細(xì)內(nèi)容,更多關(guān)于react context 優(yōu)化教程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于React Context實(shí)現(xiàn)一個(gè)簡單的狀態(tài)管理的示例代碼
本文主要介紹了基于React Context實(shí)現(xiàn)一個(gè)簡單的狀態(tài)管理的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07React-Native左右聯(lián)動(dòng)List的示例代碼
本篇文章主要介紹了React-Native左右聯(lián)動(dòng)List的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09react-router-dom v6版本跳轉(zhuǎn)路徑的實(shí)現(xiàn)方法
這篇文章主要介紹了react-router-dom v6版本跳轉(zhuǎn)路徑的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03Input標(biāo)簽自動(dòng)校驗(yàn)功能去除實(shí)現(xiàn)
這篇文章主要為大家介紹了Input標(biāo)簽的自動(dòng)拼寫檢查功能去除實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07react 項(xiàng)目 中使用 Dllplugin 打包優(yōu)化技巧
在用 Webpack 打包的時(shí)候,對(duì)于一些不經(jīng)常更新的第三方庫,比如 react,lodash,vue 我們希望能和自己的代碼分離開,這篇文章主要介紹了react 項(xiàng)目 中 使用 Dllplugin 打包優(yōu)化,需要的朋友可以參考下2023-01-01react 報(bào)錯(cuò)Module build failed: Browserslis
這篇文章主要介紹了react 報(bào)錯(cuò)Module build failed: BrowserslistError: Unknown browser query `dead`問題的解決方法,需要的朋友可以參考下2023-06-06Webpack3+React16代碼分割的實(shí)現(xiàn)
這篇文章主要介紹了Webpack3+React16代碼分割的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03React項(xiàng)目配置axios和反向代理和process.env環(huán)境配置等問題
這篇文章主要介紹了React項(xiàng)目配置axios和反向代理和process.env環(huán)境配置等問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12