在react中,context是一個無需為每層元件手動新增props就能在元件樹之間進行資料傳遞的方法;context提供了一種在元件之間共用指定值的方式,而且不必顯式的通過元件樹的逐層傳遞props。
本教學操作環境:Windows10系統、react17.0.1版、Dell G3電腦。
Context 提供了一個無需為每層元件手動新增 props,就能在元件樹間進行資料傳遞的方法。在一個典型的 React 應用中,資料是通過 props 屬性自上而下(由父及子)進行傳遞的,但這種做法對於某些型別的屬性而言是極其繁瑣的(例如:地區偏好,UI 主題),這些屬性是應用程式中許多元件都需要的。Context 提供了一種在元件之間共用此類值的方式,而不必顯式地通過元件樹的逐層傳遞 props。
Context 設計目的是為了共用那些對於一個元件樹而言是「全域性」的資料,例如當前認證的使用者、主題或首選語言。舉個例子,在下面的程式碼中,我們通過一個 「theme」 屬性手動調整一個按鈕元件的樣式
class App extends React.Component { render() { return <Toolbar theme="dark" />; } } function Toolbar(props) { // Toolbar 元件接受一個額外的「theme」屬性,然後傳遞給 ThemedButton 元件。 // 如果應用中每一個單獨的按鈕都需要知道 theme 的值,這會是件很麻煩的事, // 因為必須將這個值層層傳遞所有元件。 return ( <p> <ThemedButton theme={props.theme} /> </p> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme} />; } } // 通過props傳遞:App -> Toolbar -> ThemedButton // 如果巢狀很深,那麼需要逐層傳遞props,即使中間不需要該props,顯得很繁瑣
使用 context, 我們可以避免通過中間元素傳遞 props
// Context 可以讓我們無須明確地傳遍每一個元件,就能將值深入傳遞進元件樹。 // 為當前的 theme 建立一個 context("light"為預設值)。 const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // 使用一個 Provider 來將當前的 theme 傳遞給以下的元件樹。 // 無論多深,任何元件都能讀取這個值。 // 在這個例子中,我們將 「dark」 作為當前的值傳遞下去。 return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // 中間的元件再也不必指明往下傳遞 theme 了。 function Toolbar() { return ( <p> <ThemedButton /> </p> ); } class ThemedButton extends React.Component { // 指定 contextType 讀取當前的 theme context。 // React 會往上找到最近的 theme Provider,然後使用它的值。 // 在這個例子中,當前的 theme 值為 「dark」。 static contextType = ThemeContext; render() { return <Button theme={this.context} />; } } // 也可以使用 ThemedButto.contextType = ThemeContext;
React.createContext
const MyContext = React.createContext(defaultValue);
建立一個 Context 物件。當 React 渲染一個訂閱了這個 Context 物件的元件,這個元件會從元件樹中離自身最近的那個匹配的 Provider
中讀取到當前的 context 值。
只有當元件所處的樹中沒有匹配到 Provider 時,其 defaultValue
引數才會生效。這有助於在不使用 Provider 包裝元件的情況下對元件進行測試。注意:將 undefined
傳遞給 Provider 的 value 時,消費元件的 defaultValue
不會生效。
Context.Provider
<MyContext.Provider value={/* 某個值 */}>
每個 Context 物件都會返回一個 Provider React 元件,它允許消費元件訂閱 context 的變化。
Provider 接收一個 value
屬性,傳遞給消費元件。一個 Provider 可以和多個消費元件有對應關係。多個 Provider 也可以巢狀使用,裡層的會覆蓋外層的資料。
當 Provider 的 value
值發生變化時,它內部的所有消費元件都會重新渲染。Provider 及其內部 consumer 元件都不受制於 shouldComponentUpdate
函數,因此當 consumer 元件在其祖先元件退出更新的情況下也能更新。
Class.contextType
掛載在 class 上的 contextType
屬性會被重賦值為一個由 React.createContext() 建立的 Context 物件。這能讓你使用 this.context
來消費最近 Context 上的那個值。你可以在任何生命週期中存取到它,包括 render 函數中
import MyContext from './MyContext'; class MyClass extends React.Component { componentDidMount() { let value = this.context; /* 在元件掛載完成後,使用 MyContext 元件的值來執行一些有副作用的操作 */ } componentDidUpdate() { let value = this.context; /* ... */ } componentWillUnmount() { let value = this.context; /* ... */ } render() { let value = this.context; /* 基於 MyContext 元件的值進行渲染 */ } // 或者如上邊例子一樣使用 static contextType = MyContext; } MyClass.contextType = MyContext;
Context.Consumer
import MyContext from './MyContext'; function ToolList() { return ( <MyContext.Consumer {value => /* 基於 context 值進行渲染*/} </MyContext.Consumer> ) }
這裡,React 元件也可以訂閱到 context 變更。這能讓你在函數式元件中完成訂閱 context。
這需要函數作為子元素(function as a child)這種做法。這個函數接收當前的 context 值,返回一個 React 節點。傳遞給函數的 value
值等同於往上元件樹離這個 context 最近的 Provider 提供的 value
值。如果沒有對應的 Provider,value
引數等同於傳遞給 createContext()
的 defaultValue
。
Context.displayName
context 物件接受一個名為 displayName
的 property,型別為字串。React DevTools 使用該字串來確定 context 要顯示的內容。
如下述元件在 DevTools 中將顯示為 MyDisplayName
const MyContext = React.createContext(/* some value */); MyContext.displayName = 'MyDisplayName'; <MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中 <MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
對於上面的 theme 例子,使用動態值(dynamic values)後更復雜的用法
theme-context.js
export const themes = { light: { foreground: '#000000', background: '#eeeeee', }, dark: { foreground: '#ffffff', background: '#222222', }, }; export const ThemeContext = React.createContext(themes.dark); // 該處為預設值
themed-button.js
import { ThemeContext } from './theme-context'; class ThemedButton extends React.Component { render() { let props = this.props; // 獲取到ThemeContext中的預設值 let theme = this.context; return ( <button {...props} style={{backgroundColor: theme.background}} /> ); } // static contextType = ThemeContext; } ThemedButton.contextType = ThemeContext; export default ThemedButton;
app.js
import { ThemeContext, themes } from './theme-context'; import ThemedButton from './themed-button'; // 一個使用 ThemedButton 的中間元件 function Toolbar(props) { return ( <ThemedButton onClick={props.changeTheme}> Change Theme </ThemedButton> ); } class App extends React.Component { constructor(props) { super(props); this.state = { theme: themes.light, }; this.toggleTheme = () => { this.setState(state => ({ theme: state.theme === themes.dark ? themes.light : themes.dark, })); }; } render() { // 在 ThemeProvider 內部的 ThemedButton 按鈕元件使用 state 中的 theme 值, // 而外部的元件使用預設的 theme 值 return ( <Page> <ThemeContext.Provider value={this.state.theme}> <Toolbar changeTheme={this.toggleTheme} /> </ThemeContext.Provider> <Section> <ThemedButton /> </Section> </Page> ); } } ReactDOM.render(<App />, document.root); // 使用ThemeContext.Provider包裹的元件,可以消費到ThemeContext中的value // 即Toolbar、ThemedButton中都可以使用this.context來獲取到value // 注意觀察,更新state的方法是通過props向下傳遞,由子孫元件觸發更新,下面會講到通過context的方式傳遞更新函數
在上面的例子中,我們通過 props 的方式向下傳遞一個更新函數,從而改變 App 中 themes 的值。我們知道,從一個在元件樹中巢狀很深的元件中更新 context 是很有必要的。在這種場景下,你可以通過 context 傳遞一個函數,使得 consumers 元件更新 context
theme-context.js
// 確保傳遞給 createContext 的預設值資料結構是呼叫的元件(consumers)所能匹配的! export const ThemeContext = React.createContext({ theme: themes.dark, toggleTheme: () => {}, // 定義更新主題的方法,向下傳遞 });
theme-toggler-button.js
import { ThemeContext } from './theme-context'; function ThemeTogglerButton() { // Theme Toggler 按鈕不僅僅只獲取 theme 值,它也從 context 中獲取到一個 toggleTheme 函數(下面app.js部分) return ( <ThemeContext.Consumer> {({theme, toggleTheme}) => ( <button onClick={toggleTheme} style={{backgroundColor: theme.background}}> Toggle Theme </button> )} </ThemeContext.Consumer> ); } export default ThemeTogglerButton;
app.js
import { ThemeContext, themes } from './theme-context'; import ThemeTogglerButton from './theme-toggler-button'; class App extends React.Component { constructor(props) { super(props); this.toggleTheme = () => { this.setState(state => ({ theme: state.theme === themes.dark ? themes.light : themes.dark, })); }; // State 也包含了更新函數,因此它會被傳遞進 context provider。 this.state = { theme: themes.light, toggleTheme: this.toggleTheme, // 定義更新函數,通過context方式向下傳遞 }; } render() { // 整個 state 都被傳遞進 provider return ( <ThemeContext.Provider value={this.state}> <Content /> </ThemeContext.Provider> ); } } function Content() { return ( <p> <ThemeTogglerButton /> </p> ); } ReactDOM.render(<App />, document.root);
為了確保 context 快速進行重渲染,React 需要使每一個 consumers 元件的 context 在元件樹中成為一個單獨的節點
// Theme context,預設的 theme 是 "light" 值 const ThemeContext = React.createContext('light'); // 使用者登入 context const UserContext = React.createContext({ name: 'Guest', }); class App extends React.Component { render() { const { signedInUser, theme } = this.props; // 提供初始 context 值的 App 元件 return ( <ThemeContext.Provider value={theme}> <UserContext.Provider value={signedInUser}> <Layout /> </UserContext.Provider> </ThemeContext.Provider> ); } } function Layout() { return ( <p> <Sidebar /> <Content /> </p> ); } // 一個元件可能會消費多個 context function Content() { return ( <ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer> ); }
如果兩個或者更多的 context 值經常被一起使用,那你可能要考慮一下另外建立你自己的渲染元件,以提供這些值。
因為 context 會使用參考標識(reference identity)來決定何時進行渲染,這裡可能會有一些陷阱,當 provider 的父元件進行重渲染時,可能會在 consumers 元件中觸發意外的渲染。舉個例子,當每一次 Provider 重渲染時,以下的程式碼會重渲染所有下面的 consumers 元件,因為 value
屬性總是被賦值為新的物件
class App extends React.Component { render() { return ( <MyContext.Provider value={{something: 'something'}}> <Toolbar /> </MyContext.Provider> ); } }
為了防止這種情況,將 value 狀態提升到父節點的 state 裡
class App extends React.Component { constructor(props) { super(props); // 多次渲染,state 會被保留,當value不變時,下面的 consumers 元件不會重新渲染 this.state = { value: {something: 'something'}, }; } render() { return ( <Provider value={this.state.value}> <Toolbar /> </Provider> ); } }
【相關推薦:、】
以上就是react中context是什麼的詳細內容,更多請關注TW511.COM其它相關文章!