劉杰
    • 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 No publishing access yet

      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.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      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 New
    • Engagement control
    • Make a copy
    • 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 Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy 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 No publishing access yet

    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.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    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
    1
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # React - Behide the Scenes ###### tags: `Javascript, React` # 這個 module 在說什麼? * How does React work behide the scenes? * Understanding the virtual DOM& real DOM updates * Understanding State & State updates ![](https://i.imgur.com/NVNHO6R.png) React 只會在乎: 1. Props 2. Context 3. State 4. 當然還有更多 API 不過上述是核心 所以當 React 想要修改真實 DOM 上面的資料時必須透過 ReactDom 作為中介傳遞 virtualDOM 的更動來更新至 DOM 上 # virtual DOM 這時候最大的問題是 components 這邊如何跟 real Dom 作用 這邊會在 React 處理 component tree(也就是 App.js 底下的樹狀結構) 目前應該要長什麼樣子以及他將會變成什麼樣子(這邊就是 virtual DOM) 然後 ReactDom 會接收到不一樣的地方並且操作 real DOM 把修改更新上去頁面 ![](https://i.imgur.com/anuxCC7.png) # re-excuted component func and re-evaluate page is not the samething 這邊要非常注意 ![](https://i.imgur.com/TE06i36.png) 1. 當 components 的 props, states, contexts 改變時 re-evaluate 會發生 2. 這時 React 會 re-excute component 3. 然後 ReacrDom 會在此時修改 realDOM 去 re-render 那些不同的地方,而非全部更新因此可以節省很多的效能 ![](https://i.imgur.com/4ON0ZDX.png) 因為 realDOM 的修改是非常耗能的行為,所以透過這樣對比 virtualDOM 以及 realDOM 不同的地方,不 re-render 整個頁面是 React 運作的方式 ## 範例 假設目前的 virtual DOM 要做出改變,要加個 p tag 進去,這時候這個 p tag 就是 virtual DOM 跟 real DOM 的改變,所以 p tag 會被插進去 real DOM 也就只修改這個地方 ![](https://i.imgur.com/Bv9o4aQ.png) ### 程式碼解釋 ![](https://i.imgur.com/nGOniCA.png) 會得到一個畫面可以 toggle p tag 的內容,當點擊按鈕時會讓 p tag 出現或是消失藉由 useState 改變狀態來幫助其內容的呈現 ![](https://i.imgur.com/TiPidrb.png) ![](https://i.imgur.com/B6CB8wc.png) 這邊要來證明 state, context, props 的改變會讓整個 component re-excuted re-evaluated 只需要在 component 中印出文字即可知道當上述內容改變時,是否會讓整個函式重新跑一次 ![](https://i.imgur.com/416ryGf.png) 在一開始畫面 load 進去的時候會立馬印出一次,因為那是 React 第一次跑這個 component 並把內容帶入瀏覽器 當按下按鈕,畫面更新出 p tag 時,就印出第二遍了,這也證明了所有的 state 改變都會引發 component 的重新執行,因為印出了第二遍 ![](https://i.imgur.com/UqRdxs5.png) 可以從開發者工具觀察到,如果有畫面的更新元素會閃一下,這邊只有 p tag 新增進來的時候有閃以及 p tag 被移除了時候父層的 div 有閃,表示跟 virtual DOM 做完對比 real DOM 只有這些地方有不同,所以只有這些地方有做更動 ![](https://i.imgur.com/uEuVIuG.png) ### App.js 重新執行內部的 components 也會跟著一起重新執行 這邊把 p tag 拉出去變成 DemoOutput component 這邊用來解釋儘管傳入 DemoOutput 的值都是 false 畫面因此不會更新,但是因為 DemoOutput 還是 App.js 的 child component,所以當 state 改變(點擊按鈕時)在 App.js 內部時,造成 App.js re-excuted/ re-evaluated 時,其內部的 components 也會跟著重新被跑一次 ![](https://i.imgur.com/esWvhAf.png) 儘管畫面沒有更新 DemoOutput 裡面依舊有印出那個值來,就是最好的證明 ![](https://i.imgur.com/UPzKHwc.png) Button component 也一樣是 App.js 的 child component 所以也會印出其值 ![](https://i.imgur.com/vwGfS11.png) 由此可證畫面的 re-rendering 跟 component 的 re-excuted/ re-evaluated 不一定有關 ![](https://i.imgur.com/pOjgEFc.png) ### 這帶出一個大問題就是 function component state 造成的 re-excuted/ re-evaluated 就讓其 child component tree 底下所有的 components 重新跑,會造成效能浪費(因為有些是非必要的) # 避免不必要的 re-evaluations 使用 React.memo() 這邊介紹使用在 function components 中的用法使用 React.memo() 來達成( class component 會在後面介紹 ![](https://i.imgur.com/Q9ENo3V.png) 這邊的 memo 運作的方式會是他會他會監聽其參數的 component 的 props 是否有改變 所以如果 parent component state 有所改變,但是傳進來參數 component 的 props 沒有改變的話, component 的 re-excuted/ re-evaluate 就會被忽略 下圖可以看到 DemoOutput 的 log 內容被忽略了,並且可以知道的是其底下的 child components 也同樣不會被 re-excuted/ re-evaluated ![](https://i.imgur.com/xTnsAQ6.png) ## 使用 memo 的代價 要做到監聽其參數的 component 的 props 是否有改變,也是需要耗能的,React 必須儲存先前的 props 然後對比當下的 props 看是否有改變,因此必須判斷你要使用 memo 的 comoponent 是否值得花這樣的效能去做處理 不過 memo 依舊是一個很棒的工具,當 component tree 非常龐大並且 props 非常多時,可以使用 memo 避免一些不必要的 re-render ,精準的使用在一些關鍵的 components 上而不是所有 components 都使用,會讓程式更有效率 ## memo 參數的 props 傳值或是傳址的區別 假設我們在 DemoOutput, Button 都使用 memo 因為其實都是寫死的內容都適合使用 ![](https://i.imgur.com/MuxCywE.png) 但是問題來了,經過操作之後發現 DemoOutput 會順利的被阻斷,但是 Button 卻沒有,原因就是傳值跟傳址的區別 DemoOutput 傳的是純值,因此儘管每一次 parent re-excuted 後得出來的 props 會得到新的 false 但是因為他是純值所以 `false === false` 是成立的 但是 Button 傳入的 props 是函式,函式就是物件,物件是傳址的 `toggleParagraphHandler === toggleParagraphHandler` 是不成立的,因為他們在記憶體中的位置不同 memo 就這樣被物件擊敗了嗎?並沒有下個篇章中會有解答 # 避免不必要的函式 re-creation 使用 useCallback() 這邊就要解釋如何使用物件當作 React.memo() 的 props ,透過使用 useCallback() 來操作產生的物件 `useCallback()` 的作用在於,它會儲存一個 function 在 components 執行之間,告訴 React 被儲存的這個 function 不會被重新創造在每一次 component 執行之間,透過這樣的方式 function 物件,就不會被重新創造因而可以操作在 `React.memo()` 之中摟 ## `useCallback()` 做的事情跟下面例子很像: 它會儲存我們所選擇的函式在 React 內部的儲存空間中,因此我們就可以重複使用我們存的那個函式物件當其所在 component function 執行時 ```javascript= let obj1 = {}; let obj2 = {}; obj1 === obj2; // false obj1 = obj2; obj1 === obj2 // true ``` ## 使用方法如下 * 我們要操作在 toggleParagraphHandler 這個函式中 * 直接使用一個箭頭函式包裹住其內容 * 放入 array 當作 dependency 來操作用法跟 useEffect 一樣作為監聽使用放入的 state ,這邊可以放 setShowParagraph 不過 React 已經保證過 useState 返回的操作 state 函式內容不會被變更所以可以保持為空,如果有放入內容則會監聽其內容,如果改變了則會更新記憶的函式到最新 ![](https://i.imgur.com/QKnPTRn.png) 這個時候我們再去操作 memo 內放入 toggleParagraphHandler 的 Button 發現 memo 的功能生效了,也就代表函式已經被 React 儲存好並且不會隨著 component function 作出更新函式的動作了 # 介紹更多 useCallback() 以及他的 dependencies 範例處會新增一個 allow toggle 按鈕來開關 toggle paragraph 按鈕 這邊要介紹到 dependencies 的部分就要先了解 JS 的 closure 特性,也就是為了取用到外部的變數,函式會儲存要使用的外部變數鎖在函式內部做使用,但是搭配 useCallback() 的特性就會造成取用到的變數內容是過期的,因此使用了 dependencies 就可以監聽放入的內容( `[]`內的內容),當 `[]` 內容改變時,React 會更新被記憶的 function 如此一來就可以取得最新的 closure 儲存的變數摟! 這邊我們新增一個按鈕來讓開關 toggleParagraphHandler 並且多使用一個 state allowToggle 來做到這件事情 ![](https://i.imgur.com/2hS8Cz2.png) ![](https://i.imgur.com/sTkyMyz.png) 必須 allow 按下去之後才會可能 toggle paragraph ![](https://i.imgur.com/Pt9hz7z.png) 目前因為 dependencies 欄位是空的,所以 useCallback() 是在任何情況下都不會更新被記憶的函式,並且 closure 內容都是沒有更新的 allowToggle 的 state 也就是初始值 false ,所以目前怎麼點擊都是不會有效果的 ![](https://i.imgur.com/ubXBcBV.png) 這邊在 dependencies 內放入 allowToggle 後代表, useCalback() 會監聽它,當 allowToggle 更新時,就會重新記憶內部的 function ,這時候 function closure 內部的 allowToggle 也就可以被更新了!按鈕就可以正常使用了 # In Summary * 在 React 中你會運作的幾乎都是 Functional components ,他們的工作最終都是要輸出 JSX 到畫面中 * 在 React component 中,可能會使用到 state, props, context ,不過其他最終他們都是 state 的改變,用來所在的改變 component 或是相關的 data 甚至是整個 app * 每當你在 component 中改變了 state ,此時就會引發 re-evaluated 意思是當下的 component 會 re-executed * React 會使用下最新的 evaluation 結果去對比之前的,並把不一樣的地方給 React DOM 使用,這時候 React DOM 就會應用到 real DOM 上面更新瀏覽器畫面,並且只會新增新的部分,不會動到其他地方 * 當 re-evaluated, re-executed 發生時, component 中所有底下使用的 component 中的 function 也會同時重新被執行一遍,所以為了避免一些不必要的 child component re-execution 就可以操作 memo 來達成,memo 會監聽其 props 如果沒有改變則不會重新執行裡面的 functions * 這時候就會注意到一些 memo 的小漏洞,像是 re-execution 時,如果函式內操作了函式這時候因為物件是傳址的緣故,儘管 function 是一樣的但是在 stack 中的指向是不一樣的地方,因此會被判定為 props 有改變,所以 memo 會達不到所要的效果 * 這時候就是 `useCallback()` 的使用時機,讓 React 記憶函式後不會在每次 component re-execution 時重新創造函式內容,並且使用 dependencies 來確保當 props 修改時,才會更新函式,既可以操作 memo 又可以保證其 props 不會過期 # Components & States > 只要跟 re-rendering components 有關,所有的東西都會回歸到 state 在上一篇的最後有提出一個疑問,如果 component re-execution 發生了,那 useState 也會重新被執行,照理說其中的 state 也會更新,但是卻沒有 原因是 React 會確保 state 被記憶起來,當 component 第一次 render 到畫面上,並會使用 useState 的 default 值來呈現,接續下來的 re-execution 都不會再有新的 state 被更新出來,React 會認出屬於這個 component 的 state 並且只會在有需要的時候才會更新這個 state,只有一個例外就是當 component 被移除又在被重新 render 時才會初始化 state ,這個觀念也適用於 useReducer ## State updates & Scheduling ![](https://i.imgur.com/IKflzsz.png) 1. 假設有個 setProduct 按鈕可以切換商品內容,初始值為 DVD 2. 使用者點擊了按鈕並且輸入 Book 來修改 state 3. React 對這個 update state 行為做預約排序,如果有更急的可以先處理,比方說 input 的值輸入 4. 這時候如果使用者又點了一次想改成 carpet 則 state 修改順序一定會按照 DVD => Book => Carpet 這個順序走 5. React 會 re-evaluated component state 後 re-executed component ![](https://i.imgur.com/fS6g1te.png) 正因為 複數個 state update 可以同時被排序,因此有可能 state 在輪到他執行之前,就又被改變了有可能會有 state 過期發生,因此需要使用函式的方式來使用 preState 抓取最新的 state 避免過期(useEffect 有 dependencies 也可以避免 state 過期) ![](https://i.imgur.com/mbmw8Ai.png) ## state batch 批次處理 state ,剛剛提到 React 會針對 state 會做預約排序不會馬上處理他們,那如果同一個 function 中處理兩種 state 他們會分開成兩條路線做預約排程嗎? ![](https://i.imgur.com/HYu1yQ0.png) 答案是不會,他們會被同捆包成同一包 state update ,只要他們是寫在同一個 function 中並且沒有非同步的寫法,他們就會同步進行 # Optimizing with useMemo() 這邊需要操作另一個扣的範例來說明: ## App.js ![](https://i.imgur.com/AuYy7t6.png) 輸出的範例內容如下 * 點擊按鈕後 title 內容會跟新 * 可以發現 從 App 傳下的 props 是沒有經過排序的 items 內容 ![](https://i.imgur.com/niae9s7.png) ## DemoList.js 在此檔案中使用 sort 來回傳排序後的 items array ,這邊要解釋的是假設要 sort 的資料量非常的大則會非常耗能,所以你不會想要每次 re-evaluated component 的時候,他都跑一次 ![](https://i.imgur.com/kwJ9G88.png) 這邊我們從之前章節學到的,可以使用 memo 來處理,如果 items 沒有改變的話則不會更新 DemoList.js 的內容 ![](https://i.imgur.com/0kgwlwF.png) 但是 DemoList 取得的 props 是 array 代表他是傳址的,所以當我們點擊 Change List title 按鈕時,component re-execution 這時候傳進來的 props 也指向了不同的位置因此代表 props 也更新了,因此 memo 這時候就沒辦法執行它在這邊應該執行的功能 就算不是點擊了按鈕,也可能因為 app.js 本身的任何改動都會導致其 child component DemoList 的 props 因而有所變動而導致 sort 再次觸發 有鑒於不想要每次都跑到那些很好效能的 component ,在這邊的範例中 sorting 就是耗能的任務 ## 解法使用 useMemo() 來救場啦 使用 useMemo 包裹著 sort ,讓他只有在 dependencies 有修改的時好也就是 items 有更動時才會跑 sort ![](https://i.imgur.com/T7dyzFm.png) 然後我們興高采烈地按下 Change List Title 按鈕以為會成功時,發現失敗了!為什麼他又跑了一次 sort 呢? 很簡單因為 items 確實在按下按鈕的時候被更新的,因為他是 array ,所以也要把它包起來就萬事OK!! 這邊 dependencies 就為空就好因為他的 items 是寫死的 ![](https://i.imgur.com/HcC7iIq.png)

    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
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    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