🔥从零开始学React:useEffect和useState详解(第一期)🔥

306 阅读8分钟

前言

在React的世界中,Hooks无疑是一场革命性的变革。自2019年2月React 16.8版本正式引入Hooks以来,它彻底改变了我们构建React组件的方式,让函数组件拥有了之前只有类组件才能实现的状态管理和生命周期控制能力。

作为本系列文章的第一篇,我特意选择了三个最基础也最重要的Hooks——useStateuseEffect和作为起点,而至于为什么先选择这三个Hooks呢?是因为:

  • 这2个比较基础useState处理组件状态,useEffect管理副作用,它们几乎涵盖了日常开发中的大部分场景。

  • 渐进式学习:从简单到复杂是学习的最佳路径。useState是最容易理解的Hook,useEffect稍复杂但逻辑清晰

在接下来的内容中,我们将深入探讨每个Hook的工作原理、最佳实践以及常见陷阱。我会通过清晰的代码示例和实际应用场景,帮助你真正理解这些概念,而不仅仅是记住语法。

无论你是React新手,还是有一定经验的开发者想要系统梳理Hooks知识,这篇文章都将为你打下坚实的基础。OK,Let us go!

什么是Hook?

Hook 函数 是 React 16.8 引入的一种新特性,它允许你在函数组件(Function Components) 中使用 React 的状态(State)副作用(Side Effects)上下文(Context) 等原本只能在类组件(Class Components)中使用的功能。

简而言之,Hook的出现让我们编写代码更加方便了,之前用类组件编写代码会非常冗长且麻烦。

2C8A977359DC22E8961AB06F4344BB3A.jpg

useState

为什么需要 useState

如何介绍一下useState呢?在这之前,我们先来做一个计数器的例子来深入理解一下useState存在的必要吧!

我们来创建一个按钮,按一下count就+1,并且把count展示在页面上,如果利用传统做法的话,我们应该是这么做的:

function App() {
  let count = 0;

  const addCount = () => {
    count++;
    document.querySelector('p').innerHTML = `count:${count}`;
    console.log(count);
  }

  return (
    <>
      <p>count:{count}</p>
      <button onClick={addCount}>点击+1</button>
    </>
  )
}

export default App

在这里当元素发生变化时,我们需要手动操作DOM,让标签内的内容进行更新,并重新渲染在页面上,如果我们不手动操作DOM元素更新,就会这样:

屏幕截图 2025-08-18 155431.png

count的值确实是改变了,但是页面上的值并没有及时更新,当遇到成千上万条数据更新时,就麻烦了,所以我们有了react的hook函数!

利用我们的useState,我们可以实现 当数据发生变化时,自动检测DOM并渲染更新!

useState详解

接下来我们可以把代码修改为react应有的特色:

import { useState } from 'react'

import './App.css'

function App() {
  const [count, setCount] = useState(0);

  const addCount = () => {
    setCount((count) => count + 1);
    console.log(count);
  }

  return (
    <>
      <p>count:{count}</p>
      <button onClick={addCount}>点击+1</button>
    </>
  )
}

export default App

这样我们就可以不用手动修改DOM,也能实现DOM的自动更新了!

接下来我们来讲一下useState的方方面面:

使用一个useState是这样用的:

image.png

[]解构用的,useState返回一个具有两个值的数组

第一个值是初始值,我们用state作为第一个参数接收;

第二个值是一个函数,我们用setState作为第二个参数接收,这个函数可以修改数据状态,也就是修改state的值

image.png

接下来是修改数据状态,我们需要一个函数,并且按按钮之后触发,那么就定义了addCount

  const addCount = () => {
    setCount((count) => count + 1);
    console.log(count);
  }

我们利用了setCount来修改了数据状态,实现了count+1,并由相关部分检测到了数据变化,实现了DOM的自动更新,那么这个setCount是如何用的?且看下方:

image.png

setState是用来修改数据状态的函数,它接收一个回调函数,回调函数接收一个参数 state ,即原来的数据状态

setState 的回调函数的唯一职责就是基于当前状态(state)计算并返回新的状态值。React 会严格按照你返回的值更新状态。

也就是说,你这个回调函数返回了什么,React就会把原来的数据更新成什么。

箭头函数后加括号 () => () 的含义

在 React 的 setCountsetState 中,你可能会看到这样的写法:

setCount((prevCount) => (prevCount + 1));

这里的 () => () 是一种返回值的箭头函数写法,直接加括号,就相当于return......

setState?异步?

细心的小伙伴可能发现了,当我们每次都点击按钮的时候,控制台总是输出上一次count的值,这是为什么呢?

