useEffect 处理副作用的
useEffect(setup,dependencies),setup初始化,是一个函数,dependencies依赖项,是一个数组
useEffect执行机制
第二个参数不传则会在每次渲染的时候执行
第二个参数如果传空数组,会在组件首次渲染的时候执行
第二个参数如果不是空数组, React 会将数组的内容当成依赖项存起来,在下次渲染的时候进行比较,发生改变了就会把 useEffect 的第一个参数回调函数推入任务队列等待执行
React每次挂载到页面执行对应的setup函数,是一个异步的执行setup
挂载:React将一个组件渲染到页面的过程叫挂载,对应的是类组件的挂载阶段onComponentDidMount
useEffect(()=>{console.log('挂载')},[])
更新:当依赖发生变更时,会执行对应的setup函数
useEffect(()=>{console.log('更新')},[count])
//若依赖项是对象或数组,使用useMemo或useCallback缓存,避免引用变化触发重复执行。
const memoizedConfig = useMemo(() => ({ id }), [id]);
useEffect(() => {
updateConfig(memoizedConfig);
}, [memoizedConfig]); //仅在id变化时触发
卸载:一般用作dom事件绑定,定时器清除等
//在组件卸载或依赖变化前,取消订阅、定时器或请求
useEffect(() => {
const timer = setInterval(() => {}, 1000);
return () => clearInterval(timer); // ✅ 清理定时器
}, []);
//使用AbortController取消未完成的请求
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.catch(() => { /* 处理取消错误 */ });
return () => controller.abort(); // ✅ 取消请求
}, [url]);
避免无限循环
//避免在useEffect中直接修改依赖项触发的状态,除非有终止条件。
useEffect(() => {
setCount(prev => prev + 1); // 可能导致循环
}, [count]);
//仅在必要时更新状态。
useEffect(() => {
if (data && !isLoaded) {
setIsLoaded(true); // ✅ 条件阻止循环
}
}, [data, isLoaded]);
异步操作处理
//将异步逻辑放在useEffect内
useEffect(() => {
const fetchData = async () => {
const result = await axios.get(url);
setData(result.data);
};
fetchData();
}, [url]); // url变化时重新获取
多个useEffect执行
// 数据获取
useEffect(() => { ... }, [url]);
// 事件监听
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
性能优化
//用useCallback/useMemo减少函数或对象重新创建
const handleClick = useCallback(() => { ... }, [dependency]);
useEffect(() => {
element.addEventListener('click', handleClick);
return () => element.removeEventListener('click', handleClick);
}, [handleClick]); // 依赖稳定的回调
//使用ref保存可变值:存储无需触发渲染的值
const intervalRef = useRef();
useEffect(() => {
intervalRef.current = setInterval(() => { ... });
return () => clearInterval(intervalRef.current);
}, []);
useEffect执行顺序
useEffect 是在 render 之后执行的就好解决了
题目一
function App() {
const [count, setCount] = React.useState(0);
console.log('before useEffect');
React.useEffect(() => {
console.log('useEffect', count);
});
console.log('after useEffect');
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>
Click me
</button>
</div>
);
}
点击一次按钮,打印的日志顺序是怎样的呢?
初始化时,依次打印 'before useEffect', 'after useEffect', 'useEffect 0'
点击一次后,依次打印 'before useEffect', 'after useEffect', 'useEffect 1'
题目二
function App() {
const [count, setCount] = React.useState(0);
console.log('before useEffect');
React.useEffect(() => {
console.log('useEffect', count);
return () => {
console.log('useEffect 的 return 回调', count);
}
}, [count]);
console.log('after useEffect');
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>
Click me
</button>
</div>
);
}
初始化时打印:
"before useEffect"
"after useEffect"
"useEffect" 0
点击一次按钮后:
"before useEffect"
"after useEffect"
"useEffect 的 return 回调" 0
"useEffect" 1
题目三
function App() {
const [visible, setVisible] = React.useState(true);
React.useEffect(() => {
console.log('app useEffect', visible);
return () => {
console.log('app useEffect return', visible);
}
}, [visible]);
console.log('app render');
const handleClick = () => {
setVisible((visible) => {
return !visible;
});
}
const Child = () => {
React.useEffect(() => {
console.log('child useEffect');
return () => {
console.log('child useEffect return');
}
}, []);
console.log('child render');
return (
<div>I' m child</div>
)
}
return (
<div>
<button onClick={handleClick}>
Click me toggle child1
</button>
{visible ? <Child /> : null}
</div>
);
}
初始化时:
"app render"
"child render"
"child useEffect"
"app useEffect" true
第一次点击按钮,隐藏 Child:
"app render"
"child useEffect return"
"app useEffect return" true
"app useEffect" false
题目四
function App() {
const [count, setCount] = React.useState(0);
const getCount = () => {
console.log('getCount', count);
return count;
}
console.log('before useEffect');
React.useEffect(() => {
console.log('useEffect', count);
}, [getCount()]);
console.log('after useEffect');
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>
Click me
</button>
</div>
);
}
初始化时:
"before useEffect"
"getCount" 0
"after useEffect"
"useEffect" 0
getCount 这个函数的调用竟然是和 before useEffect, after useEffect 它们一样,当成类似同步代码来执行的。
点击按钮后:
"before useEffect"
"getCount" 1
"after useEffect"
"useEffect" 1
即使我依赖的是一个函数,React 其实也会执行这个函数,并把这个函数的返回值给 useEffect 做比较,再来决定后续要不要执行 useEffect 的回调。
useEffect 和 useLayoutEffect 的区别
useLayoutEffect的作用和 useEffect几乎差不多,useEffect更新可能闪烁,useLayoutEffect不会,一般useEffect使用多
1、useEffect 是在render之后执行的,而useLayoutEffect是在render之前执行的。
2、为了用户体验,一般优先使用 useEffect ,除非useEffect内需要改变 Layout 布局,再考虑使用useLayoutEffect , 因为useLayoutEffect会导致render后执行,间接导致用户要慢几秒才能看到页面。