Redux做爲通用的狀態管理器,能夠搭配任意界面框架。因此並搭配react使用的話就要藉助redux官方提供的React綁定庫react-redux,以高效靈活的在react中使用redux。下面咱們一塊兒看看是react-redux如何靈活高效的html
在開始之間仍是大概提一下redux的內容,以避免脫節。比較早的時候也解讀了下redux的源碼實現,能夠參考一下react
Redux 是 JavaScript 狀態容器,旨在提供可預測化的狀態管理。 其包括action、store、reducer等三部分:git
在理解各部分做用以前,咱們能夠經過一個更新數據的例子來捋下思路:github
根據上面的例子咱們再對比下redux的流程圖(圖片來自阮一峯大佬): 能夠對照着來理解不一樣部分的做用。數據庫
就如上面所說負責更新字段和更新時機。 用戶接觸到的是view(即用戶界面),對應的更新信息經過acton傳遞給reducer。redux
function addTodo(text) {
return {
// 更新類型及具體內容
type: ADD_TODO,
text
}
}
複製代碼
負責更新數據的具體邏輯。
即根據當前state及action攜帶的信息合成新的數據。api
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
// 不一樣更新類型的處理邏輯不一樣
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
default:
return state
}
}
複製代碼
store就是負責維護和管理數據。 此外還有dispatch,subscrible等api來完成更新事件的分發。 例如:緩存
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
import {
addTodo} from './actions'
// 註冊監聽事件
const unsubscribe = store.subscribe(() => console.log(store.getState()))
// 發起一系列 action
store.dispatch(addTodo('Learn about actions'))
// 中止監聽
unsubscribe()
複製代碼
到這裏,咱們應該就大概明白redux如何更新管理數據的了。app
那麼對照react-redux的實例官方demo,來結合React的時候,會發現redux使用有些不一樣之處。框架
大概能夠有下面這三點:
能夠猜想,上述差別是React-redux幫咱們封裝了綁定監聽等過程,避免須要每一個應用都重複相同的操做。使得React組件的數據源只關注props和state。
下面帶着這些問題深刻了解React-redux.
本質上 react-redux也是react高階組件HOC的一種實現。其基於 容器組件和展現組件相分離 的開發思想來實現的。 其核心是經過兩部分來實現: 一、Provider 二、container經過connect來解除手動調用store.subscrible
provider用法以下,綁定以後,再通過connect處理,就能夠在組件中經過props訪問對應信息了。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp)
render(
// 綁定store
<Provider store={store}>
<App /> </Provider>,
document.getElementById('root')
)
複製代碼
在看源碼以前,咱們先自行猜想一下。
前面也提到了Provider是React組件。
那麼爲了讓子組件都能方便的訪問到store,store這個屬性會如何傳遞呢。props?context?
import { Component, Children } from 'react'
export default class Provider extends Component {
// 聲明context 以被子組件獲取。
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
// 掛載store到Provider
this.store = props.store
}
render() {
// 判斷是否只有一個child,是則返回該child節點,不然拋錯
return Children.only(this.props.children)
}
}
複製代碼
Provider將store傳遞給子組件,具體如何和組件綁定就是conect作的事情了。
connect鏈接組件和store,該操做並不修改原組件而是返回一個新的加強了關聯store的組件。
根據這個描述,這顯然就是個React高階組件(HOC)嗎。先看一下使用:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
複製代碼
接收四個參數,具體每一個參數的做用詳細能夠參考cn.redux.js.org/docs/react-…
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { VisibilityFilters } from '../actions'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(t => t.completed)
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
// 將store中的state做爲props傳遞給被包裹組件
// mapStateToProps對應當前組件所須要的props,不過這個props顯然是要從store中抽取的,不是全部store都須要,因此只會取state.todos
const mapStateToProps = state => ({
todos: getVisibleTodos(state.todos, state.visibilityFilter)
})
// 將action 與被包裹組件相綁定。
// 其實就是將action中的方法賦值到Props上,以便在組件中調用toggleTodo方法
const mapDispatchToProps = dispatch => ({
toggleTodo: id => dispatch(toggleTodo(id))
})
// 被包裹組件就對應TodoList
export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
複製代碼
connect實現比較複雜一點,返回的是個高階函數咱們能夠先看該函數實現了什麼。
首先該方法接受相關參數,進行參數的判斷和兼容處理(不指定使用默認)。 並返回一個 wrapWithConnect 方法來裝飾傳入的容器組件。
// 每一個參數的默認實現
const defaultMapStateToProps = state => ({}) // eslint-disable-line no-unused-vars
const defaultMapDispatchToProps = dispatch => ({ dispatch })
const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
...parentProps,
...stateProps,
...dispatchProps
})
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
// 須要store中的state纔會去監聽
const shouldSubscribe = Boolean(mapStateToProps)
// 更新state 方法的兼容,無mapStateToProps則使用默認
const mapState = mapStateToProps || defaultMapStateToProps
let mapDispatch
// action creater是否爲 函數
if (typeof mapDispatchToProps === 'function') {
// 函數直接賦值
mapDispatch = mapDispatchToProps
} else if (!mapDispatchToProps) {
// 不存在,則使用默認方法
mapDispatch = defaultMapDispatchToProps
} else {
// 不然 將action Creater 包裝起來
mapDispatch = wrapActionCreators(mapDispatchToProps)
}
const finalMergeProps = mergeProps || defaultMergeProps
const { pure = true, withRef = false } = options
const checkMergedEquals = pure && finalMergeProps !== defaultMergeProps
function wrapWithConnect(WrappedComponent) {
const connectDisplayName = `Connect(${getDisplayName(WrappedComponent)})`
class Connect extends Component {/****/}
// ****
return hoistStatics(Connect, WrappedComponent)
}
複製代碼
wrapWithConnect 函數接受一個組件(connect這就是個HOC。返回一個connect組件
// ****省略*****
// hoistStatics的做用:經常使用語高階組件中,將被包裹元素的靜態方法,「同步」到容器元素中。
// 也就是 connect中那些WrappedComponent屬性的mix
return hoistStatics(Connect, WrappedComponent)
複製代碼
這裏,就是HOC常見的增長功能的實現了。 也就是加強與redux的關聯,讓使用者只須要關注props,而非每次都要本身手動綁定。
既然connect存在生命週期,那就順着生命週期看看
this.store 即Provider中掛載的Store
// 構造函數,獲取store中的state
constructor(props, context) {
super(props, context)
this.version = version
// props或者context中,這是provider中掛載的store
this.store = props.store || context.store
// 獲取state
const storeState = this.store.getState()
// 初始化state
this.state = { storeState }
this.clearCache()
}
複製代碼
shouldComponentUpdate這裏會根據options裏面的參數來看是否 pure 選擇不一樣的更新策略
shouldComponentUpdate() {
return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged
}
複製代碼
componentDidMount 根據前面的shouldSubscribe標識(mapStateToProps是否爲true)決定是否增長監聽事件
componentDidMount() {
this.trySubscribe()
}
trySubscribe() {
// 存在監聽必要 而且沒有註冊過監聽事件
if (shouldSubscribe && !this.unsubscribe) {
// 業務組件中沒有使用的subscribe 在這裏實現,這也是HOC的用法之一,公共方法的抽離
// 註冊完成以後,this.unsubscribe爲對一個unsubscribe回調
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
this.handleChange()
}
}
複製代碼
componentWillReceiveProps 判斷是否更新 ,對於pure 組件 這裏就涉及到了shallowEqual。 經過shallowEqual的實現,咱們能夠獲得Immtable的重要性
componentWillReceiveProps(nextProps) {
if (!pure || !shallowEqual(nextProps, this.props)) {
this.haveOwnPropsChanged = true
}
}
複製代碼
由此能夠看到Immutable的重要性。對於引用類型的數據,只是比較了引用地址是否相同。
對於嵌套引用數據類型,只比較key的長度和value引用地址,並無進一步深刻比較。致使嵌套結構並不適用。
export default function shallowEqual(objA, objB) {
// 引用地址是否相同
if (objA === objB) {
return true
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
// key長度是否相同
if (keysA.length !== keysB.length) {
return false
}
// 循環比較,vakue思否相同,對於嵌套引用類型,這種比較是不能知足預期的。
const hasOwn = Object.prototype.hasOwnProperty
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) ||
objA[keysA[i]] !== objB[keysA[i]]) {
return false
}
}
return true
}
複製代碼
再下面是render,對因而否更新進行判斷,便是否更新傳遞給子組件的props render的關注點在於 傳遞給WrappedComponent的props如何得到。
// this.mergedProps 的計算
if (withRef) {
this.renderedElement = createElement(WrappedComponent, {
...this.mergedProps,
ref: 'wrappedInstance'
})
} else {
this.renderedElement = createElement(WrappedComponent,
this.mergedProps
)
}
複製代碼
計算this.mergedProps 最終傳遞下去的props是通過mapStateToProps,mapDispatchToProps計算以後,最後再由mergeProps計算以後的state。
// 簡化代碼
this.mergedProps = nextMergedProps = computeMergedProps(this.stateProps, this.dispatchProps, this.props)
/** * 得到最終props 即通過參數中的 * @param {*} stateProps 通過mapStateToProps以後的結果 * @param {*} dispatchProps mapDispatchToProps以後的結果 * @param {*} parentProps 此處即爲connect組件的props this.props * @returns */
function computeMergedProps(stateProps, dispatchProps, parentProps) {
// finalMergeProps 即爲參數中的mergeProps 或者 defaultMergeProps。 將前兩參數計算結果再進行處理。
const mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps)
if (process.env.NODE_ENV !== 'production') {
checkStateShape(mergedProps, 'mergeProps')
}
return mergedProps
}
複製代碼
到這裏connect的做用也體現出來了:
此時再回過頭去看上面的例子應該更清晰了。
到這裏就結束了react-redux的源碼解析,更可能是本身的學習筆記吧。 使用必定程度以後再回頭看,可能對本身的理解更有幫助。 另外閱讀源碼不是要盲目去讀,而是在應用以後帶着問題去讀。 這樣會更清晰如何去優化如何去提高。由於水平有限確定有錯漏指出,歡迎指出。