react-redux簡介html
redux是一個數據管理框架,而react-redux是專門針對react開發的一個插件。react-redux提供了2個API,Provider和connect。原本打算在一篇文章同時講解2個API的實現,不過看了一下connect的源碼,368行,仍是分開解析吧。node
本文帶領你們分析Provider的核心代碼。react
如何使用Providergit
咱們先了解在react項目中是如何使用Provider。github
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
const store = configureStore();
ReactDOM.render((
<Provider store={store}>
</Provider>),
document.getElementById('root')
);複製代碼
上面的代碼能夠看出,使用Provider分爲下面幾個步驟:redux
一、導入Provider 這裏跟小白分享一個小知識,你能夠看到Provider加了個大括號,而第二個import configureStore沒有加大括號,這是由於react-redux的文件中沒有指定default輸出。若是指定了export default,則不須要加大括號,注意一個js文件只能有一個default。promise
import { Provider } from 'react-redux';複製代碼
二、將store做爲參數傳入Provider。瀏覽器
<Provider store={store}>
</Provider>複製代碼
Provider源碼bash
import { Component, Children } from 'react'
import PropTypes from 'prop-types'
import storeShape from '../utils/storeShape'
import warning from '../utils/warning'
let didWarnAboutReceivingStore = false
function warnAboutReceivingStore() {
if (didWarnAboutReceivingStore) {
return
}
didWarnAboutReceivingStore = true
warning(
'<Provider> does not support changing `store` on the fly. ' +
'It is most likely that you see this error because you updated to ' +
'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' +
'automatically. See https://github.com/reactjs/react-redux/releases/' +
'tag/v2.0.0 for the migration instructions.'
)
}
export default class Provider extends Component {
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
render() {
return Children.only(this.props.children)
}
}
if (process.env.NODE_ENV !== 'production') {
Provider.prototype.componentWillReceiveProps = function (nextProps) {
const { store } = this
const { store: nextStore } = nextProps
if (store !== nextStore) {
warnAboutReceivingStore()
}
}
}
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired
}
Provider.childContextTypes = {
store: storeShape.isRequired
}複製代碼
Provider源碼解析react-router
Provider只有一個參數,很是簡單,代碼也僅有55行。
一、Provider是一個react組件
import { Component, Children } from 'react'
import PropTypes from 'prop-types'
import storeShape from '../utils/storeShape'
import warning from '../utils/warning'
export default class Provider extends Component {
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
render() {
return Children.only(this.props.children)
}
}複製代碼
Provider組件寫了3個方法,getChildContext、constructor、render。
constructor是構造方法,this.store = props.store中的this表示當前的組件。在構造函數定義this.store的做用是爲了可以在getChildContext方法中讀取到store。
你最不熟悉的可能就是getChildContext,翻譯過來就是上下文。什麼意思呢?又有什麼用呢?咱們看到getChildContext方法是返回store。接着,就看不到store哪去了。
最後執行render渲染,返回一個react子元素。Children.only是react提供的方法,this.props.children表示的是隻有一個root的元素。
二、給Provider組件設置propTypes驗證。storeShape是一個封裝的方法。
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired
}
//storeShape
import PropTypes from 'prop-types'
export default PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
})複製代碼
三、驗證childContextTypes 它的做用就是讓Provider下面的子組件可以訪問到store。 詳細解釋和用法看 react關於context的介紹
Provider.childContextTypes = {
store: storeShape.isRequired
}複製代碼
四、node運行環境判斷 若是不是生產環境,也就是在開發環境中,實現componentWillReceiveProps()。
if (process.env.NODE_ENV !== 'production') {
Provider.prototype.componentWillReceiveProps = function (nextProps) {
const { store } = this
const { store: nextStore } = nextProps
if (store !== nextStore) {
warnAboutReceivingStore()
}
}
}複製代碼
其實也能夠把這段代碼寫到Provider組件內部去。
他的做用是當接收到新的props的時候,若是是在開發環境下,就判斷當前的store和下一個store是否是不相等,若是是,就執行warnAboutReceivingStore()。
export default class Provider extends Component {
componentWillReceiveProps(nextProps) {
if (process.env.NODE_ENV !== 'production') {
const { store } = this
const { store: nextStore } = nextProps
if (store !== nextStore) {
warnAboutReceivingStore()
}
}
}
render() {
return Children.only(this.props.children)
}
}複製代碼
五、warnAboutReceivingStore的做用。 上面說到執行了warnAboutReceivingStore,那麼warnAboutReceivingStore的做用是什麼呢?
let didWarnAboutReceivingStore = false
function warnAboutReceivingStore() {
if (didWarnAboutReceivingStore) {
return
}
didWarnAboutReceivingStore = true
warning(
'<Provider> does not support changing `store` on the fly. ' +
'It is most likely that you see this error because you updated to ' +
'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' +
'automatically. See https://github.com/reactjs/react-redux/releases/' +
'tag/v2.0.0 for the migration instructions.'
)複製代碼
didWarnAboutReceivingStore是一個開關的做用,默認是false,也就是不執行warning操做。當props更新的時候,執行了warnAboutReceivingStore(),若是didWarnAboutReceivingStore爲true,則return,不然就將didWarnAboutReceivingStore設置爲true。而後就會執行warning的警告機制。
這樣作的目的是不容許在componentWillReceiveProps作store的更新操做。
總結
很快就到尾聲了,Provider是一個react組件,提供了一個參數store,而後渲染了一個子組件,咱們一般把路由渲染成子組件,最後還處理了一個異常狀況,提供了warning提示。
大部分時候是這樣用的。在react-router4中,也支持這種寫法,Provider也能夠直接嵌套在自定義的react組件中。
<Provider store={store}>
<Router history={hashHistory}>
{routes}
</Router>
</Provider>複製代碼
在redux的配置文件中,若是你使用了redux-logger,也許你會寫下面這樣一段代碼:
import thunk from 'redux-thunk';
import promise from 'redux-promise';
import createLogger from 'redux-logger';
const logger = createLogger();
const createStoreWithMiddleware = applyMiddleware(thunk, promise, logger)(createStore);
const store = createStoreWithMiddleware(reducer);複製代碼
如今,咱們只關注redux-logger,咱們能夠看到使用redux-logger分爲下面幾個步驟:
一、導入redux-logger
import createLogger from 'redux-logger';複製代碼
二、運行createLogger方法,將返回結果賦值給常量
const logger = createLogger();複製代碼
三、將looger傳入applyMiddleware()
applyMiddleware(logger)複製代碼
有2個難點,第一是createLogger()的返回值究竟是如何實現的。第二就是applyMiddleware方法如何處理返回值。由於本文是講redux-logger的實現,因此咱們只分析createLogger()
redux-logger中createLogger方法源碼
const repeat = (str, times) => (new Array(times + 1)).join(str);
const pad = (num, maxLength) => repeat(`0`, maxLength - num.toString().length) + num;
//使用新的性能API能夠得到更好的精度(若是可用)
const timer = typeof performance !== `undefined` && typeof performance.now === `function` ? performance : Date;
function createLogger(options = {}) {
return ({ getState }) => (next) => (action) => {
const {
level, //級別
logger, //console的API
collapsed, //
predicate, //logger的條件
duration = false, //打印每一個action的持續時間
timestamp = true, //打印每一個action的時間戳
transformer = state => state, //在打印以前轉換state
actionTransformer = actn => actn, //在打印以前轉換action
} = options;
const console = logger || window.console;
// 若是控制檯未定義則退出
if (typeof console === `undefined`) {
return next(action);
}
// 若是謂詞函數返回false,則退出
if (typeof predicate === `function` && !predicate(getState, action)) {
return next(action);
}
const started = timer.now();
const prevState = transformer(getState());
const returnValue = next(action);
const took = timer.now() - started;
const nextState = transformer(getState());
// 格式化
const time = new Date();
const isCollapsed = (typeof collapsed === `function`) ? collapsed(getState, action) : collapsed;
const formattedTime = timestamp ? ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` : ``;
const formattedDuration = duration ? ` in ${took.toFixed(2)} ms` : ``;
const formattedAction = actionTransformer(action);
const message = `action ${formattedAction.type}${formattedTime}${formattedDuration}`;
const startMessage = isCollapsed ? console.groupCollapsed : console.group;
// 渲染
try {
startMessage.call(console, message);
} catch (e) {
console.log(message);
}
if (level) {
console[level](`%c prev state`, `color: #9E9E9E; font-weight: bold`, prevState);
console[level](`%c action`, `color: #03A9F4; font-weight: bold`, formattedAction);
console[level](`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
} else {
console.log(`%c prev state`, `color: #9E9E9E; font-weight: bold`, prevState);
console.log(`%c action`, `color: #03A9F4; font-weight: bold`, formattedAction);
console.log(`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
}
try {
console.groupEnd();
} catch (e) {
console.log(`—— log end ——`);
}
return returnValue;
};
}
export default createLogger;複製代碼
解析redux-logger
一、入口函數createLogger(options = {}) 咱們在redux配置文件中調用的就是這個函數,也是redux-logger中惟一一個函數,它只有一個參數option,option是object。
二、return ({ getState }) => (next) => (action) => {} 這行代碼看起來很複雜,一堆的箭頭函數,其實很簡單,createLogger()必定會有一個返回值,可是,咱們在控制檯打印action信息的時候,須要獲取state和action的信息,因此,首先傳入getState方法,getState是redux提供的一個方法,用來獲取store的state。而後再傳入next方法,接着傳入action方法。next和action都是redux提供的方法,到這一步,咱們就把須要的參數都傳入到函數中,能夠進行下一步操做了。
三、定義option的配置參數 咱們在使用redux-logger的時候,習慣了不配置任何參數,直接調用createLogger(),使用默認的配置。但其實還能夠手動傳入一個option配置,不過並不經常使用。
const {
level, //級別
logger, //console的API
collapsed, //
predicate, //logger的條件
duration = false, //打印每一個action的持續時間
timestamp = true, //打印每一個action的時間戳
transformer = state => state, //在打印以前轉換state
actionTransformer = actn => actn, //在打印以前轉換action
} = options;複製代碼
四、定義console 若是你給option配置了console相關的API,那麼就使用你的配置,若是沒有配置,就使用window.console
const console = logger || window.console;複製代碼
五、添加2個異常狀況作退出處理 第一個if語句是控制檯未定義就返回下一個action操做,可是我想不到在瀏覽器中會出現console方法不存在的狀況。 第二個if語句的predicate表示warn、log、error等屬於console的方法。&&表示2個條件要同時知足才執行下面的操做。predicate(getState, action)其實就是相似console.log(getState, action)
// 若是控制檯未定義則退出
if (typeof console === `undefined`) {
return next(action);
}
// 若是謂詞函數返回false,則退出
if (typeof predicate === `function` && !predicate(getState, action)) {
return next(action);
}複製代碼
六、給各個常量賦值 爲何會有這麼多常量呢?咱們來看一張圖,圖上展現了須要打印的各類信息。
總結出來就是:
action action.type @ timer
prev state {}
action {}
next state {}
這裏須要的是action.type, timer, 各類狀態下的state
const started = timer.now();
const prevState = transformer(getState());
const returnValue = next(action);
const took = timer.now() - started;
const nextState = transformer(getState());
// 格式化
const time = new Date();
const isCollapsed = (typeof collapsed === `function`) ? collapsed(getState, action) : collapsed;
const formattedTime = timestamp ? ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` : ``;
const formattedDuration = duration ? ` in ${took.toFixed(2)} ms` : ``;
const formattedAction = actionTransformer(action);
const message = `action ${formattedAction.type}${formattedTime}${formattedDuration}`;
const startMessage = isCollapsed ? console.groupCollapsed : console.group;複製代碼
上面代碼信息量比較大,咱們還能夠拆分出來看看。
a、先獲取一個開始時間started,而後讀取state,這個state是以前的狀態prevState。returnValue是返回值,返回下一個action。took是你執行完前面3行代碼以後的真實時間,在這裏由於沒有用到異步處理,因此我暫且認爲transformer()和next()是同步的。nextState是新的state。
這段代碼概括起來看就是先讀取開始時間,而後讀取state,這個state由於還有更新action,因此是舊的state,而後執行next傳入新的action,更新完成以後,獲取結束時間,計算更新action的時間差,而後再獲取更新後的state。
const started = timer.now();
const prevState = transformer(getState());
const returnValue = next(action);
const took = timer.now() - started;
const nextState = transformer(getState());複製代碼
b、下面的代碼作了一件事情,設置打印的信息。
formattedTime是打印出來的時間,格式是 時:分:秒,formattedDuration是時間差,formattedAction是當前的action方法。isCollapsed用處不大,無論他。
// 格式化
const time = new Date();
const isCollapsed = (typeof collapsed === `function`) ? collapsed(getState, action) : collapsed;
const formattedTime = timestamp ? ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` : ``;
const formattedDuration = duration ? ` in ${took.toFixed(2)} ms` : ``;
const formattedAction = actionTransformer(action);
const message = `action ${formattedAction.type}${formattedTime}${formattedDuration}`;
const startMessage = isCollapsed ? console.groupCollapsed : console.group;複製代碼
這幾行代碼作的事情也很是簡單,給須要打印的常量賦值。而後組合以後賦值給message:
const message = `action ${formattedAction.type}${formattedTime}${formattedDuration}`;複製代碼
message == action action.type @ time
七、try {} catch() {} 部分通常不會用到,也能夠無論。
startMessage.call(console, message);表示將message當作參數傳入startMessage,call的第一個參數是指運行環境,意思就是在console打印message信息。
try {
startMessage.call(console, message);
} catch (e) {
console.log(message);
}複製代碼
八、打印console的信息,這就圖上打印出來的部分了。
由於咱們一般沒有配置level,因此執行的是else語句的操做。
if (level) {
console[level](`%c prev state`, `color: #9E9E9E; font-weight: bold`, prevState);
console[level](`%c action`, `color: #03A9F4; font-weight: bold`, formattedAction);
console[level](`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
} else {
console.log(`%c prev state`, `color: #9E9E9E; font-weight: bold`, prevState);
console.log(`%c action`, `color: #03A9F4; font-weight: bold`, formattedAction);
console.log(`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
}複製代碼
九、遊戲結束
try {
console.groupEnd();
} catch (e) {
console.log(`—— log end ——`);
}複製代碼
十、返回值
return returnValue;複製代碼
總結
redux-logger作的事情是在控制檯輸出action的信息,因此首先要獲取前一個action,當前action,而後是下一個action。看完以後,你對redux-logger源碼的理解加深了嗎?
在react開發中,一部分人使用redux-thunk,一部分人使用redux-saga,彼此各有優勢。
今天咱們來研究一下redux-thunk的源碼,看看它到底作了什麼事情。
使用場景
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
//註冊thunk到applyMiddleware
const createStoreWithMiddleware = applyMiddleware(
thunk
)(createStore);
const store = createStoreWithMiddleware(rootReducer);
//action方法
function increment() {
return {
type: INCREMENT_COUNTER
};
}
//執行一個異步的dispatch
function incrementAsync() {
return dispatch => {
setTimeout(() => {
dispatch(increment());
}, 1000);
};
}複製代碼
主要代碼:
一、導入thunk
import thunk from 'redux-thunk';複製代碼
二、添加到applyMiddleware()
const createStoreWithMiddleware = applyMiddleware(
thunk
)(createStore);複製代碼
咱們能夠猜想thunk是一個object。
redux-thunk源碼
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;複製代碼
一共11行,簡潔,超簡潔,5K+ star。
源碼分析
一、定義了createThunkMiddleware()方法,能夠傳入參數extraArgument。
function createThunkMiddleware(extraArgument){}複製代碼
二、該方法返回的是一個action對象。
咱們知道action自己是一個object,帶有type和arguments。咱們將dispatch和getState傳入action,next()和action()是redux提供的方法。接着作判斷,若是action是一個function,就返回action(dispatch, getState, extraArgument),不然返回next(action)。
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};複製代碼
三、執行createThunkMiddleware()
這一步的常量thunk是一個對象,相似{type: "", arg1, arg2, ...}
const thunk = createThunkMiddleware();複製代碼
四、給thunk設置一個變量withExtraArgument,而且將createThunkMiddleware整個函數賦給它。
thunk.withExtraArgument = createThunkMiddleware;複製代碼
五、最後導出thunk。
export default thunk;複製代碼
總結
什麼是thunk?thunk是一箇中間函數,它的返回值是一個表達式。action裏面可能傳遞多個參數,咱們不可能再專門替每一個action寫一個傳遞方法。那麼就有了thunk的出現,thunk能夠將多個參數的函數做爲一個參數傳遞。
例若有這樣一個action,帶有多個參數:
function test(arg1, arg2, ...) {
return {
type: "TEST",
arg1,
arg2,
...
}
}複製代碼
而後咱們執行dispatch()方法,咱們須要把test()函數做爲一個參數傳遞。這樣就解決了多參數傳遞的問題,這個test()就成了一個thunk。
若是你對redux-thunk還有疑問,能夠查看這個解釋:redux-thunk of stackoverflow