Hooks 与生命周期
Hooks 与生命周期
useEffect Hook 副作用操作
useEffect 是 React 提供给我们的一个逃生舱口, 它允许我们在函数式组件中
import {useEffect} from "react";
useEffect(() => {
// 副作用操作
return () => {
// 清理操作
};
}, [依赖项]);模拟组件挂载和卸载
当我们需要副作用仅仅在组件第一次渲染到屏幕上之后执行一次,且之后不在重复执行时,我们可以向 useEffect 传递一个空的依赖数组, 返回值就是清理函数也就是组件卸载时需要执行的操作。
import {useEffect} from "react";
useEffect(() => {
// 副作用操作
return () => {
// 清理操作
};
}, []);模拟组件更新
当我们需要副作用在每次组件渲染或也定数据变化后都执行。我们可以向 useEffect 传递一个依赖数组, 数组中的项就是我们需要监听的状态。当数组中的项发生变化时,副作用就会执行。
import {useEffect, useState} from "react";
const Timer = () => {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// 副作用操作
const intervalId = setInterval(() => {
setSeconds(seconds + 1);
}, 1000);
return () => {
// 清理操作
clearInterval(intervalId);
};
}, [seconds]);
return (
<div>
<p>{seconds}</p>
</div>
);
};- 不提供数组 每次渲染后都执行
- 提供空数组 只在组件挂载和卸载时执行
- 提供依赖数组 当数组中的项发生变化时执行 第一次渲染后执行,并且在每次依赖项变化后执行
核心原则: 依赖项数组中的项只能是状态变量或 props。如果依赖项数组中包含了其他的变量,那么当这些变量发生变化时,副作用就会执行。
常见的错误
- 无限循环: 当依赖项数组中包含了状态变量时,副作用就会在每次状态变化后执行。如果副作用中修改了状态,那么状态就会再次变化,导致副作用无限循环执行。
import {useEffect, useState} from "react";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// 副作用操作
const intervalId = setInterval(() => {
setCount(count + 1);
}, 1000);
}, [count]);
return (
<div>
<p>{count}</p>
</div>
);
};- 陈旧的闭包: 这是更隐蔽的问题,如果你的 effect 引用了某个 state 或 props,但你忘记将它加入依赖数组,那么 effect 函数将捕获该变量的第一次渲染时的值,而不是最新的值。
使用 useRef 访问 Dom 和存储可变值
useState 和 useEffect 满足了我们大部分的需求,但是还有一类特殊场景:当我们需要一个值在多次渲染之间保持持久,但它的改变不希望触发组件的重新渲染时,为了应对这个场景,React 提供了 useRef Hook。useRef 返回一个可变的 ref 对象,该对象只有一个.current 属性。我们可以将任何值存放在 myRef.current 中。
useRef 主要有两个用途:
- 访问 Dom 元素:这是 useRef 最常见的用途。在某些情况下,我们确实需要跳出 React 的声明式世界,直接去操作一一个底层的 DOM 节点,例如:管理表单焦点的切换、触发动画、或者集成一个需要传入的 DOM 节点的第三方库。
操作步骤如下
- 使用 useRef 创建一个 ref 对象。
- 通过 JSX 的 ref 属性,将这个 ref 对象赋值给需要操作的 DOM 元素。
- 可以在组件的任何地方通过 ref.current 来访问或修改 DOM 元素。
示例
import {useRef} from "react";
const FocusInput = () => {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text"/>
<button onClick={handleClick}>Focus Input</button>
</div>
);
};存储任意可变值
useRef 的.current 属性就像是 class 组件中的一个实例属性。它是一个通用容器,可以在组件的整个生命周期内持久保存任何值,且对它的修改不会触发重新渲染。这在需要存储定时器 ID,WebSocket 连接实例或任何与渲染无关的数据时非常有用。
示例
import {useRef, useState} from "react";
const DebuouncedSearch = () => {
const [query, setQuery] = useState<string>("");
const timeoutRef = useRef(null);
useEffect(() => {
timeoutRef.current = setTimeout(() => {
console.log("debounced query:", query);
}, 500);
return () => {
clearTimeout(timeoutRef.current);
};
}, [query]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
type="text"
/>
</div>
);
};useDeferredValue 优化渲染和用户操作
例子: 延迟列表的重新渲染
基本用法
在新内容加载之前显示旧内容
当某个UI 部分渲染很慢,没有简单的优化方法,同时又希望避免阻塞其他UI 的渲染时,
比如,输入一些东西会重新渲染列表或表格时。就可以拿到延迟的数据去更新
官方案例
延迟列表的渲染
import {useState, useDeferredValue} from 'react';
import SlowList from './SlowList.js';
export default function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)}/>
<SlowList text={deferredText}/>
</>
);
}import {memo} from 'react';
const SlowList = memo(function SlowList({text}) {
// 仅打印一次。实际的减速是在 SlowItem 组件内部。
console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />');
let items = [];
for (let i = 0; i < 250; i++) {
items.push(<SlowItem key={i} text={text}/>);
}
return (
<ul className="items">
{items}
</ul>
);
});
function SlowItem({text}) {
let startTime = performance.now();
while (performance.now() - startTime < 1) {
// 每个 item 暂停 1ms,模拟极其缓慢的代码
}
return (
<li className="item">
Text: {text}
</li>
)
}
export default SlowList;useImperativeHandle (类似于Vue 中的DefineExpose)
暴露组件内部的一些方法
示例
import {useRef, useImperativeHandle} from 'react';
function MyInput({ref}) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input ref={inputRef}/>;
};