您好,登錄后才能下訂單哦!
本篇內容介紹了“react context優化的方法有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
我們在使用react的過程中,經常會遇到需要跨層級傳遞數據的情況。props傳遞數據應用在這種場景下會極度繁瑣,且不利于維護,于是context應運而生
官方解釋: Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過組件樹的逐層傳遞 props
在正文之前,先簡單介紹一下context的三種消費方法:
1.通過Consumer
來消費上下文
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.通過靜態變量contextType
來消費上下文
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
來消費上下文
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
既可以在類組件中使用,也可以在函數組件中使用
contextType
只能在類組件中使用
useContext
只能在函數組件中使用
這里有一個例子:
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> ); };
當我們改變value
值時,會導致Son1
、Son2
都發生重渲染,但這與我們的初衷相悖,造成了額外的開銷,我們期望做到的是Son1
不執行,Son2
重新渲染。在較長的一段時間內,我都認為是使用了context
導致Provider
下面的子組件發生了重渲染。網上也有很多解釋沒有說清楚,容易誤導人。
實際情況是value
的變化導致了Son1
、Son2
發生重渲染。如下示例: 即使我們不使用·context
,當value發生變化時,Son1
、Son2
也會重渲染。
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的時候,必然要向<globalContext.Provider value={value}>
Provider的value中傳入一個狀態,但是當狀態改變時又不可避免的造成Provider
下的所有子組件重新渲染,我們期望只有消費了上下文的子組件重新渲染,那么有什么方法能夠避免這種額外的開銷嗎?
我們知道,所有消費了context的地方,只要Provider
的value
值發生變化,都會發生重渲染.只要我們有什么辦法能夠避開父組件狀態發生變化,引起的子組件狀態發生變化,那就可以減少很多不必要的開銷。
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 })}> 點擊 </button> <TestUseContextSon2 /> </globalContext.Provider> ); }
初始化的時候,兩個console各執行一遍
點擊按鈕之后,TestUseContextSon2----render
沒有打印,Consumer----handle
打印,達到預期結果。
此處由于作者比較任性,省略100字,基本效果其實和PureComponent
一致,不做過多描述。
React.memo
既可以用于函數組件,也可以用于類組件
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 })}> 點擊 </button> <TestUseContextSon3 /> </globalContext.Provider> ); }
點擊按鈕之后,TestUseContextSon2----render
沒有打印,Consumer----handle
打印,達到預期結果。 那如果我們使用useContext來消費上下文呢?
const TestUseContextSon4 = React.memo(function (props) { const con = useContext(globalContext); console.log("TestUseContextSon4----render"); return <div>{con.num}</div>; });
點擊按鈕之后,TestUseContextSon4----render
打印,也就是說當我們使用useContext
來消費上下文的時候,整個函數組件會重新執行。而Consumer
僅僅只是局部執行,這意味更少的性能消耗。
上面所述的三種方法都存在一個弊端,Provider的直接下級組件都需要用memo
、PureComponent
、shouldComponentUpdate
處理,才能屏蔽掉父級狀態變化帶來的影響,那么有沒有一種更方便的方式呢?
代碼如下:
/** 主題 */ 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
組件,并將狀態管理也放在ThemeProvider
組件中,然后通過props.children
來引入組件子節點。
效果如下:
當我們點擊按鈕時,打印如下:
點擊按鈕,setTheme
執行,狀態由{ theme: "red" }
變為{ theme: "blue" }
,引起ThemeProvider
組件重新執行,打印ThemeProvider----- blue
,組件Son2
由于消費了上下文,重新執行,打印Son2---- blue
那么問題來了,為什么沒有打印Son4
呢?我們沒有使用memo、PureComponent等處理Son4
組件,但是它確實不會重新執行。
出現這種現象,其實是props.children
引起的,props.children
指向一個對象,這個對象中存放著<Son1 />
、<Son2 />
、<Son3 />
執行的結果,ThemeProvider
執行的時候,props.children
指向的對象沒有發生變化,只有當ContextChildren
組件重新渲染的時候,<Son1 />
、<Son2 />
、<Son3 />
才會重新執行,由于我們將狀態放置于ThemeProvider
組件中,所以ContextChildren
組件不會重新渲染,<Son1 />
、<Son2 />
、<Son3 />
也就不會重新執行,所以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
依然沒有執行
再改一下:
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
執行了,我想這是因為克隆之后指向發生變化,導致組件重新執行
“react context優化的方法有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。