從這節起咱們開始學習 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
的模式抽離出來,讓它變得更加通用。