隨着 React 16.3.0 的發佈,context api 也有了很大的更新。我已經從舊版的 api 更新到了新版。這裏就分享一下我(做者)的心得體會。javascript
下面是一個展現如何使用舊版 api 的例子:java
function Usage(props) { return ( <Toggle onToggle={props.onToggle}> <Toggle.On>The button is on</Toggle.On> <Toggle.Off>The button is off</Toggle.Off> <div> <Toggle.Button /> </div> </Toggle> ) }
上面的代碼會返回一個複合組件Toggle
。這個組件可讓子組件共享隱式的狀態。在某些簡單的狀況下能夠用React.Children.map
來處理。可是,這個例子須要使用 context api 來達到在 React 的某個組件樹的任意節點分享 state 的目的。react
這是一箇舊版 context api 的應用例子:git
const TOGGLE_CONTEXT = "__toggle__"; // Toggle on function ToggleOn({ children }, context) { const { on } = context[TOGGLE_CONTEXT]; return on ? children : null; } ToggleOn.contextTypes = { [TOGGLE_CONTEXT]: PropTypes.object.isRequired }; // Toggle off function ToggleOff({ children }, context) { const { on } = context[TOGGLE_CONTEXT]; return on ? null : children; } ToggleOff.contextTypes = { [TOGGLE_CONTEXT]: PropTypes.object.isRequired }; // Toggle button function ToggleButton(props, context) { const { on, toggle } = context[TOGGLE_CONTEXT]; return <Switch on={on} onClick={toggle} {...props} />; } ToggleButton.contextTypes = { [TOGGLE_CONTEXT]: PropTypes.object.isRequired }; // Toggle class Toggle extends React.Component { static On = ToggleOn; static Off = ToggleOff; static Button = ToggleButton; static defaultProps = { onToggle: () => {} }; static childContextTypes = { [TOGGLE_CONTEXT]: PropTypes.object.isRequired }; state = { on: false }; toggle = () => this.setState( ({ on }) => ({ on: !on }), () => this.props.onToggle(this.state.on) ); getChildContext() { return { [TOGGLE_CONTEXT]: { on: this.state.on, toggle: this.toggle, } }; } render(){ return <div>{this.props.children</div> } }
在就的 API 裏,你必須用一個字符串指定要分享的 state。而後經過getChildContext
方法返回實際的 context。在父組件裏經過childContextTypes
來指定發出的 context 的類型,在子組件裏用contextTypes
來指定接收的 context 的類型。我(做者, 下文同)歷來不喜歡這樣不直接的方式, 平時也儘可能不這麼作。另外,還要使用靜態屬性,才能保證 context 值傳入子組件。 我也不喜歡這樣。github
另外一個問題是若是shouldComponentUpdate
方法返回 false 的話,context 的值是不會變的。固然這也有變通的方法,具體能夠參考這個repo。npm
新的 context api 沒有這些問題,這也是我爲何這麼激動的緣由。上面的例子能夠更新爲:api
const ToggleContext = React.createContext({ on:false, toggle: () => {}, }); class Toggle extends React.Component { static On = ({children}) => ( <ToggleContext.Consumer> {({on})=>(on ? children: null)} </ToggleContet.Consumer> ) static Off = ({children}) => ( <ToggleContext.Consumer> {({on}) => (on ? null : children)} </ToggleContext.Consumer> ); static Button = props => ( <ToggleContext.Consumer> {({on, toggle}) => ( <Switch on={on} toggle={toggle} {...props} /> )} </ToggleContext.Consumer> ) toggle = () => this.setState( ({on}) => ({on: !on}), () => this.props.onToggle(this.state.on) ); state = {on: false, toggle: this.toggle}; render() { return ( <ToggleContext.Provider value={}> {this.props.children} </ToggleContext.Provider> ); } }
舊的API的問題都沒有了。如今不只沒有了不直接的字符串,還有了明顯的組件:Provider
和Consumer
分別提供和消費context。
每個子組件都須要用consumer子組件,就想在就api裏須要有靜態屬性同樣)。可是,兩個api的這個問題均可以經過基於render props的高階組件來解決。很是簡單!ide
另外一個經過shouldComponentUpdate
返回false來更新的問題也解決了。新的context api會自動處理這個問題。ui
最後一個很是好的改變是子組件都使用了render props模式。這樣在新的context api裏也能夠對外界暴露很是優雅的接口。this
這就是在新的api裏經過Provider
的value屬性給子組件消費的值,只有在你想要子組件重繪的時候纔會改變。
這也就是說在render方法裏使用`value={{on: this.state.on, toggle: this.toggle}}是不被推薦的。
這是由於沒次render都會傳入一個新的對象,即便state自己沒有改變。由於是一個新的對象,那麼全部的子組件也都會重繪。
這個影響在實際使用的時候會很大。通常來講最好是傳入一個只有在state改變時才改變的值。也就是爲何說value={this.state}
。
若是你不想傳入整個state給消費者,那麼你可使用這個Ryan Florence的這個小技巧。
只不過這也仍是會有一個小問題,我須要把toggle
方法存入state裏。這也顯得很奇怪,不過這只是一個小瑕疵,不影響大局。
新的context api絕對是React團隊帶來的一個很是好的改變。但願你也能和我同樣的喜歡。
另外:若是你還不能用16.3.0這個版本,那麼你能夠添加一個polyfill來使用新的api。這個polyfill是create-react-context。
from:https://blog.kentcdodds.com/migrating-to-reacts-new-context-api-b15dc7a31ea0