Flux、Redux到react-redux發展衍變三部曲之Redux解讀
續上篇,在Flux後,為了更好的實現MVC,Redux模式出現。
不同於 Flux ,Redux 不再有 dispatcher 的概念(Store已經集成了dispatch方法)。其次它依賴純函式來替代事件處理器(即原來Flux中Dispatcher.register((action) 註冊邏輯處理這塊),這個純函式叫做Reducer。另外使用到了一個新概念 context ,在React 元件間,資料是通過 props 屬性由上向下(由父及子)進行傳遞的,當遇到多個層級多個元件間共享一個props,這種樹形的由上而下的傳參方式就顯得過於繁瑣,context 便很巧妙的解決了這個問題,引數只需從樹頂點設定一次,便可在其所有枝節點都能共享到。
看了react-redux官方原始碼,總結出其redux思想主要由四個部分組成:reducers、store(redux)、react-redux和view。大致畫了個圖,其邏輯關係如下:
為了讓大家更好理解Redux思想,以設定/更改全域性主題顏色為例,本demo暫不會引用官方已封裝好的 'redux'和'react-redux' 模組,而是抽離出核心程式碼綜合編寫了一個demo,下一篇將會介紹並使用官方的 'reudx'和'react-redux' 庫。
原始碼地址:https://github.com/smallH/redux-demo.git
reducers
reducers,入參為:元件當前所在狀態state,將要處理的動作 action。action通常是一個物件,由型別和值{type, value}組成,通過switch(action.type)來篩選型別。簡單來說,reducers就是元件狀態發生變化時主要邏輯處理的地方。其程式碼如下:
// reducers.js const themeReducer = (state, action) => { if (!state) return { themeColor: 'red' } // 處理各類action,並返回最新的狀態 switch (action.type) { case 'CHANGE_COLOR': return { ...state, themeColor: action.themeColor } default: return state } } export default themeReducer
上面程式碼表示,當 action 型別為'CHANGE_COLOR'時(我告訴你我要改變顏色啦),則改變顏色狀態值 state.themeColor 為action.themeColor。其中{ ...state, themeColor: action.themeColor }是一種語法糖寫法,表示返回一個新物件 newState ,它不僅繼承了原有入參 state的資料結構和值,還順道修改了themeColor屬性值。注意哦,這種寫法的好處就是實現了返回的新狀態值newState 和 state 在記憶體中沒有指向同一引用,是兩個各自不想關的物件,也可以理解為深度拷貝吧。
store(redux)
核心其實就是就是官方模組中的引用的redux:
import { createStore } from 'redux'
但在本demo中我們並不直接引用,我們先來看看程式碼:
// redux.js
export const createStore = (reducer) => {
let state = null
const listeners = []; // 事件監聽列表
const subscribe = (listener) => listeners.push(listener); // 定義新增事件對外介面
const getState = () => state; // 定義獲取狀態總值對外介面
// 定義驅動 Aciton 的對外介面,每次驅動會遍歷執行listeners列表裡的所有事件
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach((listener) => listener())
}
dispatch({}); // 首次初始化state
return {
getState,
dispatch,
subscribe
}
}
該模組以reducer為入參,返回了三個帶有核心功能的物件{getState, dispatch, subscribe},目的是對外提供了狀態獲取和更新的渠道。
getState:獲取所有通過store管理的元件的狀態值。
dispatch:驅動reducer執行狀態更新,並遍歷事件監聽列表,使在狀態更新後自動重新整理(渲染)dom節點。
subscribe:新增需要自動重新整理的dom節點的_updateProps()函式到監聽列表。
react-redux
該模組比較複雜,它提供了兩個高階元件Provider和 connect 函式。在看本模組前如果不瞭解高階函式的意義和context功能,可以先看一下:react系列(21)高階元件 和 react系列(17)跨元件樹傳遞資料 context
高階元件Provider:很簡單,主要功能是提供 context 的全域性狀態入參 store 設定。
// 高階元件 Provider
export class Provider extends React.Component {
static propTypes = {
store: PropTypes.object,
children: PropTypes.any
}
static childContextTypes = {
store: PropTypes.object
}
// 通過對context呼叫設定store
getChildContext() {
return {
store: this.props.store
}
}
render() {
return(
<div>{this.props.children}</div>
)
}
}
高階元件connect:主要功能是為了連線起檢視層view和store。
import React from 'react'
import PropTypes from 'prop-types'
// 高階元件 contect
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends React.Component {
// 通過對context呼叫獲取store
static contextTypes = {
store: PropTypes.object
}
constructor() {
super()
this.state = {
allProps: {}
}
}
// 第一遍需初始化所有元件初始狀態
componentWillMount() {
const store = this.context.store
this._updateProps()
store.subscribe(() => this._updateProps()); // 加入_updateProps()至store裡的監聽事件列表
}
// 執行action後更新props,使元件可以更新至最新狀態(類似於setState)
_updateProps() {
const store = this.context.store;
let stateProps = mapStateToProps ?
mapStateToProps(store.getState(), this.props) : {} // 防止 mapStateToProps 沒有傳入
let dispatchProps = mapDispatchToProps ?
mapDispatchToProps(store.dispatch, this.props) : {
dispatch: store.dispatch
} // 防止 mapDispatchToProps 沒有傳入
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render() {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
}
高階元件connect有三個入參:mapStateToProps, mapDispatchToProps 和 WrappedComponent。
首先需要明白,react-redux模組的一個主要目的就是可以把view和store連線起來,store儲存了所有元件的狀態值state和事件處理方法action,但並不代表所有的元件都需要用到全部的state和action,於元件而言,最好的辦法是我告訴store我需要那些 state和action 你給我就好,mapStateToProps和mapDispatchToProps就是幹這個事情的。
mapStateToProps:告訴store ,本元件渲染時所需的props值。
mapDispatchToProps :告訴store,本元件觸發事件時所需的action。
WrappedComponent:將要被包裝升級的原元件,最好為Dumb元件。Dumb元件是指只可以也僅可以通過props來控制組件渲染內容,它也是最符合react設計思想的元件設計,複用性高耦合性低。
現在,回過頭來看看最開始的邏輯圖,是不是清楚了很多。
view
即將要被渲染的元件。
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from '../react-redux'
class ThemeSwitch extends React.Component {
// 設定所需引數
static propTypes = {
themeColor: PropTypes.string,
onSwitchColor: PropTypes.func
}
handleSwitchColor(color) {
if(this.props.onSwitchColor) {
this.props.onSwitchColor(color)
}
}
render() {
return(
<div>
<button
style={{ color: this.props.themeColor }}
onClick={this.handleSwitchColor.bind(this, 'red')}>Style-Red</button>
<button
style={{ color: this.props.themeColor }}
onClick={this.handleSwitchColor.bind(this, 'blue')}>Style-Blue</button>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
themeColor: state.themeColor
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onSwitchColor: (color) => {
dispatch({
type: 'CHANGE_COLOR',
themeColor: color
})
}
}
}
ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)
export default ThemeSwitch
結合上面的react-redux模組高階元件的意圖,看起來就很明白了,沒什麼好講述的了,有問題可以留言。
最終demo執行效果:點選按鈕Style-Red主題顏色變為紅色,點選按鈕Style-Red主題顏色變為藍色。
最後,我們試著把上面例子中的 redux.js 和 react-redux.js 檔案刪除,改為直接引用官方的 'redux' 和 'react-redux':
// 安裝
$ npm install redux -S
$ npm install react-redux -S
// 引用
import { createStore } from 'redux'
import { Provider } from 'react-redux'
會發現程式依然執行起來了,而且結果是一樣的!這就是Redux模式了,而官方提供的 'redux' 和 'react-redux' 模組,只過不是對上面程式碼的封裝和增加了一些外掛,下一篇將介紹這些外掛的用法。