当前位置: 首页 > news >正文

react问一问

文章目录

  • 前言
  • 1. 在 `useEffect` 中,空数组 `[]` 作为依赖数组的作用
      • 具体解释:
      • 例子:
      • 代码解析:
      • 总结:
  • 2. 为什么不推荐在 `setTimeout` 中直接使用 `setState` ?
      • 解决方法
      • 总结
  • 3. **`useReducer` 和 `useMemo` 的基本概念**
      • 2. **`useReducer` 和 `useMemo` 结合使用**
  • 4.结合 `useReducer` 和 `useMemo` 使用
        • 代码示例:
      • **为什么要这样结合使用?**
      • **什么时候结合使用 `useReducer` 和 `useMemo`?**
      • 总结


前言

随笔记录react面试题

1. 在 useEffect 中,空数组 [] 作为依赖数组的作用

useEffect 中,空数组 [] 作为依赖数组的作用是告诉 React 该副作用函数(useEffect)只会在组件的 首次渲染 时执行一次,且 不再执行 后续的渲染。简单来说,空数组表示 “没有依赖”,所以这个副作用函数不会响应任何变化,只会在组件加载时执行。

具体解释:

  1. useEffect 的基本工作原理
    useEffect 接受两个参数:

    • 第一个参数是一个副作用函数,它将在组件渲染后执行。
    • 第二个参数是一个依赖数组,useEffect 会监视数组中的变量,一旦其中的任何一个变量发生变化,副作用函数就会重新执行。
  2. 空数组 [] 的作用
    当你传递一个空数组 [] 作为 useEffect 的第二个参数时,React 会将其视为没有任何依赖项。这意味着副作用函数只会在 组件首次挂载时执行一次,并且 不再执行。也就是说,只有组件加载时才会执行 setTimeout 设置定时器,之后组件的任何重新渲染都不会触发 setTimeout 的重新执行。

  3. 常见用途
    使用空数组通常用于以下几种情况:

    • 组件挂载时执行一次副作用(比如设置定时器、发送请求、订阅数据等)。
    • 组件卸载时清理副作用(如清除定时器、取消订阅、移除事件监听器等)。

例子:

