原文地址:https://github.com/rccoder/blog/issues/30
媽媽,我再也不會濫用 Redux 了
前言
context 是各種前後端框架中經常會用到的一個概念,著名 Node 框架 Koa
更是把 context 玩的盡興。React 在很早之前就有 context 的概念,雖然是一個實驗性質的 API,但 react-redux、react -router 等框架類庫卻把它玩了個夠。
React 中爺爺和孫子,甚至是重孫之間傳遞值或者事件一直是個比較麻煩的事情,隨著 Redux 等狀態管理類庫的出現,大家紛紛開始用這種框架去解決這種隔代傳資訊的問題,並且在或大或小的專案中都開始使用。
一回喜,二回憂,在前端視資源體積為金子的情況下 “濫用 Redux” 的情況越來越多。
React 新版本中的 context 終於要轉正了,並且經過了重新的思考與沉澱,與之前 context 在設計哲學上不一樣,但解決的卻是同一個問題。這樣,或許資料流真的不是很多的專案或許可以真的擺脫一下 Redux 等去試試自帶的 Context 了。
本文將介紹新老 Context 的一些用法和自己的一些思考。
子孫傳值問題
首先看一下最簡單的 爺爺給孫子 傳值的問題:
class Children extends React.Component {
render() {
return (
<div>{ this.props.text }</div>
)
}
}
class Parent extends React.Component {
render() {
return (
<Children text="this.props.text"/>
)
}
}
class GrandParent extends React.Component {
render() {
return (
<Parent text="Hi, my baby"/>
)
}
}
複製程式碼
這幾行程式碼中都是手動的讓 text 一輩一輩 的往下傳,如果是傳給重孫的話還需要繼續手動往下傳。
利用 Context 就能讓這種資訊自動的傳遞,不再讓 中間輩 去擔任訊息傳遞人,做一些沒太大意義的事。
老 Context
const PropTypes = require('prop-types');
class Children extends React.Component {
static contextTypes = {
text: PropTypes.string
}
render() {
return (
<div>{ this.context.text }</div>
)
}
}
class Parent extends React.Component {
render() {
return (
<Children/>
)
}
}
class GrandParent extends React.Component {
static childContextTypes = {
text: PropTypes.string,
}
getChildContext() {
return {
text: 'Hi, my baby'
}
}
render() {
return (
<Parent text="Hi, my baby" />
)
}
}
複製程式碼
在 GrandParent
上通過 getChildContext
給 context 物件新增了 text
的屬性,這個屬性可以在 GrandParent
的任何一個子孫(子元件)中訪問。
同時,為了方便在各種生命週期中使用 context,部分生命週期都給 context 留了介面,具體可以參考 Referencing Context in Lifecycle Methods。
新 Context
老式的 context 和依靠中間輩一層層去傳遞資料相比確實是酷酷的,但總感覺不 React。或許這也是為什麼之前一直不建議使用的原因吧。
同時還有一個比較?的 context 更新問題,更多可以參考 How to safely use React context。
在 React 16.3 中 新版本的 Context 出爐了,感興趣的可以參考 rfc提案。
新版 context 之下上面的程式碼這樣寫:
const AppContext = React.createContext();
class Children extends React.Component {
render() {
return (
<AppContext.Consumer>
{
context => {
return (
<div>{context.text}</div>
)
}
}
</AppContext.Consumer>
)
}
}
class Parent extends React.Component {
render() {
return (
<Children />
)
}
}
class GrandParent extends React.Component {
render() {
return (
<AppContext.Provider
value={{
text: 'Hi, my baby'
}}
>
<Parent />
</AppContext.Provider>
)
}
}
複製程式碼
新版 Context 用 createContext
去初始化一個 context,返回的物件中有 provider
和 consumer
方法。provider
是以外層容器的方式去包裹住 context 要作用的最外層元件(使用過 Redux 的同學對這點應該有種似曾相似的感覺),然後需要使用到 context 的時候需要用 consumer
去包裹一下。需要注意的是 consumer
包裹的裡面的寫法,不是普通的元件。
雖然 context 可以是一個 Object,但還是避免不了業務邏輯中會出現多個 context 的問題,consumer
與 provider
一一對應的模式會造成花式巢狀地獄,可以使用偉大社群產生的 react-context-composer 對 context 進行 composer,原始碼也非常簡單易懂。
Context 會讓 Redux 消失嗎?
不會,解決的終極問題不完全一樣!
Redux 解決的是大型軟體架構中資料流傳輸的問題;context 解決的是子孫之間方便資料互動的問題。有一定的相似性,但不屬於同等性質。
延伸閱讀
- context - react docs
- How to safely use React context
- rfcs pr: 0002-new-version-of-context.md
- rfcs: 0002-new-version-of-context.md
- React's new Context API - kentcdodds
- 知乎:如何解讀 react 16.3 引入的新 context api?
題外
文章內容第一時間更新地址為:https://github.com/rccoder/blog/issues/30。為保證看到的是最新的,交流 & 閱讀優先點選 這裡。