六個 React 常見可優化的用法的解題技巧
在這篇文章中,我探討了六個常見的 React 優化技巧,幫助開發者避免常犯錯誤並提升應用性能。例如,當不需要即時取得輸入值時,使用 useRef 代替 useState 可以避免不必要的渲染;理解 setState 的異步特性和使用上一狀態的重要性;以及正確使用 useMemo 來避免因引用類型更新導致的額外渲染。這些技巧不僅提高了代碼的效率,也加深了對 React 內部工作機制的理解。
1. 不需要及時取得值的時候用 useState
在此狀況中不需要及時的取得輸入值,只需要按下 submit 按鈕時取得即可。
- 待優化的用法
import { useState } from "react"
function App(){
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
function onSubmit(e) {
e.preventDefault()
console.log({email, password})
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="email">Email</label>
<input
value={email}
onChange={e => setEmail(e.target.value)}
type="email"
id=email
>
<label htmlFor="password">Password</label>
<input
value={password}
onChange={e => setPassword(e.target.value)}
type="password"
id="password"
>
<button type="submit">Submit</button>
</form>
)
}
export default App
- 優化後的用法
import { useRef } from "react"
function App(){
const emailRef = useRef()
const passwordRef = useRef()
function onSubmit(e) {
e.preventDefault()
console.log({
email: emailRef.current.value,
password: passwordRef.current.value
})
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="email">Email</label>
<input ref={emailRef} type="email" id=email>
<label htmlFor="password">Password</label>
<input ref={passwordRef} type="password" id="password">
<button type="submit">Submit</button>
</form>
)
}
export default App
2. 沒有正確了解 setState 之 previous value 的特性
- 待優化的用法
import { useState } from "react"
export function Counter(){
const [count, setCount] = useState(0)
const adjustCount = amount => {
// 0 + 1
setCount(count + amount)
// 0 + 1
setCount(count + amount)
}
return (
<>
<button onClick={() => adjustCount(-1)}>-</button>
<span>{count}</span>
<button onClick={() => adjustCount(+1)}>-</button>
</>
)
}
- 優化後的用法
import { useState } from "react"
export function Counter(){
const [count, setCount] = useState(0)
const adjustCount = amount => {
// 0 + 1
setCount(prev => {
return prev + amount
})
// 1 + 2
setCount(prev => {
return prev + amount
})
}
return (
<>
<button onClick={() => adjustCount(-1)}>-</button>
<span>{count}</span>
<button onClick={() => adjustCount(+1)}>-</button>
</>
)
}
3. 沒有正確了解 setState 之非同步的特性
- 待優化的用法
import { useState } from "react"
export function Counter(){
const [count, setCount] = useState(0)
const adjustCount = amount => {
setCount(prev => {
return prev + amount
})
console.log(count) // 0
}
return (
<>
<button onClick={() => adjustCount(-1)}>-</button>
<span>{count}</span>
<button onClick={() => adjustCount(+1)}>-</button>
</>
)
}
- 優化後的用法
import { useState, useEffect } from "react"
export function Counter(){
const [count, setCount] = useState(0)
const adjustCount = amount => {
setCount(prev => {
return prev + amount
})
}
useEffect(() => {
console.log(count)
}, [count])
return (
<>
<button onClick={() => adjustCount(-1)}>-</button>
<span>{count}</span>
<button onClick={() => adjustCount(+1)}>-</button>
</>
)
}
4. 沒有正確了解物件 call by reference 的不同
當改變 darkMode,Component 會重複渲染,即使 age 和 name 值是一樣,但物件的位置改變了也會觸發 useEffect。
useMemo 則是會在 dependencies 沒有改變的情況下,把某個運算的結果保存下來,在此例子中會在物件中的值沒有改變的狀況下,把原始物件保存下來。
- 待優化的用法
import { useState, useEffect } from "react"
export function Counter(){
const [age, setAge] = useState(0)
const [name, setName] = useState("")
const [darkMode, setDarkMode] = useState(false)
const person = { age, name}
useEffect(() => {
console.log(person)
}, [person])
return (
<div>
Age: {" "}
<input value={age} type="number" onChange={e => setAge(e.target.value)} />
<br />
Name: {" "}
<input value={name} onChange={e => setAge(e.target.value)} />
<br />
Dark Mode: {" "}
<input
type="checkbox"
value={darkMode}
onChange={e => setDarkMode(e.target.value)}
/>
</div>
)
}
- 優化後的用法
import { useState, useEffect, useMemo } from "react"
export function Counter(){
const [age, setAge] = useState(0)
const [name, setName] = useState("")
const [darkMode, setDarkMode] = useState(false)
const person = useMemo(() => {
return {age, name}
}, [agem name])
useEffect(() => {
console.log(person)
}, [person])
return (
<div>
Age: {" "}
<input value={age} type="number" onChange={e => setAge(e.target.value)} />
<br />
Name: {" "}
<input value={name} onChange={e => setAge(e.target.value)} />
<br />
Dark Mode: {" "}
<input
type="checkbox"
value={darkMode}
onChange={e => setDarkMode(e.target.value)}
/>
</div>
)
}
5. 不必要的重複使用 useState 導致需要不必要的 useEffect
- 待優化的用法
import { Counter } from "react"
function App(){
const [firstName, setFirstName] = useState("")
const [firstName, setLastName] = useState("")
const [fullName, setFullName] = useState("")
useEffect(() => {
setFullName(`${firstName} ${LastName}`)
}, [firstName, lastName])
return (
<input value={firstName} onChange={e => setFirstName(e.target.value)}>
<input value={lastName} onChange={e => setLastName(e.target.value)}>
{fullName}
)
}
export default App
- 優化後的用法
import { Counter } from "react"
function App(){
const [firstName, setFirstName] = useState("")
const [firstName, setLastName] = useState("")
const fullName = `${firstName} ${LastName}`
return (
<input value={firstName} onChange={e => setFirstName(e.target.value)}>
<input value={lastName} onChange={e => setLastName(e.target.value)}>
{fullName}
)
}
export default App
6. 在 useEffect 中沒有考慮到非同步的問題使用 fetch
- 待優化的用法
import { useEffect } from "react"
export function useFetch()
const [loading, setLoading] = useState(true)
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
setLoading(true)
fetch(url)
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [url])
return {loading, data, error}
}
// 0. 0ms + 50ms = 50ms
// 1. 100ms + 300ms = 400ms
// 3. 200ms + 100ms = 300ms
- 優化後的用法
import { useEffect } from "react"
export function useFetch()
const [loading, setLoading] = useState(true)
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
const controller = new AbortController()
setLoading(true)
fetch(url, {signal: controller.signal})
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
return () => {
controller.absort()
}
}, [url])
}
資料來源:Top 6 React Hook Mistakes Beginners Make