爲何要寫這篇文章?html
之前想寫來着,發現有太多這種文章,就不想去總結了,結果太長時間沒作web開發了,就忘記了,在阿里面試被問了好幾回,回答的都不很理想,因此仍是那句話好記性不如爛筆頭,更況且尚未好記性!react
先看一個示例:git
import React,{Component,PropTypes} from 'react'
import ReactDOM from 'react-dom'
import {createStore } from 'redux'
import {Provider,connect }from 'react-redux'
// React component
class Counter extends Component {
render() {
const {value,onIncreaseClick} = this.props
return (
<div>
<span>{value} </span>
<button onClick={onIncreaseClick}>Increase</button>
</div>
)
}
}
const increaseAction = {
type: 'increase'
}
function counter(state = {
count: 0
},
action) {
const count = state.count
switch (action.type) {
case 'increase':
return {
count:
count + 1
}
default:
return state
}
}
const store = createStore(counter)
function mapStateToProps(state) {
return {
value: state.count
}
}
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () = >dispatch(increaseAction)
}
}
const App = connect(mapStateToProps, mapDispatchToProps)(Counter)
ReactDOM.render(
<Provider store = {store}>
<App/>
</Provider>,
document.getElementById('root'))複製代碼
這是Redux在react中的應用,其實redux和react是沒有關係的,redux能夠在其餘任何框架中使用github
這裏只是經過react-redux將react和redux結合在一塊兒了!web
接下來咱們先拋棄react咱們來分析源碼:面試
function createStore(reducer, preloadedState, enhancer) {
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
function getState() {
return currentState
}
function subscribe(listener) {
nextListeners.push(listener)
}
function dispatch(action) {
currentState = currentReducer(currentState, action) const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i] listener()
}
return action
}
function replaceReducer(nextReducer) {
currentReducer = nextReducer dispatch({
type: ActionTypes.INIT
})
}
dispatch({
type: ActionTypes.INIT
})
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
}複製代碼
簡化後的代碼能夠看到createStore接受三個參數,後面兩個參數先忽略,也就是createStore接受傳入一個reducerredux
返回:設計模式
return {
dispatch,
subscribe,
getState,
replaceReducer,
}複製代碼
能夠很容易看到返回的store就是一個訂閱發佈設計模式bash
getState: 讀取store裏面的state閉包
replaceReducer: 替換reducer,改變state修改的邏輯
subscribe傳入function訂閱
dispatch(action)發佈消息,將action和當前的state傳入定義好的reducer獲得新的state
接着通知以前經過store.subscribe訂閱消息的函數,這樣看是否是特別簡單
// 先寫一個,成爲reducer
function count (state, action) {
state=state || 2020;
switch (action.type) {
case 'add':
return {
year: state.year + 1
};
case 'sub':
return {
year: state.year - 1
}
default :
return state;
}
}
var store = createStore(count);
// store裏面的數據發生改變時,觸發的回調函數
store.subscribe(function () {
console.log('the year is: ', store.getState().year);
});
var action = { type: 'add' };
// 改變store裏面的方法
store.dispatch(action); // 'the year is: 2021複製代碼
怎麼辦呢,這時候要寫多個reducer,就用到了
var reducer_0 = function(state = {},action) {
switch (action.type) {
case 'SAY_SOMETHING':
return {...state,
message: action.value
}
default:
return state;
}
}
var reducer_1 = function(state = {},action) {
switch (action.type) {
case 'SAY_SOMETHING':
return {...state,
message: action.value
}
case 'DO_SOMETHING':
// ...
case 'LEARN_SOMETHING':
// ...
default:
return state;
}
}複製代碼
和並多個reducer,使用combineReducers直接搞定
import {
createStore,
combineReducers
}
from 'redux'
var reducer = combineReducers({
user: reducer_0,
items: reducer_1
})複製代碼
接下來看下combineReducers是怎麼作到的
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
return function combination(state = {},
action) {
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)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState: state
}
}複製代碼
combineReducers方法將多個子reducers合併爲一個對象finalReducers
當dispatch(action)觸發合併後的combination,combination經過key在調用各個子模塊,返回state,最後合併爲最新的nextState,是否是很簡單
看到這裏可能會有疑問,dispatch(action)後就是觸發reducers,那異步請求怎麼辦呢?
那就想辦法唄,既然reducer是獲取更新State,異步請求是獲取最新的數據,那隻能在reducer以前加一層!
要發送異步請求就須要中間件,先看一個最簡單的中間件redux-thunk.
// 咱們爲異步 action creator 提供的中間件叫 thunk middleware// 它的代碼在:https://github.com/gaearon/redux-thunk.
var thunkMiddleware = function({dispatch,getState}) {
return function(next) {
return function(action) {
return typeof action === 'function' ? action(dispatch, getState) : next(action)
}
}複製代碼
代碼很是簡單,來結合一個例子看看中間件怎麼使用:
import {
createStore,
applyMiddleware
} from 'redux'
//將redux-thunk代碼直接放在這裏方便閱讀
var thunkMiddleware = function({dispatch,getState}) {
return function(next) {
return function(action) {
return typeof action === 'function' ? action(dispatch, getState) : next(action)
}
}
}
var middleware = applyMiddleware(thunkMiddleware)
var reducer = function(state = {},action) {
switch (action.type) {
case 'SAY':
return Object.assign(state, {
message: action.value
})
default:
return state
}
}
const store = createStore(reducer, undefined, middleware)
// 如今 store 的 middleware 已經準備好了,再來嘗試分發咱們的異步 action:
var asyncSayAction = function(message) {
return function(dispatch) {
setTimeout(function() {
console.log(new Date(), 'Dispatch action now:') dispatch({
type: 'SAY',
message
})
},
2000)
}
}
store.dispatch(asyncSayAction('Hi'));複製代碼
// dispatch(action) ---> middleware 1 ---> middleware 2 ---> middleware 3 ...--> reducers
由上圖可知dispatch是要先通過中間件的,以前提交dispatch(action)都是對象{type:'SAY'},
redux-thunk是經過提交action判讀是否是function,決定下一步操做
接下來分析中間件是怎麼加入redux的
applyMiddleware
function applyMiddleware(...middlewares) {
return function(createStore) {
return function(reducer, preloadedState, enhancer) {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) = >dispatch(action)
}
chain = middlewares.map(middleware = >middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
}複製代碼
根據createStore的傳入參數調用enhancer(createStore)(reducer, preloadedState)
即applyMiddleware返回的函數
function(createStore) {
return function(reducer, preloadedState, enhancer) {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) = >dispatch(action)
}
chain = middlewares.map(middleware = >middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}複製代碼
這個函數先建立store,跟沒有中間件的流程同樣,接着看是怎麼把中間件加入到
經過chain = middlewares.map(middleware => middleware(middlewareAPI))
傳入第一層提供分發函數和 getState 函數(由於你的中間件或 action creator 可能須要從 state 中讀取數據)
仍是看redux-thunk
var thunkMiddleware = function({dispatch,getState}) {
return function(next) {
return function(action) {
return typeof action === 'function' ? action(dispatch, getState) : next(action)
}
}
}
複製代碼
接着經過 dispatch = compose(...chain)(store.dispatch)
組裝中間件,返回的是一個包裝函數,最好本身打斷點調試看一下,
返回的包裝函數爲dispatch,當咱們再次使用dispatch提交的時候就會先調用中間件,
中間件中的next表明下一個中間件,一直到最後一箇中間件結束,調用的next是store.dispatch
而後出發reducer
dispatch(action) ---> middleware 1 ---> middleware 2(第一個next) ---> middleware 3(第二個next) ...store.dispatch(最後一個next) --> reducer
打斷點能夠清楚的看出上面redux-thunk中間件
因爲這裏只用到一箇中間件redux-thunk,因此next直接是包裝前dispatch,調用後直接觸發reducer
到這裏redux就結束了,是否是很簡單,中間件的加入就是對原有dispatch的再包裝,包裝的代碼有點難懂,要好好的理解閉包和高階函數打斷點才能看懂喲!!!
目前已經有現成的工具react-redux
來實現兩者的結合:
react-redux提供了Provider connect兩個工具
React經過Context屬性,屬性(props)直接給子孫component,無須經過props層層傳遞, Provider僅僅起到得到store,而後將其傳遞給子孫元素而已:
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) }}複製代碼
是否是很簡單!
connect是一個高階函數,首先傳入mapStateToProps、mapDispatchToProps,而後返回一個生產Component
的函數(wrapWithConnect)
看看connect是怎麼實現高階組件,更新數據的
function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
const mapState = mapStateToProps let mapDispatch = mapDispatchToProps
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.version = version
this.store = props.store || context.store
const storeState = this.store.getState()
this.state = {
storeState
}
}
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange()
}
}
componentDidMount() {
this.trySubscribe()
}
componentWillReceiveProps(nextProps) {
if (!pure || !shallowEqual(nextProps, this.props)) {
this.haveOwnPropsChanged = true
}
}
handleChange() {
const storeState = this.store.getState() this.setState({
storeState
})
}
render() {
this.renderedElement = createElement(WrappedComponent, this.mergedProps)
return this.renderedElement
}
}
Connect.displayName = connectDisplayName Connect.WrappedComponent = WrappedComponent Connect.contextTypes = {
store: storeShape
}
Connect.propTypes = {
store: storeShape
}
return hoistStatics(Connect, WrappedComponent)
}
}複製代碼
將connect代碼簡化後能夠看到,HOC高階組件就是對自定義組件的封裝
封裝後的組件,經過獲取redux的store,而後經過this.store.subscribe監聽數據變化
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange()
}
}複製代碼
一但數據有變化觸發handleChange
調用setState觸發render,在render中獲取須要的參數合併傳遞給自定義組件完成更新:
handleChange() {
const storeState = this.store.getState() this.setState({
storeState
})
}複製代碼
到這裏就接近尾聲了,只要理解原理,之後再使用是否是就很容易上手1
最後從網上搞了張圖,很是好!