React 基础 —— 各种 hooks 的使用场景
hooks
1. useRef
ref 属于组件实例的共享变量(相当于class 组件中的 this.xxx)。直接修改 ref.current 不会触发组件的重渲染。
Caveats
① 常用于事件处理函数中共享与读写 ref
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
② 不应在渲染期间读写 ref.current,除非渲染时直接初始化,且遵循下面的写法:
避免直接使用
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
而应使用
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
2. useEffect
产生副作用(与外部系统通信)。
Caveats
浏览器通常在运行 effect 之前更新画面,但由于 useEffect 是异步的,可能会导致页面闪烁(页面更新多次),此时可以使用 useLayoutEffect,因为它是同步的。
3. useMemo
重新渲染时,缓存计算结果。又称记忆化(Memoization)。有时会丢弃缓存,比如组件在初始化挂载期间。
4. useCallback
重新渲染时,缓存函数定义。
使用场景举例:
① 在使用 React.memo 缓存某个子组件时,若该子组件依赖一个父组件的函数。当父组件重渲染时,该组件的实例变量(函数)也会重新创建,故而子组件将重渲染。此时,可以使用 useCallback 包装这个函数,从而避免子组件跳过重渲染。
import React, { useCallback, useState } from "react"
function App() {
const [text, updateText] = useState("")
//由于缓存了 handleClick,从而 MyButton 可以跳过重渲染
const handleClick = useCallback(() => {
alert("2233")
}, [])
return (
<>
<input value={text} onChange={(e) => updateText(e.target.value)} />
<MyButton onClick={handleClick} />
</>
)
}
export default App
const MyButton = React.memo(function MyButton({ onClick }) {
console.log(233)
return <button onClick={onClick}>click me</button>
})
② 当 useEffect 的依赖是一个函数时,可使用 useCallback 避免函数重创建导致的额外执行
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ Only changes when createOptions changes
此外,对于这种场景可以直接提取真正要依赖的变量,将函数的创建放在 useEffect 内部,故而直接消除父组件函数的重创建带来的影响。
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() { // ✅ No need for useCallback or function dependencies!
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
③ 如果自定义 hook 需要返回函数,通常可以使用 useCallback 包装这个函数
function useRouter() {
const { dispatch } = useContext(RouterStateContext);
const navigate = useCallback((url) => {
dispatch({ type: 'navigate', url });
}, [dispatch]);
const goBack = useCallback(() => {
dispatch({ type: 'back' });
}, [dispatch]);
return {
navigate,
goBack,
};
}
PS:useCallback 用于缓存函数引用,useRef 用于缓存变量引用。
233