redux是一個用於管理狀態的前端框架,不僅可以用於react,還可以用於vue。它的源碼和設計思想,我們在《顫抖吧!一起手寫一個redux框架!》已經講過,沒有看過的小夥伴們可以去看一下。
redux的核心是發明了store,通過dispatch來更改store裏的值。
可redux要想在react上使用,需要把兩者連接起來,這個時候react-redux就出現了。它的主要作用是:
- 在
componentDidMount
時store.subscribe
,這樣在store.dispatch
的時候就能回調subscribe
的listener
。
具體看Provider.js中的源碼實現:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
class Provider extends Component {
//...
componentDidMount() {
this._isMounted = true
this.subscribe()
}
subscribe() {
const { store } = this.props
this.unsubscribe = store.subscribe(() => {
const newStoreState = store.getState()
if (!this._isMounted) {
return
}
this.setState(providerState => {
//小tip:return null代表不更新state
if (providerState.storeState === newStoreState) {
return null
}
return { storeState: newStoreState }
})
})
// ...
}
}
export default Provider
- 提供了context,可以實現跨組件通信,而不用去狀態提升。
這其實用到了react自帶的api:context
。
具體實現是在Provider.js
中聲明context
,並把store.getState()
賦值給它。這樣其子組件中就可以拿到context
值,只不過要寫一些模板代碼,這些代碼都封裝在了connect.js
中。這也就是爲什麼我們只要把我們的組件傳給高階組件connect
中就可以獲取到redux
的state
。 mapStateToProp
用來把store中的state傳給connect組件,所以在組件中可以通過this.props獲取store中state的值。mapDispatchToProps
用來把用dispatch包裹的actionCreator傳給組件(bindActionCreators中把dispatch給bind到actionCreator上),所以,在組件中通過props可以直接dispatch一個action。
以上大概就是要在react中使用redux要寫的代碼。
其實,react-redux封裝的功能並不一定適合我們的業務場景,相反我們還多寫了很多與業務無關的模板代碼!
因此,我們可以嘗試着自己去封裝一個更易用的redux框架。它有如下幾個特點:
- 可以成功地連接react和redux。
- 不需要在業務代碼中手動bindActionCreator,也不需要寫mapDispatchToProps和mapStateToProps。
- 不用寫完reducer,再去寫action了。使用過react-redux的小夥伴們應該都發現了reducer和action實際上都是爲了去更新數據層,卻要寫兩次非常類似的代碼。
它的使用方法也很簡單,只需要讓你的頁面繼承這個basePage
就可以了。
import React from "react";
import { createStore, applyMiddleware } from "redux";
export default class BasePage {
constructor(props) {
super(props);
this.$actions = null;
this.$store = null;
this.state = {};
}
render() {
let { View } = this;
if (!View) return null;
let pageContext = {
page: this,
state: this.state
};
return <View state={this.state} page={this} />;
}
get store() {
if (this.$store) return this.$store;
this.$store = this.createStore();
return this.$store;
}
get actions() {
if (this.$actions) return this.$actions;
this.store; // trigger createStore
this.$actions = {};
if (!util.isObject(this.model)) return this.$actions;
Object.keys(this.model).forEach(type => {
this.$actions[type] = payload => this.store.dispatch({ type, payload });
});
return this.$actions;
}
createReducer() {
return (state, action) => {
let model = this.model;
let nextState = state;
if (util.isObject(model)) {
let handler = reducer[action.type];
if (util.isFunction(handler)) {
nextState = handler(nextState, action.payload);
}
} else if (util.isFunction(model)) {
nextState = reducer(nextState, action);
}
return nextState;
};
}
createStore() {
const middlewares = [];
if (process.env.NODE_ENV === "development") {
const reduxLogger = require("redux-logger");
const logger = reduxLogger.createLogger();
middlewares.push(logger);
}
const createStoreWithMiddleware = applyMiddleware(...middlewares)(
createStore
);
let store = createStoreWithMiddleware(this.createReducer(), this.state);
store.unsubscribe = store.subscribe(() => {
let nextState = store.getState();
this.setState(nextState);
});
Object.defineProperty(store, "actions", {
get: () => this.actions
});
return store;
}
可以看到,如上框架代碼行數並不多,下面我來爲大家來講解一下。
首先,先看render()
,在render
中給每個view
傳入了page
:
<View state={this.state} page={this} />
這主要是用來在組件中調用action來更新state。
例如,我們在CounterView
中調用INCRE_BY
。
export default function CounterView({ state, page }) {
const { count } = state;
return (
<View>
<TouchableOpacity onPress={()=> {
page.actions.INCRE_BY
}}>
<Text style={{ backgroundColor: "red" }}>increase</Text>
</TouchableOpacity>
</View>
);
}
此時頁面纔開始createStore
。。或者說,只有調用頁面第一個action來開始createStore
。因爲,在調用page.actions.INCRE_BY
時,實際上會先調用
get actions() {
if (this.$actions) return this.$actions;
this.store; // trigger createStore
this.$actions = {};
if (!util.isObject(this.reducer)) return this.$actions;
Object.keys(this.reducer).forEach(type => {
this.$actions[type] = payload => this.store.dispatch({ type, payload });
});
return this.$actions;
}
此時,this.$actions
實際上是空的,所以這個時候就會用this.model
來構建action
。這裏的model
不光可以構建action
,還可以構建出reducer
!
我們來看下mode.js是怎樣的:
export const INCRE = state => {
let count = state.count + 1;
return {
...state,
count
};
};
export const DECRE = state => {
let count = state.count - 1;
return {
...state,
count
};
};
export const INCRE_BY = (state, aaa = 2) => {
let count = state.count + aaa;
return {
...state,
count
};
};
看到這個model
我相信你大致就能猜到我是如何通過model
來構建reducer
和action
的了——構建action
時,取出model
的key
作爲action
名,並把每個function
用dispatch
包裹一下;構建reducer
時,就直接調用model
中相應key
的function
,注意這裏每一個function
都return
一個新的state
。
而上面在調用action
時,當發現store
爲空時,會接着去createStore
。
createStore() {
const middlewares = [];
if (process.env.NODE_ENV === "development") {
const reduxLogger = require("redux-logger");
const logger = reduxLogger.createLogger();
middlewares.push(logger);
}
const createStoreWithMiddleware = applyMiddleware(...middlewares)(
createStore
);
let store = createStoreWithMiddleware(this.createReducer(), this.state);
store.unsubscribe = store.subscribe(() => {
let nextState = store.getState();
this.setState(nextState);
});
Object.defineProperty(store, "actions", {
get: () => this.actions
});
return store;
}
以上我們就完成了一個比react-redux更輕量級的redux框架,該框架使得我們能儘可能少地在業務代碼中寫無腦的模板代碼。
最後,該框架是參考自@工業聚,他對於技術的態度,給了我很多前進的動力!