React 常用 Hook 基本用法以及注意事项

一、useState

useState 堪称是 React Hook 家族中最基础且使用频率极高的成员之一,它在函数组件里扮演着引入和管理状态的重要角色。

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

const Counter = () => {
  // 定义一个名为 count 的状态变量,初始值被设定为 0。这里的 useState 函数接受初始状态值作为参数,并返回一个包含两个元素的数组。
  const [count, setCount] = useState(0);

  const increment = () => {
    // 更新 count 状态,每次点击按钮时,通过调用 setCount 函数并传入新的状态值(这里是 count 加 1)来触发组件的重新渲染。
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default Counter;

注意事项:

  • 不要直接修改 state,而是始终使用 setState 函数来更新状态。直接修改 state 不会触发组件的重新渲染,可能导致数据不一致和难以排查的错误。
  • 如果新的状态值依赖于之前的状态值,推荐使用回调函数的形式传递给 setState,例如 setCount(prevCount => prevCount + 1)。这样可以确保在异步操作或者连续多次更新状态时,获取到的是最新的状态值,避免出现意外的结果。

二、useEffect

useEffect 犹如一位多面手,专门用于处理组件中的各种副作用。无论是数据的获取、对外部事件的订阅,还是手动对 DOM 进行修改等操作,它都能游刃有余地应对。它会在组件的生命周期特定阶段,即挂载、更新或者卸载时,精准地执行特定的操作。

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

const DataFetchingComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    // 模拟数据获取的异步操作。在这里,使用了 async/await 语法来使代码更加简洁和易读。
    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data');
      const jsonData = await response.json();
      setData(jsonData);
    };

    fetchData();

    // 返回一个清理函数,这个清理函数会在组件卸载时被执行。它的作用类似于类组件中的 componentWillUnmount 生命周期方法,用于清理在组件挂载或更新过程中创建的一些资源,比如取消订阅、清除定时器等。
    return () => {
      console.log('Component unmounted, clean up');
    };
  }, []); // 空数组表示只在组件挂载时执行一次。如果这里传入了依赖项数组,那么当依赖项中的任何一个值发生变化时,useEffect 就会再次执行。

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

export default DataFetchingComponent;

注意事项:

  • 确保正确设置依赖项数组。如果依赖项数组设置不正确,可能会导致 useEffect 执行次数过多或者过少,从而引发性能问题或者数据更新不及时的情况。例如,如果在 useEffect 内部使用了某个变量,但是没有将其添加到依赖项数组中,那么在该变量发生变化时,useEffect 不会重新执行,可能导致数据与预期不符。反之,如果依赖项数组中包含了一些不必要的变量,可能会导致 useEffect 频繁执行,影响性能。
  • 清理函数必须返回正确且有效的清理操作。如果在 useEffect 中进行了订阅操作,那么在清理函数中必须正确地取消订阅,否则可能会导致内存泄漏或者其他意外行为。

三、useRef

useRef 就像是一个神奇的容器,它返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。这个特性使得它在获取 DOM 元素或者保存一个能够跨越多个渲染周期的值时,发挥着不可替代的作用。

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

