1. 程式人生 > >Redux 架構理解

Redux 架構理解

Redux 是一種前端“架構模式”,是 Flux 架構的一種變種,用來提供可預測的狀態管理。雖然經常和 React 一起被提及,但是 Redux 卻不僅僅只能用於 React,還可以將其運用到其他前端庫中,Vue Angular甚至是 jQuery。Redux 只是一種架構模式而已,並沒有和其他庫繫結在一起。而 React-redux 就是把 Redux 和 React.js 結合起來的一個庫。就像 Vuex 一樣,是一個與 Vue.js 結合的 Flux變種。

 

為什麼要用 Redux

也許有人會問:為什麼我們會需要 redux 呢?  嗯... 確實,我們必須要先了解我們為什麼需要 redux? redux 的出現是為了解決什麼問題?

那麼,我們來考慮這麼一種場景,在你構建的一棵元件樹中,有A、B那麼兩個元件,它們需要共享同一個狀態,你會怎麼辦呢?

我們可以通過狀態提升的思路,將該狀態提升到附近的公共父元件上面,然後通過 props 把狀態傳遞給子元件,這樣就可以在A、B元件之間共享資料了。確實可以,但是如果A、B的父元件在元件樹向上好幾個元件的位置呢?就需要將狀態通過 props 一級一級往下傳遞,那麼狀態的傳遞路徑就會非常長,而且中間元件根本就不需要訪問這個狀態。而且,如果後續有一個 C 元件也要訪問該狀態並且A、B、C的公共父元件還要往上呢?你就不得不修改之前程式碼了。很顯然,這不是一種很好的解決方案,它會讓我們的程式碼維護起來非常痛苦。

難道就沒有其他方法可以解決這個問題嗎?其實也有的,那就是 react 的 context,一個元件只要往自己的 context 裡面放了某些狀態,那麼這個元件的所有子元件都可以直接訪問這個狀態而不需要通過中間元件的傳遞,看起來問題解決了嘛。

我們雖然解決了狀態傳遞的問題卻引入了新的問題,我們引入的 context 打破了元件和元件之間通過 props 傳遞資料的規範,極大地增強了元件之間的耦合性。而且 context 就像全域性變數一樣,裡面的資料可以被子元件隨意更改,可能會導致程式不可預測的執行。

這時候我們就該考慮使用 Redux 了,Redux 可以幫你建立應用的共享狀態,並且不能隨意的更改這些狀態。

 

Redux 的基本概念

我們已經瞭解了為什麼要使用 Redux,那麼我們先了解下 Redux 的三個基本概念。

 

Store

我們可以通過 createStore 來建立 store

import { createStore } from 'redux';
const store = createStore(reducers);

在 Redux 中,應用程式只能擁有一個 store,用來儲存整個應用程式的 state,相當於一個應用程式的共享狀態。

我們可以通過 store.getState() 來獲取應用程式的當前狀態。但是我們卻不能隨意的修改狀態,我們只能通過 store.dispatch(action) 來修改狀態。

修改完狀態之後,我們希望可以做些 view 層的改變,這時可以通過 store.subscribe(() => {}) 來註冊檢視變化的回撥函式。

 

Actions

Actions 是一個 JavaScript 普通物件,用來描述應用程式中發生的一些事情,也是把資料從應用傳遞給 store 的唯一途徑。

我們約定,action 內必須使用一個字串型別的 type 欄位來表示將要執行的動作,type 一般會被定義成字串常量。

const ADD_TODO = 'ADD_TODO'

{
  type: ADD_TODO,
  data: 'some data'
}

我們除了直接以 JavaScript 普通物件的形式來定義 action 之外,也可以通過函式形式來定義 action,這個函式被稱作 Action 建立函式( actionCreator )。

const ADD_TODO = 'ADD_TODO';

function addTodo(data) {
  return {
    type: ADD_TODO,
    data
  }
}

這裡 action 建立函式 addTodo 很簡單,只是返回一個 action。 我們可以通過 store.dispatch 來通知需要修改狀態

store.dispatch(addTodo('some data'));

 

Reducers

我們已經知道可以通過 action 來修改狀態,但是 action 傳遞過來的只是簡單的物件,並沒有具體處理狀態的邏輯,這就是 reducers 要做的事情了。

Reducer 必須是一個純函式(一個函式的返回結果只依賴於它的引數,並且在執行過程裡面沒有副作用,這個函式就叫做純函式)。因為純函式非常“靠譜”,執行一個純函式不會產生不可預料的行為,也不會對外部產生影響。

function todoApp(state = { title: 'todoApp', todos: [] }, action) {
  switch (action.type) {
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            data: action.data,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}

todoApp 接收舊的 state 和 action,並返回新的state。注意這裡我們是將 state 拷貝一份,再新增我們改動的值去覆蓋原來的資料,重新組合成新的 state 返回,而不是直接修改 state。

這是因為如果你直接去改變 state 裡物件的屬性,那麼就需要去比較新舊兩個 state 的區別,而比較兩個 Javascript 物件所有的屬性是否相同就需要對它們進行深比較。但是在真實的應用中 js 的物件都很大,進行深比較的代價十分昂貴。而如果你返回的是一個全新的物件,就只需要比較新舊兩個物件的儲存地址是否相同就可以了。Redux 就是這麼做的,如果你在 reducer 內部直接修改舊的 state 物件的屬性值,那麼新的 state 和舊的 state 將都指向同一個儲存地址,Redux 會認為沒有任何改變。

 

我們可以有多個 reducer,每個 reducer 只負責管理全域性 state 中它負責的那部分,每個 reducer 的 state 引數可以都不同,分別對應它管理的那部分 state 資料。然後通過 combineReducers 組成根 reducer 用來建立一個store。

import { combineReducers } from 'redux';

const todosReducer = (state = [], action) => {
  // do something
}

const titleReducer = (state = '', action) => {
  // do something
}

const reducer = combineReducers({
  todos: todosReducer,
  title: titleReducer
});

// 等價於
function reducer(state = {}, action) {
  return {
    todos: todosReducer(state.todos, action),
    title: titleReducer(state.title, action)
  }
}

combineReducers 這個函式會呼叫你的定義的 reducer,每個 reducer 根據它們的 key(todos, title) 來篩選出 state 中的一部分資料處理並返回一份副本,根 reducer 會把這些副本組合起來形成一個新的大物件。最後根 reducer 將這個大物件傳回給 store,store 再將它設為最終的狀態。 

 

Redux 工作流程

redux 一些基本概念我們都清楚了,我們來總結一下,Redux 為我們所做的事情:

1.  一個存放應用程式共享 state 的地方
2. 一個去分發 actions 通過純函式修改應用程式共享 state 的機制
3. 一個可以訂閱 state 更新的機制

 

嚴格的單向資料流是 Redux 架構的設計核心。

我們只要清楚了 redux 中的資料流動的過程就明白 redux 整個工作流程了,我們從產生一個 action 的切入點來分析資料是怎樣流動的。

1. 通過使用者在檢視層的互動產生了一個 action,這個 action 可能是通過 actionCreator 返回的。

2. store 接受這這個 action 之後,將當前的 state 和 action 一起傳遞給根 reducer。

3. 根 reducer 將 state 分配給子 reducer 進行處理,子 reducer 返回修改後的副本給根 reducer,根 reducer 整合子 reducer 返回的副本生成一個新的 state 副本返回給 store。

4. store 根據新的 state 觸發檢視層的渲染。

5. 使用者看到互動後檢視的變化,又高興地發起了一個 action ...

&n