使用場景
如果你在組件間傳遞的數據邏輯比較復雜,可以使用redux;
如果組件層級不多,可以使用props;
如果層級較深,數據邏輯簡單,可以使用context或者發布-訂閱模式。
在 React 16.3 之前,Context API 由於存在種種局限性,並不被 React 官方提倡使用,開發者更多的是把它作為一個概念來探討。而從 v 16.3.0 開始,React 對 Context API 進行了改進,新的 Context API 具備更強的可用性。
以下是代碼演示:
1.創建一個Context,const ThemeContext = React.createContext('light')
2.在父組件中用ThemeContext.Provider包裹子組件,用value傳遞數據
3.如果子組件是函數組件,要使用context時,需要用ThemeContext.Consumer包裹,通過value拿到數據;如果子組件是類組件,要使用context時,需要指定 contextType 讀取當前的 theme context,這有兩種方式,一種是在類組件中聲明靜態屬性static contextType = ThemeContext,另一種是在組件外定義ThemedButton.contextType = ThemeContext,通過this.context拿到數據
import React from 'react' // 創建 Context 填入默認值(任何一個 js 變量) const ThemeContext = React.createContext('light') let { Consumer, Provider } = ThemeContext; // 底層組件 - 函數是組件 function ThemeLink(props) { // const theme = this.context // 會報錯。函數式組件沒有實例,即沒有 this // 函數式組件可以使用 Consumer return <Consumer> {value => <p>link's theme is {value}</p>} </Consumer> } // 底層組件 - class 組件 class ThemedButton extends React.Component { // 指定 contextType 讀取當前的 theme context。 // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext render() { const theme = this.context // React 會往上找到最近的 theme Provider,然后使用它的值。 return <div> <p>button's theme is {theme}</p> </div> } } ThemedButton.contextType = ThemeContext // 指定 contextType 讀取當前的 theme context。 // 中間的組件再也不必指明往下傳遞 theme 了。 function Toolbar(props) { return ( <div> <ThemedButton /> <ThemeLink /> </div> ) } class App extends React.Component { constructor(props) { super(props) this.state = { theme: 'light' } } render() { // 用Provider包起來 return <Provider value={this.state.theme}> <Toolbar /> <hr /> <button onClick={this.changeTheme}>change theme</button> </Provider> } changeTheme = () => { this.setState({ theme: this.state.theme === 'light' ? 'dark' : 'light' }) } } export default App
發布-訂閱模式
class myEventEmitter { constructor() { // eventMap 用來存儲事件和監聽函數之間的關系 this.eventMap = {}; } // type 這里就代表事件的名稱 on(type, handler) { // hanlder 必須是一個函數,如果不是直接報錯 if (!(handler instanceof Function)) { throw new Error("哥 你錯了 請傳一個函數"); } // 判斷 type 事件對應的隊列是否存在 if (!this.eventMap[type]) { // 若不存在,新建該隊列 this.eventMap[type] = []; } // 若存在,直接往隊列里推入 handler this.eventMap[type].push(handler); } // 別忘了我們前面說過觸發時是可以攜帶數據的,params 就是數據的載體 emit(type, params) { // 假設該事件是有訂閱的(對應的事件隊列存在) if (this.eventMap[type]) { // 將事件隊列里的 handler 依次執行出隊 this.eventMap[type].forEach((handler, index) => { // 注意別忘了讀取 params handler(params); }); } } off(type, handler) { if (this.eventMap[type]) { // >>> 無符號位移 自然數(大於等於0的整數)>>>0 還是該自然數 // -1 >>> 0 =4294967295 對數組沒有影響 // 關於位移相關內容,查看二進制和位移知識篇 this.eventMap[type].splice(this.eventMap[type].indexOf(handler) >>> 0, 1); } } }
待續。。。