因爲公司項目的技術棧是 React,與之配套的公共狀態管理的庫是 Redux,最近也研究了其中的原理。因爲我以前是硬背 Redux 的用法,時間擱久了老是忘記如何使用。每次要用的時候,就去翻文檔,不只效率低下,用起來也感受到噁心本身了。搞清楚了背後的機制,寫起來就很順手。相信你們弄懂的基本的實現細節,用起來也會駕輕就熟。react
首先思考 Redux 解決了什麼問題,爲何要使用它。每當有一些數據要在不少組件中使用的時候,而這些組件跨層級時,props 就再也不適合。就須要把這些數據提取出來放在一個模塊,咱們把它稱爲公共狀態,每當數據改變時,每當數據改變時,它會通知訂閱它的說有組件。web
要想實現這樣的共享的數據,首先想到的必是建一個全局模塊,裏面存放不少公共的數據。就像這樣redux
const obj = {
count: 0
}
複製代碼
當狀態須要改變的時候,直接顯示的賦值,固然,這裏要經過觀察者模式數據已經改變。不過這樣確定很差閉包
既然是共享數據,外部又不能直接改變它,固然就是一個閉包啦。沒錯,Redux 內部這樣的一個機制,經過 store 的一些方法,咱們能夠搭出大體的骨架app
function createStore() {
// 公共的狀態
let currentState = {}
// getter
function getState() {
return currentState
}
// setter
function dispatch() {
}
function subscribe() {
}
return {
getState,
dispatch,
subscribe
}
}
複製代碼
毫無疑問,每次調用 getState 就能夠拿到最新的 currentState 的值異步
在 Redux 裏,每當要改變數據,都是調用 dispatch 一個 action,根據 actionType 的值來返回不一樣的 state,好了,咱們就能夠這樣寫編輯器
function createStore() {
// 公共的狀態
let currentState = {}
// getter
function getState() {
return currentState
}
// setter
function dispatch(action) {
swicth(action.type) {
case "increament":
return {
...currentState,
count: currentState.count + 1
}
default:
return currentState
}
}
function subscribe() {
}
return {
getState,
dispatch,
subscribe
}
}
複製代碼
這樣就實現了一個 dispatch,但這樣寫很差,咱們把 switch 這樣的邏輯能夠提取成一個函數,這也是所說的 reducer,那麼,代碼就成了這樣ide
const initialState = {
count: 1
}
function reducer(state = initialState, {type, payload}) {
switch (type) {
case 'increament':
return {
...state,
count: count + payload
}
case 'decrement':
return {
...state,
count: count - payload
}
default:
return initialState
}
}
function createStore() {
// 公共的狀態
let currentState = {}
// getter
function getState() {
return currentState
}
// setter
function dispatch(action) {
currentState = reducer(currentState, action)
}
function subscribe() {
}
return {
getState,
dispatch,
subscribe
}
}
複製代碼
在咱們建立 store 的時候,會初始化一次 state,也就是內部會 dispatch 一次,於是函數
function createStore() {
// 公共的狀態
let currentState = {}
// getter
function getState() {
return currentState
}
// setter
function dispatch(action) {
currentState = reducer(currentState, action)
}
dispatch({ type: '@@REDUX_INIT' })
function subscribe() {
}
return {
getState,
dispatch,
subscribe
}
}
複製代碼
到此,即完成了獲取和改變狀態的功能,驗證一下代碼ui
const store = createStore(reducer)
console.log(store.getState().count) // 1
store.dispatch({type: 'increament', payload: 5})
console.log(store.getState().count) // 6
複製代碼
儘管如此,當數據改變了,仍是不能通知我視圖更新。這裏須要監聽數據的變化,用到了觀察者模式,也有人說是發佈訂閱模式,這兩種模式基本的思想一致,但仍是有一些區別的,這裏觀察者模式更加準確。狀態數據至關於被觀察者,在 createStore 中,咱們要維護一個觀察者的隊列,當執行 subscribe時,把他們放入隊列,dispatch的時候,執行隊列裏的回調
function createStore() {
// 公共的狀態
let currentState = {}
// 存放觀察者的隊列
let observers = []
// getter
function getState() {
return currentState
}
// setter
function dispatch(action) {
currentState = reducer(currentState, action)
observers.foreach(fn => fn())
}
dispatch({ type: '@@REDUX_INIT' })
function subscribe(fn) {
observers.push(fn)
}
return {
getState,
dispatch,
subscribe
}
}
複製代碼
如此以來,就完成了數據變化,通知組件改變視圖的功能,模擬組件運行代碼
const store = createStore(reducer)
store.subscribe(() => console.log('組件1收到通知', store.getState()))
store.subscribe(() => console.log('組件2收到通知', store.getState()))
store.dispatch({ type: 'increament', payload: 5 })
複製代碼
一個最簡單的 redux 的應用已經完成,這裏還缺飯了大量的參數類型判斷,異常處理,異步action、中間件等等。但基本的原理大體相同
在 react 的項目中,儘管 redux 能實現咱們的需求,但寫法太過冗餘,和 react 的組件寫法也不太契合,因而,就有了 react-redux。簡單來講,它提供了兩大功能:
Provider原理是把 store 綁定在 context 上,至於 context,能夠爲子孫組件的上下文提供 store 對象
class Provider extends Component {
static childContextTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.store = props.store;
}
getChildContext() {
return {
store: this.store
}
}
render() {
return this.props.children
}
}
複製代碼
connect 是一個高階函數,接收 mapStateToProps, mapDispatchToProps 爲參數,而且它返回一個高階組件。其中,這兩個參數都是函數,mapStateToProps 接收 state 返回一個對象,這搞清楚了,mapDispatchToProps 接收一個 dispatch 返回一個對象,咱們再來實現它:
// 形如 mapTostate、mapDispatchToProps
const mapStateToProps = state => {}
const mapDispatchToProps = dispatch => {}
function connect(mapStateToProps, mapDispatchToProps) {
return function(WrappedComponent) {
class Connect extends Component{
constructor(props) {
super(props)
this.state = mapStateToProps(store.getState())
this.mapDispatch = mapDispatchToProps(store.dispacth)
}
ComponentDidMount() {
}
<WrappedComponent {...this.props} {this.state} {this.mapDispatch} />
}
return Connect
}
}
複製代碼
知道這裏,咱們經過高階函數把一些屬性掛載到了高階組件的 props 上,接下來就能夠經過 this.props.xxx 調用。此時,咱們被 connect 包裹的新組件的 props 上雖然有了值,可是還不具有自動更新的功能,繼續改進 connect
function connect(mapStateToProps, mapDispatchToProps) {
return function(WrappedComponent) {
class Connect extends Component{
constructor(props) {
super(props)
this.state = mapStateToProps(store.getState())
this.mapDispatch = mapDispatchToProps(store.dispacth)
}
ComponentDidMount() {
// 若是 store 中的狀態改變會執行回調函數,此時新獲取的 mappedState 和舊的作對比,如如有變化,就setState
this.unsub = store.subscribe(() => {
const mappedState = mapStateToProps(store.getState());
if(shallowEqual(this.state, mappedState)) {
return;
}
this.setState(mappedState);
});
}
<WrappedComponent {...this.props} {this.state} {this.mapDispatch} />
}
return Connect
}
}
複製代碼
如此,一個基本功能的 react-redux 已經實現。