動手實現 Redux(一):優雅地修改共享狀態

從這節起咱們開始學習 Redux,一種新型的前端「架構模式」。常常和 React.js 一併提出,你要用 React.js 基本都要伴隨着 Redux 和 React.js 結合的庫 React-redux。html

要注意的是,Redux 和 React-redux 並非同一個東西。Redux 是一種架構模式(Flux 架構的一種變種),它不關注你到底用什麼庫,你能夠把它應用到 React 和 Vue,甚至跟 jQuery 結合都沒有問題。而 React-redux 就是把 Redux 這種架構模式和 React.js 結合起來的一個庫,就是 Redux 架構在 React.js 中的體現。前端

若是把 Redux 的用法從新介紹一遍那麼這本書的價值就不大了,我大可把官網的 Reducers、Actions、Store 的用法、API、關係重複一遍,畫幾個圖,說兩句很玄乎的話。可是這樣對你們理解和使用 Redux 都沒什麼好處,本書初衷仍是跟開頭所說的同樣:但願你們對問題的根源有所瞭解,瞭解這些工具到底解決什麼問題,怎麼解決的。react

如今讓咱們忘掉 React.js、Redux 這些詞,從一個例子的代碼 + 問題開始推演。redux

用 create-react-app 新建一個項目 make-redux,修改 public/index.html 裏面的 body 結構爲:架構

  <body>
    <div id='title'></div>
    <div id='content'></div>
  </body>

刪除 src/index.js 裏面全部的代碼,添加下面代碼,表明咱們應用的狀態:app

const appState = {
  title: {
    text: 'React.js 小書',
    color: 'red',
  },
  content: {
    text: 'React.js 小書內容',
    color: 'blue'
  }
}

咱們新增幾個渲染函數,它會把上面狀態的數據渲染到頁面上:函數

function renderApp (appState) {
  renderTitle(appState.title)
  renderContent(appState.content)
}

function renderTitle (title) {
  const titleDOM = document.getElementById('title')
  titleDOM.innerHTML = title.text
  titleDOM.style.color = title.color
}

function renderContent (content) {
  const contentDOM = document.getElementById('content')
  contentDOM.innerHTML = content.text
  contentDOM.style.color = content.color
}

很簡單,renderApp 會調用 rendeTitle 和 renderContent,而這二者會把 appState裏面的數據經過原始的 DOM 操做更新到頁面上,調用:工具

renderApp(appState)

你會在頁面上看到:學習

這是一個很簡單的 App,可是它存在一個重大的隱患,咱們渲染數據的時候,使用的是一個共享狀態 appState,每一個人均可以修改它。若是我在渲染以前作了一系列其餘操做:spa

loadDataFromServer()
doSomethingUnexpected()
doSomthingMore()
// ...
renderApp(appState)

renderApp(appState) 以前執行了一大堆函數操做,你根本不知道它們會對 appState作什麼事情,renderApp(appState) 的結果根本無法獲得保障。一個能夠被不一樣模塊任意修改共享的數據狀態就是魔鬼,一旦數據能夠任意修改,全部對共享狀態的操做都是不可預料的(某個模塊 appState.title = null 你一點意見都沒有),出現問題的時候 debug 起來就很是困難,這就是老生常談的儘可能避免全局變量。

你可能會說我去看一下它們函數的實現就知道了它們修改了什麼,在咱們這個例子裏面還算比較簡單,可是真實項目當中的函數調用和數據初始化操做很是複雜,深層次的函數調用修改了狀態是很難調試的。

但不一樣的模塊(組件)之間確實須要共享數據,這些模塊(組件)還可能須要修改這些共享數據,就像上一節的「主題色」狀態(themeColor)。這裏的矛盾就是:「模塊(組件)之間須要共享數據」,和「數據可能被任意修改致使不可預料的結果」之間的矛盾。

讓咱們來想辦法解決這個問題,咱們能夠學習 React.js 團隊的作法,把事情搞複雜一些,提升數據修改的門檻:模塊(組件)之間能夠共享數據,也能夠改數據。可是咱們約定,這個數據並不能直接改,你只能執行某些我容許的某些修改,並且你修改的必須大張旗鼓地告訴我。

咱們定義一個函數,叫 dispatch,它專門負責數據的修改:

function dispatch (action) {
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      appState.title.text = action.text
      break
    case 'UPDATE_TITLE_COLOR':
      appState.title.color = action.color
      break
    default:
      break
  }
}

全部對數據的操做必須經過 dispatch 函數。它接受一個參數 action,這個 action是一個普通的 JavaScript 對象,裏面必須包含一個 type 字段來聲明你到底想幹什麼。dispatch 在 swtich 裏面會識別這個 type 字段,可以識別出來的操做纔會執行對 appState 的修改。

上面的 dispatch 它只能識別兩種操做,一種是 UPDATE_TITLE_TEXT 它會用 action的 text 字段去更新 appState.title.text;一種是 UPDATE_TITLE_COLOR,它會用 action 的 color 字段去更新 appState.title.color。能夠看到,action 裏面除了 type 字段是必須的之外,其餘字段都是能夠自定義的。

任何的模塊若是想要修改 appState.title.text,必須大張旗鼓地調用 dispatch

dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小書》' }) // 修改標題文本
dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改標題顏色

咱們來看看有什麼好處:

loadDataFromServer() // => 裏面可能經過 dispatch 修改標題文本
doSomethingUnexpected()
doSomthingMore() // => 裏面可能經過 dispatch 修改標題顏色
// ...
renderApp(appState)

咱們不須要擔憂 renderApp(appState) 以前的那堆函數操做會幹什麼奇奇怪怪得事情,由於咱們規定不能直接修改 appState,它們對 appState 的修改必須只能經過 dispatch。而咱們看看 dispatch 的實現能夠知道,你只能修改 title.text 和 title.color

若是某個函數修改了 title.text 可是我並不想要它這麼幹,我須要 debug 出來是哪一個函數修改了,我只須要在 dispatch的 switch 的第一個 case 內部打個斷點就能夠調試出來了。

原來模塊(組件)修改共享數據是直接改的:

咱們很難把控每一根指向 appState 的箭頭,appState 裏面的東西就沒法把控。但如今咱們必須經過一個「中間人」 —— dispatch,全部的數據修改必須經過它,而且你必須用 action 來大聲告訴它要修改什麼,只有它容許的才能修改:

咱們不再用擔憂共享數據狀態的修改的問題,咱們只要把控了 dispatch,全部的對 appState 的修改就無所遁形,畢竟只有一根箭頭指向 appState 了。

本節完整的代碼以下:

let appState = {
  title: {
    text: 'React.js 小書',
    color: 'red',
  },
  content: {
    text: 'React.js 小書內容',
    color: 'blue'
  }
}

function dispatch (action) {
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      appState.title.text = action.text
      break
    case 'UPDATE_TITLE_COLOR':
      appState.title.color = action.color
      break
    default:
      break
  }
}

function renderApp (appState) {
  renderTitle(appState.title)
  renderContent(appState.content)
}

function renderTitle (title) {
  const titleDOM = document.getElementById('title')
  titleDOM.innerHTML = title.text
  titleDOM.style.color = title.color
}

function renderContent (content) {
  const contentDOM = document.getElementById('content')
  contentDOM.innerHTML = content.text
  contentDOM.style.color = content.color
}

renderApp(appState) // 首次渲染頁面
dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小書》' }) // 修改標題文本
dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改標題顏色
renderApp(appState) // 把新的數據渲染到頁面上

下一節咱們會把這種 dispatch 的模式抽離出來,讓它變得更加通用。

相關文章
相關標籤/搜索