建立事件處理器函式,把函式作為 prop 傳給 JSX tag
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 達成:
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 都會回傳一個陣列,其陣列包含兩個值:
Hooks,use 開頭的函式,只能在元件的 top level
處呼叫,不可以寫在 conditions、loops 或是其他 function 中,就像 import 模組一樣,把 Hooks 寫在元件一開始的地方。
在官方文件中有一篇推薦閱讀 React hooks: not magic, just arrays,利用圖像解釋得很清楚。
其中說到 useState
執行過程:
初始化階段
建立兩個空陣列,分別為 setters
和 state
,以及設定 cursor 為 0。(cursor 代表位置的概念)
元件第一次 render
每一次呼叫 useState()
執行時,會分別 push 一個 setter 函式到 setters 陣列中以及 push state 到 state 陣列中。useState 就是一個大陣列,cursor 等同於 index,每遇到一個 useState,cursor + 1。
接下來的 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 畫面的呈現過程,有三個階段:
第一個階段 Triggering
createRoot
傳入 DOM 節點,再使用 render()
觸發 initial render,一但元件已經 render 過,可以再透過 set 更新 state,再次觸發 render第二個階段 Rendering
第三個階段 Committing
appendChild()
這個 API 更新整個 DOM 節點的內容在畫面上在結束 rendering 以及 React 更新 DOM 之後, 瀏覽器會再進行 repaint (browser rendering)
* React rendering 和 Browser rendering 不同