盘点React常用Hooks(1)—— useState、useEffect | 青训营

228 阅读6分钟

React Hooks

在现代的React开发中,函数组件已经成为了构建用户界面的主要方式。为了更好地管理组件状态和副作用React引入了Hooks,它们能够让你在函数组件中使用状态和其他React特性。本文将介绍一些常用的React Hooks,帮助你优化你的函数组件代码。

1. useState - 状态管理

函数定义

useState 用于在函数组件中引入状态,通过调用 useState(initialValue)返回一个包含状态变量和更新状态的函数的数组。这个函数组合使得在函数组件中保存和更新状态变得非常方便。

useState 函数返回数组包含两个参数:

  • state:这是一个变量,代表当前的状态。它会保存着组件的当前状态值,并在组件渲染时显示这个值。
  • setState:这是一个函数,用于更新状态。通过调用这个函数,你可以更新 state 的值,并触发组件的重新渲染。

使用示例

以下是如何使用 useState 来声明和管理状态的示例:

import React, { useState } from 'react';

function Counter() {
  // 使用 useState 声明一个名为 count 的状态变量,初始值为 0
  const [count, setCount] = useState(0);
  
  // setCount 是一个函数,调用它可以更新 count 的值
  const increment = () => { setCount(count + 1); };

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

浏览器实时响应效果展示:

动画.gif

在上面的示例中,useState(0) 表示初始状态为 0,返回的数组的第一个元素 count 是当前状态的值,第二个元素 setCount 是一个用于更新状态的函数。通过调用 setCount 函数,你可以更新 count 的值。在函数式更新的情况下,你还可以传递一个函数,以便基于先前的状态进行更新,避免出现因异步更新而导致的问题。

请注意,每个 useState 调用都会创建一个独立的状态变量,它与特定的组件实例关联。所以你可以在组件中多次使用它来管理不同的状态。

与普通变量声明的区别

一些初学者(比如苯人)最开始一直不能理解useState 和普通变量声明的区别,感觉使用这个Hook好像没有什么必要。实际上它们在React函数组件中的使用还是很不同的,主要是因为 useState 是用来管理组件状态的特殊Hook。下面是它们之间的一些主要区别:

  • 触发重新渲染:

    • 当使用useState来声明状态并通过对应的更新函数修改状态时,React会自动识别状态变化,并重新渲染组件,以反映状态的变化。这是useState内部的特性,使得组件状态的更新与重新渲染变得无缝连接。
    • 在普通变量声明中,即使变量的值发生了变化,React并不会自动触发组件的重新渲染。你需要手动调用setState或其他触发重新渲染的方法来通知 React,让它知道组件需要重新渲染以反映变量的变化。
  • React 的状态更新机制:

    • 当使用useState声明状态并更新状态时,React 会根据状态的变化,以最优的方式批量更新组件,以提高性能。
    • 在普通变量声明中,即使更新了多个变量,React 并不会像 useState 那样进行批量更新,可能会导致不必要的性能开销。

总的来说,useState是专门为React函数组件提供的状态管理机制,具有自动触发重新渲染、状态持久性和优化的能力。普通变量声明则更适合于一般的JavaScript逻辑,但在React组件中可能需要更多的手动操作来管理状态和更新。

2. useEffect - 副作用管理

函数定义

当在 React 组件中使用 useEffect 时,你可以传递一个回调函数作为其第一个参数,这个回调函数就被称为 "副作用函数" 或 "effect 函数"。这个 effect 函数在组件渲染后(初始渲染及每次更新渲染)都会被调用。它允许你在组件渲染后执行各种副作用操作,比如数据获取、订阅、DOM 操作等。

useEffect 函数接收两个参数:

  1. Effect 函数(必需): 这是一个包含你要在组件渲染后执行的副作用操作的回调函数。它可以包含异步操作、订阅、状态更新等。
  2. 依赖数组(可选): 这是一个数组,包含了在 effect 函数中使用的状态变量。只有当依赖数组中的某个状态变量发生变化时,effect 函数才会重新执行。如果不传递这个数组,effect 会在每次组件更新时都执行。

使用示例

以下是一个示例,展示了 useEffect 的基本语法和用法:

import React, { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 模拟数据获取的异步操作
    setTimeout(() => {
      const fakeData = ['Apple', 'Banana', 'Orange'];
      setData(fakeData);
    }, 2000); // 2秒后更新数据
  }, []); // 传递空数组作为第二个参数以触发仅在组件挂载时运行

  return (
    <div>
      <h1>数据获取示例</h1>
      {data ? (
        <ul>
          {data.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
      ) : (
        <p>正在获取数据...</p>
      )}
    </div>
  );
}

