redux 官方示例 todomvc 中的 todoList 過濾事件解析
如果已經安裝 Git for Windows 客戶端工具(傳送門),在工作資料夾下,右鍵 -> Git Bash Here,依次執行下面的程式碼,檢視執行效果,執行之後,可以修改原始碼,如果編譯通過,頁面會自動重新整理。
git clone https://github.com/reduxjs/redux.git
cd redux/examples/todomvc/
cnpm i
npm start
理解程式碼邏輯
點選【All】、【Active】、【Completed】,頁面做了哪些操作?在哪裡呼叫了重新獲取 todoList 並更新到頁面上的呢?
點選以上三個連結的時候,FilterLink 元件做的事情,僅僅是把自身資料 state.visibilityFilter 的值改成點選連結的 props.filter。
FilterLink 的 props 有一個成員,叫:filter,它是在哪兒賦值的呢?
看下面的程式碼(src/components/Footer.js
<ul className="filters">
{Object.keys(FILTER_TITLES).map(filter =>
<li key={filter}>
<FilterLink filter={filter}>
{FILTER_TITLES[filter]}
</FilterLink>
</li>
)}
</ul>
從以上程式碼可知,是通過陣列 FILTER_TITLES 的 key 來初始化過濾連結(全部、待辦、完成)的。
再看一下陣列 FILTER_TITLES 的定義:
const FILTER_TITLES = {
[SHOW_ALL]: 'All',
[SHOW_ACTIVE]: 'Active',
[SHOW_COMPLETED]: 'Completed'
}
上面的程式碼,SHOW_ALL、SHOW_ACTIVE、SHOW_COMPLETED 是常量,定義在 src/constants/TodoFilters.js
程式碼如下:
export const SHOW_ALL = 'show_all' export const SHOW_COMPLETED = 'show_completed' export const SHOW_ACTIVE = 'show_active'
通過 WebStorm 的除錯視窗(在 WebStorm 除錯 react 專案的方法,傳送門),可以看到,陣列 FILTER_TITLES,實際的值是:
{
"show_all": "All",
"show_active": "Active",
"show_completed": "Completed"
}
看到這裡,就知道了下面的程式碼中,傳給 FilterLink 元件的 props 成員 filter 的值,其實就是陣列 FILTER_TITLES 的 key,即:show_all、show_active、show_completed。
<ul className="filters">
{Object.keys(FILTER_TITLES).map(filter =>
<li key={filter}>
<FilterLink filter={filter}>
{FILTER_TITLES[filter]}
</FilterLink>
</li>
)}
</ul>
在介面上,頁面末尾那三個連結【All】、【Active】、【Completed】,就是三個 FilterLink 元件,通過上面的分析,這三個元件的 props.filter 分別是 show_all、show_active、show_completed。
FilterLink 元件,又用了一個UI元件 Link,在 Link 元件中,執行的點選事件是: onClick={() => setFilter()}
。
setFilter 函式是在容器元件 FilterLink 的 mapDispatchToProps 中定義的:
const mapDispatchToProps = (dispatch, ownProps) => ({
setFilter: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
})
它要做的事,只是向 store 發出一個 dispatch,那麼,最終執行者在哪兒呢?
回答這個問題,需要了解 redux 原理。
- redux 的 store 由函式 createStore 返回,該函式的第一個引數是 reducers,是包含了各個模組的 reducer。
- 而 reducers,在這個例子中,是用 redux 提供的 combineReducers() 函式來整合得到的一個集合(更準確的說,是一個數組),這樣,store 就可以根據各個模組的 reducer key 來統一管理各個模組的 state 以及 actions(模組的行為,體現在自己模組的 reducer 中定義的各個函式)。
- 對於 combineReducers(),官方是這樣描述的:combineReducers() 所做的只是生成一個函式,這個函式來呼叫你的一系列 reducer,每個 reducer 根據它們的 key 來篩選出 state 中的一部分資料並處理,然後這個生成的函式再將所有 reducer 的結果合併成一個大的物件。
瞭解了這個原理之後,回到剛才的問題,Link 元件的點選事件,最終的行為,是 reducer(檔案 src/reducers/visibilityFilter.js
定義的函式) visibilityFilter,其定義如下
const visibilityFilter = (state = SHOW_ALL, action) => {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
};
該方法的預設行為是返回 SHOW_ALL(常量),即返回字串 show_all
,點選某一個連結時,返回的是 action 傳過來的 filter。那這個 action 又是在哪兒定義的呢?
從原始碼中可以看出,派發的 dispatch 是:dispatch(setVisibilityFilter(ownProps.filter))
。
再看上下文,不難發現,該 action 是在 src/actions/index.js
下定義的,setVisibilityFilter 的定義如下:
export const setVisibilityFilter = filter => ({ type: types.SET_VISIBILITY_FILTER, filter });
分析到這裡,問題來了,visibilityFilter 接收到這個 action 並執行之後,直接返回的是 action.filter,接下來又發生了什麼?
先看一下 rcux 文件關於 reducer 的描述(傳送門):
- reducers 指定了應用狀態的變化如何響應 actions 併發送到 store 的,記住 actions 只是描述了有事情發生了這一事實,並沒有描述應用如何更新 state。
- reducer 就是一個純函式,接收舊的 state 和 action,返回新的 state。
- 注意每個 reducer 只負責管理全域性 state 中它負責的一部分。每個 reducer 的 state 引數都不同,分別對應它管理的那部分 state 資料。
再回到 visibilityFilter,執行之後,返回的 filter 其實是會更新到 visibilityFilter 這個 reducer 負責的 state。
通過瀏覽器的 Redux DevTools 外掛,我們很容易看到,在 state 樹上,有兩個物件,key 分別為:todos 和 visibilityFilter。
接下來,因為 state 發生了變化,這會導致頁面重新渲染,而頁面渲染的時候,todoList 會根據 visibilityFilter 的值進行過濾,從而實現了三個連結應該有的功能。
相關程式碼如下:
import { createSelector } from 'reselect'
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
const getVisibilityFilter = state => state.visibilityFilter
const getTodos = state => state.todos
export const getVisibleTodos = createSelector(
[getVisibilityFilter, getTodos],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case SHOW_ALL:
return todos
case SHOW_COMPLETED:
return todos.filter(t => t.completed)
case SHOW_ACTIVE:
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + visibilityFilter)
}
}
)
export const getCompletedTodoCount = createSelector(
[getTodos],
todos => (
todos.reduce((count, todo) =>
todo.completed ? count + 1 : count,
0
)
)
)
createSelectorg
上面的程式碼,用到了 createSelectorg 來優化效能,有關 createSelectorg 方法,這裡不做分析,請參考:
- 《深入淺出React和Redux》P122,【5.3 用reselect 提高資料獲取效能】章節。
- 翻譯|Redux的中介軟體-Reselect
- 模擬程式碼幫助理解reselect的createSelector函式