redux 時間旅行,你值得擁有!

啥叫時間旅行?

顧名思義,就是能夠隨時穿越到之前和將來,讓應用程序切換到任意時間的狀態。咱們都知道,通常應用狀態都很複雜,建立、維護、修改和弄明白有哪些行爲會影響狀態都不是一件容易的事兒。react

redux 的解決方案

整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。並使用純函數計算下一個應用程序狀態(不容許其餘途徑對 state 進行修改)。這些特徵使 Redux 成爲了一個可預測 的狀態容器,這意味着若是給定一個特定應用程序狀態和一個特定操做,那麼應用程序的下一個狀態將始終徹底相同。這種可預測性使得實現時間旅行變得很容易。redux 也相應的開發了一個帶時間旅行的開發者工具redux-devtoolsgit

就是上面這個東西。下面就讓咱們跟隨例子一塊兒來了解下 redux 時間旅行的工做原理。

閱讀要求

  • react 基礎
  • redux 基礎,明白 action,reducer,state 的關係。明白 combineReducer 的原理。

開始

項目地址:(github)[github.com/wuyafeiJS/r…]

預覽:github

既然咱們要實現時間旅行,那麼第一步咱們須要一個對象來記錄每一次狀態: 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。讓咱們大體簡單實現一下:redux

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

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()}>
      &#8594;
    </a>
  );

  const LeftArrow = () => (
    // 後退
    <a href="#" style={styles.link} onClick={() => undo()}>
      &#8592;
    </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 有所幫助。this

相關文章
相關標籤/搜索