此篇文章為看過 Scrimba 線上課程 (The Frontend Developer Career Path) 之教學影片後的筆記整理,內容與例子大多出自該教學影片。
簡介
React 中的 component 傳遞是由上往下的,無法在同級的 component 間 / 在其他的分支間傳遞。如果要在同級間傳遞,就要將 state 提升到上一層 component,如果在不同分支,就要一直往上提升到兩個 component 間都有共用的為止。往上提升後,再把 props 一層一層往下傳,傳到天荒地老。
Context 可以解決這種狀況。提供資料的稱為 Provider ,使用資料的為 Consumer。把需要共用的資料包在 Provider 裡,需要調用資料的用 Comsumer 包起來。Consumer 不用通過中間一層一層的傳遞,就可以直接使用 Provider 中的資料。除了 Data , Method 也可以通過這個方法傳遞,如果當某個 components 更新後,需要同時將共用這個 method 的 component 一併更新也可以做到。
使用方式
用 Provider 把 包起來
1 2 3 4 5 6
| const Context名字 = React.createContext()
<Context名字.Provider value={"dark"}> <App /> </Context名字.Provider>
|
Consumer 調用 data
class component
- 方法一 : 在component 外引用
1 2 3 4
| 需要引用資料的 component.contextType = 被創造的 provider
Button.contextType = ThemeContext
|
- 方法二 : 在 conponent 內引用 ( render() 前)
1 2 3 4
| static contextType = 被創造的 provider
static contextType = ThemeContext
|
function component
1 2 3
| <Context名字.Consumer> function </Context名字.Consumer>
|
- 使用 <Context名字.Consumer> 包起來,裡面必須要是 function
- 是 render props pattern
🌰 栗子 : 使用 context 處理點擊 button 後轉換主題的效果
基本的 context 架構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
import React from "react"
import Header from "./Header" import Button from "./Button"
function App() { return ( <div> <Header /> <Button /> </div> ) }
export default App
|
1 2 3 4 5 6
|
import React from "react" const ThemeContext = React.createContext() export default ThemeContext
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import ThemeContext from "./ThemeContext"
ReactDOM.render( <ThemeContext.Provider value={"dark"}> <App /> </ThemeContext.Provider>, document.getElementById('root') )
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
import React from "react" import ThemeContext from "./ThemeContext"
function Button(props) { return ( <ThemeContext.Consumer> {theme => ( <button className={`${theme}-theme`}>Switch Theme</button> )} </ThemeContext.Consumer> ) }
export default Button
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
import React from "react" import ThemeContext from "./ThemeContext"
function Header (){ return ( <ThemeContext.Consumer> {theme => ( <header className={`${theme}-theme`}> <h2>{theme === "light" ? "Light" : "Dark"} Theme</h2> </header> )} </ThemeContext.Consumer> ) }
export default Header
|
截至這裡,效果如下。如果將 <ThemeContext.Provider value={"dark"}>
更換成 “light” 就會是 Light Theme. 現在要添加 switch button 切換主題的功能。如果要實現這個功能,需要使用到 state ,但現在 Context Provider 沒有自己的 component,因此要處理這個部分。
把 Context Provider 移到自己的 component
現在要把 Context Provider 移到自己的 component,後續才能在該 component 裡處理 state.
1 2 3 4 5 6
|
import React from "react" const ThemeContext = React.createContext() export default ThemeContext
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
import React, { Component } from "react" const {Provider, Consumer} = React.createContext()
class ThemeContextProvider extends Component { render() { return ( <Provider value={"light"}> {this.props.children} </Provider> ) } }
export {ThemeContextProvider, Consumer as ThemeContextConsumer}
|
第 7 行 : 創造 ThemeContextProvider component, 這個 component 是之後要在 index.js 中被引用的。index.js 中引入的是 <ThemeContext.Provider> , 所以才這個 component 中要放入 <ThemeContext.Provider>.
第 11 行 : 確保所有 children 都會被 render.
第 5 行 : ThemeContext 本身就帶有 ThemeContext.Provider
以及 ThemeContext.Consumer
,因此可以寫成 {Provider, Consumer}
,
第 10 行 : 同時 return 內的 <ThemeContext.Provider> 就可以只寫成 < Provider >.
第 12 行 : 要把 ThemeContextProvider export 出去,但不能只寫 export default ThemeContextProvider
, 這樣只會 export 這個 component. Header.js & Button.js 會用到 ThemeContext
,因此要把兩個都 export 出去。
第 17 行 : 在export 時,除了 ThemeContextProvider , Consumer 會以 ThemeContextConsumer
export 出去。
由於 ThemeContext.js 中 export 出去的東西改變了,因此其他文件中也要修改。修改完後,結果會與上面的一樣。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
import React from "react" import {ThemeContextConsumer} from "./ThemeContext"
function Button(props) { return ( <ThemeContextConsumer> {theme => ( <button className={`${theme}-theme`}>Switch Theme</button> )} </ThemeContextConsumer> ) }
export default Button
|
修改 context : 增加 state
在 ThemeContext.js 中增加 state 以及轉換 theme 的 function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
import React, { Component } from "react" const {Provider, Consumer} = React.createContext()
class ThemeContextProvider extends Component { state = { theme : "light" }
toggleTheme = () =>{ this.setState(prevstate =>{ return{ theme : prevstate.theme === "light" ? "dark":"light" } }) } render() { return ( <Provider value={{theme: this.state.theme, toggleTheme: this.toggleTheme}}> {this.props.children} </Provider> ) } }
export {ThemeContextProvider, Consumer as ThemeContextConsumer}
|
把 state 和 function toggleTheme 連接到 Button.js, Header.js 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
import React from "react" import {ThemeContextConsumer} from "./ThemeContext"
function Button(props) { return ( <ThemeContextConsumer> {context => ( <button onClick={context.toggleTheme} className={`${context.theme}-theme`}>Switch Theme</button> )} </ThemeContextConsumer> ) }
export default Button
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
import React from "react" import {ThemeContextConsumer} from "./ThemeContext"
function Header (){ return ( <ThemeContextConsumer> {context => ( <header className={`${context.theme}-theme`}> <h2>{context.theme === "light" ? "Light" : "Dark"} Theme</h2> </header> )} </ThemeContextConsumer> ) }
export default Header
|
🌰 另一個栗子 : 使用 context 處理讓用戶更換 username
基本的 context 架構
1 2 3 4 5 6
| import React from "react"
const UserContext = React.createContext() export default UserContext
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import React from "react" import ReactDOM from "react-dom" import "./index.css" import App from "./App" import UserContext from "./userContext"
ReactDOM.render(
<UserContext.Provider value={"Luke Skywalker"}> <App /> </UserContext.Provider>, document.getElementById("root") )
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
import React from "react" import Header from "./Header" import UserContext from "./userContext"
class App extends React.Component { static contextType = UserContext render() { const username = this.context return ( <div> <Header /> <main> <p className="main">No new notifications, {username}! 🎉</p> </main> </div> ) } }
export default App
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
import React, {Component} from "react" import UserContext from "./userContext"
function Header(props){ return( <UserContext.Consumer> {username => ( <header> <p>Welcome, {username}!</p> </header> )} </UserContext.Consumer> ) }
export default Header
|
增加功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import React from "react" import ReactDOM from "react-dom" import "./index.css" import App from "./App" import {UserContextProvider} from "./userContext"
ReactDOM.render( <UserContextProvider> <App /> </UserContextProvider>, document.getElementById("root") )
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
import React from "react"
import Header from "./Header" import {UserContextConsumer} from "./userContext"
class App extends React.Component { state = { newUsername: "" } handleChange = (e) => { const {name, value} = e.target this.setState({[name]: value}) } render() { return ( <div> <Header /> <UserContextConsumer> {({username, changeUsername}) => ( <main> <p className="main">No new notifications, {username}! 🎉</p> <input type="text" name="newUsername" placeholder="New username" value={this.state.newUsername} onChange={this.handleChange} /> <br/> <button onClick={() => changeUsername(this.state.newUsername)}>Change Username</button> </main> )} </UserContextConsumer> </div> ) } }
export default App
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
import React, { Component } from "react" const { Provider, Consumer } = React.createContext()
class UserContextProvider extends Component { state = { username: "Luke Skywalker" } changeUsername = (username) => { this.setState({username}) } render () { const {username} = this.state return ( <Provider value={{username, changeUsername : this.changeUsername}}> {this.props.children} </Provider> ) } }
export { UserContextProvider, Consumer as UserContextConsumer }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
import React from "react" import {UserContextConsumer} from "./userContext"
function Header(){ return ( <header> <UserContextConsumer> {({username}) => ( <p>Welcome, {username}!</p> )} </UserContextConsumer> </header> ) } export default Header
|
特別情況小栗子 🌰
- 相同的 component 一個受 Provider 影響,另一個不
- 不在該 component 本身處理 context
- 可以在引入該 component 的地方做
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import React from "react"
import Header from "./Header" import Button from "./Button" import ThemeContext from "./ThemeContext"
function App() { return ( <div> <Header />
<ThemeContext.Consumer> {theme =>( <Button theme={theme}/> )} </ThemeContext.Consumer>
<Button theme="light"/>
</div> ) }
export default App
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
import React from "react" import PropTypes from "prop-types" import ThemeContext from "./ThemeContext"
function Button(props) { return ( <button className={`${props.theme}-theme`}>Switch Theme</button> ) }
Button.propTypes = { theme: PropTypes.oneOf(["light", "dark"]) }
Button.defaultProps = { theme: "light" }
export default Button
|
這篇筆記為個人學習記錄,若有錯誤或是可以改進的地方再麻煩各位大大指點(鞠躬
參考資料
上下文(Context)
聊一聊我对 React Context 的理解以及应用