image.png

image.png

这是因为setState函数其实是一个异步函数,这意味着当你调用 setCount 时,状态不会立即更新。相反,React 会将这些更新放入队列中,并且会在稍后的某个时间点批量处理它们以优化渲染性能。

再比如下面的例子:

import { useState } from 'react'

import './App.css'

function App() {
  const [count, setCount] = useState(0);
  const handleClick = function () {
    // setState 函数式更新语法
    // 保证每个更新都基于上一个最新的数据更新
    // 界面的更新合并为一次更新
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    console.log(count);
    setCount(prev => prev + 1);
    

  }
  return (
    <>
      <p>当前记录数字:{count}</p>
      <button onClick={handleClick}>+3</button>
    </>
  )
}

export default App

运行后,结果是这样的:

image.png

image.png

这充分说明setState是一个异步函数,在这里我们每一次点击按钮都是先执行了console.log.

关于命名

针对于useState,我们解构的时候总是命名为const [xxx,setXxx] = useState('')(set后首字母大写),这些其实不是强制要求的,只是大家的俗成约定,让它们的可读性更高,让别人一看就看出来这个变量是干什么的,在这里还是建议大家都这么用哈。

useEffect

什么时候会用到 useEffect?

useEffectreact最强大的API之一,作为初学者,你更多的可能会用来在页面渲染的一瞬间获取API。

useEffect可以让你在组件渲染后运行一段代码。

比如说,我有一个数据状态,假如是一个计时器的count,我想让它在每一次改变的时候都输出Yeah!!!Changed!!,这个时候我们就可以用useEffect,再比如,我想让每个人第一次访问我的博客的时候都看到一个框,写着欢迎你,我就可以利用useEffect

useEffect详解

它的语法是这样的:

image.png

后面是依赖项数组,里面可以添加依赖项:

如果里面不添加任何依赖项,即为空数组时,这个函数将在页面初次渲染时执行。

如果后面成这个样子的时候(无依赖),则会在每次渲染的时候都执行,会引起性能问题:

useEffect(()=>{})

里面可以添加数据状态,假如我希望在count更新时执行函数:

useEffect(()=>{console.log('updated')},[count]);

我希望 date 变化的时候也能更新,那就这样:

useEffect(()=>{console.log('updated')},[count,date]);

这样每次countdate更新渲染的时候,控制台都会输出updated.

如何在页面渲染时获取API?

在这里我们把上面提到的在页面一开始渲染时获取API的操作给大家看看,当然获取API不是只和useEffect一起用,后面还有更多的API...

   useEffect(()=>{
    // api 数据 动态的
    console.log('console after render');
    const fetchRepos = async()=>{
      const response = await fetch('https://api.github.com/users/yourName/repos');
      const data = await response.json();
      console.log(data);
      
      setRepos(data);
    }
    fetchRepos();
   },[])

在这里我们定义了一个数据状态repos负责接收fetch到的数据,在useEffect中执行了函数,获取到了数据,并修改了数据状态。

useEffect 模拟组件的生命周期

看到这里你也许会好奇:组件还有生命周期?

没错!组件挂载的时候就是相当于出生了,每一次更新都是一次成长,而最后卸载的时候,组件就死亡了,就完成了它的任务。

而 useEffect 在展现组件的生命周期时是这样的:

let count = 1;

// count ++ , 让它++的逻辑就不写了

useEffect(()=>{
console.log('start');
console.log('changed')
return ()=>{
    console.log('end')
}
},[count])   

假如上面的生命周期和 count 有关,第一次,当我们加载页面的时候,组件挂载,生命周期开始,

输出 start,changed,

当其发生改变后,原来的组件卸载,输出end,新的组件挂载,继续输出输出 start,changed

(在这里你可以这么理解,return后的执行函数是函数卸载时执行的,而每一次成长代表着旧自己的消亡和新自己的诞生

而当组件完全卸载,不存在于DOM时,只会输出end

总结

useState是用来定义数据状态的,也就是说用来定义变量的,useState返回两个值,一个初始值,一个函数,函数一般用setXxx来接收,它用来修改数据状态。

image.png

useEffect是个“副作用”,用了它就能在渲染后做自己想做的事情:

依赖项的数组什么都没有的时候,它只会在页面渲染后执行一次回调函数

image.png

连依赖项数组都没有的时候,它就疯了,不管谁渲染,都会让回调函数执行一次

image.png

当依赖项数组有多个元素时,这些元素无论是谁发生变化,回调函数都要执行一次

image.png