<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # Reducer [Action](#) 只是描述了**有事情發生了**這一事實,并沒有指明應用如何更新 state。這是 reducer 要做的事情。 ### 設計 State 結構 應用所有的 state 都被保存在一個單一對象中(我們稱之為 state 樹)。建議在寫代碼前先想一下這個對象的結構。如何才能以最簡的形式把應用的 state 用對象描述出來? 以 todo 應用為例,需要保存兩個不同的內容: - 當前選中的任務過濾條件; - 真實的任務列表。 通常,這個 state 樹還需要存放其它一些數據,還有界面 state。這樣做沒問題,但盡量把這些數據與界面 state 分開。 ~~~ { visibilityFilter: 'SHOW_ALL', todos: [{ text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false }] } ~~~ > ##### 處理 Reducer 關系時注意 > 開發復雜的應用時,不可避免會有一些數據相互引用。建議你盡可能地把 state 范式化,不存在嵌套。把所有數據放到一個對象里,每個數據以 ID 為主鍵,不同數據相互引用時通過 ID 來查找。把應用的 state 想像成數據庫。這種方法在 [normalizr](https://github.com/gaearon/normalizr) 文檔里有詳細闡述。例如,實際開發中,在 state 里同時存放 `todosById: { id -> todo }` 和 `todos: array<id>` 是比較好的方式(雖然你可以覺得冗余)。 ### Action 處理 現在我們已經確定了 state 對象的結構,就可以開始開發 reducer。reducer 就是一個函數,接收舊的 state 和 action,返回新的 state。 ~~~ (previousState, action) => newState ~~~ 之所以稱作 reducer 是因為和 [`Array.prototype.reduce(reducer, ?initialValue)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) 格式很像。保持 reducer 純凈非常重要。永遠**不要**在 reducer 里做這些操作: - 修改傳入參數; - 執行有副作用的操作,如 API 請求和路由跳轉。 在[高級篇](#)里會介紹如何執行有副作用的操作。現在只需要謹記 reducer 一定要保持純凈。**只要傳入參數一樣,返回必須一樣。沒有特殊情況、沒有副作用,沒有 API 請求、沒有修改參數,單純執行計算。** 明白了這些之后,就可以開始編寫 reducer,并讓它來處理之前定義過的 [actions](#)。 我們在開始時定義默認的 state。Redux 首次執行時,state 為 `undefined`,這時候會返回默認 state。 ~~~ import { VisibilityFilters } from './actions'; const initialState = { visibilityFilter: VisibilityFilters.SHOW_ALL, todos: [] }; function todoApp(state, action) { if (typeof state === 'undefined') { return initialState; } // 這里暫不處理任何 action, // 僅返回傳入的 state。 return state; } ~~~ 這里一個技巧是使用 [ES6 參數默認值語法](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/default_parameters) 來精簡代碼。 ~~~ function todoApp(state = initialState, action) { // 這里暫不處理任何 action, // 僅返回傳入的 state。 return state; } ~~~ 現在可以處理 `SET_VISIBILITY_FILTER`。需要做的只是改變 state 中的 `visibilityFilter`。 ~~~ function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }); default: return state; } } ~~~ 注意: 1. **不要修改 `state`。** 使用 [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 新建了一個副本。不能這樣使用 `Object.assign(state, { visibilityFilter: action.filter })`,因為它會改變第一個參數的值。**一定**要把第一個參數設置為空對象。也可以使用 ES7 中還在試驗階段的特性 `{ ...state, ...newState }`,參考 [對象展開語法](https://github.com/sebmarkbage/ecmascript-rest-spread)。 1. **在 `default` 情況下返回舊的 `state`。**遇到未知的 action 時,一定要返回舊的 `state`。 > ##### `Object.assign` 使用提醒 > [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 是 ES6 特性,但多數瀏覽器并不支持。你要么使用 polyfill,[Babel 插件](https://github.com/babel-plugins/babel-plugin-object-assign),或者使用其它庫如 [`_.assign()`](https://lodash.com/docs#assign) 提供的幫助方法。 > ##### `switch` 和樣板代碼提醒 > `state` 語句并不是嚴格意義上的樣板代碼。Flux 中真實的樣板代碼是概念性的:更新必須要發送、Store 必須要注冊到 Dispatcher、Store 必須是對象(開發同構應用時變得非常復雜)。為了解決這些問題,Redux 放棄了 event emitters(事件發送器),轉而使用純 reducer。 > 很不幸到現在為步,還有很多人存在一個誤區:根據文檔中是否使用 `switch` 來決定是否使用它。如果你不喜歡 `switch`,完全可以自定義一個 `createReducer` 函數來接收一個事件處理函數列表,參照["減少樣板代碼"](#)。 ### 處理多個 action 還有兩個 action 需要處理。讓我們先處理 `ADD_TODO`。 ~~~ function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }); case ADD_TODO: return Object.assign({}, state, { todos: [...state.todos, { text: action.text, completed: false }] }); default: return state; } } ~~~ 如上,不直接修改 `state` 中的字段,而是返回新對象。新的 `todos` 對象就相當于舊的 `todos` 在末尾加上新建的 todo。而這個新的 todo 又是在 action 中創建的。 最后,`COMPLETE_TODO` 的實現也很好理解: ~~~ case COMPLETE_TODO: return Object.assign({}, state, { todos: [ ...state.todos.slice(0, action.index), Object.assign({}, state.todos[action.index], { completed: true }), ...state.todos.slice(action.index + 1) ] }); ~~~ 因為我們不能直接修改卻要更新數組中指定的一項數據,這里需要先把前面和后面都切開。如果經常需要這類的操作,可以選擇使用幫助類 [React.addons.update](https://facebook.github.io/react/docs/update.html),[updeep](https://github.com/substantial/updeep),或者使用原生支持深度更新的庫 [Immutable](http://facebook.github.io/immutable-js/)。最后,時刻謹記永遠不要在克隆 `state` 前修改它。 ### 拆分 Reducer 目前的代碼看起來有些冗余: ~~~ function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }); case ADD_TODO: return Object.assign({}, state, { todos: [...state.todos, { text: action.text, completed: false }] }); case COMPLETE_TODO: return Object.assign({}, state, { todos: [ ...state.todos.slice(0, action.index), Object.assign({}, state.todos[action.index], { completed: true }), ...state.todos.slice(action.index + 1) ] }); default: return state; } } ~~~ 上面代碼能否變得更通俗易懂?這里的 `todos` 和 `visibilityFilter` 的更新看起來是相互獨立的。有時 state 中的字段是相互依賴的,需要認真考慮,但在這個案例中我們可以把 `todos` 更新的業務邏輯拆分到一個單獨的函數里: ~~~ function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [...state, { text: action.text, completed: false }]; case COMPLETE_TODO: return [ ...state.slice(0, action.index), Object.assign({}, state[action.index], { completed: true }), ...state.slice(action.index + 1) ]; default: return state; } } function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }); case ADD_TODO: case COMPLETE_TODO: return Object.assign({}, state, { todos: todos(state.todos, action) }); default: return state; } } ~~~ 注意 `todos` 依舊接收 `state`,但它變成了一個數組!現在 `todoApp` 只把需要更新的一部分 state 傳給 `todos` 函數,`todos` 函數自己確定如何更新這部分數據。**這就是所謂的 **reducer 合成**,它是開發 Redux 應用最基礎的模式。** 下面深入探討一下如何做 reducer 合成。能否抽出一個 reducer 來專門管理 `visibilityFilter`?當然可以: ~~~ function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } } ~~~ 現在我們可以開發一個函數來做為主 reducer,它調用多個子 reducer 分別處理 state 中的一部分數據,然后再把這些數據合成一個大的單一對象。主 reducer 并不需要設置初始化時完整的 state。初始時,如果給子 reducer 傳入 `undefined` 只要返回它們的默認值即可。 ~~~ function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [...state, { text: action.text, completed: false }]; case COMPLETE_TODO: return [ ...state.slice(0, action.index), Object.assign({}, state[action.index], { completed: true }), ...state.slice(action.index + 1) ]; default: return state; } } function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } } function todoApp(state = {}, action) { return { visibilityFilter: visibilityFilter(state.visibilityFilter, action), todos: todos(state.todos, action) }; } ~~~ **注意每個 reducer 只負責管理全局 state 中它負責的一部分。每個 reducer 的 `state` 參數都不同,分別對應它管理的那部分 state 數據。** 現在看過起來好多了!隨著應用的膨脹,我們已經學會把 reducer 拆分成獨立文件來分別處理不同的數據域了。 最后,Redux 提供了 [`combineReducers()`](#) 工具類來做上面 `todoApp` 做的事情,這樣就能消滅一些樣板代碼了。有了它,可以這樣重構 `todoApp`: ~~~ import { combineReducers } from 'redux'; const todoApp = combineReducers({ visibilityFilter, todos }); export default todoApp; ~~~ 注意上面的寫法和下面完全等價: ~~~ export default function todoApp(state, action) { return { visibilityFilter: visibilityFilter(state.visibilityFilter, action), todos: todos(state.todos, action) }; } ~~~ 你也可以給它們設置不同的 key,或者調用不同的函數。下面兩種合成 reducer 方法完全等價: ~~~ const reducer = combineReducers({ a: doSomethingWithA, b: processB, c: c }); ~~~ ~~~ function reducer(state, action) { return { a: doSomethingWithA(state.a, action), b: processB(state.b, action), c: c(state.c, action) }; } ~~~ [`combineReducers()`](#) 所做的只是生成一個函數,這個函數來調用你的一系列 reducer,每個 reducer **根據它們的 key 來篩選出 state 中的一部分數據并處理**,然后這個生成的函數所所有 reducer 的結果合并成一個大的對象。[沒有任何魔法。](https://github.com/gaearon/redux/issues/428#issuecomment-129223274) > ##### ES6 用戶使用注意 > `combineReducers` 接收一個對象,可以把所有頂級的 reducer 放到一個獨立的文件中,通過 `export` 暴露出每個 reducer 函數,然后使用 `import * as reducers` 得到一個以它們名字作為 key 的 object: ~~~ import { combineReducers } from 'redux'; import * as reducers from './reducers'; const todoApp = combineReducers(reducers); ~~~ > 由于 `import *` 還是比較新的語法,為了避免[困惑](https://github.com/gaearon/redux/issues/428#issuecomment-129223274),我們不會在文檔使用它。但在一些社區示例中你可能會遇到它們。 ### 源碼 #### `reducers.js` ~~~ import { combineReducers } from 'redux'; import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions'; const { SHOW_ALL } = VisibilityFilters; function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } } function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [...state, { text: action.text, completed: false }]; case COMPLETE_TODO: return [ ...state.slice(0, action.index), Object.assign({}, state[action.index], { completed: true }), ...state.slice(action.index + 1) ]; default: return state; } } const todoApp = combineReducers({ visibilityFilter, todos }); export default todoApp; ~~~ ### 下一步 接下來會學習 [創建 Redux store](#)。store 能維持應用的 state,并在當你發起 action 的時候調用 reducer。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看