iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
Modern Web

職缺資訊平台—Jobscanner系列 第 20

[開發] React 從 0 到 0.1 (4)

  • 分享至 

  • xImage
  •  

建立事件處理器函式,把函式作為 prop 傳給 JSX tag

  1. 在 Button 元件中宣告一個函式 handleClick
  2. 在函式中撰寫要做的事
  3. onClick={handleClick} 加在 <button> tag 上
export default function Button() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

關於事件處理函式 (Event handler function):

  • 一定要在元件內部定義
  • 可以用 handle 作為名稱開頭,後面接事件名稱

也可以用 inline 方式寫:

<button onClick={function handleClick() {
  alert('You clicked me!');
}}>

<button onClick={() => {
  alert('You clicked me!');
}}>

事件處理函式是在元件內部宣告,所以也可以取得傳入該元件的 props,例如:

在處理 click 事件的函式中可以使用 message

// AlertButton 元件接收 message 和 children 兩個 props
function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="Playing!">
        Play Movie
      </AlertButton>
      <AlertButton message="Uploading!">
        Upload Image
      </AlertButton>
    </div>
  );
}

預設的 <button> <div> 只支援瀏覽器事件名稱,像是:onClick。在 React 中,事件處理函式也可以作為 props 傳入至元件中,所以函式名稱不受限。自己建立的元件,事件名稱可以任意命名,建議是以 on 開頭。

父元件會捕捉到所有子元件的事件,稱為 event bubble 或是 propagate,當事件在某個節點發生,會像冒泡般的往上傳遞。可以透過 event 物件停止傳遞,使用 e.stopPropagation()的函式。


元件的記憶體:State

當 React 重新執行(render)元件時,不會考慮到區域變數的變化,更改區域變數不會觸發 render, React 不會知道需要使用新資料來更新元件。如果要利用新資料來更新元件,可以透過 useState Hook 達成:

  • 即使重新 render,state 變數每次都會保留
  • state 的 setter 函式用來更新變數,並且觸發 React 再一次 render 元件
import { useState } from 'react';

const [index, setIndex] = useState(0);

index 為 state 變數,setIndex 為 setter 函式

setter 函式的使用:

function handleClick() {
  setIndex(index + 1);
}

當呼叫 useState 時,正是在告訴 React 想要這個元件記住某些東西

// 讓 React 記住 index
const [index, setIndex] = useState(0);

useState 只需傳入一個參數,傳入 state 變數的初始值 (useState(0) 代表將 index 變數的初始值設為 0)

每一次 render 元件,useState 都會回傳一個陣列,其陣列包含兩個值:

  1. state 變數 (index)
  2. state setter 函式 (setIndex),

Hooks ?

Hooks,use 開頭的函式,只能在元件的 top level 處呼叫,不可以寫在 conditions、loops 或是其他 function 中,就像 import 模組一樣,把 Hooks 寫在元件一開始的地方。

在官方文件中有一篇推薦閱讀 React hooks: not magic, just arrays,利用圖像解釋得很清楚。

其中說到 useState 執行過程:

  1. 初始化階段
    建立兩個空陣列,分別為 settersstate,以及設定 cursor 為 0。(cursor 代表位置的概念)

  2. 元件第一次 render
    每一次呼叫 useState() 執行時,會分別 push 一個 setter 函式到 setters 陣列中以及 push state 到 state 陣列中。useState 就是一個大陣列,cursor 等同於 index,每遇到一個 useState,cursor + 1。

  3. 接下來的 render
    cusor 會從 0 開始遞增,依序讀取,setter 都會有一個參考的 cursor,所以當呼叫 setter 時,會去找到對應位置的 state。


不正確的範例:將 useState 寫在條件句中

let firstRender = true;

function RenderFunctionComponent() {
  let initName;
  
  if(firstRender){
    // 不正確!
    [initName] = useState("Rudi");
    firstRender = false;
  }
  const [firstName, setFirstName] = useState(initName);
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}

元件第一次 render 時,位置 0 存的是 [initName] = useState("Rudi") 這組
但接下來的 render,cursor 0、1 的 state 都變成 Rudi,cursor 2 沒有東西,React 無法 match 對應的資料...

每一次執行都會依序操作 state,將 Hooks 寫在 top level 才能確保每次 render 一定會執行到,且執行順序相同。

所以,React 怎麼知道要回傳哪一組 state?當呼叫 useState 時,並沒有傳入 state 變數的參照給 useState,React 要怎麼做識別?

因為 Hooks 每次都會被一樣的順序被呼叫,React 會替元件每一組 state 用陣列儲存


拆解 State 更動過程:

import { useState } from 'react'; 
export default function Counter() {
    const [counter, setCounter] = useState(0);
    
    const handleClick = () => {
      setCounter(counter + 1);
    }
    return (
      <div>
        <span>Now is: {counter}</span>
        <button onClick={handleClick}>+</button>
      </div>
    )
}

元件第一次 render,傳了 0 給 useState 當作 counter 的初始值,useState 會回傳 [0, setCounter],而 React 會記得 0 是 state 最新的數值。

當使用者點擊按鈕 +,呼叫 setCounter(counter + 1)),counter 當下為 0,所以執行 setCounter(1),告訴 React 記住 counter 目前為 1,並且觸發另一次 render

元件重新 render,React 依序執行元件,依然會遇到 useState(0) 這行,但 React 記得你已經將 counter 設成 1,所以回傳 [1, setCounter]

State 是存在元件實體中的,假設同一個元件 render 兩次,每一個元件都有自己獨立的 state,彼此不互相影響!不像 props,state 完全屬於宣告自己的那個元件,也沒辦法從外部父元件去更改。


想像元件就像是在廚房的餐點,React 是個服務生,React 需要將客人點餐的項目告知廚房,並且做好的餐點端到客人面前,也就是 UI 畫面的呈現過程,有三個階段:

  1. Triggering:觸發執行 (像是廚房收到客人的點餐)
  2. Rendering:執行元件 (像是廚房準備餐點)
  3. Committing:更新 DOM (像是將餐點送到客人面前)
  • 第一個階段 Triggering

    • 觸發執行有兩種,元件的第一次執行(initial render)或是元件 state 更新後的執行 (re-render)
    • 呼叫 createRoot 傳入 DOM 節點,再使用 render() 觸發 initial render,一但元件已經 render 過,可以再透過 set 更新 state,再次觸發 render
  • 第二個階段 Rendering

    • 當觸發 render 後,React 會呼叫元件,找出哪些是必須顯示在畫面上的
    • Rendering 指的是 React 呼叫執行元件的過程
    • 元件有可能會 return 另一個元件,不斷地執行元件,是一個遞迴
  • 第三個階段 Committing

    • 結束 rendering,React 會更新 DOM
    • initial render,React 會使用 appendChild() 這個 API 更新整個 DOM 節點的內容在畫面上
    • re-renders,React 會盡可能只處理必要的 DOM 更新 (透過在 rendering 階段的計算)

在結束 rendering 以及 React 更新 DOM 之後, 瀏覽器會再進行 repaint (browser rendering)

* React rendering 和 Browser rendering 不同


參考資料

Rules of Hooks


上一篇
[開發] React 從 0 到 0.1 (3)
下一篇
[開發] React 從 0 到 0.1 (5)
系列文
職缺資訊平台—Jobscanner31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言