Stories

Detail Return Return

Redux 簡介 - Stories Detail

Redux

Store

創建

Redux 是一個狀態管理框架,可以與包括 React 在內的許多不同的 Web 技術一起使用。

在 Redux 中,有一個狀態對象負責應用程序的整個狀態, 這意味着如果有一個包含十個組件且每個組件都有自己的本地狀態的 React 項目,那麼這個項目的整個狀態將通過 Redux store 被定義為單個狀態對象, 這是學習 Redux 時要理解的第一個重要原則:Redux store 是應用程序狀態的唯一真實來源。

這也意味着,如果應用程序想要更新狀態,只能通過 Redux store 執行, 單向數據流可以更輕鬆地對應用程序中的狀態進行監測管理。

Redux store 是一個保存和管理應用程序狀態的state, 可以使用 Redux 對象中的 createStore() 來創建一個 redux store, 此方法將 reducer 函數作為必需參數, 它只需將 state 作為參數並返回一個 state 即可。

const reducer = (state = 5) => {
  return state;
}
let store = Redux.createStore(reducer);

獲取狀態

Redux store 對象提供了幾種與之交互的方法, 比如,可以使用 getState() 方法檢索 Redux store 對象中保存的當前 state

const store = Redux.createStore(
  (state = 5) => state
);

let currentState = store.getState();

Store 監聽器

在 Redux store 對象上訪問數據的另一種方法是 store.subscribe()。 這允許將監聽器函數訂閲到 store,只要 action 被 dispatch 就會調用它們。 這個方法的一個簡單用途是為 store 訂閲一個函數,它只是在每次收到一個 action 並且更新 store 時記錄一條消息。

const ADD = 'ADD';

const reducer = (state = 0, action) => {
  switch(action.type) {
    case ADD:
      return state + 1;
    default:
      return state;
  }
};

const store = Redux.createStore(reducer);

let count = 0;
const addOne = () => (count += 1);
store.subscribe(addOne);

store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);

Action

由於 Redux 是一個狀態管理框架,因此更新狀態是其核心任務之一。 在 Redux 中,所有狀態更新都由 dispatch action 觸發, action 只是一個 JavaScript 對象,其中包含有關已發生的 action 事件的信息。 Redux store 接收這些 action 對象,然後更新相應的狀態。 有時,Redux action 也會攜帶一些數據。 例如,在用户登錄後攜帶用户名, 雖然數據是可選的,但 action 必須帶有 type 屬性,該屬性表示此 action 的類型。

可以將 Redux action 視為信使,將有關應用程序中發生的事件信息提供給 Redux store, 然後 store 根據發生的 action 進行狀態的更新。

let action = {
  type: 'LOGIN'
}

Action Creator

創建 action 後要將 action 發送到 Redux store,以便它可以更新其狀態。 在 Redux 中,可以定義動作創建器來完成此任務, action creator 只是一個返回動作的 JavaScript 函數。 換句話説,action creator 創建表示動作事件的對象。

function actionCreator() {
  return action;
}

Action Event

dispatch 方法用於將 action 分派給 Redux store, 調用 store.dispatch() 將從 action creator 返回的值發送回 store。

下面的行是等效的,兩者都會調度類 LOGIN 類型的 action:

store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });
const store = Redux.createStore(
  (state = {login: false}) => state
);

const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};

store.dispatch(loginAction());

Store 裏處理 Action (Reducer)

在一個 action 被創建並 dispatch 之後,Redux store 需要知道如何響應該操作。 這就是 reducer 函數存在的意義。 Redux 中的 Reducers 負責響應 action 然後進行狀態的修改。 reducerstateaction 作為參數,並且它總是返回一個新的 state。 要知道這是 reducer 的唯一的作用。 它不應有任何其他的作用:比如它不應調用 API 接口,也不應存在任何潛在的副作用。 reducer 只是一個接受狀態和動作,然後返回新狀態的純函數。

Redux 的另一個關鍵原則是 state 是隻讀的。 換句話説,reducer 函數必須始終返回一個新的 state,並且永遠不會直接修改狀態。 Redux 不強制改變狀態,但是需要在 reducer 函數的代碼中強制執行它。

請注意,當前 state 和 dispatch 的 action 將被傳遞給 reducer,因此可以使用 action.type 直接獲取 action 的類型。

const defaultState = {
  login: false
};

const reducer = (state = defaultState, action) => {
  if (action.type === 'LOGIN') return ({
    login: true
  });
  else return state;
};

const store = Redux.createStore(reducer);

const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};

Switch 處理 Action

可以定義 Redux store 處理多種 action 類型。 假設在 Redux store 中管理用户身份驗證。 希望用狀態表示用户登錄和註銷。 使用 state 的 authenticated 屬性表示它。 還需要使用 action creators 創建與用户登錄和用户註銷相對應的 action,以及 action 對象本身。

可以在 reducer 裏通過使用 JavaScript 的 switch 來響應不同的 action 事件。 這是編寫 Redux reducers 的標準模式。 switch 語句應該切換 action.type 並返回適當的身份驗證狀態。

