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);
    
        // 组件卸载时清除定时器并设置标志为 false
        return () => {
          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;  // 组件挂载时设置为 true
    
        const 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>
      <input
        type="text"
        value={state.firstName}
        onChange={(e) => dispatch({ type: 'SET_FIRST_NAME', payload: e.target.value })}
        placeholder="First Name"
      />
      <input
        type="text"
        value={state.lastName}
        onChange={(e) => dispatch({ type: 'SET_LAST_NAME', payload: e.target.value })}
        placeholder="Last Name"
      />
      <input
        type="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 缓存计算结果,优化性能,减少不必要的重新渲染和计算。

你可能感兴趣的:(react,react.js,javascript,前端)