深入理解redux之從redux原始碼到react-redux的原理

LT_bear發表於2019-05-06

在使用react的過程中,用redux來管理應用中的狀態,使應用流更清晰的同時也會有小小的疑惑,比如reducer在redux中時怎麼發揮作用的,為什麼只要寫好reducer,和dispatch特定action配合,store的狀態就會更新了,而react元件又是如何和store的狀態產生關係的,為什麼隨著store狀態的更新,react元件會被自動更新,下面就從redux的原始碼開始來總結下這其中的緣由~

redux管理狀態的原理

redux是繼承自flux體系,但它放棄了dispatcher,無需使用 event emitters(事件傳送器)(在dispatcher中特定action邏輯裡觸發事件,元件裡監聽事件),而使用純 reducer來代替,那麼reducer是如何被排程的,又是如何影響狀態更新的,不妨通過原始碼的邏輯來了解和加深一下~

從redux原始碼看action,reducer,和store之間的關係

reducer通常是由我們自己來寫,在呼叫createStore函式生成store時來傳入這個reducer,後續呼叫storedispatch方法來觸發action時,則reducer函式會自動被呼叫來解析actin更新state,這一切的核心都在crateStore方法中

export  default function createStore(reducer, preloadedState, enhancer) {
    ...
    let currentReducer = reducer
    let currentState = preloadedState
    let currentListeners = []
    let nextListeners = currentListeners
    let isDispatching = false
    function getState() {
       ...
    }
    function subscribe(listener) {
        ...
    }
    function dispatch(action) {
        ...
    }
    function replaceReducer(nextReducer) {
        ...
    }
    function observable() {
        ...
    }
    return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
複製程式碼

可以看到createStore方法返回一個屬性集合,我們所呼叫的redux的相關方法都是定義在createStroe方法內部,最後被通過這個屬性集合中暴露出來,如處理action的dispath方法,同currentReducer,currentState是createStore方法中的私有變數,由dispath,subscribe,getState等方法共享,我們設定的reducer,redux的state狀態,以及state改變之後應該自動觸發哪些函式,這些邏輯都是通過這幾個內部變數和函式來實現的,下面先來看一下幾個核心方法,由我們直接接觸到的dispath開始~

dispacth處理action

function dispatch(action) {
    ...
    try {
      //將flag置為true,表明處於分發邏輯中
      isDispatching = true 
      
      //currentReducer即為函入的reducer函式,這裡會自動呼叫currentReducer函式,並將返回值賦給currentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    
    //呼叫監聽函式
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    return action
}
複製程式碼

可以看到,在dispath中呼叫reducer方法處理action之後,返回值(新的state)會直接賦值給currentState,由此可以推測currentState應該就是getState要返回的狀態

getState返回當前的狀態

  function getState() {
    if (isDispatching) {
      throw new Error(
      ...
      )
    }
    return currentState //直接返回currentState內部變數
  }
複製程式碼

subscribe訂閱狀態

function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error(...)
    }
    if (isDispatching) {
      throw new Error(
        ...
      )
    }
    let isSubscribed = true
    ...
    nextListeners.push(listener)

    return function unsubscribe() {
      ...
    }
複製程式碼

在subscribe中,傳入的listener函式會被新增進nextListeners陣列中,當dispatch方法被呼叫時自動觸發,react-redux的狀態更新時,UI自動更新的特性是通過subscribe來實現的

通過redux來管理react的狀態

首先,設想我們不知道react-redux庫來連線react和redux,來試一下單純的通過redux作為react元件的狀態管理器

建立store

//首先,建立一個reducer處理函式
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return ...
    case ADD_TODO:
      return ...
    default:
      return state
  }
}
//建立store
const store = createStore(
  todoApp
);
複製程式碼

各元件之間共享store的狀態

要在各元件之間共享變數store有兩種方式可取,一種是通過props共享,一種是通過context實現,其中props共享變數需要每一層元件之間層層傳遞該變數,這樣做無疑很麻煩,尤其是元件之前巢狀層次比較深的時候,所以我們這裡用react的context屬性來實現共享store

根元件提供context

class App extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return <ToDoList />;
  }
}

App.childContextTypes = {
  store: React.PropTypes.object
}
複製程式碼

子元件獲取context

class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();
    // ...
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object
}

複製程式碼

如上所示,在需要獲取store狀態的元件中,在元件載入完成後需要獲取關心得context的變數值store,同時訂閱事件,當store的狀態變化後觸發元件本身的強制更新,而render中只需用store.getState獲取store的狀態值即可

用react-redux來簡化元件的寫法

上例中寫了一個元件的實現還好,但當元件多的時候,每個元件都需寫自己的獲取context,訂閱事件強制更新自身,獲取state,這樣的樣板程式碼,實際是沒必要的,完全可以把這部分抽象出來,而react-redux就幫我們做了這些,讓我們省去了自定義context和訂閱事件,獲取state等操作

省去自定義含有context屬性的根元件

要利用redux來管理狀態,需要在祖先元件的context屬性中指定store,而這一定式化的操作可以有react-redux庫中的Provider來完成,示例如下

<Provider store={store}>
    <App />
  </Provider>
複製程式碼

省去手動訂閱store的狀態變化事件

上節中提到過,要實現store的狀態更新後能自動更新react元件,則元件需在掛載後呼叫storesubscribe方法來訂閱store中狀態的變更,而這塊兒樣板程式碼則可以由react-redux庫中的connect建立的容器元件來自動完成


class TodoList extends Component {
 render(){
     ...
 }
}
const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}
const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)
複製程式碼

其中mapStateToProps提供從Redux store state到展示元件的 props的對映 ,mapDispatchToProps接收dispatch方法並提供期望注入到展示元件的 props 中的回撥方法。

相關文章