原文地址react
中文翻譯git
翻譯水平有限,部份內容比較晦澀,所以可能錯誤較多,請理解或指教。github
介紹用於解決現有侷限性的全新Context API。緩存
type Theme = 'light' | 'dark';
// Pass a default theme to ensure type correctness
//傳遞默認的主題確保類型的正確
const ThemeContext: Context<Theme> = React.createContext('light');
class ThemeToggler extends React.Component {
state = {theme: 'light'};
render() {
return (
/*傳遞現有的Context的值給Provider的props上的`value`屬性
數據變化使用Object.is來進行嚴格比較*/
<ThemeContext.Provider value={this.state.theme}>
<button
onClick={() =>
this.setState(state => ({
theme: state.theme === 'light' ? 'dark' : 'light',
}))
}>
Toggle theme
</button>
{this.props.children}
</ThemeContext.Provider>
);
}
}
class Title extends React.Component {
render() {
return (
//消費層使用一個渲染的prop API,以免與props命名空間衝突
<ThemeContext.Consumer>
{theme => (
<h1 style={{color: theme === 'light' ? '#000' : '#fff'}}>
{this.props.children}
</h1>
)}
</ThemeContext.Consumer>
);
}
}
複製代碼
一般狀況下,React裏面的數據是按照top-down(parent to child)的順序,經過props來傳遞的。但有些時候,跳過多個抽象層級來傳遞一些值每每是頗有用處的。就如例子中所傳遞的UI主題參數同樣。不少組件都依賴於此,可你卻不想在每一層組件上都使用prop來向下傳遞。安全
React裏面的Context API正是爲了解決這一問題所誕生的。在本文中,咱們將祖先組件成爲Provider(提供者),把孩子組件稱爲Consumer(消費者)。markdown
如今的Context API的主要問題就是如何與shouldComponentUpdate
交互。若是一箇中間組件使用了shouldComponentUpdate
來操做,那麼它的下方組件在沒有等待更新的時候,React將認爲整個子樹都沒有發生改變。若是子樹包含了Context的消費者,那麼消費者將不會收到任何最新的Context。換句話來講,Context的更改將不會在shouldComponentUpdate
返回爲false上的組件上傳播。數據結構
在React應用中,shouldComponentUpdate
是常用的優化操做方式。在共享組件與開源庫中它的使用每每很頻繁。在實踐中,這意味着Context在廣播變化的時候是不可靠的。框架
如今,開發者們經過使用訂閱來繞過了shouldComponentUpdate
的問題異步
setState
來更新並觸發重渲染。訂閱被開源軟件廣發使用,例如Redux
和React Broadcast
。它頗有用,但它也有一些明顯的缺點:介紹全新的組件類型:Provider
和Consumer
:ide
type Provider<T> = React.Component<{ value: T, children?: React.Node, }>; type Consumer<T> = React.Component<{ children: (value: T) => React.Node, }>; 複製代碼
Provider
和Comsumer
是成對出現的,對於每個Provider
,都會有一個對應的Consumer
。一個Provider
-Consumer
組合是用React.createContext()
來產生的:
type Context<T> = { Provider: Provider<T>, Consumer: Consumer<T>, }; interface React { createContext<T>(defaultValue: T): Context<T>; } 複製代碼
createContext
須要一個默認值來確保類型的正確。
請注意,即便任何提供者與任何消費者的值的類型相同,它們也不能聯合使用。它們必須是同一個createContext
產生的結果。
Provider
在props
上接收一個value
,不管嵌套的深度如何,它都能被提供者的任何匹配的消費者所訪問。
render() { return ( <Provider value={this.state.contextValue}> {this.props.children} </Provider> ); } 複製代碼
爲了更新Context的值,父級從新渲染並傳遞一個不一樣的值。Context的變化將被檢測到,檢測所使用的是Object.is
來比較的。這意味着鼓勵使用不可變或持久的數據結構。在典型的場景中,經過調用提供者父級的setState
來更新Context。
消費者使用一個render prop API
:
render() { return ( <Consumer> {contextValue => <Child arbitraryProp={contextValue} />} </Consumer> ) } 複製代碼
請注意上面這個例子,Context的值能夠傳遞給子組件上的任意prop。render prop API
的優勢就是避免了破壞prop的命名空間。
若是一個Consumer
沒有提供一個匹配的Provider
做爲它的祖先,它會接收搭配傳遞給createContext
的默認值,確保類型安全。
該提案使用嚴格(參考)比較來檢測對Context的更改。這部分鼓勵使用不可變性或持久的數據結構。但許多常見的數據源都依賴與突變。例如Flux
的某些實現,甚至像Relay Modern
這樣的更新的庫。
可是,與異步渲染結合時,突變存在固有的問題,主要與撕裂有關。 對於依賴變異的體系結構,開發人員要麼決定某種程度的撕裂是能夠接受的,要麼演變爲更好地支持異步。 不管如何,這些問題並不只限於Context API。(From Google Translation)
一些依賴於突變的庫的一個技巧就是克隆產生一個全新的外部容器(或者甚至只是在他們之間交替)。React將檢測到一個新對象的引用而且觸發一個更改。
建議的API只容許消費者從單一提供者類型讀取值,這與當前的API不一樣,後者容許消費者從任意數量的提供者類型讀取。
解決方法是使用合成消費者(待定):
<FooConsumer> {foo => ( <BarConsumer> {bar => ( // Render using both foo and bar <Child foo={foo} bar={bar} /> )} </BarConsumer> )} </FooConsumer>; 複製代碼
大多數圍繞上下文的抽象已經使用了相似的模式。
咱們可使用像setState
同樣工做的setContext
API,而不是依賴於引用相等來檢測對上下文的更改。 可是,若是不考慮實現的開銷,這個API只有在與突變結合使用時纔有價值,咱們專門致力於阻止這種突變。
一個說法是,咱們能夠經過將context做爲參數傳遞給該方法來避免shouldComponentUpdate問題,將傳入的Context與前一個Context進行比較,若是它們不一樣,則返回true。 問題是,與prop或state不一樣,咱們沒有類型信息。 上下文對象的類型取決於組件在React樹中的位置。 您能夠對這兩個對象執行淺層比較,但只有在咱們假定這些值是不可變的時纔有效。 若是咱們假設這些值是不可變的,那麼React可能會自動進行比較
咱們可使用咱們如今使用的基於類的API,而不是render prop:
class ThemeToggler extends React.Component { state = {theme: 'light'}; getChildContext() { return this.state.theme; } render() { return ( <> <button onClick={() => this.setState(state => ({ theme: state.theme === 'light' ? 'dark' : 'light', })) }> Toggle theme </button> {this.props.children} </> ); } } class Title extends React.Component { static contextType = ThemeContext; componentDidUpdate(prevProps, prevState, prevContext) { if (this.context !== prevContext) { alert('Theme changed!'); } } render() { return ( <h1 style={{color: this.context.theme === 'light' ? '#000' : '#fff'}}> {this.props.children} </h1> ); } } 複製代碼
這個API的優勢是您能夠更輕鬆地訪問生命週期方法中的上下文,可能避免在樹中須要額外的組件。
可是,儘管增長React樹的深度會帶來一些開銷,但使用特殊組件類型的優點在於,爲消費者掃描樹會更快,由於咱們能夠快速跳過其餘類型。 使用基於類的API時,咱們必須檢查每一個類組件,它稍微慢一些。 這足以抵消額外組件的成本。
與類API不一樣,render prop API還具備與現有Context API充分不一樣的優點,咱們能夠在過渡時期支持這兩個版本,而不會形成太多混淆。
(注意,原文部分接下來的一些章節並未翻譯,這些章節涉及的內容是React官方的一些計劃)
對於依賴默認Context的值,這是頗有效的。在不少狀況下,這種錯誤應該是開發者所形成的,所以咱們能夠打印警告出來。爲了關閉這個,你能夠傳遞true給allowDetached
。
render() {
return (
// If there's no provider, this renders with the default theme.
// `allowDetached` suppresses the development warning
<ThemeContext.Consumer allowDetached={true}>
{theme => (
<h1 style={{color: theme === 'light' ? '#000' : '#fff'}}>
{this.props.children}
</h1>
)}
</ThemeContext.Consumer>
);
}
複製代碼
對於警告和React DevTools,若是提供者和消費者具備displayName,則會有所幫助。 問題是這是否應該要求。 咱們可使其成爲可選項,並使用Babel變換自動添加名稱。 這是咱們用於createClass的策略。
children
做爲prop或命名prop?