使用场景
如果你在组件间传递的数据逻辑比较复杂,可以使用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); } } }
待续。。。