## redux
接下來要使用redux了,首先如果沒有基礎的話,需要對redux有一個大概的了解,推薦閱讀阮一峰大神的[Redux 入門教程(一):基本用法](http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html)。
如果要對`redux`有一個非常詳細的認識,我推薦閱讀[中文文檔](http://cn.redux.js.org/index.html),寫的非常好。讀了這個教程,有一個非常深刻的感覺,`redux`并沒有任何魔法。但是對第一次接觸狀態管理框架的童鞋來說可能有一些概念難以接受,但是隨著慢慢試用就會理解了,不要被`reducers`,`middleware`,`store`嚇到了,其實它們就字面意思不容易理解而已。
## 核心概念(來自Redux中文文檔)
一個應用會有一個`state`,它通常是一個普通對象,例如一個todo應用的`state`可能是這樣的:
~~~
{
todos: [
{ text: 'Eat Food', completed: true },
{ text: 'Run', completed: false }
],
visibilityFilter: 'show_all'
}
~~~
這個對象就像是'Model',區別是它并沒有 `setter`(修改器方法)。因此其它的代碼不能隨意修改它,造成難以復現的 bug。
想要更新`state`中的數據,你需要發起一個`action`。Action(動作)就是一個普通的`JavaScript`對象,這個對象用來描述發生了什么事情。例如:
~~~
{ type: 'ADD_TODO', text: 'Go to swimming pool' } // 添加一個todo事項,內容是去游泳池。
{ type: 'TOGGLE_TODO', index: 1} // 切換需要完成的todo。
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' } // 設置過濾條件。
~~~
強制使用 `action` 來描述所有變化帶來的好處是可以清晰地知道應用中到底發生了什么。如果一些東西改變了,就可以知道為什么變。`action` 就像是描述發生了什么的面包屑。最終,**為了把 `action` 和 `state` 串起來(通過`action`改變`state`),開發一些函數,這就是 `reducer`**。再次地,沒有任何魔法,**`reducer` 只是一個接收 `state` 和 `action`,并返回新的 `state` 的函數。** 對于大的應用來說,可能很難開發這樣的函數,所以我們編寫很多小函數來分別管理 state 的一部分:
~~~
function visibilityFilter(state = 'SHOW_ALL', action) {
// 通過判斷 Action 的 類型操作 state
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter
} else {
return state
}
}
function todos(state = [], action) {
switch(action.type) {
case 'ADD_TODO': return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO': return state.map((todo, index) => {
action.index === index ? { text: todo.text, completed: !todo.completed } : todo
})
default: return state
}
}
~~~
最后用一個reducer管理所有的小reducer,進而來管理整個應用的`state`:
~~~
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
~~~
這差不多就是 Redux 思想的全部。注意到沒我們還沒有使用任何 Redux 的 API。Redux 里有一些工具來簡化這種模式,但是主要的想法是如何根據這些 action 對象來更新 state,而且 90% 的代碼都是純 JavaScript,沒用 Redux、Redux API 和其它魔法。
## 三大原則:單一數據源、State是只讀的、使用純函數來執行修改任務
1. 單一數據源
整個應用的`state`被儲存在一棵`object tree`中,并且這個`object tree`只存在于唯一一個`store`中。
2. State 是只讀的
唯一改變`state`的辦法就是觸發一個`action`,`action`是一個用于描述已發生事件的普通對象。
這樣確保了視圖和網絡請求都不能直接修改 state,相反它們只能表達想要修改的意圖。
因為所有的修改都被集中化處理,且嚴格按照一個接一個的順序執行,因此不用擔心 race condition 的出現。
Action 就是普通對象而已,因此它們可以被日志打印、序列化、儲存、后期調試或測試時回放出來。
3. 使用純函數來執行修改
為了描述 `action` 如何改變 `state tree` ,你需要編寫 `reducers`。
Reducer 只是一些純函數,它接收先前的 state 和 action,并返回新的 state。
剛開始你可以只有一個 reducer,隨著應用變大,你可以把它拆成多個小的 reducers,分別獨立地操作 state tree 的不同部分。
因為 reducer 只是函數,你可以控制它們被調用的順序,傳入附加數據,甚至編寫可復用的 reducer 來處理一些通用任務,如分頁器。
現在開始安裝`redux`。
`npm install --save redux`
~~~
cd src
mkdir redux
cd redux
mkdir actions // 創建一個放action的目錄
mkdir reducers // 創建一個放reducer的目錄
touch reducers.js // 合并所有reducer,管理一個個reducer
touch store.js // 管理整個redux的倉庫
touch actions/counter.js // 創建一個action
touch reducers/counter.js // 創建一個reducer
~~~
已經建好了基礎目錄,下一節來創建一個`action`。
不過在此之前,既然已經建好了目錄,順便把路徑別名給改了吧:
~~~
// 靜態文件服務器配置
devServer: {
// ... 省略devServer配置代碼
},
resolve: {
alias: {
'@': resolve('src'),
// ...省略一些代碼
actions: resolve('src/redux/actions'),
reducers: resolve('src/redux/reducers')
}
}
~~~