Heidi-Liu
    • 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 New
    • 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 Note Insights Versions and GitHub Sync 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
    2
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    ###### tags: `React` `state` `hooks` `useState` `useRef` # [week 21] React Hooks API:useState & 再戰 Todo List > 本篇為 [[FE302] React 基礎 - hooks 版本](https://lidemy.com/p/fe302-react-hooks) 這門課程的學習筆記。如有錯誤歡迎指正! ``` 學習目標: P1 我知道 React 的目的以及原理 P1 我知道我們為什麼需要 React P1 我知道使用 React 跟之前使用 jQuery 的區別 P1 我理解 state 跟 props 的不同 ``` --- ## React Hooks API 根據[官網介紹](https://zh-hant.reactjs.org/docs/hooks-intro.html?no-cache=1): > Hook 是 React 16.8 中增加的新功能。它讓你不必寫 class 就能使用 state 以及其他 React 的功能。 先前提到,React 的核心概念之一是 component,而根據寫法又可分為兩種: - class compent:透過 class 寫成,可以控制 state 和生命週期 - function compent:透過 function 寫成,主要用來呈現 UI ### hook 其實就是 function 根據[官方文件](https://zh-hant.reactjs.org/docs/hooks-overview.html): > hook 是 function,讓我們可以從 function component「hook into」React state 與生命週期功能。 也就是說,React 因為加入了 hooks,再結合 props、state、context、refs 以及 lifecycle 等概念,提供了更直接的 API 使用,讓我們能在 functional component 管理狀態和使用生命周期等功能,藉此簡化程式碼與提高重用性。 引入 hook 的語法如下,原本要寫成 React.useState,透過 ES6 解構語法即可直接使用 useState: ```javascript= import { useState, useEffect, useRef } from 'react'; ``` ### hooks 只能寫在 component 第一層 因為 React 背後的機制,hooks 只能寫在 component 第一層。 也就是說,不能把 hooks 寫在 if - else 等條件判斷句裡面,像是符合 XX 條件才使用,否則會出現錯誤訊息: > React Hook "useRef" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks 只能自行在 hooks 裡面進行判斷是否使用,例如下方寫法: ```javascript= useEffect(() => { // 若不符合就直接 return if (!todos) return; // 若符合要做的事情 }) ``` ### useState:用來設定 react 中的 state 語法如下: ```javascript= const [currentValue, setCurrentValue] = useState(initialValue); ``` - currentValue:存放 state 的值 - setCurrentValue:用來設定 state 值 - initialValue:state 的初始值 還記得我們前面舉的 Counter component 範例嗎?簡單來說,就是在 `useState([初始值])` 傳入初始值,然後回傳一個陣列: ```javascript= function App(){ const [todos, setTodos] = useState([1]) } ``` 其中 todos 具有 immutable 特性,也就是無法再賦值,必須用 setTodos 來產生新的 state,才能在每次 render 畫面時改變 todos 的值。 透過這個概念,我們也能實作出簡單的 Todo List: ```javascript= // 解構語法 import { useState } from 'react'; function App() { const [todos, setTodos] = useState([ 1 ]); const handleButtonClick = () => { // 傳入參數: 新的 todo + 解構 todos setTodos(["new todo", ...todos]); } return ( <div className="App"> /* 在 JSX 中,單標籤必須 /> 結尾 */ <input type="text" placeholder="Add todo..." /> <button onClick={handleButtonClick}>Add Todo</button> { todos.map((todo, index) => <TodoItem key={index} content={todo} /> ) } </div> ); } ``` ![](https://i.imgur.com/f6EsvhC.png) ### controlled vs uncontrolled > 詳細可參考[官方文件](https://zh-hant.reactjs.org/docs/forms.html#controlled-components)。 在 React 中,表單元素的處理可分為 uncontrolled 和 controlled,兩者之間的差別,在於 component 的資料是否受到 React 的控制: - uncontrolled component:資料不受 React 的控制 - 例如 input、textarea 等表單元素,通常會維持本身的 state,並根據使用者的輸入來更新該元素的 state - 若想取得 uncontrolled component 的值,可透過直接操作 DOM 或使用 useRef 來選取特定元素 - controlled component:資料受到 React 的控制 - 如果將資料的控制權交給 React 來處理,畫面就會根據 state 是否改變來重新渲染 參考文章: - [[Day 27 - 即時天氣] React 中的表單處理(Controlled vs Uncontrolled)以及 useRef 的使用](https://ithelp.ithome.com.tw/articles/10227866) --- ## 再戰 Todo List ### 新增 Todo 透過下方程式碼,可存取 input 的值,並藉由 setValue 來更新 todos 狀態: ```javascript= import TodoItem from './TodoItem' // 解構語法 import { useState, useRef } from 'react'; function App() { const [todos, setTodos] = useState([ 1 ]); const [value, setValue] = useState(''); const handleButtonClick = () => { // 傳入參數: 新的 todo + 解構 todos setTodos([value, ...todos]); // 新增完 todo 後清空 value setValue(''); } const handleInputChange = (e) => { // 拿到 input 的 value setValue(e.target.value); } return ( <div className="App"> <input type="text" placeholder="Add todo..." value={value} onChange={handleInputChange} /> <button onClick={handleButtonClick}>Add Todo</button> { todos.map((todo, index) => <TodoItem key={index} content={todo} /> ) } </div> ); } ``` ![](https://i.imgur.com/EAY1tzO.png) ### useRef:用來抓取 DOM 節點的 hook 基本用法: ```javascript= const refContainer = useRef(initialValue); ``` 根據[官方文件](https://zh-hant.reactjs.org/docs/hooks-reference.html#useref)介紹: > useRef 回傳一個可變的 ref object,其 .current 屬性被初始為傳入的參數(initialValue)。回傳的 object 在 component 的生命週期將保持不變。 簡單來說,useRef 是可持有 mutable(可變的)值、具有 .current 屬性的「盒子」,並具有以下特性: - 當 .current 屬性有變動時不會觸發重新 render - 在每次 render 時都會給同一個的 ref object 我們可透過宣告 `id = useRef(2)`,就能夠在每次 render 時,更改每個 todo 中 id.current 的值: ```javascript= function App() { const [todos, setTodos] = useState([ {id: 1, content: 'todo1'} ]); const [value, setValue] = useState(''); // useRef: 能我們抓取到 DOM 節點的 hooks // 會回傳一個物件,要以 id.current 讀取 const id = useRef(2); const handleButtonClick = () => { setTodos([ { id: id.current, content: value }, ...todos] ); setValue(''); id.current++; } const handleInputChange = (e) => { setValue(e.target.value); } return ( <div className="App"> <input type="text" placeholder="Add todo..." value={value} onChange={handleInputChange} /> <button onClick={handleButtonClick}>Add Todo</button> { todos.map(todo => <TodoItem key={todo.id} todo={todo} /> ) } </div> ); ``` 傳入 todo 這個參數,並放到 TodoItem: ```javascript= export default function TodoItem({ content, todo }) { return ( <TodoItemWrapper data-todo-id={todo.id}> <TodoContent>{todo.content}</TodoContent> <TodoButtonWrapper> <Button>未完成</Button> <RedButton>刪除</RedButton> </TodoButtonWrapper> </TodoItemWrapper> ); } ===上方寫法等同於=== function TodoItem() { // ... } export default TodoItem; ``` ### 刪除 todo #### 1. 把要做的 function 寫在 Parent,並傳入參數給 Children 把 handleDeleteTodo 這個 function 當作 props 傳給 TodoItem: ```javascript= const handleDeleteTodo = id => { } return ( <div className="App"> <input type="text" placeholder="Add todo..." value={value} onChange={handleInputChange} /> <button onClick={handleButtonClick}>Add Todo</button> { todos.map(todo => <TodoItem key={todo.id} todo={todo} handleDeleteTodo={handleDeleteTodo} /> ) } </div> ); ``` #### 2. 再由 Children 呼叫 function TodoItem 就可以接收這個 funtcion,並透過刪除按鈕的監聽事件,來呼叫 handleDeleteTodo 以及回傳該 `todo.id`: ```javascript= export default function TodoItem({ content, todo, handleDeleteTodo }) { return ( <TodoItemWrapper data-todo-id={todo.id}> <TodoContent>{todo.content}</TodoContent> <TodoButtonWrapper> <Button>未完成</Button> <RedButton onClick={() => { handleDeleteTodo(todo.id) }}>刪除</RedButton> </TodoButtonWrapper> </TodoItemWrapper> ); } ``` #### 3. 接著在 Parent 處理 function ```javascript= const handleDeleteTodo = id => { // 若用 splice() 會改到原本的 todo,因此要用 filter(),留下該 id 以外的 todo setTodos(todos.filter(todo => todo.id !== id)) } ``` ### 編輯 todo 在實作編輯 todo 之前,要先來檢視資料結構,也就是把 todo 的狀態加上 isDone,用來判斷是否已完成: ```javascript= function App() { const [todos, setTodos] = useState([ { id: 1, content: 'done', isDone: true }, { id: 2, content: 'not done', isDone: false } ]); const [value, setValue] = useState(''); const id = useRef(3); ``` 接著在 TodoItem 的按鈕加上三元運算子,用來判斷 todo 狀態,再藉由 $isDone 這個參數,判斷式是否執行後面的 JSX 語法: ```javascript= const TodoContent = styled.div` font-size: 26px; color: ${props => props.theme.colors.primary_300}; // 若 isDone 這個參數為 true(已完成) 則接續後面的 JSX 語法 ${props => props.$isDone && ` text-decoration: line-through; `} ` export default function TodoItem({ content, todo, handleDeleteTodo }) { return ( <TodoItemWrapper data-todo-id={todo.id}> // 傳入 isDone 這個參數 <TodoContent $isDone={todo.isDone}>{todo.content}</TodoContent> <TodoButtonWrapper> <Button> // 透過三元運算子判斷 todo 狀態 {todo.isDone ? '已完成' : '未完成'} </Button> <RedButton onClick={() => { handleDeleteTodo(todo.id) }}>刪除</RedButton> </TodoButtonWrapper> </TodoItemWrapper> ); } ``` 除了三元運算子,也可改寫成邏輯運算子 && 的寫法,適用於多種可能的情況: ```javascript= <Button onClick={handleToggleClick}> {todo.isDone && '已完成'} {!todo.isDone && '未完成'} </Button> ``` 接著實作 handleToggleIsDone 修改 todo 功能: ```javascript= const handleToggleIsDone = id => { setTodos(todos.map(todo => { // 如果不是要修改的 todo id 就直接回傳 if (todo.id !== id) return todo; // 要修改的 todo id return { // todo 原本的東西 ...todo, // 要修改的屬性 isDone: !todo.isDone } })); } ``` 傳入參數到 TodoItem: ```javascript= { todos.map(todo => <TodoItem key={todo.id} todo={todo} handleDeleteTodo={handleDeleteTodo} handleToggleIsDone={handleToggleIsDone}/> ) } ``` 由 TodoItem 接收參數,可以把 click 事件抽出來寫,相較於原本的 inline function,能夠提高程式碼的可讀性: ```javascript= export default function TodoItem({ todo, handleDeleteTodo, handleToggleIsDone }) { const handleToggleClick = () => { handleToggleIsDone(todo.id); } const handleDeleteClick = () => { handleDeleteTodo(todo.id); } return ( <TodoItemWrapper data-todo-id={todo.id}> <TodoContent $isDone={todo.isDone}>{todo.content}</TodoContent> <TodoButtonWrapper> <Button onClick={handleToggleClick}> {todo.isDone ? '已完成' : '未完成'} </Button> <RedButton onClick={handleDeleteClick}>刪除</RedButton> </TodoButtonWrapper> </TodoItemWrapper> ); } ``` ### Transient props:`$<props>` 在上方程式碼中,加在 TodoContent 的 $isDone 這個 props,會被視為 style component props,不會被繼續傳到下一個 DOM 元素,也就不會顯示在 TodoContent 標籤上。 如果沒有加上 $ 符號,這個 props 就會被直接加在 TodoContent 這個 DOM 結構上。 再以下方程式碼為例: ```javascript= <TodoContent id="abc" $isDone={todo.isDone}>{todo.content}</TodoContent> ``` 可以發現經過 render 之後,在 DOM 元素只會出現 `id="abc"` 這個屬性,而不會有 `$isDone`,這是因為 Transient props 不會被往下傳: ![](https://i.imgur.com/K4ROwak.png) ## Todo List 總結 透過實作簡單的 Todo List,其實我們就差不多學會了有關 React 的基礎: 1. Component 組件 開發 React 很重要的一點,就是去思考在頁面有哪些重複性高或相似的 Element,再透過 JSX 將這些 Element 建立成一個 Components,讓每個 Components 擁有重複性及可擴充性。 2. Props 參數 Props 主要提供值給 Component,用來設定屬性或資料,因此就算是同一個組件,也會根據提供的 Props 而有所不同。 3. Style 樣式 可透過幾種方式撰寫 React 中的 CSS,目前主流方法是透過 styled-components 這個套件來撰寫 CSS 語法。 4. Event handler 事件機制 和過去在網頁添加事件監聽不同,必須先以 `querySelector()` 選取 DOM 元素: ```javascript= function sayHello() { alert('Hello!'); } document.querySelector('.sayHello').addEventListener('click', sayHello); ``` 而 React 把 DOM 和 JavaScript 程式碼寫在一起,因此可直接在 DOM 元素加上 onClick、onSubmit、onKeyDown 等事件監聽: ```javascript= function TodoItem({ todo, handleDeleteTodo }) { const handleDeleteClick = () => { handleDeleteTodo(todo.id); } return ( <RedButton onClick={handleDeleteClick}>刪除</RedButton> ); } /* 也可簡化成箭頭函式 */ function TodoItem({ todo, handleDeleteTodo }) { return ( <RedButton onClick={() => { handleDeleteTodo(todo.id) }}>刪除</RedButton> ); } ``` 5. JSX 語法 透過 JSX 語法,即可將 HTML 語法轉成 JavaScript 的形式,讓我們用來建立 React elements。 使用 JSX 時需注意下列幾點: - class 是保留字,必須改寫成 className - 可在大括號內寫程式碼,例如:`{ JS code }`,也因此 inline-style 需改為駝峰式命名 - 沒有迴圈的概念,也沒有 if-else 判斷式 - 解決方法:透過三元運算子,或是邏輯運算子 && 進行判斷 此外,當我們要 render 一系列 list 的時候,會使用 `map()` 把資料變成一個陣列,然後 render 需要提供 key: ```javascript= { todos.map(todo => <TodoItem key={todo.id} todo={todo} /> ) } ``` 6. State 狀態 是 React 中最重要的觀念,可透過 useState 設定 state 初始值,再以 setState 去改變 state。state 會對應到一個 UI,一旦 state 有變動,就會自動呼叫 render()。 state 最基本的語法如下: ```javascript= const [currentValue, setCurrentValue] = useState(initialValue); ``` 在 React 當中,若要進行新增、編輯、刪除功能,雖然有許多方法能夠達成,但基本上有固定的作法: - 新增功能:解構語法 ```javascript= const handleButtonClick = () => { setTodos([ { // 要新增的 todo id: id.current, content: value // 解構語法 }, ...todos] ); setValue(''); id.current++; } ``` - 編輯功能:map() ```javascript= const handleToggleIsDone = id => { setTodos(todos.map(todo => { // 如果不是要修改的 todo id 就直接回傳 if (todo.id !== id) return todo; // 要修改的 todo id return { // todo 原本的東西 ...todo, // 要修改的屬性 isDone: !todo.isDone } })); } ``` - 刪除功能:filter() ```javascript= const handleDeleteTodo = id => { // 留下該 id 以外的 todo setTodos(todos.filter(todo => todo.id !== id)) } ``` ### --- ## 結論 這和以往的思考模式其實很不一樣,像是在切好的 UI 畫面上新增各種功能;而 React 則是先思考 state 狀態,再去想會如何改變畫面。 記住一個重點,就是 Component 之間可透過 props 把 state 傳遞下去。並且,只要 state 所有變動,就會觸發 render() 來更新 UI 畫面。 參考資料: - [常見的幾個 React hooks 教學-useState、useEffect、useRef](https://snh90100.medium.com/%E5%B8%B8%E8%A6%8B%E7%9A%84%E5%B9%BE%E5%80%8B-react-hooks-%E4%BB%8B%E7%B4%B9-usestate-useeffect-useref-40c9acd0cc4c) - [React 性能優化大挑戰:一次理解 Immutable data 跟 shouldComponentUpdate](https://blog.techbridge.cc/2018/01/05/react-render-optimization/)

    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