useEffect 首次渲染问题
- 在使用 useEffect 时候,会在页面渲染完成后执行一次
- 可以使用 useRef,避免 useEffect 的第一次渲染
- useRef 在组件的整个生命周期中持续存在
const renderRef = useRef(true);
useEffect(() => {
// 阻止第一次渲染
if (renderRef.current) {
renderRef.current = false;
return;
}
// todo something
}, [data]);
useEffect hook 学习
useEffect
- 该 Hook 接收一个包含命令式、且可能有副作用代码的函数
- 赋值给
useEffect
的函数会在组件渲染到屏幕之后执行 - 默认情况下,Effect 将在每轮渲染结束后执行,但可以让它在某些值改变时候执行
useEffect(didUpdate);
什么是副作用代码?
- 在函数组件主体内,这里指在 React 渲染阶段
- 改变 DOM、添加订阅、设置定时器、记录日志及其他包含副作用的操作
- 这可能产生莫名其妙的 bug 并破坏 UI 的一致性
清除 Effect
- 通常,组件卸载时需要清除 Effect 创建的诸如订阅或定时器 ID 等资源
- 要实现这一点,useEffect 函数需返回一个清除函数
- 为防止内存泄漏,清除函数会在组件卸载前执行
- 如果组件多次渲染(通常如此),在执行下一个 effect 之前,上一个 effect 就已被清除
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
Effect 的执行时机
- 浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用
- 因此不应在函数中执行阻塞浏览器更新屏幕的操作
- 并非所有 effect 都可以被延迟执行,例如在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致
- React 为此提供了一个额外的
useLayoutEffect
Hook 来处理这类 effect - useLayoutEffect 与 useEffect 的结构相同,区别只是调用时机不同
useEffect 第二个参数
- 是 effect 所依赖的值数组
- 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组(
[]
)
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
useRef hook 学习
- 访问 DOM 的主要方式,返回的 ref 对象在组件的整个生命周期内持续存在
useRef
返回一个可变的 ref 对象,其.current
属性被初始化为传入的参数useRef()
和自建一个{current: ...}
对象的唯一区别是,useRef
会在每次渲染时返回同一个 ref 对象
const refContainer = useRef(initialValue);
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useRef 的常见用途
- 获取和操作DOM元素
- 在动画中保存元素的位置信息
- 与第三方库集成,例如Chart.js 或 D3.js
- 保存上一个渲染周期的值,以进行比较
- 跟踪焦点管理
- 与 setInterval 或 setTimeout 等定时器一起使用