React 組件通訊之 React context

**React 組件之間的通訊是基於 props 的單向數據流,即父組件經過 props 向子組件傳值,亦或是子組件執行傳入的函數來更新父組件的state,這都知足了咱們大部分的使用場景。
那 React 在兄弟組件之間如何通訊呢?**react


通常地,對於兄弟組件之間的通訊,是經過它們共同的祖先組件進行的,即 Lifting State Up,官方文檔也有介紹。組件一經過事件將狀態變動通知它們共同的祖先組件,祖先組再將狀態同步到組件二。ide

可是,若是組件之間嵌套的比較深,即便提高狀態到共同父組件,再同步狀態到相應的組件仍是須要一層一層的傳遞下去,可能會比較繁瑣。函數

React 官方文檔中介紹了 context 的用法, 在對應的場景中,context 就能夠縮短父組件到子組件的屬性傳遞路徑。具體看例子:this

Container.jsxspa

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.jsxcode

import React from "react"

const Parent = (props) => (
    <div {...props} />
)

export default Parent

ChildOne.jsxcomponent

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 就能夠相互通訊了。blog

注意,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 是嵌套層次較深的兄弟組件之間通訊的一種便捷方式,在某些使用場景做用是很強大的,因此須要謹慎使用。

最後推薦閱讀這篇文章:How to safely use React context

相關文章
相關標籤/搜索