另外,不要忘記在 switch 語句中寫一個 default case,返回當前的 state。 這是很重要的,因為當程序有多個 reducer,當每一個 action 被 dispatch 時它們都會運行,即使 action 與該 reducer 無關。 在這種情況下,要確保返回當前的 state

const defaultState = {
  authenticated: false
};

const authReducer = (state = defaultState, action) => {
  switch(action.type) {
    case "LOGIN": return {authenticated: true};
    case "LOGOUT": return {authenticated: false}
    default: return state;
  }
};

const store = Redux.createStore(authReducer);

const loginUser = () => {
  return {
    type: 'LOGIN'
  }
};

const logoutUser = () => {
  return {
    type: 'LOGOUT'
  }
};

Action Types

在使用 Redux 時的一個常見做法是將操作類型指定為只讀,然後在任何使用它們的地方引用這些常量。 可以通過將 action types 使用 const 聲明重構正在使用的代碼。

const LOGIN = "LOGIN";
const LOGOUT = "LOGOUT";


const defaultState = {
  authenticated: false
};

const authReducer = (state = defaultState, action) => {

  switch (action.type) {
    case LOGIN: 
      return {
        authenticated: true
      }
    case LOGOUT: 
      return {
        authenticated: false
      }
    default:
      return state;
  }
};

const store = Redux.createStore(authReducer);

const loginUser = () => {
  return {
    type: 'LOGIN'
  }
};

const logoutUser = () => {
  return {
    type: 'LOGOUT'
  }
};

注意: 通常以全部大寫形式寫出常量,這也是 Redux 的標準做法。

發送 Action Data 給 Store

到目前為止,這些 action 並未包含除 type之外的任何信息。 還可以和把 action 和特定數據一起發送。 事實上,這是非常常見的,因為 action 通常源於一些用户交互,並且往往會攜帶一些數據, Redux store 經常需要知道這些數據。

const ADD_NOTE = 'ADD_NOTE';

const notesReducer = (state = 'Initial State', action) => {
  switch(action.type) {
  
case ADD_NOTE: return action.text;
    
    default:
      return state;
  }
};

const addNoteText = (note) => {
 
  return {
    type: ADD_NOTE,
    text: note
  }
 
};

const store = Redux.createStore(notesReducer);

console.log(store.getState());
store.dispatch(addNoteText('Hello!'));
console.log(store.getState());

組合多個 Reducers

當應用程序的狀態開始變得越來越複雜時,可能會將 state 分成多個塊。 相反,請記住 Redux 的第一個原則:所有應用程序狀態都保存在 store 中的一個簡單的 state 對象中。 因此,Redux 提供 reducer 組合作為複雜狀態模型的解決方案。 定義多個 reducer 來處理應用程序狀態的不同部分,然後將這些 reducer 組合成一個 root reducer。 然後將 root reducer 傳遞給 Redux createStore()方法。

為了將多個 reducer 組合在一起,Redux 提供了combineReducers()方法。 該方法接受一個對象作為參數,在該參數中定義一個屬性,該屬性將鍵與特定 reducer 函數關聯。 Redux 將使用給定的鍵值作為關聯狀態的名稱。

通常情況下,當它們在某種程度上是獨一無二的,為每個應用程序的 state 創建一個 reducer 是一個很好的做法。 例如,在一個帶有用户身份驗證的記筆記應用程序中,一個 reducer 可以處理身份驗證而另一個處理用户提交的文本和註釋。 對於這樣的應用程序,可能會編寫 combineReducers() 方法,如下所示:

const rootReducer = Redux.combineReducers({
  auth: authenticationReducer,
  notes: notesReducer
});
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

const counterReducer = (state = 0, action) => {
  switch(action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;
    default:
      return state;
  }
};

const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';

const authReducer = (state = {authenticated: false}, action) => {
  switch(action.type) {
    case LOGIN:
      return {
        authenticated: true
      }
    case LOGOUT:
      return {
        authenticated: false
      }
    default:
      return state;
  }
};

const rootReducer = Redux.combineReducers({
  auth: authReducer,
  count: counterReducer
});

const store = Redux.createStore(rootReducer);

中間件 Middleware

目前為止的挑戰都在避免討論異步操作,但它們是 Web 開發中不可避免的一部分。 在某些時候,需要在 Redux 應用程序中使用異步請求,那麼如何處理這些類型的請求? Redux 中間件專為此目的而設計,稱為 Redux Thunk 中間件。 這裏簡要介紹如何在 Redux 中使用它。

如果要使用 Redux Thunk 中間件,請將其作為參數傳遞給 Redux.applyMiddleware()。 然後將此函數作為第二個可選參數提供給 createStore() 函數, 看一下編輯器底部的代碼。 然後,要創建一個異步的 action,需要在 action creator 中返回一個以 dispatch 為參數的函數。 在這個函數中,可以 dispatch action 並執行異步請求。

在此示例中,使用 setTimeout() 模擬異步請求。 通常在執行異步行為之前 dispatch action,以便應用程序狀態知道正在請求某些數據(例如,這個狀態可以顯示加載圖標)。 然後,一旦收到數據,就會發送另一個 action,該 action 的 data 是請求返回的數據同時也代表 API 操作完成。

