ItIron2023
react
昨天我們看了一個常見的race-condition問題並給出了三種主流的解決方案,今天我們放輕鬆一點,看一個相對簡單卻仍很實務的問題吧! 不用擔心,今天的題目很快就能結束的!
老樣子請你觀察一下這個codesandbox以及下方的gif
今天的程式碼相當簡單,我們在一個簡單的form裡面有兩個input欄位讓使用者輸入信箱與密碼,最後有個提交按鈕log出使用者剛剛輸入的資訊,實際功能並沒有什麼異常,你可以看到最後console確實是有順利log出正確的資料,但我想你也發現了整個component在這短短的密碼輸入過程卻讓組件渲染了數十次,請你觀察以下的程式碼並試著避免這些不必要的re-renders。
export default function App() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
console.log("The whole component re-render");
const handleSubmit = (e) => {
e.preventDefault();
console.log(`Email: ${email}, Password: ${password}`);
};
return (
<>
<h1>Sometimes useState just not good enough</h1>
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email: </label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<label htmlFor="password">Password: </label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
</>
);
解答之前,我們要先問自己為什麼你需要去解決這樣的問題,畢竟功能本身過得好好的,不改似乎也行.這麼講也沒錯,至少在這個範例中他沒有造成太多的問題,何況重新渲染並不是什麼十惡不赦的敵人,它是react很重要的一部分,並不是所有的重新渲染都是問題。不過想像一下萬一你今天的組件中同時有其他的組件、且這些組件更加的複雜,每一次的重新渲染就是額外的大消耗,在這類的情況下避免太多不必要的重新渲染就會是你身為工程師應做的事情。
首先複習一下什麼情況會觸發組件的重新渲染?
那麼我想配合標題你應該知道我們今天要從哪邊下手了,沒錯,你需要用useState以外的東西去儲存使用者輸入的結果,在這種情況下就是useRef hook該出場的時刻了,一般來說你會在以下的幾種情況使用useRef
當然實務上還有更多useRef可以變出的花樣,這個我們會在之後的題目看到,目前為止你只需要知道這些就夠了。很明顯我們今天是要透過useRef去達到避免觸發重新渲染的效果,因此你只要將程式碼做以下的修改就可以順利完成今天的需求囉!
export default function App() {
const email = useRef(""); // 將原本的state改為ref控制
const password = useRef("");
console.log("The whole component re-render");
const handleSubmit = (e) => {
e.preventDefault();
console.log(`Email: ${email.current}, Password: ${password.current}`);
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email: </label>
<input
id="email"
type="email"
defaultValue={email.current}
onChange={(e) => (email.current = e.target.value)}
/>
<label htmlFor="password">Password: </label>
<input
id="password"
type="password"
defaultValue={password.current}
onChange={(e) => (password.current = e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
}
我們今天透過useRef去達到更新值但不觸發重新渲染的效果,它並不算一個非常熱門的hook,在初次接觸時會感到困惑是很正常的,若你覺得即便今天的題目也讓你感覺到有些吃力,那我建議你可以至少看一下官網的說明與練習去補充一下相關的背景知識,下次再碰到類似的情境時你就可以如魚得水了!那麼我們就明天見囉!
本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!
import React, { useState, useRef } from "react";
import "./styles.css";
export default function App() {
const emailRef = useRef(null);
const passwordRef = useRef(null);
console.log("The whole component re-render");
const handleSubmit = (e) => {
e.preventDefault();
console.log(
`Email: ${emailRef.current.value}, Password: ${passwordRef.current.value}`
);
};
return (
<>
<h1>Sometimes useState just not good enough</h1>
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email: </label>
<input ref={emailRef} id="email" type="email" />
<label htmlFor="password">Password: </label>
<input ref={passwordRef} type="password" />
<button type="submit">Submit</button>
</form>
</>
);
}
好像也可以不用onChange
jacky0326 你說的完全正確!useRef本身就會提供對於DOM節點的直接存取,在這個情境下確實不需要onChange去做控制!這個示範中主要是演示更新某個之前用state控制的值卻不觸發re-render的情況,但以form的情境來說,在實務中你這樣的寫法絕對是更直覺的做法!