前言
筆者最近在學習使用react
,提到react就繞不過去redux
。redux是一個狀態管理架構,被廣泛用於react專案中,但是redux並不是專為react而生,兩者還需要react-redux
建立一座橋樑。同時,redux架構規定只能傳送同步action
,要想傳送非同步action
就需要結合中介軟體如redux-thunk
、redux-saga
等,所以說要想搞定redux還真是不容易啊,光名詞就這麼多。筆者以前也接觸過一點vuex
,vuex對筆者這樣的菜雞相對友好,但是vuex是和vue
配套的,是不可能用在react中的,這輩子都別想用在react中。但是我不服,那麼這篇文章就探索下如何製作一個可以在react中使用的類似vuex的狀態管理工具,我將它取名為reux。
vuex <=> redux + react-redux + redux-saga
複製程式碼
正文
-
響應式資料觀測系統
vue的一大特色就是響應式資料觀測系統,它可以在get
資料時收集依賴,在set
資料時觸發更新。vuex藉助於vue的資料觀測系統,可以輕鬆的收集資料依賴,並且依賴可以精細到元件的粒度,也就是說某一狀態改變時,只有依賴到這一狀態的元件才會觸發rerender
,這樣看來redux體系就比較傻,只要提交action,就會從根元件rerender
(react-redux內部自動進行shouldCompoentUpdate判斷)。
上圖來自於vue官網對vuex架構的說明,連結。
上圖中的component
是vue component
,只要vue component執行render,那麼vuex的資料響應系統就可以自動的收集依賴,當狀態改變時,依賴於此狀態的元件就會重新渲染。既然我們要實現的是一個類vuex的狀態管理工具,即支援以get
的方式收集依賴,以set
的方式觸發更新,所以reux利用了vue的響應式資料觀測系統,正所謂前人種樹,後人乘涼。
-
如何收集依賴
我們已經有了響應式資料系統,接下來要解決的問題就是如何收集依賴,收集依賴必須要觸發get
,而觸發get的前提是元件可以拿到store
,因此第一步是向元件注入store。類似react-redux,reux提供了Provider使子元件可以拿到store。
class Provider extends Component {
getChildContext() {
return {store: this.props.store};
}
render() {
const { children } = this.props;
return children;
}
}
Provider.childContextTypes = {
store: PropTypes.object
};
複製程式碼
相應的子元件可以context拿到store,如下
class Child extends Component {
render() {
// store => this.context.store
}
}
Child.contextTypes = {
store: PropTypes.object
};
複製程式碼
這樣寫的缺點顯而易見,每個子元件都需要定義contextTypes,同樣的類似於react-redux,reux提供了connect
函式,用於對映state => props
const connect = (mapStateToProps = () => {}) => {
return (WrappedComponent) => {
const Wrapper = class extends Component {
render() {
const store = this.context.store;
const props = Object.assign({}, this.props, mapStateToProps(store.state, this.props), {dispatch: store.dispatch, commit: store.commit});
return <WrappedComponent {...props} />
}
}
Wrapper.contextTypes = {
store: PropTypes.object
};
reaturn Wrapper;
}
}
複製程式碼
這樣一來,只要元件執行render方法,便會觸發get
鉤子,從而使得store自動收集依賴,我們再想下依賴是什麼,其實依賴應該是元件例項,那麼當set
鉤子觸發時,每個依賴(即元件例項)只要執行forceUpdate方法就可以達到rerender的效果。
但是問題是,get
鉤子觸發時,如何確定依賴到底是誰呢?借鑑vue,我們定義一個stack,當componentWillMount
時進棧,當componentDidMount
時出棧
componentWillMount() {
pushTarget(this);
}
componentDidMount() {
popTarget(this);
}
複製程式碼
這樣當get
鉤子觸發時,當前target就是目標依賴。同時應當注意,當元件update時應當重新收集依賴,因為update之後依賴關係很可能已經變化了
update() {
// 清空依賴
this.clear();
pushTarget(this);
this.forceUpdate(() => {
popTarget(this);
})
}
複製程式碼
至此,我們的小目標已經完成了,在react中使用vuex不再是夢!