redux 時間旅行,你值得擁有!
顧名思義,就是可以隨時穿越到以前和未來,讓應用程式切換到任意時間的狀態。我們都知道,一般應用狀態都很複雜,建立、維護、修改和弄明白有哪些行為會影響狀態都不是一件容易的事兒。
redux 的解決方案
整個應用的 state 被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個 store 中。並使用純函式計算下一個應用程式狀態(不允許其他途徑對 state 進行修改)。這些特徵使 Redux 成為了一個可預測 的狀態容器,這意味著如果給定一個特定應用程式狀態和一個特定操作,那麼應用程式的下一個狀態將始終完全相同。這種可預測性使得實現時間旅行變得很容易。redux 也相應的開發了一個帶時間旅行的開發者工具 ofollow,noindex">redux-devtools

就是上面這個東西。下面就讓我們跟隨例子一起來了解下 redux 時間旅行的工作原理。
閱讀要求
- react 基礎
- redux 基礎,明白 action,reducer,state 的關係。明白 combineReducer 的原理。
開始
專案地址:(github)[ github.com/wuyafeiJS/r… ]
預覽:

stateHistory.js
export default { past: [], futrue: [], present: undefined, gotoState(i) { const index = i * 1; const allState = [...this.past, this.present, ...this.futrue]; this.present = allState[index]; this.past = allState.slice(0, index); this.futrue = allState.slice(index + 1, allState.length); } }; 複製程式碼
我們把狀態分為三個時間段:過去,現在(只有一個狀態),將來。gotoState 函式則是用來做時間旅行的,他的實現方式就是整合所有狀態 allState,重新分配,present 前面是 past,後面是 future。
那麼我們如何去存放每一次變更的狀態呢?我們需要找到一個入口,這個入口必須是每次觸發狀態變更都會經過的地方。而觸發狀態變更唯一的方式就是 dispatch(action)
,想想,這樣的地方好像只有一個地方,看過 redux 原始碼的同學肯定就是不陌生,那就是 combineReducer 生成的 reducers 純函式。 combineReducer 負責整合多個 reducer,最終返回一個能夠處理所有 action 的 reducers。讓我們大致簡單實現一下:
const combineReducer = obj => (state, action) => { const finalState = {}; for (key in obj) { finanlState[key] = obj[key](state[key], action); } return finalState; // 全域性state }; 複製程式碼
接下來,讓我們利用函數語言程式設計的思想加強下 reducers 的功能,讓它能記錄 state: reducers.js
import stateHistory from './stateHistory';// 引入我們之前宣告的history物件 // 原本我們是這樣返回reducers的 export default combineReducers({ books: fetchReducer, displayMode: bookDisplayReducer, currentStatus: statusReducer, topic: topicReducer }) // 改造後如下: export default history( combineReducers({ books: fetchReducer, displayMode: bookDisplayReducer, currentStatus: statusReducer, topic: topicReducer }) ); // 我們用history包裹combineReducer,history實現如下 const history = reducers => (state, aciton) => { switch (action.type) { case 'UNDO': // 後退 stateHistory.undo(); break; case 'REDO': // 前進 stateHistory.redo(); break; case 'GOTO': // 定點指向 stateHistory.gotoState(action.stateIndex); break; default: const newState = reducer(state, action); stateHistory.push(newState);// 每次dipatch(action)都會像將狀態儲存到stateHistory } return stateHistory.present; // 返回當前狀態 } 複製程式碼
完善下 stateHistory.js
export default { ... hasRecord(type) {// 查詢是否有過去或者將來的狀態 return this[type].length > 0; }, hasPresent() { // 查詢是否有現在的狀態 return this.present !== undefined; }, setPresent(state) { this.present = state; }, movePresentToPast() { this.past.push(this.present); }, push(currentState) { // 將狀態都儲存,並更新當前狀態 if (this.hasPresent()) { this.past.push(this.present); } this.setPresent(currentState); }, getIndex() { // 獲取當前狀態index return this.past.length; }, undo() { // 後退 if (this.hasRecord('past')) { this.gotoState(this.getIndex() - 1); } }, redo() { // 前進 if (this.hasRecord('futrue')) { this.gotoState(this.getIndex() + 1); } }, ... }; 複製程式碼
配置 action: actions.js
... export const redo = () => ({ type: 'REDO' }); export const undo = () => ({ type: 'UNDO' }); export const gotoState = stateIndex => ({ type: 'GOTO', stateIndex }); 複製程式碼
準備工作都已經做完,接下來咱們直接在 react 元件內加上觸發程式碼即可 components/History.js
const History = ({ past, futrue, present, redo, undo, gotoState }) => { const styles = { container: { marginLeft: '20px', cursor: 'pointer' }, link: { textDecoration: 'none' }, input: { cursor: 'pointer' } }; const RightArrow = () => ( // 前進 <a href="#" style={styles.link} onClick={() => redo()}> </a> ); const LeftArrow = () => ( // 後退 <a href="#" style={styles.link} onClick={() => undo()}> </a> ); const max = () => (past ? past.length : 0) + (present ? 1 : 0) + (futrue ? futrue.length : 0) - 1; const value = () => (past ? past.length : 0); return ( <span> <input type="range" min={0} max={max()} value={value()} onChange={e => { gotoState(e.target.value); }} style={styles.input} /> {past && past.length > 0 ? <LeftArrow /> : null} {futrue && futrue.length > 0 ? <RightArrow /> : null} </span> ); }; 複製程式碼
以上!希望對大家理解 redux 有所幫助。