美文网首页
3、React 组件通信之 React context

3、React 组件通信之 React context

作者: world_7735 | 来源:发表于2019-10-12 03:01 被阅读0次

    React 组件之间的通信是基于 props 的单向数据流,即父组件通过 props 向子组件传值,亦或是子组件执行传入的函数来更新父组件的state,这都满足了我们大部分的使用场景。
    那 React 在兄弟组件之间如何通信呢?

    一般地,对于兄弟组件之间的通信,是通过它们共同的祖先组件进行的,即 Lifting State Up,官方文档也有介绍。组件一通过事件将状态变更通知它们共同的祖先组件,祖先组再将状态同步到组件二。

    但是,如果组件之间嵌套的比较深,即使提升状态到共同父组件,再同步状态到相应的组件还是需要一层一层的传递下去,可能会比较繁琐。

    React 官方文档中介绍了 context 的用法, 在对应的场景中,context 就可以缩短父组件到子组件的属性传递路径。具体看例子:

    Container.jsx

    import Parent from './Parent'
    import ChildOne from '../components/ChildOne'
    import ChildTwo from '../components/ChildTwo'
    
    export default class Container extends React.Component {
        constructor(props) {
            super(props);
            this.state = { value: '' }
        }
    
        changeValue = value => {
            this.setState({ value })
        }
    
        getChildContext() {
            return {
                value: this.state.value,
                changeValue: this.changeValue
            }
        }
    
        render() {
            return (
                <div>
                    <Parent>
                        <ChildOne />
                    </Parent>
                    <Parent>
                        <ChildTwo />
                    </Parent>
                </div>
            )
        }
    }
    
    Container.childContextTypes = {
        value: PropTypes.string,
        changeValue: PropTypes.func
    }
    

    Parent.jsx

    import React from "react"
    
    const Parent = (props) => (
        <div {...props} />
    )
    
    export default Parent
    

    ChildOne.jsx

    export default class ChildOne extends React.Component {
    
        handleChange = (e) => {
            const { changeValue } = this.context
            changeValue(e.target.value)
        }
    
        render() {
            return (
                <div>
                    子组件一
                    <input onChange={this.handleChange} />
                </div>
            )
        }
    }
    
    ChildOne.contextTypes = {
        changeValue: PropTypes.func
    }
    

    ChildTwo.jsx

    export default class ChildTwo extends React.Component {
        render() {
            return (
                <div>
                    子组件二
                    <p>{this.context.value}</p>
                </div>
            )
        }
    }
    
    ChildTwo.contextTypes = {
        value: PropTypes.string
    }
    

    Container.childContextTypes 中进行接口的声明,通过 getChildContext 返回更新后的state,在 Child.contextTypes 中声明要获取的接口,这样在子组件内部就能通过 this.context 获取到。通过 Context 这样一个中间对象,ChildOne 和 ChildTwo 就可以相互通信了。

    注意,React Context 也有一些局限性

    1. React Context 目前在 React 在是一个 experimental feature,在未来的版本中会有变化说不定,这点官方文档有说明。
    2. 在组件树中,如果中间某一个组件 ShouldComponentUpdate returning false 了,会阻碍 context 的正常传值,导致子组件无法获取更新。
    3. 组件本身 extends React.PureComponent 也会阻碍 context 的更新。

    解决 ShouldComponentUpdate 与 Context 之间冲突的方案也是有的,例如使用 Redux 或者 Mobx 等全局单一状态管理。
    这里抛砖引玉,介绍一种简单的解决 Context 不起作用的方法,它必须满足两个条件:

    1. Context 应该是唯一不可变的
    2. 组件只在初始化的时候去获取 Context

    具体看代码:

    // Theme stores the state of the current theme, and allows components 
    class Theme {
        constructor(color) {
            this.color = color
            this.subscriptions = []
        }
    
        setColor(color) {
            this.color = color
            this.subscriptions.forEach(f => f())
        }
    
        subscribe(f) {
            this.subscriptions.push(f)
        }
    
        unsubscribe(f) {
            this.subscriptions = this.subscriptions.filter(m => m !== f)
        }
    }
    
    export default class ThemeProvider extends React.Component {
        constructor(p, c) {
            super(p, c)
            this.theme = new Theme(this.props.color)
        }
    
        componentWillReceiveProps(next) {
            this.theme.setColor(next.color)
        }
    
        getChildContext() {
            return {theme: this.theme}
        }
    
        handleChange = (e) => {
            const color = e.target.value
            this.theme.setColor(color)
        }
    
        render() {
            return (
                <div>
                    <select name="colors" onChange={this.handleChange}>
                        <option value="red">red</option>
                        <option value="green">green</option>
                        <option value="blue">blue</option>
                    </select>
                    {this.props.children}
                </div>
            )
        }
    }
    
    ThemeProvider.childContextTypes = {
        theme: PropTypes.object
    }
    
    export default class ThemedText extends React.Component {
        state = { color: '' }
    
        componentDidMount() {
            this.updateTheme()
            this.context.theme.subscribe(this.updateTheme)
        }
    
        updateTheme = () => {
            this.setState({color: this.context.theme.color })
        }
    
        componentWillUnmount() {
            this.context.theme.unsubscribe(this.updateTheme)
        }
    
        render() {
            return (
                <div style={{color: this.state.color}}>
                    {this.props.children}
                </div>
            )
        }
    }
    
    ThemedText.contextTypes = {
        theme: PropTypes.object
    }
    

    示例:

    <ThemeProvider color="red">
      <ThemedText>
        <p>xxxxxxxxxxxxxxxxxxxxxxx</p>
      </ThemedText>
    </ThemeProvider>
    

    小结:
    React Context 是嵌套层次较深的兄弟组件之间通信的一种便捷方式,在某些使用场景作用是很强大的,所以需要谨慎使用。

    相关文章

      网友评论

          本文标题:3、React 组件通信之 React context

          本文链接:https://www.haomeiwen.com/subject/uyrzkctx.html