平時使用React作開發的同窗對redux都不會陌生,這是一個基於flux架構的十分優秀的狀態管理庫。這是Redux官方文檔對它的描述。react
Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。可讓你構建一致化的應用,運行於不一樣的環境(客戶端、服務器、原生應用),而且易於測試。
實際上優秀的Redux並非只做爲React的拓展,它能夠融合任何框架。本文做爲筆者的學習筆記,一樣也但願分享給你們。若是錯漏,懇請指出。您的批評與指正是我前進路上的一大動力。redux
首先先來介紹它的三個核心概念,分別是action
、reducer
以及store
數組
action
action
是數據的惟一來源,一般使用store.dispatch()
將action
傳入store
bash
reducer
中通常放入一些邏輯處理,響應action
併發送到store
服務器
store
就是把它們聯繫到一塊兒的對象。包含了咱們熟悉的getState()
、dispatch()
等方法
架構
更爲詳盡的資料請閱讀官方文檔,筆者在這裏就不在贅(抄)述(襲)併發
接下來咱們先來實現一個計數器,直接上代碼吧框架
app.js
import React, {
Component
} from 'react'
import store from './store'
import { addCount, decCOUNT, decCount } from './actions/Count'
class App extends Component {
constructor(props) {
super(props)
this.state = {
count: null
}
this.add = this.add.bind(this)
this.dec = this.dec.bind(this)
}
componentDidMount() {
console.log(store.getState())
this.setState({
count: store.getState().count
})
}
add() {
store.dispatch(addCount())
this.setState({
count: store.getState().count
})
}
dec() {
store.dispatch(decCount())
this.setState({
count: store.getState().count
})
}
render() {
return (
<div>
{this.state.count}
<button onClick={this.add}>add</button>
<button onClick={this.dec}>dec</button>
</div>
)
}}
export default App複製代碼
這是十分簡單的代碼,你們看的時候也能夠直接略過。async
actionTypes.js
裏面的定義了咱們action
的類型
actionTypes.js
// ------------------add--------------------
export const ADD_COUNT = 'ADD_COUNT'
export const DEC_COUNT = 'DEC_COUNT'複製代碼
/actions/Count.js
裏面就是定義咱們的action
,而後咱們把action
傳到reducer
裏面去處理
// /actions/Count.js
import {
ADD_COUNT,
DEC_COUNT} from "../actionTypes";
export function addCount() {
return {
type: ADD_COUNT
}}
export function decCount() {
return {
type: DEC_COUNT
}}
// /reducers/Count.js
import {
ADD_COUNT,
DEC_COUNT} from "../actionTypes";
const initialState = {
count: 0
}
export function Count(state = initialState, action) {
const count = state.count
switch (action.type) {
case ADD_COUNT:
return {
count: count + 1
}
case DEC_COUNT:
return {
count: count - 1
}
default:
return state
}}複製代碼
接下來就是store.js
//store.js
import {createStore} from 'redux'
import {Count} from './reducers/Count'
const store = createStore(Count)
export default store複製代碼
這樣,一個簡單的計數器咱們就作好了 看看效果吧。
React-Redux
是Redux
的官方React
綁定庫。它可以使你的React
組件從Redux store
中讀取數據,而且向store
分發actions
以更新數據
讓咱們來看看它怎麼使用
//app.js
import React, {Component} from 'react'
import store from './store'
import { connect } from 'react-redux'
import { addCount, decCount } from './actions/Count'
class App extends Component {
constructor(props) {
```
}
render() {
const { count } = this.props
return (
`````
)
}}
function mapStateToProps(state) {
return {
count: state.count
}
}
export default connect(mapStateToProps)(App)
//index.js
ReactDOM.render(<Provider store={store}> <App /> </Provider>, document.getElementById('root'))複製代碼
只要這樣就OK啦,編寫mapStateToprops
函數,能夠將store
中的state
和props
關聯起來,取值的時候只要 相似於 const { count } = this.props
就能夠了
咱們建立store
的時候是調用了createStore
函數,並將一個reducer
做爲參數傳進去,如今咱們來看看它的源碼吧
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
````
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}複製代碼
能夠看到createStore.js
中,默認導出的是createStore
函數,其接受的參數有三個,而後返回一個對象,也就是說咱們建立的store
對象中return中的幾個方法。咱們來一個一個看。
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
```
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
```
}
if (typeof enhancer !== 'undefined') {
```
}複製代碼
首先它會作一些判斷,咱們只傳入了reducer
參數,因此不會走這些邏輯,而後定義一些變量
let currentReducer = reducer
//就是咱們傳入的reducer
let currentState = preloadedState //undefined
let currentListeners = []
let nextListeners = currentListeners //listener數組,存儲subscribe方法傳入的函數
let isDispatching = false複製代碼
而後就到了咱們熟悉的store.getState()
方法,若是在reducer
計算的時候調用這個方法,就會報一個錯誤,。正常使用的話是返回 currentState
function getState() {
if (isDispatching) {
throw new Error(
``` )
}
return currentState
}複製代碼
而後就到了subscribe
方法,這裏採用的是一個觀察者模式
function subscribe(listener) {
if (typeof listener !== 'function') { ``` }
if (isDispatching) { throw new Error( ``` ) }
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener) //push進觀察者數組
return function unsubscribe() {
//移除
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error( ``` )
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}複製代碼
下面就是咱們的dispatch
方法了,它接收一個action
做爲參數,利用一開始傳入的reducer
去計算新的state
,並在計算完成後依次調用subscribe
方法傳入的函數
function dispatch(action) {
if (!isPlainObject(action)) { throw new Error( '``` ) } if (typeof action.type === 'undefined') { throw new Error( ``` ) } if (isDispatching) { ``` } try { isDispatching = true currentState = currentReducer(currentState, action) //這裏就是調用reducer去計算新的state } finally { isDispatching = false } const listeners = (currentListeners = nextListeners)//計算完以後開始執行觀察者數組裏面的函數 for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }複製代碼
咱們不可能全部的邏輯都放在一個reducer
裏面,要拆分reducer
,這時候能夠用到combineReducer
。
//Goods.js
const initialState = {
goods: [{
price: 100,
size: 'M',
id: 0
}, {
price: 200,
size: 'L',
id: 1
}]}
export function Goods(state = initialState, action) {
return state
}複製代碼
//store.js
const rootReducers = combineReducers({
Count,
Goods})
const store = createStore(rootReducers)複製代碼
這個時候咱們發現state
被合併成了一個新的state
讓咱們來解開combineReducer
的面紗吧!看combineReducers
方法以前,咱們先看assertReducerShape
方法
assertReducerShape
主要是對傳入的reducers作了一層篩選,保證reducers
的initialState
存在,以及它們的action
須要有本身的命名空間
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') { throw new Error( ``` ) }
if (
typeof reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined' ) {
throw new Error( ``` )
}
}
)
}
複製代碼
接下來就是combineReducers
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
//對reducers的第一層篩選
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
//最後把reducers放在finalReducers數組裏
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
//這裏就是對reducers的又一層篩選
assertReducerShape(finalReducers)
}
catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') { ``` }
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') { ``` }
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}複製代碼
咱們能夠看到最後它返回了一個combination
函數,實際上就是合併事後的store,只要有一個小store(這邊咱們姑且這麼叫,每個reducer對應一個小store)發生了從新的計算,就會返回一個新的state狀態,也就是咱們最終獲得的store
Redux
還爲咱們提供了強大的中間件拓展,讓咱們來看一下。
這裏咱們以redux-thunk
爲例,來學習中間件的用法
//store.js
import thunk from 'redux-thunk'
const store = createStore(rootReducers, applyMiddleware(thunk))
//actions/Count.js
export function asyncTest() {
return (dispatch,getState) => {
setTimeout(() => {
dispatch(asyncData())
}, 2000);
}
}
export function asyncData(data) {
return {
type: ASYNC_DATA,
count: -100
}
}
//App.js
componentDidmount(){
store.dispatch(asyncTest()
}複製代碼
咱們來看一下效果
而後就是得來看看applyMiddleware
幹了啥了。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}複製代碼
這個createStore
是個啥意思呢,這個時候就要看回咱們createStore
的源碼了。跟沒使用中間件的時候不同。這個時候會走到這個邏輯
return enhancer(createStore)(reducer, preloadedState)
咱們使用的時候是這樣
import { createStore, combineReducers, applyMiddleware} from 'redux'
const store = createStore(rootReducers //reducer,
//preloadedState=undefined
applyMiddleware(thunk) //這裏的返回結果纔是真正的enhancer,因此咱們要看applyMiddleware返回了什麼
)複製代碼
applyMidderware
調用是會返回一個函數,咱們姑且稱他爲函數A,函數A接受一個createStore
參數,返回一個函數B,函數B能夠調用createStore
,生成store
。因此這裏應該是函數B執行,參數是createStore
。這個createStore
是咱們import
進來的。這裏調用createStore
生成一個store
,而後對middlewares
遍歷(咱們只傳入了thunk
),爲每一層中間件傳入getState
和dispatch
。而後就執行compose
方法
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}複製代碼
compose
很短,並且註釋直接告訴咱們,把傳入的函數數組從前日後進行嵌套調用。咱們傳入compose
函數的參數都是形如next=>action=>{}
這樣的函數,通過compose
的處理後,每個函數的next
實則是action=>{}
的返回值,執行完以後就將原生的dispatch
方法傳入,更新state
dispatch = compose(...chain)(store.dispatch)
這個時候咱們再來看咱們是用的thunk
中間件。咱們就能夠理解了它的_ref
裏面爲何有dispatch
和getState
,由於是在applyMiddleware
函數中傳入的
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
}
}
var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;複製代碼
若是他的action
是一個函數,那麼它就會直接終止中間件的調用,直接執行action
,更新state
Redux
我以爲最難理解的就是它的applyMiddleware
方法了,我如今的弟弟水平也只能理解到這種程度。但願之後再回來看的時候會有更深的理解吧!