「 React 」系列,手動實現一個 redux 與 react-redux

1、實現一個簡易的 Redux

因爲公司項目的技術棧是 React,與之配套的公共狀態管理的庫是 Redux,最近也研究了其中的原理。因爲我以前是硬背 Redux 的用法,時間擱久了老是忘記如何使用。每次要用的時候,就去翻文檔,不只效率低下,用起來也感受到噁心本身了。搞清楚了背後的機制,寫起來就很順手。相信你們弄懂的基本的實現細節,用起來也會駕輕就熟。react

首先思考 Redux 解決了什麼問題,爲何要使用它。每當有一些數據要在不少組件中使用的時候,而這些組件跨層級時,props 就再也不適合。就須要把這些數據提取出來放在一個模塊,咱們把它稱爲公共狀態,每當數據改變時,每當數據改變時,它會通知訂閱它的說有組件。web

要想實現這樣的共享的數據,首先想到的必是建一個全局模塊,裏面存放不少公共的數據。就像這樣redux

const obj = {
   count: 0
}
複製代碼

當狀態須要改變的時候,直接顯示的賦值,固然,這裏要經過觀察者模式數據已經改變。不過這樣確定很差閉包

  • 容易誤操做,試想我一不當心清空了對象,會影響其餘組件都受到污染,確定要有條件的改變公共狀態
  • 不易排查錯誤

一、 實現 getState

既然是共享數據,外部又不能直接改變它,固然就是一個閉包啦。沒錯,Redux 內部這樣的一個機制,經過 store 的一些方法,咱們能夠搭出大體的骨架app

function createStore({
  // 公共的狀態
  let currentState = {}
  
  // getter
  function getState({
    return currentState
  } 
  
  // setter
  function dispatch({   
  } 
  
  function subscribe({
  } 
  
  return { 
    getState,
    dispatch,
    subscribe
  }
}
複製代碼

毫無疑問,每次調用 getState 就能夠拿到最新的 currentState 的值異步

二、實現 dispatch

在 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 = {
  count1
}

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、中間件等等。但基本的原理大體相同

2、實現 react-redux

在 react 的項目中,儘管 redux 能實現咱們的需求,但寫法太過冗餘,和 react 的組件寫法也不太契合,因而,就有了 react-redux。簡單來講,它提供了兩大功能:

  • Provider 組件
  • connect 高階函數

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 {
            storethis.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 已經實現。

相關文章
相關標籤/搜索