請記住,正在將 dispatch 作為參數傳遞給這個特殊的 action creator。 需要 dispatch action 時只需將 action 直接傳遞給 dispatch,中間件就可以處理其餘的部分。

const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'

const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }

const handleAsync = () => {
  return function(dispatch) {
 

    dispatch(requestingData());

    setTimeout(function() {
      let data = {
        users: ["Jeff", "William", "Alice"]
      };
  

      dispatch(receivedData(data));
    }, 2500);
  }
};

const defaultState = {
  fetching: false,
  users: []
};

const asyncDataReducer = (state = defaultState, action) => {
  switch(action.type) {
    case REQUESTING_DATA:
      return {
        fetching: true,
        users: []
      }
    case RECEIVED_DATA:
      return {
        fetching: false,
        users: action.users
      }
    default:
      return state;
  }
};

const store = Redux.createStore(
  asyncDataReducer,
  Redux.applyMiddleware(ReduxThunk.default)
);

計數器

const INCREMENT = 'INCREMENT'; 
const DECREMENT = 'DECREMENT'; 

const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case INCREMENT:
      return state + 1;

    case DECREMENT:
      return state - 1;

    default:
      return state;
  }
};

const incAction = () => {
  return {
    type: INCREMENT
  };
}; 

const decAction = () => {
  return {
    type: DECREMENT
  };
}; 

const store = Redux.createStore(counterReducer); 

永不改變狀態 Never Mutate State

最後的幾個例子描述了在 Redux 中強制執行狀態不變性關鍵原則的幾種方法。 不可變狀態意味着永遠不直接修改狀態,而是返回一個新的狀態副本。

如果拍攝 Redux 應用程序一段時間狀態的快照,會看到類似 state 1state 2state 3state 4...等等,每個狀態可能與最後一個狀態相似,但每個狀態都是一個獨特的數據。 事實上,這種不變性提供了時間旅行調試等功能。

Redux 並沒有主動地在其 store 或者 reducer 中強制執行狀態不變性,責任落在程序員身上。 幸運的是,JavaScript(尤其是 ES6)提供了一些有用的工具,可以用來強制執行狀態的不變性,無論是 stringnumberarrayobject。 請注意,字符串和數字是原始值,並且本質上是不可變的。 換句話説,3 總是 3, 不能改變數字 3 的值。 然而,arrayobject 是可變的。 實際上,狀態可能會包括 arrayobject,因為它們經常用來描述一些代表信息的數據結構。

const ADD_TO_DO = 'ADD_TO_DO';

const todos = [
  'Go to the store',
  'Clean the house',
  'Cook dinner',
  'Learn to code',
];

const immutableReducer = (state = todos, action) => {
  switch(action.type) {
    case ADD_TO_DO:
  return state.concat(action.todo);
 
      return
    default:
      return state;
  }
};

const addToDo = (todo) => {
  return {
    type: ADD_TO_DO,
    todo
  }
}

const store = Redux.createStore(immutableReducer);

數組中使用擴展運算符

ES6 中有助於在 Redux 中強制執行狀態不變性的一個解決方案是擴展運算符:...。 擴展運算符具有很多的應用,其中一種非常適合通過一個已有的數組生成一個新數組。 這是相對較新的但常用的語法。

const immutableReducer = (state = ['Do not mutate state!'], action) => {
  switch(action.type) {
    case 'ADD_TO_DO': return [...state, action.todo]
     
    default:
      return state;
  }
};

const addToDo = (todo) => {
  return {
    type: 'ADD_TO_DO',
    todo
  }
}

const store = Redux.createStore(immutableReducer);

從數組中刪除項目

const immutableReducer = (state = [0,1,2,3,4,5], action) => {
  switch(action.type) {
    case 'REMOVE_ITEM':
    
       return [
        ...state.slice(0, action.index),
        ...state.slice(action.index + 1, state.length)
      ];

    default:
      return state;
  }
};

const removeItem = (index) => {
  return {
    type: 'REMOVE_ITEM',
    index
  }
}

const store = Redux.createStore(immutableReducer);

Object.assign()

最後幾個挑戰適用於數組,但是當狀態是 object 時,有一些方法可以實現狀態不變性。 處理對象的一個常用的方法是 Object.assign()Object.assign() 獲取目標對象和源對象,並將源對象中的屬性映射到目標對象。 任何匹配的屬性都會被源對象中的屬性覆蓋。 通常用於通過傳遞一個空對象作為第一個參數,然後是要用複製的對象來製作對象的淺表副本。 下面是一個示例:

const newObject = Object.assign({}, obj1, obj2);
const defaultState = {
  user: 'CamperBot',
  status: 'offline',
  friends: '732,982',
  community: 'freeCodeCamp'
};

const immutableReducer = (state = defaultState, action) => {
  switch(action.type) {
    case 'ONLINE':
      return Object.assign({}, state, {status: "online"});
    default:
      return state;
  }
};

const wakeUp = () => {
  return {
    type: 'ONLINE'
  }
};

const store = Redux.createStore(immutableReducer);

Add a new Comments

Some HTML is okay.