React從入門到精通系列之(20)用上下文管理應用

二10、用上下文管理應用

使用React能夠很容易經過React組件跟蹤數據流。 當你看到一個組件,你就能夠看到哪些props正在傳遞,這使得你的應用很容易知道在作什麼。javascript

在某些狀況下,你但願經過組件樹傳遞數據,而不是在每一個級別的中組件手動傳遞props,你能夠直接在React中使用強大的「context」來作到這一點。java

爲何不去使用Context

絕大多數的應用不須要直接使用Context。react

若是你但願你的應用是穩定的,那麼就不要使用Context。 由於這是一個實驗性質的API,它可能會在將來的React版本中移除。dom

若是你不熟悉state管理庫如ReduxMobX,不要使用Context。 對於許多實際的應用,這些state管理庫和React一塊兒使用來管理那些與組件相關的state一個很不錯的選擇。 不少狀況下使用Redux就能夠解決你的問題,而不是使用Context。函數

若是你不是一個有經驗的React開發人員,不要使用Context。 使用propsstate來實現功能是一個更好的方法。ui

儘管有上面這些警告你還堅持使用Context,那麼請將Context單獨隔離到一個小區域中,並儘量地避免直接使用Context,以便在這個API更改時應用能更容易升級。this

如何使用Context

假設你有一個結構:code

import React from 'react';
import ReactDOM from 'react-dom';

class MyButton extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const style = {backgroundColor: this.props.color};
        return <button style={style}>{this.props.children}</button>;
    }
}
class Message extends React.Component {
    render() {
        return (
            <div>
                {this.props.text}
                <MyButton color={this.props.color}>刪除</MyButton>
            </div>
        )
    }
}
class MessageList extends React.Component {
    render() {
        const color = 'red';
        const children = this.props.messages.map(msg => <Message text={msg} color={color}/>);
        return <div>{children}</div>;
    }
}
const messages = ['zhangyato', 'Re: zhangyatao', 'Re:Re:zhangyatao'];
ReactDOM.render(
    <MessageList messages={messages}/>,
    document.getElementById('root')
);

在這個例子中,咱們手動傳入一個color props進行傳遞,以便適當地設置MyButtonMessage組件的樣式。 使用Context,咱們可讓color自動在組件樹中傳遞:component

import React from 'react';
import ReactDOM from 'react-dom';

class MyButton extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const style = {backgroundColor: this.context.color};
        return <button style={style}>{this.props.children}</button>;
    }
}
MyButton.contextTypes = {
    color: React.PropTypes.string.isRequired
};
class Message extends React.Component {
    render() {
        return (
            <div>
                {this.props.text}
                <MyButton>刪除</MyButton>
            </div>
        )
    }
}
class MessageList extends React.Component {
    getChildContext() {
        return {color: 'red'};
    }

    render() {
        const children = this.props.messages.map(msg => <Message text={msg}/>);
        return <div>{children}</div>;
    }
}
MessageList.childContextTypes = {
    color: React.PropTypes.string.isRequired
};
const messages = ['zhangyato', 'Re: zhangyatao', 'Re:Re:zhangyatao'];
ReactDOM.render(
    <MessageList messages={messages}/>,
    document.getElementById('root')
);

經過向MessageList(Context提供者)添加childContextTypesgetChildContext,React就會自動傳遞Context信息,子組件樹中的任何組件(Button)均可以經過定義contextTypes來訪問它。對象

若是未定義contextTypes,那麼Context將是一個空對象。

父子組件之間的耦合

Context還可讓你實現父組件和子組件之間的交互。 例如,以這種方式工做的一個比較知名的庫爲React Router V4

const RouterExample = () => {
    <Router>
        <div>
            <ul>
                 <li><Link to="/">主頁</Link></li>
                 <li><Link to="/recyclebin">回收站</Link></li>
                 <li><Link to="/timeline">圖片</Link></li>
            </ul>

            <hr />

            <Match exactly pattern="/" component={Home} />
            <Match pattern="/recyclebin" component={RecycleBin} />
            <Match pattern="/timeline" component={TimeLine} />

        </div>
    </Router>
}

經過從RouterExample組件傳遞一些信息,每一個LinkMatch能夠回傳到包含的Router中。

在使用相似於此的API進行構建組件以前,請考慮是否有更好的替代品。 例如,你能夠傳遞整個React組件做爲props。

生命週期方法中使用Context

若是在一個組件中定義了contextTypes,則如下生命週期方法將多接收個參數,就是Context對象:

  • constructor(props, context)

  • componentWillReceiveProps(nextProps, nextContext)

  • shouldComponentUpdate(nextProps, nextState, nextContext)

  • componentWillUpdate(nextProps, nextState, nextContext)

  • componentDidUpdate(prevProps, prevState, prevContext)

在無狀態功能性組件中使用Context

若是contextTypes被定義爲無狀態功能性組件的屬性,無狀態功能性組件也可以引用Context。 下面的代碼顯示了被寫成一個無狀態的功能組件的MyButton組件。

function MyButton(props, context) {
     const children = props.children;
     return (
         <button style={{backgroundColor: context.color}}>
             {children}
         </button>
     );
}
MyButton.contextTypes = {
    color: React.PropTypes.string.isRequired
};

更新Context

千萬不要這麼作!!!

React有一個API來更新Context,但它從根本上破壞了Context,因此你不該該使用它。

當state或props改變時,getChildContext函數將被調用。 爲了更新Context中的數據,使用this.setState()觸發組件內的state更新。 這也將觸發一個新的Context,而且Context改變的內容也會被子組件接收到。

import React from 'react';
import ReactDOM from 'react-dom';

class MediaType extends React.Component {
    render() {
        return <div>type is {this.context.type}</div>
    }
}
MediaType.contextTypes = {
    type: React.PropTypes.string
};
class MediaQuery extends React.Component {
    constructor(props) {
        super(props);
        this.state = {type: 'PC端'};
        this.checkMediaQuery = this.checkMediaQuery.bind(this);
    }

    getChildContext() {
        return {type: this.state.type}
    }

    checkMediaQuery() {
        let type = window.matchMedia('<code>zhangyatao</code>(max-width: 760px)').matches ? '移動端' : 'PC端';
        this.setState({type: type});
    }

    componentDidMount() {
        window.addEventListener('resize', this.checkMediaQuery, false);
        this.checkMediaQuery();
    }

    render() {
        return <div>{this.props.children}</div>;
    }
}
MediaQuery.childContextTypes = {
    type: React.PropTypes.string
};
ReactDOM.render(
    <MediaQuery>
        <MediaType />
    </MediaQuery>,
    document.getElementById('root')
);

問題是,Context的值經過組件更新來提供,若是中間的父組件在shouldComponentUpdate()返回false,那麼使用該Context值的後代組件永遠不會被更新。使用Context徹底不受組件的控制,因此基本上沒有辦法可靠地更新Context。 這篇文章很好的解釋了爲何這是一個問題,以及你應該如何擺脫它。

相關文章
相關標籤/搜索