<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>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 實現撤銷歷史 在應用中構建撤銷和重做功能往往需要開發者刻意地付出一些精力。對于經典的 MVC 框架來說,這不是一個簡單的問題,因為你需要克隆所有相關的 model 來追蹤每一個歷史狀態。此外,你需要考慮整個撤銷堆棧,因為用戶的初始更改也是可撤銷的。 這意味著在 MVC 應用中實現撤銷和重做功能時,你不得不使用一些類似于 [Command](https://en.wikipedia.org/wiki/Command_pattern) 的特殊的數據修改模式來重寫你的應用代碼。 然而你可以用 Redux 輕而易舉地實現撤銷歷史,因為以下三個原因: - 不存在多個模型的問題,你需要關心的只是 state 的子樹。 - state 是不可變數據,所有修改被描述成獨立的 action,而這些 action 與預期的撤銷堆棧模型很接近了。 - reducer 的簽名 `(state, action) => state` 可以自然地實現 “reducer enhancers” 或者 “higher order reducers”。它們在你為 reducer 添加額外的功能時保持著這個簽名。撤銷歷史就是一個典型的應用場景。 在動手之前,確認你已經閱讀過[基礎教程](../basics/README.md)并且良好掌握了 [reducer 合成](../basics/Reducers.md)。本文中的代碼會構建于[基礎教程](../basics/README.md)的示例之上。 文章的第一部分,我們將會解釋實現撤銷和重做功能所用到的基礎概念。 在第二部分中,我們會展示如何使用 [Redux Undo](https://github.com/omnidan/redux-undo) 庫來無縫地實現撤銷和重做。 [![demo of todos-with-undo](http://i.imgur.com/lvDFHkH.gif)](https://twitter.com/dan_abramov/status/647038407286390784) ## 理解撤銷歷史 ### 設計狀態結構 撤銷歷史也是應用 state 的一部分,我們沒有必要以不同的方式實現它。當你實現撤銷和重做這個功能時,無論 state 如何隨著時間不斷變化,你都需要追蹤 state 在不同時刻的**歷史記錄**。 例如,一個計數器應用的 state 結構看起來可能是這樣: ```js { counter: 10 } ``` 如果我們希望在這樣一個應用中實現撤銷和重做的話,我們必須保存更多的 state 以解決下面幾個問題: - 撤銷或重做留下了哪些信息? - 當前的狀態是什么? - 撤銷堆棧中過去(和未來)的狀態是什么? 為此我們對 state 結構做了以下修改以便解決上述問題: ```js { counter: { past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], present: 10, future: [] } } ``` 現在,如果按下“撤銷”,我們希望恢復到過去的狀態: ```js { counter: { past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ], present: 9, future: [ 10 ] } } ``` 再按一次: ```js { counter: { past: [ 0, 1, 2, 3, 4, 5, 6, 7 ], present: 8, future: [ 9, 10 ] } } ``` 當我們按下“重做”,我們希望往未來的狀態移動一步: ```js { counter: { past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ], present: 9, future: [ 10 ] } } ``` 最終,當處于撤銷堆棧中時,用戶發起了一個操作(例如,減少計數),那么我們將會丟棄所有未來的信息: ```js { counter: { past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], present: 8, future: [] } } ``` 有趣的一點是,我們在撤銷堆棧中保存的是數字、字符串、數組或是對象都不重要,因為整個結構始終保持一致: ```js { counter: { past: [ 0, 1, 2 ], present: 3, future: [ 4 ] } } ``` ```js { todos: { past: [ [], [ { text: 'Use Redux' } ], [ { text: 'Use Redux', complete: true } ] ], present: [ { text: 'Use Redux', complete: true }, { text: 'Implement Undo' } ], future: [ [ { text: 'Use Redux', complete: true }, { text: 'Implement Undo', complete: true } ] ] } } ``` 它看起來通常都是這樣: ```js { past: Array<T>, present: T, future: Array<T> } ``` 我們可以在頂層保存單一的歷史記錄: ```js { past: [ { counterA: 1, counterB: 1 }, { counterA: 1, counterB: 0 }, { counterA: 0, counterB: 0 } ], present: { counterA: 2, counterB: 1 }, future: [] } ``` 也可以分離歷史記錄,這樣我們可以獨立地執行撤銷和重做操作: ```js { counterA: { past: [ 1, 0 ], present: 2, future: [] }, counterB: { past: [ 0 ], present: 1, future: [] } } ``` 接下來我們將會看到如何合適地分離撤銷和重做。 ### 設計算法 無論何種特定的數據類型,重做歷史記錄的 state 結構始終一致: ```js { past: Array<T>, present: T, future: Array<T> } ``` 讓我們討論一下如何通過算法來操作上文所述的 state 結構。我們可以定義兩個 action 來操作該 state:`UNDO` 和 `REDO`。在 reducer 中,我們希望以如下步驟處理這兩個 action: #### 處理 Undo - 移除 `past` 中的**最后一個**元素。 - 將上一步移除的元素賦予 `present`。 - 將原來的 `present` 插入到 `future` 的**最前面**。 #### 處理 Redo - 移除 `future` 中的**第一個**元素。 - 將上一步移除的元素賦予 `present`。 - 將原來的 `present` 追加到 `past` 的**最后面**。 #### 處理其他 Action - 將當前的 `present` 追加到 `past` 的**最后面**。 - 將處理完 action 所產生的新的 state 賦予 `present`。 - 清空 `future`。 ### 第一次嘗試: 編寫 Reducer ```js const initialState = { past: [], present: null, // (?) 我們如何初始化當前狀態? future: [] } function undoable(state = initialState, action) { const { past, present, future } = state switch (action.type) { case 'UNDO': const previous = past[past.length - 1] const newPast = past.slice(0, past.length - 1) return { past: newPast, present: previous, future: [present, ...future] } case 'REDO': const next = future[0] const newFuture = future.slice(1) return { past: [...past, present], present: next, future: newFuture } default: // (?) 我們如何處理其他 action? return state } } ``` 這個實現是無法使用的,因為它忽略了下面三個重要的問題: - 我們從何處獲取初始的 `present` 狀態?我們無法預先知道它。 - 當處理完外部的 action 后,我們在哪里完成將 `present` 保存到 `past` 的工作? - 我們如何將 `present` 狀態的控制委托給一個自定義的 reducer? 看起來 reducer 并不是正確的抽象方式,但是我們已經非常接近了。 ### 初識 Reducer Enhancers 你可能已經熟悉 [higher order function](https://en.wikipedia.org/wiki/Higher-order_function) 了。如果你使用過 React,也應該熟悉 [higher order component](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750)。我們把這種模式加工一下,將其運用到 reducers。 **reducer enhancer**(或者 **higher order reducer**)作為一個函數,接收 reducer 作為參數并返回一個新的 reducer,這個新的 reducer 可以處理新的 action,或者維護更多的 state,亦或者將它無法處理的 action 委托給原始的 reducer 處理。這不是什么新模式,[`combineReducers()`](../api/combineReducers.md)也是 reducer enhancer,因為它同樣接收多個 reducer 并返回一個新的 reducer。 這是一個沒有任何功能的 reducer enhancer 示例: ```js function doNothingWith(reducer) { return function(state, action) { // 僅僅調用傳入的 reducer return reducer(state, action) } } ``` 一個組合其他 reducer 的 reducer enhancer 看起來類似于這樣: ```js function combineReducers(reducers) { return function(state = {}, action) { return Object.keys(reducers).reduce((nextState, key) => { // 調用每一個 reducer 并將其管理的部分 state 傳給它 nextState[key] = reducers[key](state[key], action) return nextState }, {}) } } ``` ### 第二次嘗試: 編寫 Reducer Enhancer 現在我們對 reducer enhancer 有了更深的了解,我們可以明確所謂的`可撤銷`到底是什么: ```js function undoable(reducer) { // 以一個空的 action 調用 reducer 來產生初始的 state const initialState = { past: [], present: reducer(undefined, {}), future: [] } // 返回一個可以執行撤銷和重做的新的reducer return function(state = initialState, action) { const { past, present, future } = state switch (action.type) { case 'UNDO': const previous = past[past.length - 1] const newPast = past.slice(0, past.length - 1) return { past: newPast, present: previous, future: [present, ...future] } case 'REDO': const next = future[0] const newFuture = future.slice(1) return { past: [...past, present], present: next, future: newFuture } default: // 將其他 action 委托給原始的 reducer 處理 const newPresent = reducer(present, action) if (present === newPresent) { return state } return { past: [...past, present], present: newPresent, future: [] } } } } ``` 我們現在可以將任意的 reducer 封裝到`可撤銷`的 reducer enhancer,從而處理 `UNDO` 和 `REDO` 這兩個 action。 ```js // 這是一個 reducer。 function todos(state = [], action) { /* ... */ } // 處理完成之后仍然是一個 reducer! const undoableTodos = undoable(todos) import { createStore } from 'redux' const store = createStore(undoableTodos) store.dispatch({ type: 'ADD_TODO', text: 'Use Redux' }) store.dispatch({ type: 'ADD_TODO', text: 'Implement Undo' }) store.dispatch({ type: 'UNDO' }) ``` 還有一個重要注意點:你需要記住當你恢復一個 state 時,必須把 `.present` 追加到當前的 state 上。你也不能忘了通過檢查 `.past.length` 和 `.future.length` 確定撤銷和重做按鈕是否可用。 你可能聽說過 Redux 受 [Elm 架構](https://github.com/evancz/elm-architecture-tutorial/) 影響頗深,所以不必驚訝于這個示例與 [elm-undo-redo package](http://package.elm-lang.org/packages/TheSeamau5/elm-undo-redo/2.0.0) 如此相似。 ## 使用 Redux Undo 以上這些信息都非常有用,但是有沒有一個庫能幫助我們實現`可撤銷`功能,而不是由我們自己編寫呢?當然有!來看看 [Redux Undo](https://github.com/omnidan/redux-undo),它可以為你的 Redux 狀態樹中的任何部分提供撤銷和重做功能。 在這個部分中,你會學到如何讓 [示例:Todo List](../basics/ExampleTodoList.md) 擁有可撤銷的功能。你可以在 [`todos-with-undo`](https://github.com/reactjs/redux/tree/master/examples/todos-with-undo)找到完整的源碼。 ### 安裝 首先,你必須先執行 ``` npm install --save redux-undo ``` 這一步會安裝一個提供`可撤銷`功能的 reducer enhancer 的庫。 ### 封裝 Reducer 你需要通過 `undoable` 函數強化你的 reducer。例如,如果之前導出的是 todos reducer,那么現在你需要把這個 reducer 傳給 `undoable()` 然后把計算結果導出: #### `reducers/todos.js` ```js import undoable, { distinctState } from 'redux-undo' /* ... */ const todos = (state = [], action) => { /* ... */ } const undoableTodos = undoable(todos, { filter: distinctState() }) export default undoableTodos ``` 這里的 `distinctState()` 過濾器會忽略那些沒有引起 state 變化的 actions,可撤銷的 reducer 還可以通過[其他選擇](https://github.com/omnidan/redux-undo#configuration)進行配置,例如為撤銷和重做的 action 設置 action type。 值得注意的是雖然這與調用 `combineReducers()` 的結果別無二致,但是現在的 `todos` reducer 可以傳遞給 Redux Undo 增強的 reducer。 #### `reducers/index.js` ```js import { combineReducers } from 'redux' import todos from './todos' import visibilityFilter from './visibilityFilter' const todoApp = combineReducers({ todos, visibilityFilter }) export default todoApp ``` 你可以在 reducer 合并層次中的任何層級對一個或多個 reducer 執行 `undoable`。我們只對 `todos` reducer 進行封裝而不是整個頂層的 reducer,這樣 `visibilityFilter` 引起的變化才不會影響撤銷歷史。 ### 更新 Selectors 現在 `todos` 相關的 state 看起來應該像這樣: ```js { visibilityFilter: 'SHOW_ALL', todos: { past: [ [], [ { text: 'Use Redux' } ], [ { text: 'Use Redux', complete: true } ] ], present: [ { text: 'Use Redux', complete: true }, { text: 'Implement Undo' } ], future: [ [ { text: 'Use Redux', complete: true }, { text: 'Implement Undo', complete: true } ] ] } } ``` 這意味著你必須通過 `state.todos.present` 訪問 state 而不是原來的 `state.todos`: #### `containers/VisibleTodoList.js` ```js const mapStateToProps = state => { return { todos: getVisibleTodos(state.todos.present, state.visibilityFilter) } } ``` ### 添加按鈕 現在只剩下給撤銷和重做的 action 添加按鈕。 首先,為這些按鈕創建一個名為 `UndoRedo` 的容器組件。由于展示部分非常簡單,我們不再需要把它們分離到單獨的文件去: #### `containers/UndoRedo.js` ```js import React from 'react' /* ... */ let UndoRedo = ({ canUndo, canRedo, onUndo, onRedo }) => ( <p> <button onClick={onUndo} disabled={!canUndo}> Undo </button> <button onClick={onRedo} disabled={!canRedo}> Redo </button> </p> ) ``` 你需要使用 [React Redux](https://github.com/reactjs/react-redux) 的 connect 函數生成容器組件,然后檢查 `state.todos.past.length` 和 `state.todos.future.length` 來判斷是否啟用撤銷和重做按鈕。你不再需要給撤銷和重做編寫 action creators 了,因為 Redux Undo 已經提供了這些 action creators: #### `containers/UndoRedo.js` ```js /* ... */ import { ActionCreators as UndoActionCreators } from 'redux-undo' import { connect } from 'react-redux' /* ... */ const mapStateToProps = state => { return { canUndo: state.todos.past.length > 0, canRedo: state.todos.future.length > 0 } } const mapDispatchToProps = dispatch => { return { onUndo: () => dispatch(UndoActionCreators.undo()), onRedo: () => dispatch(UndoActionCreators.redo()) } } UndoRedo = connect( mapStateToProps, mapDispatchToProps )(UndoRedo) export default UndoRedo ``` 現在把這個 `UndoRedo` 組件添加到 `App` 組件: #### `components/App.js` ```js import React from 'react' import Footer from './Footer' import AddTodo from '../containers/AddTodo' import VisibleTodoList from '../containers/VisibleTodoList' import UndoRedo from '../containers/UndoRedo' const App = () => ( <div> <AddTodo /> <VisibleTodoList /> <Footer /> <UndoRedo /> </div> ) export default App ``` 就是這樣!在[示例文件夾](https://github.com/reactjs/redux/tree/master/examples/todos-with-undo)下執行 `npm install` 和 `npm start` 試試看吧!
                  <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>

                              哎呀哎呀视频在线观看