import { useState, useEffect } from 'react';function MyComponent() {const [count, setCount] = useState(0);const [loading, setLoading] = useState(false);useEffect(() => {// 组件挂载时设置定时器const timer = setTimeout(() => {setLoading(true);setCount(prevCount => prevCount + 1);}, 2000);// 组件卸载时清除定时器return () => clearTimeout(timer);}, []);  // 依赖数组为空,意味着只会在组件首次渲染时执行return (<div>{loading ? <p>Loaded!</p> : <p>Loading...</p>}<p>Count: {count}</p></div>);
}

代码解析:

  • useEffect(() => {...}, []): 这个 useEffect 在组件首次渲染时执行,且只执行一次。

    • 设置一个 2 秒的定时器,2 秒后更新 countloading 状态。
    • 在组件卸载时(如页面跳转、组件被移除),会清除定时器,避免内存泄漏。

总结:

  • 空数组 [] 表示 useEffect 的副作用只会在组件首次挂载时执行一次,后续的更新不会重新执行这个副作用函数。
  • 这种方式通常用于需要在组件挂载时执行一次的操作,比如设置定时器、发起 API 请求、订阅事件等。

在 React 中,setState 是一个异步操作,它会触发组件的重新渲染。当你在 setTimeout 或任何异步函数中使用 setState 时,可能会遇到一些意想不到的行为,尤其是在组件已经卸载的情况下。这是因为 React 可能会在 setTimeout 完成后,仍然尝试执行 setState,而组件可能已经被卸载或没有完全渲染。

2. 为什么不推荐在 setTimeout 中直接使用 setState

  1. 异步行为导致的不确定性
    React 的 setState 是异步的,它不会立即更新状态,而是将更新排入队列。setTimeout 本身也是异步的,因此在 setTimeout 中直接调用 setState 可能导致不可预测的行为,尤其是在多个异步操作之间。

  2. 组件卸载后调用 setState 会产生警告
    如果组件在 setTimeout 调用的 setState 之前已经卸载,React 会给出警告:Can't perform a React state update on an unmounted component。如果你试图在卸载后的组件上调用 setState,会导致内存泄漏。

解决方法

  1. 清除定时器
    setTimeout 中使用 setState 时,最好的实践是确保组件在 setState 执行时仍然挂载。你可以通过在组件卸载时清除定时器来避免这种问题。

    import { useState, useEffect } from 'react';function MyComponent() {const [count, setCount] = useState(0);const [loading, setLoading] = useState(false);useEffect(() => {// 组件挂载时设置定时器const timer = setTimeout(() => {setLoading(true);setCount(prevCount => prevCount + 1);}, 2000);// 组件卸载时清除定时器return () => clearTimeout(timer);}, []);return (<div>{loading ? <p>Loaded!</p> : <p>Loading...</p>}<p>Count: {count}</p></div>);
    }
    
    • 说明:在 useEffect 中创建定时器,并且在返回的清理函数中清除它,以防组件在定时器执行之前卸载。
  2. 在异步函数中检查组件是否仍然挂载
    如果你需要在异步操作(如 setTimeoutfetch 请求等)中执行 setState,可以使用一个标志来检查组件是否仍然挂载。

    import { useState, useEffect } from 'react';function MyComponent() {const [count, setCount] = useState(0);const [loading, setLoading] = useState(false);useEffect(() => {let isMounted = true;  // 标志位,记录组件是否挂载const timer = setTimeout(() => {if (isMounted) {setLoading(true);setCount(prevCount => prevCount + 1);}}, 2000);// 组件卸载时清除定时器并设置标志为 falsereturn () => {clearTimeout(timer);isMounted = false;};}, []);return (<div>{loading ? <p>Loaded!</p> : <p>Loading...</p>}<p>Count: {count}</p></div>);
    }
    
    • 说明:通过 isMounted 标志位,确保只有在组件挂载时才调用 setState
  3. 使用 useRef 来保存组件挂载状态
    使用 useRef 可以更简洁地保存组件是否挂载的状态,并且它不会触发组件重新渲染。

    import { useState, useEffect, useRef } from 'react';function MyComponent() {const [count, setCount] = useState(0);const [loading, setLoading] = useState(false);const isMounted = useRef(false);  // 使用 useRef 保存挂载状态useEffect(() => {isMounted.current = true;  // 组件挂载时设置为 trueconst timer = setTimeout(() => {if (isMounted.current) {setLoading(true);setCount(prevCount => prevCount + 1);}}, 2000);return () => {clearTimeout(timer);isMounted.current = false;  // 组件卸载时设置为 false};}, []);return (<div>{loading ? <p>Loaded!</p> : <p>Loading...</p>}<p>Count: {count}</p></div>);
    }
    
    • 说明:使用 useRef 可以避免每次组件重新渲染时都检查挂载状态,因此相对更高效。

总结

  • 问题setTimeout 中直接调用 setState 可能导致在组件卸载后调用 setState,引发警告或错误。
  • 解决方法:可以通过清除定时器、使用 isMounted 标志位,或使用 useRef 来确保组件仍然挂载,避免内存泄漏和警告。

这些做法确保了异步操作不会影响已卸载的组件,避免了 React 的警告信息。

3. useReduceruseMemo 的基本概念

  • useReducer

    • 用于管理复杂的组件状态,尤其是状态逻辑比较复杂或涉及多个子状态的场景。
    • 类似于 Redux 的 reducer,useReducer 接受一个 reducer 函数和初始状态,并返回当前状态和一个 dispatch 函数,用于更新状态。
    • useReducer 是基于 “action”“state” 的逻辑来更新状态的,它提供了一种更加结构化的方式来管理状态,适用于大型或复杂组件。
  • useMemo

    • 用于 缓存 计算结果,避免重复执行昂贵的计算。
    • 它会记住传递给它的函数的结果,只有当依赖项发生变化时,才会重新计算。这样可以减少不必要的计算,提升性能。
    • useMemo 不会改变组件的状态,只会优化组件的渲染过程,特别是当计算值比较昂贵时。

2. useReduceruseMemo 结合使用

useReduceruseMemo 可以结合使用,但它们各自的职责不同:useReducer 用于状态管理,而 useMemo 用于性能优化。

  • 使用场景

    • useReducer 通常用于复杂的状态管理,比如多个相关的状态、需要特定的更新逻辑等。
    • useMemo 用于缓存某些值,避免重复的昂贵计算,或者当状态变化时只重新计算某些特定的部分。

4.结合 useReduceruseMemo 使用

假设你有一个复杂的表单组件,其中有多个状态和一些计算依赖于当前的状态。在这种情况下,useReducer 用于管理复杂的表单状态,而 useMemo 用于缓存一些昂贵的计算结果,减少不必要的渲染。

代码示例:
import React, { useReducer, useMemo } from 'react';// 初始化状态
const initialState = {firstName: '',lastName: '',age: 0,
};// 定义 reducer 函数
function reducer(state, action) {switch (action.type) {case 'SET_FIRST_NAME':return { ...state, firstName: action.payload };case 'SET_LAST_NAME':return { ...state, lastName: action.payload };case 'SET_AGE':return { ...state, age: action.payload };default:return state;}
}function ComplexForm() {const [state, dispatch] = useReducer(reducer, initialState);// 使用 useMemo 缓存计算结果,避免每次重新计算const fullName = useMemo(() => {console.log('Calculating full name...');return `${state.firstName} ${state.lastName}`;}, [state.firstName, state.lastName]);const isAdult = useMemo(() => {console.log('Checking adult status...');return state.age >= 18;}, [state.age]);return (<div><inputtype="text"value={state.firstName}onChange={(e) => dispatch({ type: 'SET_FIRST_NAME', payload: e.target.value })}placeholder="First Name"/><inputtype="text"value={state.lastName}onChange={(e) => dispatch({ type: 'SET_LAST_NAME', payload: e.target.value })}placeholder="Last Name"/><inputtype="number"value={state.age}onChange={(e) => dispatch({ type: 'SET_AGE', payload: Number(e.target.value) })}placeholder="Age"/><div><p>Full Name: {fullName}</p><p>Adult Status: {isAdult ? 'Adult' : 'Not an Adult'}</p></div></div>);
}export default ComplexForm;

为什么要这样结合使用?

  • 性能优化:通过 useMemo,我们避免了每次状态更新都重新计算 fullNameisAdult。例如,在用户输入名字时,如果名字没有变化,那么 fullName 就不需要重新计算,从而提高性能。

  • 简化状态管理useReducer 提供了一种集中式的方式来管理组件状态,使得复杂的状态更新逻辑更加清晰和易于维护。

  • 避免重复计算useMemo 会缓存计算结果,只有当依赖项(比如 firstNamelastNameage)发生变化时才重新计算。这意味着即使组件重新渲染,fullNameisAdult 也不会重复计算,除非相关状态发生变化。

什么时候结合使用 useReduceruseMemo

  • 当组件有复杂的状态管理,并且你需要优化某些计算过程时,结合 useReduceruseMemo 会非常有效。
  • 特别是当状态更新频繁,但某些计算过程非常昂贵时,useMemo 可以减少不必要的重新计算,而 useReducer 提供了更强的状态管理能力。

总结

  • useReducer 用于管理复杂的状态,特别是需要依赖复杂逻辑或多个状态项时。
  • useMemo 用于缓存计算结果,减少昂贵计算的频繁执行,提升性能。
  • 这两个 Hook 可以结合使用,通过 useReducer 管理状态和通过 useMemo 缓存计算结果,优化性能,减少不必要的重新渲染和计算。
http://www.xdnf.cn/news/642889.html

相关文章:

  • Axure 基本用法学习笔记
  • 修复ubuntu server笔记本合盖导致的无线网卡故障
  • 触发进程守护服务的判断因数
  • 《帝国时代1》游戏秘籍
  • 【2025】基于Springboot + vue + 协同过滤算法实现的旅游推荐系统
  • 云手机应该怎么选?和传统手机有什么区别?哪些云手机支持安卓12系统?
  • 第五十节:增强现实基础-特征点检测与匹配
  • springboot中过滤器配置使用
  • 基于RK3576+FPGA+CODESYS工控板的运动控制模块方案
  • 字节跳动GPU Scale-up互联技术白皮书
  • 使用CRTP实现单例
  • [yolov11改进系列]基于yolov11引入双层路由注意力机制Biformer解决小目标遮挡等问题python源码+训练源码
  • 优秀技术文档的构建与优化之道
  • Typescript学习教程,从入门到精通,TypeScript 进阶语法知识点及案例代码详解(13)
  • QStandardItemModel的函数和信号介绍
  • Java单例模式:懒汉模式详解
  • MyBatis-Plus一站式增强组件MyBatis-Plus-kit:打造更优雅的通用CRUD解决方案
  • 15 dart类(get,set,静态,继承,抽象,接口,混入)
  • AUTOSAR图解==>AUTOSAR_SRS_Libraries
  • java数组,ArrayList,LinkedList
  • win主机,Ubuntu,IMX6ULL开发板网络通讯
  • 神经网络学习-Day35
  • 麒麟V10 SP1 2303使用记录(一)安装google浏览器
  • 提高:RMQ问题:【例 3】与众不同
  • 固态硬盘颗粒类型、选型与应用场景深度解析
  • 基于PySide6与pycatia的CATIA几何阵列生成器开发实践
  • 5.25 note
  • uni-app学习笔记十二-vue3中创建组件
  • ISO 20000体系:需求管理与容量管理含义与解释
  • 以下是修改Java版《我的世界》字体的分步指南(DeepSeek)