const InputFocusComponent = () => {
  const inputRef = useRef(null);

  useEffect(() => {
    // 在组件挂载后,通过 inputRef.current 精准地获取到输入框的 DOM 元素,并调用 focus 方法将焦点设置到该输入框上。这一步操作依赖于组件已经挂载完成,所以放在 useEffect 中,并确保依赖项数组为空,只在挂载时执行一次。
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input type="text" ref={inputRef} />
    </div>
  );
};

export default InputFocusComponent;

注意事项:

  • useRef 的值在组件的整个生命周期内是持久化的。这意味着它不会因为组件的重新渲染而被重置。在使用过程中,要特别注意不要对其进行过度的修改,以免影响组件的其他逻辑或者导致难以理解的行为。
  • 虽然 useRef 可以用于保存一些跨渲染周期的值,但它并不是用于管理状态的首选方式。如果需要触发组件的重新渲染,仍然应该使用 useState 或者 useReducer 等专门用于状态管理的 Hook。

四、useReducer

useReducer 作为 useState 的一种有力替代方案,尤其适用于那些复杂的状态管理场景。它接受一个 reducer 函数和一个初始状态作为参数,并返回当前状态以及一个 dispatch 函数,通过 dispatch 函数来触发状态的更新,遵循着一种类似于 Redux 的状态管理模式。

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

// 定义 reducer 函数,这是整个状态管理的核心逻辑所在。它根据传入的 action 类型来精确地更新状态,就像是一个状态处理的调度中心,决定了不同操作对状态的具体影响。
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

const ReducerComponent = () => {
  const initialState = { count: 0 };
  const [state, dispatch] = useReducer(reducer, initialState);

  const increment = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const decrement = () => {
    dispatch({ type: 'DECREMENT' });
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default ReducerComponent;

注意事项:

  • reducer 函数必须是纯函数。这意味着它不能产生任何副作用,不能修改外部变量,并且对于相同的输入(即相同的 state 和 action),必须始终返回相同的结果。否则,可能会导致状态管理的混乱和不可预测性。
  • 确保在 dispatch 操作时传递正确的 action 对象。action 对象的结构和内容应该与 reducer 函数中的处理逻辑相匹配,否则可能会导致状态更新失败或者出现意外的状态值。

五、useCallback

useCallback 像是一位智能的优化大师,它的主要使命是缓存函数,有效地避免在组件重新渲染时不必要地重新创建函数。这种特性在将回调函数传递给子组件,并且希望减少不必要的子组件重新渲染以提升性能时,显得尤为重要。

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

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  // 使用 useCallback 缓存 increment 函数。只有当依赖项数组中的值(这里是 count)发生变化时,才会重新创建 increment 函数。否则,将返回之前缓存的函数实例。
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <ChildComponent increment={increment} />
    </div>
  );
};

const ChildComponent = ({ increment }) => {
  return (
    <button onClick={increment}>Increment in Child</button>
  );
};

export default ParentComponent;

注意事项:

  • 正确设置依赖项数组。如果依赖项数组设置不准确,可能会导致缓存的函数没有按照预期进行更新或者重新创建,从而影响组件的逻辑正确性和性能。例如,如果依赖项数组中遗漏了某个在函数内部使用的变量,那么在该变量发生变化时,函数不会重新创建,可能导致使用旧的变量值进行操作。
  • 不要过度使用 useCallback。虽然它可以优化性能,但在一些简单的场景中,如果函数的创建成本并不高,过度使用可能会增加代码的复杂性,反而不利于维护。

六、useMemo

useMemo 恰似一个高效的计算缓存助手,它专注于缓存计算结果。只有当依赖项发生变化时,才会重新触发计算过程,从而避免了在不必要的情况下重复进行昂贵的计算操作,大大提升了组件的性能表现。

typescript 复制代码
import React, { useState, useMemo } from 'react';

const ExpensiveCalculationComponent = () => {
  const [number, setNumber] = useState(5);

  // 使用 useMemo 缓存计算结果。只有当 number 这个依赖项发生变化时,才会重新执行 expensiveCalculation 函数并更新缓存的结果。
  const result = useMemo(() => {
    console.log('Calculating...');
    return expensiveCalculation(number);
  }, [number]);

  const increment = () => {
    setNumber(number + 1);
  };

  return (
    <div>
      <p>Result: {result}</p>
      <button onClick={increment}>Increment Number</button>
    </div>
  );
};

// 模拟一个昂贵的计算函数,这里通过一个复杂的循环来模拟计算成本较高的操作。
const expensiveCalculation = num => {
  let sum = 0;
  for (let i = 0; i < 1000000; i++) {
    sum += i * num;
  }
  return sum;
};

export default ExpensiveCalculationComponent;

注意事项:

  • 依赖项的变化检测是基于引用相等性的。对于基本数据类型(如数字、字符串、布尔值),值的变化会导致引用的变化,从而触发 useMemo 的重新计算。但对于对象或数组等引用数据类型,如果只是内部属性的变化而对象的引用不变,useMemo 不会重新计算。在这种情况下,可能需要对数据进行深拷贝或者采用其他方式来确保依赖项的正确更新检测。
  • 如同 useCallback,不要盲目使用 useMemo。只有在计算成本较高且结果会被频繁使用的情况下,使用 useMemo 才有较大的性能提升意义。否则,可能会因为过度优化而使代码变得复杂难懂。

七、useContext

useContext 如同一条信息高速公路,专门用于在组件树中高效地共享数据。它巧妙地避免了传统方式中层层传递 props 的繁琐过程,使得数据能够在组件树的不同层级之间畅通无阻地流动。

ini 复制代码
import React, { createContext, useContext, useState } from 'react';

// 创建一个 Context,这就像是构建了一个数据共享的通道,所有连接到这个通道的组件都可以获取和使用其中的数据。
const ThemeContext = createContext('light');

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
};

const Button = () => {
  // 使用 useContext 获取主题。通过连接到 ThemeContext 这个数据通道,Button 组件可以直接获取到当前的主题值,而无需从父组件一层一层地接收主题 props。
  const theme = useContext(ThemeContext);
  return (
    <button style={{ backgroundColor: theme === 'light'? 'white' : 'black', color: theme === 'light'? 'black' : 'white' }}>
      Button with Theme
    </button>
  );
};

const App = () => {
  return (
    <ThemeProvider>
      <Button />
    </ThemeProvider>
  );
};

export default App;

注意事项:

  • useContext 的使用应该谨慎。过度使用可能会导致组件之间的耦合度增加,使得组件的复用性降低。因为一个组件如果过多地依赖于 context 中的数据,那么它在不同的 context 环境下可能无法正常工作。

  • 当 context 的值发生变化时,所有依赖该 context 的组件都会重新渲染。所以在使用 useContext 时,要考虑到可能引发的性能问题。

相关推荐
GISer_Jing10 分钟前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪1 小时前
CSS复习
前端·css
咖啡の猫3 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲5 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5816 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路6 小时前
GeoTools 读取影像元数据
前端
ssshooter7 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry7 小时前
Jetpack Compose 中的状态
前端
dae bal8 小时前
关于RSA和AES加密
前端·vue.js
柳杉8 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化