React中,通過React組件可以很容易地追蹤數據流。當你關注一個組件,你可以發現哪一個props被傳遞了,這樣使得你的應用很容被推斷。
在一些情況下,你想要傳遞數據通過組件樹而不需要去手動在每一層傳遞。你可以直接使用強大的context API。
為什么不使用context
大量的應用不需要使用context。
如果你希望你的應用穩定,不要使用context。這是一個實驗性的API在未來的版本中有可能會崩潰。
如果你不熟悉state管理庫就類似於Redux或者Mobx,不要使用context。對於很多實際的應用,這些庫和它們的React綁定實現是很好的選擇來管理state,這對於很多組件都有重大意義。解決問題最好的方案更像是Redux而不是context。
如果你不是一個有經驗的React開發者,不要使用context。有更好的方式去實現功能通過使用props和state。
如果你堅持使用context而不管這些警告,那么就請試圖隔離你使用context在一個較小的范圍並且避免直接使用context API為了當API改變的時候升級方便。
怎樣使用context
假設你擁有這樣的一個結構:
class Button extends React.Component { render() { return ( <button style={{background: this.props.color}}> {this.props.children} </button> ); } } class Message extends React.Component { render() { return ( <div> {this.props.text} <Button color={this.props.color}>Delete</Button> </div> ); } } class MessageList extends React.Component { render() { const color = "purple"; const children = this.props.messages.map((message) => <Message text={message.text} color={color} /> ); return <div>{children}</div>; } }
在這個例子里,我們手動傳遞了一個color屬性為了讓Button和Message組件的有一個合適的樣式。使用context,我們可以自動傳遞屬性通過樹。
class Button extends React.Component { render() { return ( <button style={{background: this.context.color}}> {this.props.children} </button> ); } } Button.contextTypes = { color: React.PropTypes.string }; class Message extends React.Component { render() { return ( <div> {this.props.text} <Button>Delete</Button> </div> ); } } class MessageList extends React.Component { getChildContext() { return {color: "purple"}; } render() { const children = this.props.messages.map((message) => <Message text={message.text} /> ); return <div>{children}</div>; } } MessageList.childContextTypes = { color: React.PropTypes.string };
通過為MessageList組件(context提供者)添加childContextTypes屬性和getChildContext方法,React會自動傳遞信息並且任何子樹里的組件(在這個例子,Button組件)都可以獲取到這個信息通過定義contextTypes。
如果contextTypes沒有定義,那么context會是一個空對象。
父子聯合
context也可以讓你建造一套可以讓父組件和子組件通信的API。舉個例子,一個這樣運作的庫叫做React Router v4:
import { Router, Route, Link } from 'react-router-dom'; const BasicExample = () => ( <Router> <div> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> <li><Link to="/topics">Topics</Link></li> </ul> <hr /> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> <Route path="/topics" component={Topics} /> </div> </Router> );
通過從Router組件傳遞一些信息,每一個Link和Route都可以和包含的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
const Button = ({children}, context) => <button style={{background: context.color}}> {children} </button>; Button.contextTypes = {color: React.PropTypes.string};
更新context
不要這樣做。
React有一個更新context的API,但是它實質上已經損壞你不應該使用它。
getChildContext函數當props或者state改變的時候會被調用。為了在context中更新數據,使用this.setState來觸發本地的state更新。這樣將會觸發一個新的context並且改變會被子組件接收。
class MediaQuery extends React.Component { constructor(props) { super(props); this.state = {type:'desktop'}; } getChildContext() { return {type: this.state.type}; } componentDidMount() { const checkMediaQuery = () => { const type = window.matchMedia("(min-width: 1025px)").matches ? 'desktop' : 'mobile'; if (type !== this.state.type) { this.setState({type}); } }; window.addEventListener('resize', checkMediaQuery); checkMediaQuery(); } render() { return this.props.children; } } MediaQuery.childContextTypes = { type: React.PropTypes.string };
問題在於,如果一個組件提供的context值改變了,使用那個值的子節點就不會更新如果中間的組件從shouldComponentUpdate返回了false。這樣組件使用context就完全失去了控制,因此基本沒有什么方法可以可靠地更新context。這個博客有一個很好地解釋關於為什么這是一個問題以及你怎樣避開它。