Side Effects在React中的实战解析与常见坑点处理
项目初期的技术选型
最近我参与了一个新的前端项目,主要是一个电商网站的重构。这个项目的目标是提升用户体验和性能,同时减少代码维护成本。在选择技术栈时,我们决定使用React,因为它能很好地处理复杂的UI逻辑。不过,随着项目的推进,我们遇到了一些关于副作用(Side Effects)的问题。
开始用useEffect:简单的需求
一开始,我们的需求很简单,就是在用户进入某个页面时,从服务器获取数据并显示。这种情况下,useEffect 非常适合。我们写了个简单的例子:
import React, { useEffect, useState } from 'react';
function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
fetch('https://jztheme.com/api/products')
.then(response => response.json())
.then(data => setProducts(data));
}, []);
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
export default ProductList;
这段代码工作得很好,没什么问题。但是随着功能的增加,情况变得复杂了。
最大的坑:依赖管理
后来我们在产品列表中加入了过滤功能,允许用户按类别筛选商品。这导致我们需要在过滤条件变化时重新获取数据。这时候,useEffect 的依赖数组就变得重要起来。
起初,我直接把过滤条件放进依赖数组里:
const [filter, setFilter] = useState('');
useEffect(() => {
fetch(https://jztheme.com/api/products?category=${filter})
.then(response => response.json())
.then(data => setProducts(data));
}, [filter]);
这样做的确能在过滤条件变化时重新获取数据,但很快我就发现了一个问题:每次输入框中的值变化时都会触发一次请求。这不仅增加了服务器负担,还导致了用户体验下降。
为了解决这个问题,我尝试了几个方案:
- 引入节流(Throttle):通过
lodash库来控制请求频率。 - 使用防抖(Debounce):通过
lodash库来延迟请求。 - 手动控制请求:在状态变化时先取消之前的请求,再发起新的请求。
最后,我选择了防抖的方法,因为它既能保证用户体验,又能减少不必要的请求:
import { useEffect, useState } from 'react';
import { debounce } from 'lodash';
function ProductList() {
const [products, setProducts] = useState([]);
const [filter, setFilter] = useState('');
const fetchData = debounce((filter) => {
fetch(https://jztheme.com/api/products?category=${filter})
.then(response => response.json())
.then(data => setProducts(data));
}, 300);
useEffect(() => {
fetchData(filter);
}, [filter, fetchData]);
return (
<div>
<input
type="text"
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="Filter by category"
/>
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
export default ProductList;
这样一来,每次输入框中的值变化时,请求会在300毫秒后才发出,避免了频繁请求的问题。
清理函数的重要性
在项目中,我们还遇到了一个常见的问题:当组件卸载时,某些异步操作仍在进行。例如,在用户离开页面时,如果有一个未完成的网络请求,会导致内存泄漏。
为了解决这个问题,我们需要在 useEffect 中添加清理函数:
import { useEffect, useState } from 'react';
import { debounce } from 'lodash';
function ProductList() {
const [products, setProducts] = useState([]);
const [filter, setFilter] = useState('');
let cancelToken;
const fetchData = debounce((filter) => {
if (cancelToken) {
cancelToken.cancel('Operation canceled due to new request.');
}
cancelToken = axios.CancelToken.source();
fetch(https://jztheme.com/api/products?category=${filter}, {
cancelToken: cancelToken.token
})
.then(response => response.json())
.then(data => setProducts(data))
.catch(thrown => {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
}, 300);
useEffect(() => {
fetchData(filter);
return () => {
if (cancelToken) {
cancelToken.cancel();
}
};
}, [filter, fetchData]);
return (
<div>
<input
type="text"
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="Filter by category"
/>
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
export default ProductList;
这样,当组件卸载时,会调用清理函数取消未完成的请求,防止内存泄漏。
最终的效果
经过这些调整,我们的项目终于稳定了下来。虽然还有一些小问题(比如偶尔的请求延迟),但整体上用户体验和性能都有了很大的提升。
总结一下:
- 合理使用
useEffect和依赖数组,可以有效管理副作用。 - 利用防抖(Debounce)等技术,可以减少不必要的请求,提升用户体验。
- 在
useEffect中添加清理函数,可以防止内存泄漏。
以上是我的项目经验,希望对你有帮助。如果有更好的解决方案或建议,欢迎在评论区交流。
暂无评论