Side Effects在React中的实战解析与常见坑点处理

闲人芳宁 优化 阅读 1,772
赞 46 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

最近我参与了一个新的前端项目,主要是一个电商网站的重构。这个项目的目标是提升用户体验和性能,同时减少代码维护成本。在选择技术栈时,我们决定使用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 中添加清理函数,可以防止内存泄漏。

以上是我的项目经验,希望对你有帮助。如果有更好的解决方案或建议,欢迎在评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论