export default DataFetcher;

动画.gif

常见用法

除了上述示例中的用法(传递空数组作为第二个参数以触发仅在组件挂载时运行),useEffect还有一些其他的用法,以下是一些useEffect的常见用法示例:

  • 根据状态变化触发副作用:

你可以通过在useEffect的依赖数组中传递状态变量,以便在这些状态变量发生变化时触发副作用。这里的count 是状态变量,而不是普通的 JavaScript 变量。

useEffect(() => { 
    console.log(`Count has changed to: ${count}`);
    
    // 在这里可以执行基于状态变化的副作用操作 
    
}, [count]); // 当 count 发生变化时触发副作用

你可以在副作用函数内部执行基于状态变化的操作,比如数据获取、DOM操作、更新其他状态等。当 count 发生变化时,副作用函数就会根据变化的值来执行相应的操作。

  • 清理副作用:

你可以在useEffect内部返回一个清理函数,用于在组件卸载或重新渲染之前执行一些清理操作,比如取消订阅、清除定时器等。

useEffect(() => {
  const subscription = subscribe();
  
  return () => {
    // 在组件卸载或重新渲染之前执行清理操作
    unsubscribe(subscription);
  };
}, []);

这种模式用于确保在组件生命周期中管理资源的创建和清理,以便在不需要这些资源时释放它们。这有助于防止内存泄漏提高应用的性能和可维护性

  • 防止过多的副作用:

如果你的副作用函数内部会订阅事件、注册事件监听器等,可能会导致每次渲染都创建新的订阅。为了避免这种情况,你可以结合上述两种用法,配置useEffect的第二个参数,使得只在特定状态变化时才执行副作用。

useEffect(() => {
  const subscription = subscribe();

  return () => {
    unsubscribe(subscription);
  };
}, [specificDependency]);
  • 异步副作用:

如果在副作用函数内部有异步操作,你可以将异步操作封装在函数中,然后在 useEffect 内部调用这个函数。这样可以更好地处理异步操作的错误和取消。

useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await fetch('api/data');
      const data = await response.json();
      setData(data);
    } catch (error) {
      setError(error);
    }
  };

  fetchData();
}, []);

学习完上述的常见用法,这个时候有些同学可能会产生疑问,组件刚挂载时会不会触发根据状态变化执行的副作用呢? 答案是“会的”,那如果我们有些业务场景不希望在组件刚挂载时就触发这个副作用,又想根据状态变化触发,该怎么办呢?下面这个示例可以很好地解决我们的疑问:

import React, { useState, useEffect } from 'react';

function ExampleComponent() {
  const [count, setCount] = useState(0);
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    if (isMounted) {
      console.log(`Count has changed to: ${count}`);
      // 在这里执行根据状态变化的副作用操作
    } else {
      setIsMounted(true); // 组件挂载后设置 isMounted 为 true
    }
  }, [count, isMounted]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

export default ExampleComponent;

在这个示例中,我们添加了一个名为 isMounted 的状态变量,用来标识组件是否已经挂载。在 useEffect 中,我们使用了一个条件语句来控制副作用的执行。如果 isMountedtrue,则执行根据状态变化的副作用操作;如果 isMountedfalse,则将 isMounted 设置为 true,表示组件已挂载。

这种模式确保了在组件挂载时不执行副作用,而只在组件挂载后且状态发生变化时才执行副作用。这可以防止在初始渲染时执行可能不必要的操作。

总结

在实际的React开发中,useStateuseEffect是最常用也是最重要的HookReact官方提供的钩子函数还有很多,但个人认为学会了这两个钩子函数,已经可以应对90%的简单业务场景了(随口说的)。其他较常用的Hook将在接下来的文章中展开介绍。

[第二篇](盘点React常用Hooks(2)—— useRef | 青训营 - 掘金 (juejin.cn))