隨著 React 16.3.0 的發(fā)布,context api 也有了很大的更新。我已經(jīng)從舊版的 api 更新到了新版。這里就分享一下我(作者)的心得體會。
回顧
下面是一個展示如何使用舊版 api 的例子:
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>
)
}
上面的代碼會返回一個復(fù)合組件Toggle。這個組件可以讓子組件共享隱式的狀態(tài)。在某些簡單的情況下可以用React.Children.map來處理。但是,這個例子需要使用 context api 來達到在 React 的某個組件樹的任意節(jié)點分享 state 的目的。
舊的 Context Api
這是一個舊版 context api 的應(yīng)用例子:
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來指定發(fā)出的 context 的類型,在子組件里用contextTypes來指定接收的 context 的類型。我(作者, 下文同)從來不喜歡這樣不直接的方式, 平時也盡量不這么做。另外,還要使用靜態(tài)屬性,才能保證 context 值傳入子組件。 我也不喜歡這樣。
另一個問題是如果shouldComponentUpdate方法返回 false 的話,context 的值是不會變的。當(dāng)然這也有變通的方法,具體可以參考這個repo。
新的 Context API
新的 context 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的問題都沒有了?,F(xiàn)在不僅沒有了不直接的字符串,還有了明顯的組件:Provider和Consumer分別提供和消費context。
每一個子組件都需要用consumer子組件,就想在就api里需要有靜態(tài)屬性一樣)。但是,兩個api的這個問題都可以通過基于render props的高階組件來解決。非常簡單!
另一個通過shouldComponentUpdate返回false來更新的問題也解決了。新的context api會自動處理這個問題。
最后一個非常好的改變是子組件都使用了render props模式。這樣在新的context api里也可以對外界暴露非常優(yōu)雅的接口。
新api的問題
這就是在新的api里通過Provider的value屬性給子組件消費的值,只有在你想要子組件重繪的時候才會改變。
這也就是說在render方法里使用`value={{on: this.state.on, toggle: this.toggle}}是不被推薦的。
這是因為沒次render都會傳入一個新的對象,即使state本身沒有改變。因為是一個新的對象,那么所有的子組件也都會重繪。
這個影響在實際使用的時候會很大。一般來說最好是傳入一個只有在state改變時才改變的值。也就是為什么說value={this.state}。
如果你不想傳入整個state給消費者,那么你可以使用這個Ryan Florence的這個小技巧。
只不過這也還是會有一個小問題,我需要把toggle方法存入state里。這也顯得很奇怪,不過這只是一個小瑕疵,不影響大局。
總結(jié)
新的context api絕對是React團隊帶來的一個非常好的改變。希望你也能和我一樣的喜歡。
另外:如果你還不能用16.3.0這個版本,那么你可以添加一個polyfill來使用新的api。這個polyfill是create-react-context。
from:https://blog.kentcdodds.com/migrating-to-reacts-new-context-api-b15dc7a31ea0