useUpdateEffect
useUpdateEffect 用法等同于 useEffect,但是会忽略首次执行,只在依赖更新时执行。
js
import { useEffect, useRef } from 'react';
const useUpdateEffect: typeof useEffect = (effect, deps) => {
const isMounted = useRef(false);
// 首次渲染后标记为已挂载
useEffect(() => {
isMounted.current = true;
// 组件卸载时重置标志
return () => {
isMounted.current = false;
};
}, []);
// 实际的 effect,只有在已挂载且依赖项变化时才执行
useEffect(() => {
if (isMounted.current) {
return effect();
}
}, deps);
};
export default useUpdateEffect;
useUpdateLayoutEffect
useUpdateLayoutEffect 用法等同于 useLayoutEffect,但是会忽略首次执行,只在依赖更新时执行。
js
import { useLayoutEffect, useRef } from 'react';
const useUpdateLayoutEffect: typeof useLayoutEffect = (effect, deps) => {
const isMounted = useRef(false);
// 首次渲染后标记为已挂载
useLayoutEffect(() => {
isMounted.current = true;
// 组件卸载时重置标志
return () => {
isMounted.current = false;
};
}, []);
// 实际的 layout effect,只有在已挂载且依赖项变化时才执行
useLayoutEffect(() => {
if (isMounted.current) {
return effect();
}
}, deps);
};
export default useUpdateLayoutEffect;
js
// 使用场景:DOM 同步测量和调整
import { useUpdateLayoutEffect } from 'ahooks';
function ResponsiveComponent({ items }) {
const containerRef = useRef(null);
// 需要在 DOM 更新后立即测量并调整样式
useUpdateLayoutEffect(() => {
const container = containerRef.current;
if (container) {
// 测量容器尺寸
const { width } = container.getBoundingClientRect();
// 根据宽度调整子元素样式(需同步执行避免闪烁)
const children = container.querySelectorAll('.item');
children.forEach(child => {
child.style.flexBasis = width > 600 ? '50%' : '100%';
});
}
}, [items]);
return (
<div ref={containerRef}>
{items.map(item => (
<div key={item.id} className="item">
{item.content}
</div>
))}
</div>
);
}
useAsyncEffect
useEffect 支持异步函数
js
import { useEffect, useRef } from 'react';
const useAsyncEffect = (
effect: (isCanceled: () => boolean) => Promise<void | (() => void)>,
deps?: React.DependencyList
) => {
const isCanceledRef = useRef(false);
useEffect(() => {
isCanceledRef.current = false;
// 执行异步效应
const result = effect(() => isCanceledRef.current);
// 返回清理函数
return () => {
isCanceledRef.current = true;
// 如果 effect 返回了 Promise,处理其结果
if (result instanceof Promise) {
result.then(cleanup => {
if (cleanup && !isCanceledRef.current) {
cleanup();
}
});
}
};
}, deps);
};
export default useAsyncEffect;
js
// 使用场景:异步数据获取
import { useAsyncEffect } from 'ahooks';
import { useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useAsyncEffect(async (isCanceled) => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
// 检查是否已取消,避免在组件卸载后设置状态
if (!isCanceled()) {
setUser(userData);
setLoading(false);
}
} catch (error) {
if (!isCanceled()) {
console.error('Failed to fetch user:', error);
setLoading(false);
}
}
}, [userId]);
if (loading) return <div>Loading...</div>;
return <div>Hello, {user?.name}!</div>;
}