RZ-Huang
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Middleware of Redux ###### tags: `javascript`、`redux`、`middleware` Redux 的 action 傳到 reducer 以前,中間還會有 middleware 的處理,下面接著來介紹兩套middleware 的 library。 介紹 library 以前,先聲明在這篇文章所用的程式碼來自於 [Redux 的介紹與基礎概念](/3PEEcAvYRkiJ2EQQvUUjgQ) 的範例。 首先先看到 `PostsContainer.js` 和 `action.js` 兩個檔案的一小部分: `PostsContainer.js` 檔案中的 `mapDispatchToProps` 函式: ```jsx= const mapDispatchToProps = dispatch => { return { getPostsList: () => { // 先確認有沒有要開始 GET posts 了, // 有的話,isLoadingGetPosts 的值會變成 true。 dispatch(actions.getPosts()); // 這裡的 getPosts() 是從 WebAPI.js 檔案 import 進來的。 getPosts().then(res => { dispatch(actions.getPostsSuccess(res.data)) }) } } } ``` 原先的 `PostsContainer.js` 檔案中的 `mapDispatchToProps` 函式,裡面有個 `PostsContainer.js` 專屬的 `getPostsList` 這個 props,這個 props 藉由 API 串接可以拿到 post 的所有資料,但是這樣做有個缺點,如果今天其它 component 也要用上面這串程式碼去拿到全部資料,那麼勢必得再用同樣的程式碼,這樣重複性質增加會造成維護性不佳而且效率不高。 為了解決這樣的問題,我們可以把 `getPostsList` 中的程式碼移動到 `actions.js` 裡面去,新增一個 action: ```javascript= import * as actionTypes from './actionTypes.js'; import { getPosts as getPostsAPI } from './WebAPI.js'; export const handleNavPage = (page) => ({ type: actionTypes.HANDLE_NAV_PAGE, page, }); // 拿到全部文章 export const getPosts = () => ({ type: actionTypes.GET_POSTS, }); export const getPostsSuccess = (data) => ({ type: actionTypes.GET_POSTS_SUCCESS, data, }); // 增加此處 export const getPostsList = (dispatch) => { dispatch(getPosts()); getPostsAPI().then((res) => { dispatch(getPostsSuccess(res.data)); }); } ``` 然後 `PostsContainer.js` 檔案那邊就可以改成這樣: ```javascript= const mapDispatchToProps = (dispatch) => ({ getPostsList: () => { actions.getPostsList(dispatch) }, }); ``` 變成單純從 `actions.js` 檔案去拿 `getPostsList` 這個方法。因此,之後只要拿取全部文章的 component 都只需要這麼寫即可,而這樣寫如果要改也很好改,只需要到 action 那邊去改即可。 不過,這樣做的話,小專案還行,但大專案 action 項目一多的話, `actions.js` 這個檔案就會變得很亂,有 function,又有 action 的 object,因此,middeleware 的 library 就是為了解決這樣的問題而誕生。 ## Redux-thunk 以往 `actions.js` 中的 action 只能單純回傳物件資料,若使用 Redux-thunk 的話就可以在 action 當中回傳一個 function。 Redux-thunk 做的事情和我們上面剛才所更改的程式碼是差不多意思的。通常在非同步會使用這個套件。action 裡面放入一個要 return 的 function 是真正要做的事(假如是非同步動作就是非同步),而 redux-thunk 的用處就在可以對 return 的 function 執行後續動作。 ### 建置 安裝 redux-thunk:`npm install redux-thunk` 引入 `applyMiddleware ` 和 `thunk `: ```javascript= import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; ``` store 的第二個參數(也就是 store 初始化時的值)新增 `applyMiddleware(thunk)` ```javascript= const store = createStore(reducers, applyMiddleware(thunk)); ``` ### dispatch 改成收到 function 的資料 `actions.js` 檔案的 `getPostsList` 函式改成這樣: ```javascript= export const getPostsList = () => { return function (dispatch) { dispatch(getPosts()); getPostsAPI().then((res) => { dispatch(getPostsSuccess(res.data)); }); } } ``` `PostsContainer.js` 的 `mapDispatchToProps` 函式則改成這樣: ```javascript= const mapDispatchToProps = (dispatch) => { return { getPostsList: () => { dispatch(actions.getPostsList()) } } }; ``` 而這整個運作流程是這樣子的: 1. 在`Posts` 這個 component 裡面,呼叫 `this.props.getPostsList`。 2. `mapDispatchToProps` 函式當中的 `getPostsList` 方法就開始運作,首先看到 `dispatch` 包著的 `actions.getPostsList()` 是去呼叫 `actions.js` 檔案的 `getPostsList` 函式。 3. 再來我們去看 `actions.js` 檔案的 `getPostsList` 函式,這個函式被呼叫了,所以回傳裡面一整個 function 的內容。 4. 所以在 `PostsContainer.js` 的 `mapDispatchToProps` 函式當中的 `getPostsList` 方法,`dispatch` 就會接到一個 `actions.getPostsList()` 回傳回來的 function。 5. 因為我們在 store 建立的時候有傳入 redux-thunk 的函式,所以 redux-thunk 就會幫我們運作傳到 `dispatch` 中為 function 的資料(不然以往 disptach 只能處理物件的資料)。 6. 運作完 function 的資料後,後面步驟就和以往使用 Redux 一樣,資料就會被傳到 reducer 了,reducer 那邊就會再做分類的處理。 所以整個流程中,redux-thunk 扮演著一個角色是可以幫我們在 `dispatch` 當中運作 function。 上面這樣做的好處是 `actions.js` 那邊和 `dispatch` 有關的函式,就都不用再傳入 `dispatch` 的參數,因為 redux-thunk 會幫你傳 `dispatch` 進去到 function,而且,不用傳 `dispatch` 參數就會讓 `actions.js` 檔案變得比較簡潔。 ## Redux-promise-middleware ### 非同步操作 我們如果要使用非同步操作的 API 串接,可以使用一個方便的 library 叫做 redux-promise-middleware。 ### 建置 安裝:`npm i redux-promise-middleware -s` 引入 `applyMiddleware` 和 `promise`: ```javascript= import { createStore, applyMiddleware } from 'redux'; import promise from 'redux-promise-middleware' ``` store 的第二個參數改成 `promise` ```javascript= const store = createStore(reducers, applyMiddleware(promise)); ``` ### dispatch 執行非同步 在上面的 redux-thunk 例子當中,`actions.js` 檔案的 `getPostsList` 函式是長這樣: ```javascript= export const getPostsList = () => { return function (dispatch) { dispatch(getPosts()); getPostsAPI().then((res) => { dispatch(getPostsSuccess(res.data)); }); } } ``` 我們現在使用 redux-promise,把它改成這樣: ```javascript= export const getPostsList = () => { return { type: 'GET_POST_LIST', payload: getPostsAPI() } } ``` 其中,`type` 的用法就和其它 action 的 `type` 一樣是用來當作 reducer 那邊去分辨你是什麼 type 傳進來的,以便 reducer 做相對應的輸出,而 `payload` 則是 redux-promise 固定的參數,這個參數很重要,用來放是 promise 的物件,這邊的 `getPostsAPI()` 因為是個使用 `axios` 套件的一個函式,所以它是個 promise 物件。 接著我們先在 `reducer.js`(這個檔案在開頭那個連結範例裡中有,是負責 redux 操作中的 reducer 這個流程的檔案) 那邊用 `console.log(action)` 看一下有什麼 action 傳進來: ```javascript= function reducer(globalState = state, action) { console.log(action) //這裡 switch (action.type) { case actionTypes.HANDLE_NAV_PAGE: return { ...globalState, navPage: action.page, }; case actionTypes.GET_POSTS: return { ...globalState, isLoadingGetPosts: true, }; case actionTypes.GET_POSTS_SUCCESS: return { ...globalState, isLoadingGetPosts: false, posts: action.data, }; case actionTypes.GET_SINGLE_POST: return { ...globalState, isLoadingGetSinglePost: true, }; case actionTypes.GET_SINGLE_POST_SUCCESS: return { ...globalState, isLoadingGetSinglePost: false, post: action.data, }; default: return globalState; } } ``` `console.log` 的結果: ![](https://i.imgur.com/zccT68d.png) 上方看到有個 `GET_POST_LIST_PENDING` 和 `GET_POST_LIST_FULFILLED` 的 type,這兩個 type 就是從 `actions.js` 檔案的 `getPostsList` 函式那邊得來的,其中 `GET_POST_LIST_PENDING` 可以把它看作正在等待非同步資料請求回傳的 type,也就是上方 `reducer.js` 當中的 `case actionTypes.GET_POSTS:` 這個 case 在做的事情,而 `GET_POST_LIST_FULFILLED` 則是資料已經確定回傳回來了的 type,就像 `case actionTypes.GET_POSTS_SUCCESS:` 這個一樣。 所以接著我們把原先在 `reducer.js` 當中拿到全部文章的 case 改成上面那兩個 type: ```javascript= function reducer(globalState = state, action) { console.log(action) switch (action.type) { case actionTypes.HANDLE_NAV_PAGE: return { ...globalState, navPage: action.page, }; case 'GET_POST_LIST_PENDING': //這裡 return { ...globalState, isLoadingGetPosts: true, }; case 'GET_POST_LIST_FULFILLED': // 和這裡 return { ...globalState, isLoadingGetPosts: false, posts: action.payload.data, // payload 有個 data 的參數是拿到回傳資料 }; case actionTypes.GET_SINGLE_POST: return { ...globalState, isLoadingGetSinglePost: true, }; case actionTypes.GET_SINGLE_POST_SUCCESS: return { ...globalState, isLoadingGetSinglePost: false, post: action.data, }; default: return globalState; } } ``` 這樣子的話,原先在 `actions.js` 中建立的 `getPosts` 和 `getPostsSuccess` type 就不需要了,因為被 `'GET_POST_LIST_PENDING'` 和 `'GET_POST_LIST_FULFILLED'` 給替代。 另外,`'GET_POST_LIST_PENDING'` 和 `'GET_POST_LIST_FULFILLED'` 的名字由來是從 `actions.js` 的 `getPostsList` 函式裡面的 `type` 值而定,也就是說在 type 的字串當中除了 `PENDING` 和 `FULFILLED` 是 redux-promise 固定的名稱,字串其它的字元是 `type` 的值。 那麼 redux-promise 是怎麼運作的呢?我們再回頭過來看 `actions.js` 檔案的 `getPostsList` 函式: ```javascript= export const getPostsList = () => { return { type: 'GET_POST_LIST', payload: getPostsAPI() } } ``` 大概就是這樣: ```javascript= type = 'GET_POSTS' => dispatch({ type: type + '_PENDING'}) => action.payload.then((data) => dispatch({ type: type + '_FULFILLED', payload: data })).catch((err) => { dispatch({ type: type + '_REJECTED', payload: err }) }) ``` 當偵測到 payload 的值是一個 promise 的時候就會做上面的動作,先 `dispatch` 一個 type 為 `'GET_POSTS' + '_PENDING'`,接著就執行 promise 的動作(`action.payload`),然後 `.then()` 裡面包的就是要接傳回來的資料,會再 `dispatch` 一個 type 為 `'GET_POSTS' + '_FULFILLED'`,然後 `payload` 的值就是放傳回來的 `data`。 另外 redux-promise 還有一個 type 是 `_REJECTED`,是接回傳資料失敗訊息的 type。 ### 總結: redux-promise 是一個用來處理 redux 非同步操作的 library,可以讓你的程式碼更加簡潔易懂。 而 redux-thunk 和 redux-promise 可以擇一使用,各有千秋,不過 redux-thunk 比較多人使用,因為自由度較高,action 不僅限於非同步或 promise 的資料。 ## Redux-saga 很難的東西,待研究。 GitHub:[redux-saga](https://github.com/redux-saga/redux-saga) ## Redux-observable 可以處理更複雜的大專案的非同步。以 [RxJS](https://github.com/ReactiveX/RxJS) 為基底技術的一套 middleware。 GitHub:[redux-observable](https://github.com/redux-observable/redux-observable) ## Redux-logger 可以看 action 執行前後狀態的 library,也就是可以看到改變前的 state、實際執行的 action、改變後的 state。 簡單說這個 library 有助於 debug 使用。 注意在和其它 middleware 同時使用時,redux-logger 必須放在 middleware chain 的最後一個,這樣才可以偵測到所有的 middleware 跑完之後的情形。 middleware chain 是下面這種情: ```javascript= const store = createStore(reducers, applyMiddleware( promise, logger )); ``` 也就是在 `applyMiddleware` 裡面串了(使用)兩個以上的 middleware。 GitHub:[redux-logger](https://github.com/LogRocket/redux-logger)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully