什麼是Redux?react
Redux 的工做流程(核心思想):git
1.設計全局 state 的數據結構狀態樹
2.設計更改 state 數據、狀態的 actionType 常量
3.根據 actionType,編寫 actionCreator
4.根據各個 actionCreator 的返回值,用 reducer 作數據處理
5.有個 reducer 以後,用 createStore 來獲得全局惟一的 store,來管理 state
6.用 bindActionCreator 函數將 actionCreator 和 store.dispatch 綁定起來,獲得一組能更改 state 的函數
7.分發使用各個狀態修改函數(dispatch)github
源碼地址redux
src
├── utils #工具函數文件夾
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
└── index.js #入口 js
複製代碼
index.js就是整個代碼的入口:數組
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'。。。'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
複製代碼
這裏的 isCrushed 函數主要是爲了驗證在非生產環境下 redux 是否被壓縮(默認狀況下,
isCrushed.name
等於isCrushed
,若是被壓縮了,函數的名稱會變短,通常會壓縮成數字,那麼 (isCrushed.name !== 'isCrushed'
) 就是true
),若是被壓縮,就給開發者一個 warn 提示)。bash
而後就是暴露 createStore
、combineReducers
、bindActionCreators
、applyMiddleware
、compose
這幾個接口給開發者使用。數據結構
createStore
是redux的核心API。
createStore
會生成一個倉庫(store
),用來維護一個全局的state
。閉包
import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'
// 私有 action
export var ActionTypes = {
INIT: '@@redux/INIT'
}
export default function createStore(reducer, preloadedState, enhancer) {
// 判斷接受的參數個數,來指定 reducer 、 preloadedState 和 enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// 若是 enhancer 存在而且適合合法的函數,那麼調用 enhancer,而且終止當前函數執行
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// 儲存當前的 currentReducer
var currentReducer = reducer
// 儲存當前的狀態
var currentState = preloadedState
// 儲存當前的監聽函數列表
var currentListeners = []
// 儲存下一個監聽函數列表
var nextListeners = currentListeners
var isDispatching = false
// 這個函數能夠根據當前監聽函數的列表生成新的下一個監聽函數列表引用
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/* 函數
... getState ...
... subscribe ...
... dispatch ...
... replaceReducer ...
... observable ...
*/
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
複製代碼
createStore
接收3個參數:app
createStore
的返回值是dispatch
、subscribe
、getState
、replaceReducer
、[$$observable]: observable
,他們共同組成了一個store
。dom
action
表明的是用戶的操做。
redux規定
action
必定要包含一個type
屬性,且type
屬性也要惟一,相同的type
,redux視爲同一種操做,由於處理action
的函數reducer
只判斷action
中的type
屬性。
reducer 只是一個模式匹配的東西,真正處理數據的函數,通常是額外寫在別的地方(固然直接寫在reducer中也沒問題,只是不利於後期維護),只是在reducer中調用罷了。
export default (state, action) => {
switch (action.type) {
case A:
return handleA(state)
case B:
return handleB(state)
case C:
return handleC(state)
default:
return state // 若是沒有匹配上就直接返回原 state
}
}
複製代碼
reducer 接收兩個參數,state 以及 action 函數返回的 action對象,並返回最新的 state。
reducer 爲何叫 reducer 呢?由於 action 對象各類各樣,每種對應某個 case ,但最後都彙總到 state 對象中,從多到一,這是一個減小( reduce )的過程,因此完成這個過程的函數叫 reducer。
function getState() {
return currentState
}
複製代碼
整個項目的currentState
是處於一個閉包之中,因此能一直存在,getState
會返回當前最新的state。
簡單的說,就是相似於一個get
的方法,返回currentState
的值。
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
複製代碼
subscribe
接收一個listener
。
他的做用是給store添加監聽函數。nextListeners
儲存了整個監聽函數列表。
subscribe
的返回值是一個unsubscribe
,是一個解綁函數,調用該解綁函數,會將已經添加的監聽函數刪除,該監聽函數處於一個閉包之中,會一直存在,因此在解綁函數中能刪除該監聽函數。
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
複製代碼
dispatch
接收一個參數action
。
代碼會先調用createStore
傳入的參數reducer
方法,reducer
接收當前state
和action
,經過判斷actionType
,來作對應的操做,並返回最新的currentState
。
dispatch
還會觸發整個監聽函數列表,因此最後整個監聽函數列表都會按順序執行一遍。
dispatch
返回值就是傳入的action
。
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
複製代碼
replaceReducer
是替換當前的reducer
的函數。
replaceReducer
接收一個新的reducer
,替換完成以後,會執行 dispatch({ type: ActionTypes.INIT })
,用來初始化store
的狀態。
官方舉出了三種replaceReducer
的使用場景,分別是:
// 如下只留下了核心代碼
// combination 函數是 combineReducers(reducers) 的返回值,它是真正的 rootReducer
// finalReducers 是 combineReducers(reducers) 的 reducers 對象去掉非函數屬性的產物
// mapValue 把 finalReducers 對象裏的函數,映射到相同 key 值的新對象中
function combination(state = defaultState, action) {
var finalState = mapValues(finalReducers, (reducer, key) => {
var newState = reducer(state[key], action); //這裏調用子 reducer
if (typeof newState === 'undefined') {
throw new Error(getErrorMessage(key, action));
}
return newState; //返回新的子 state
});
//...省略一些無關的代碼
return finalState; //返回新 state
};
複製代碼
這個函數能夠組合一組 reducers,而後返回一個新的 reducer。
隨着整個項目愈來愈大,state 狀態樹也會愈來愈龐大,state的層級也會愈來愈深,當某個action.type
所對應的 case 要修改深層屬性時,那樣的話函數寫起來就很是難看,因此必須在這個函數的頭部驗證 state 對象有沒有那個屬性。
combineReducers
實現方法也比較簡單,它遍歷傳入的reducers
,返回一個新的reduce
r,這個新對象的 key
跟傳入的reducers
同樣,它的 value
則是傳入的reducers
的不一樣key
對應的value
展開的{ key: value }
。
舉個例子:
var reducers = {
todos: (state, action) { // 此處的 state 參數是全局 state.todos屬性
switch (action.type) {...} // 返回的 new state 更新到全局 state.todos 屬性中
},
activeFilter: (state, action) { // 拿到 state.activeFilter 做爲此處的 state
switch (action.type) {...} // new state 更新到全局 state.activeFilter 屬性中
}
}
var rootReducer = combineReducers(reducers)
複製代碼
combineReducers 內部會將 state.todos 屬性做爲 todos: (state, action) 的 state 參數傳進去,經過 switch (action.type) 以後返回的 new state 也會更新到 state.todos 屬性中;也會將 state.activeFilter 屬性做爲 activeFilter: (state, action) 的 state 參數傳進去,經過 switch (action.type) 以後返回的 new state 也會更新到 state.activeFilter 屬性中。
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
複製代碼
bindActionCreators
的代碼就是將actionCreator
和dispatch
聯結在一塊兒。
對於多個 actionCreator
,咱們能夠像reducers
同樣,組織成一個 key/action
的組合。
因爲不少狀況下,action
是 actionCreator
返回的,實際上要這樣調用 store.dispatch(actionCreator(...args))
很麻煩,只能再封裝一層,經過反覆組合,將嵌套的函數分離。
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
調用了ES5的Array.prototype.reduce
方法,將形如fn(arg1)(arg2)(arg3)...
的柯里化函數按照順序執行。
其傳入的參數爲函數數組,返回的爲reduce從左到右合併後的新的函數,是一個相似於鏈式調用的過程。
funcs.reduce((a, b) => (...args) => a(b(...args)))
複製代碼
這句特別重要,組合函數的這部很是重要,咱們發現...args
參數會依次的從右到左執行,好比將b(...args)
的執行結果,傳入a
中做爲參數繼續執行。
export default function applyMiddleware(...middlewares) {
return createStore => (reducer, initialState) => {
var store = createStore(reducer, initialState);
var dispatch = store.dispatch; //拿到真正的 dispatch
// 將最重要的兩個方法 getState/dispatch 整合出來
var middlewareAPI = {
getState: store.getState,
dispatch: action => dispatch(action)
};
// 依次傳遞給 middleware,讓它們有控制權
var chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain, dispatch); // 再組合出新的 dispatch
return {
...store,
dispatch
};
};
}
複製代碼
applyMiddleware
就是中間件的意思。
applyMiddleware
接收中間件爲參數,並返回一個以createStore
爲參數的函數;
同時applyMiddleware
又是createStore
函數中的第三個參數,因此咱們回到createStore
的代碼,找到了:
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
複製代碼
當createStore
中傳了第三個參數的時候,會執行enhancer(createStore)(reducer, preloadedState)
,這是一個柯里化函數;
咱們能夠設想中間件的使用方法:const store = createStore( reducer, applyMiddleware([...中間件]))
。
applyMiddleware([...中間件])
的返回值是一個以createStore
爲參數的函數,這個函數會在createStore
中執行,返回的函數也會繼續執行,最後返回一個store
。
繼續回到applyMiddleware
中,在返回store
以前,中間件將最重要的兩個方法 getState/dispatch
整合出來,並傳遞給中間件使用,中間件處理完以後,返回一個新的dispatch
。
applyMiddleware
把中間件放在一個chain
數組中,並經過compose
方法(咱們上面已經介紹過了),讓每一箇中間件按照順序一次傳入dispatch
參數執行,再組合出新的 dispatch
。因而可知,每一箇中間件的格式都應該是接收一個{ dispatch, getState }
,返回一個(dispatch) => { return function(action) { ... }}
一般咱們使用Redux的時候,都是React要使用
那麼就避免不了把這二者聯繫起來,其實本質上Redux
和React
是沒有任何聯繫的。
那麼咱們要用到依賴react-redux
就是把 react 和 redux 聯繫到一塊兒。
react-redux
提供兩個方法:connect
和Provider
。
connect
方法就是鏈接React
組件和Redux store
。
connect
其實是一個高階函數,返回一個新的已經與Redux store
鏈接的組件類。
例如:
const VisibleCounter = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
複製代碼
Counter 是 UI 組件,VisibleCounter就是由 react-redux
經過connect
方法自動生成的容器組件。
mapStateToProps
:從Redux狀態樹中提取須要的部分做爲props傳遞給當前的組件。mapDispatchToProps
:將須要綁定的響應事件(action)做爲props傳遞到組件上(也能夠不用寫這個,直接把actions綁上去就行了)。//須要引入actions,就是一系列的方法
const VisibleCounter = connect(
mapStateToProps,
actions
)(Counter)
複製代碼
代碼示例:
import React, { Component } from 'react';
import { connect } from 'react-redux'
import actions from '../store/actions/counter1'
class Counter1 extends Component {
constructor(props) {
super(props)
}
render() {
return (
<>
<h1>Counter1</h1>
<p>{this.props.number}</p>
<button onClick={() => this.props.increment(2)}>+</button>
<button onClick={() => this.props.decrement(2)}>-</button>
</>
)
}
}
// state 就是倉庫中的狀態
// function mapStateToProps(state){
// // number第1個number是指這個組件中的屬性
// // state.number 指的是倉庫中的number
// return {number:state.number}
// }
// let mapStateToProps = function(state){
// // number第1個number是指這個組件中的屬性
// // state.number 指的是倉庫中的number
// return {number:state.number}
// }
// ({number:state.number}) 若是返回一個對象,須要給穿上對象外面包一個小括號
// let mapStateToProps = state=>({number:state.number})
//第一種寫法------------------------------------------------
// function mapStateToProps(state) {
// return {
// number: state.counter1.number
// }
// }
// function mapDispatchToProps(dispatch) {
// return bindActionCreators(actions, dispatch)
// }
// export default connect(mapStateToProps,mapDispatchToProps)(Counter1);
//第二種寫法------------------------------------------------
// let mapStateToProps = state=>({number:state.counter1.number})
// let mapDispatchToProps = dispatch=>bindActionCreators(actions,dispatch)
// export default connect(mapStateToProps,mapDispatchToProps)(Counter1);
//第三種寫法------------------------------------------------
let mapStateToProps = state=>({number:state.counter1.number})
export default connect(mapStateToProps,actions)(Counter1);
複製代碼
其中引入的actions
就是一系列的方法:
import * as types from "../action-types"
function increment(payload) {
return { type: types.ADD1, payload }
}
function decrement(payload) {
return { type: types.SUB1, payload }
}
export default { increment, decrement }
複製代碼
Provider
能夠實現store
的全局訪問,將store
傳給每一個組件。
原理:使用
React
的context
,context
能夠實現跨組件之間的傳遞。
使用時,咱們須要在入口index.js
文件中引入使用,以下:
import React from "react"
import ReactDOM from "react-dom"
import App from './App'
//使用react-redux中的Provider
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
// 別忘了包起來,而且把store綁上去,可讓全部的組件均可以使用倉庫裏的狀態
<Provider store={store}>
<App></App>
</Provider>
, window.app)
複製代碼
而且咱們須要使用其中的connect
方法實現數據和方法的映射,使用方式也很是簡單
就是加強store.dispatch()
的功能,便可以在reducer
中進行一些異步的操做,能夠派發多種類型的東西。
使用方式也很是簡單,直接將thunk
中間件引入,放在applyMiddleware
方法之中便可。
能夠輸出日誌信息
使用的時候須要經過createLogger
建立一個logger
,再把建立這個logger
放在applyMiddleware
方法之中便可。
import { createLogger } from 'redux-logger'
const logger = createLogger({
// ...options
})
applyMiddleware(logger)
複製代碼
等等......