React基础 第二十六章(何时不需要使用Effect)

在React开发中,useEffect是一个非常强大的Hook,它允许我们在组件渲染后与外部系统同步。然而,并非所有情况都需要使用Effect。有时候,过度使用Effect会导致代码变得复杂、运行缓慢并增加出错的可能性。本文将探讨何时不需要使用Effect,并提供一些在不使用Effect的情况下处理逻辑的技巧和代码示例。

何时不需要使用Effect

转换渲染所需的数据

当你需要根据props或state的变化来更新组件的state时,不应该使用Effect。例如,你想要筛选一个列表,直觉可能是使用Effect来更新state。但这是低效的,因为它会导致不必要的渲染。你应该在组件顶层直接转换数据。

在下面的示例代码中,FilteredList组件接收一个list作为其props。这个list是一个包含多个项目的数组,每个item都有一个isActive属性。我们的目标是通过过滤仅显示那些isActivetrue的项目。

正确的示例代码:

javascript 复制代码
function FilteredList({ list }) {
  // ✅ 在渲染期间直接计算筛选后的列表
  const filteredList = list.filter(item => item.isActive);

  return (
    <ul>
      {filteredList.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

正确的方法是直接在组件的渲染逻辑中进行筛选。如示例代码所示,我们在组件函数体内部使用.filter()方法来创建一个新的数组filteredList,它仅包含isActive属性为true的项目。然后,我们直接在JSX中映射这个filteredList来生成列表项(<li>元素)。这样,每次组件渲染时,我们都会根据当前的props计算出正确的输出,而不需要额外的Effect和state。

这种方法的好处是:

  1. 性能:避免了不必要的state更新和额外的渲染。
  2. 简洁性:减少了状态管理的复杂性,使组件更容易理解和维护。
  3. 声明式:组件的输出直接反映了其输入,这是React推荐的模式。

错误的示例代码:

javascript 复制代码
import React, { useState, useEffect } from 'react';

function FilteredList({ list }) {
  // ❌ 使用useState和useEffect来设置和更新筛选后的列表
  const [filteredList, setFilteredList] = useState([]);

  // Effect将会在每次list变化时运行
  useEffect(() => {
    // 这将导致一个额外的渲染
    setFilteredList(list.filter(item => item.isActive));
  }, [list]); // 依赖列表中有list

  return (
    <ul>
      {filteredList.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

错误的方法是使用Effect来监听list的变化,并在Effect内部更新一个新的state来存储筛选后的列表。这样做不仅会增加额外的状态管理逻辑,还可能导致不必要的额外渲染,因为每次list变化时,Effect都会执行,然后设置新的state,这会触发另一次渲染。

处理用户事件

在React中,处理用户事件通常涉及到响应用户的交互,如点击按钮、发送API请求等。这些事件处理逻辑应该直接写在事件处理函数中,而不是在Effect中。

在下面的示例代码中,PurchaseButton组件接收一个productId作为其props。这个productId是用来标识用户想要购买的产品。当用户点击"Buy Now"按钮时,我们希望发送一个API请求来处理购买操作。

正确的技巧示例代码:

javascript 复制代码
function PurchaseButton({ productId }) {
  function handlePurchase() {
    // ✅ 在事件处理函数中发送API请求
    fetch(`/api/buy/${productId}`, { method: 'POST' })
      .then(response => response.json())
      .then(data => console.log(data));
  }

  return (
    <button onClick={handlePurchase}>Buy Now</button>
  );
}

正确的方法是创建一个名为handlePurchase的事件处理函数,并将其绑定到按钮的onClick事件。在这个函数中,我们使用fetch函数发送一个POST请求到/api/buy/${productId}。这个请求会在用户点击按钮时被触发。我们使用.then()链来处理响应,首先将响应转换为JSON,然后在控制台中打印出来。

这种方法的好处是:

  1. 直接性:事件处理逻辑直接与用户的交互相关联,使得代码易于理解。
  2. 组织性:将逻辑保持在事件处理函数中可以让组件的结构更清晰,逻辑更集中。
  3. 性能:避免了不必要的Effect使用,减少了组件的渲染次数。
javascript 复制代码
import React, { useEffect } from 'react';

function PurchaseButton({ productId }) {
  useEffect(() => {
    // ❌ 不应该在Effect中绑定点击事件处理
    const handlePurchase = () => {
      fetch(`/api/buy/${productId}`, { method: 'POST' })
        .then(response => response.json())
        .then(data => console.log(data));
    };

    const button = document.getElementById('purchase-button');
    button.addEventListener('click', handlePurchase);

    // 清理函数,以防多次绑定
    return () => button.removeEventListener('click', handlePurchase);
  }, [productId]); // 依赖列表中有productId

  return (
    <button id="purchase-button">Buy Now</button>
  );
}

错误的方法是使用Effect来监听productId的变化,并在Effect内部发送API请求。这样做是不合适的,因为Effect是用来处理副作用的,通常是在组件渲染后执行的。而用户点击按钮并不是副作用,它是一个直接的用户交互事件,应该在事件处理函数中处理。

在这个错误的例子中,useEffect错误地用于添加和移除点击事件的监听器。这样做有几个问题:

  1. 不必要的复杂度:在Effect中添加事件监听增加了逻辑复杂性,而这是不必要的,因为React提供了内置的事件处理机制。
  2. 较差的性能 :每次productId变更时,Effect都会重新绑定事件处理器,这可能导致性能问题和意外的行为。
  3. 直接操作DOM:在React中直接操作DOM通常是不推荐的,因为这可能导致React"丢失"对DOM的追踪,从而导致不一致的状态。

注意事项

避免不必要的state和Effect

如果可以通过当前的props或state直接计算出一个值,那么就没有必要将这个值存储为另一个state。这是因为React的渲染是声明式的,意味着UI应该直接反映出当前的props和state。

错误代码:

javascript 复制代码
// 🔴 不推荐:使用Effect来更新fullName state
const [fullName, setFullName] = useState('');
useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
  • 如果不遵循这个原则,你的组件可能会有更多的state,这使得状态管理更复杂。
  • 可能会导致不必要的渲染,因为每次更新state都会触发组件的重新渲染。
  • 可能会引入bug,因为Effect可能会在不正确的时间运行,导致state更新不同步。

正确代码:

javascript 复制代码
// ✅ 推荐:在渲染期间直接计算fullName
const fullName = `${firstName} ${lastName}`;
  • 减少组件状态,使组件更简单、更可预测。
  • 避免额外的渲染,因为每次调用setFullName都会导致组件重新渲染。
  • 避免潜在的bug,因为在Effect中同步state可能会导致状态不一致。

使用useMemo来缓存昂贵的计算

useMemo是一个Hook,它可以缓存计算的结果,只有当依赖项改变时,才会重新计算。这对于性能优化很有帮助,特别是当你有一些基于props或state的昂贵计算时。

技巧示例代码:

javascript 复制代码
const expensiveList = useMemo(() => {
  return computeExpensiveValue(list);
}, [list]);
  • 提高性能,避免在每次渲染时都进行昂贵的计算。
  • 保持计算结果的一致性,只有当依赖项改变时才重新计算。
  • 如果不使用useMemo,昂贵的计算会在每次渲染时都执行,这可能导致应用响应缓慢。

使用key来重置组件状态

当组件的key改变时,React会卸载该组件并重新挂载一个新的实例。这个特性可以用来重置组件的内部state。

技巧示例代码:

javascript 复制代码
function UserProfile({ userId }) {
  // ✅ 当userId变化时,UserProfile组件的state会被重置
  return <Profile key={userId} userId={userId} />;
}
  • 简单地通过改变key来重置组件状态,而不需要编写额外的逻辑。
  • 保持组件状态与props的同步,当props改变时,组件状态也会相应地重置。
  • 如果不使用key来重置状态,可能需要编写额外的逻辑来手动重置状态,这会使代码更加复杂。

在事件处理函数中共享逻辑

如果你有多个事件处理函数需要执行相同的逻辑,不要将逻辑放在Effect中,而是提取到一个共享函数中。

技巧示例代码:

javascript 复制代码
function ProductPage({ product, addToCart }) {
  function buyProduct() {
    addToCart(product);
    console.log(`Product ${product.name} added to cart.`);
  }

  function handleBuyClick() {
    buyProduct();
  }

  function handleCheckoutClick() {
    buyProduct();
    navigateTo('/checkout');
  }

  // ...
}
  • 代码复用,减少重复代码,使代码更简洁、更易于维护。
  • 逻辑集中,当需要修改逻辑时,只需在一个地方进行修改。
  • 如果不提取共享逻辑,可能会导致代码冗余,增加错误的风险,也使得未来的维护更加困难。

记住,Effect是一个强大的工具,但在许多情况下,你可能根本不需要它。

相关推荐
轻口味1 小时前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami1 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
wakangda1 小时前
React Native 集成原生Android功能
javascript·react native·react.js
吃杠碰小鸡2 小时前
lodash常用函数
前端·javascript
emoji1111112 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼2 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250032 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235952 小时前
web复习(三)
前端
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter