深入理解React-Hooks(useState和useEffect)

153 阅读5分钟

Hooks是什么?

Hooks出现之前,React的函数组件只能是一个纯函数,没有状态(state)和生命周期方法。如果你需要用到状态或生命周期功能,就必须使用类组件(class component)。但类组件语法相对复杂,容易出错,也不利于代码复用。

Hooks 的出现解决了这些问题:

  • ✅ 允许在函数组件中使用 state 和其他 React 特性;
  • ✅ 简化组件逻辑,避免类组件中 this 指向等问题;
  • ✅ 提高代码可读性和逻辑复用能力。

useState

先让我们从一段初中函数讲起,2x + 1 = y,其中x叫自变量,y叫因变量,在常见的七个Hooks当中,除了useRef其它的都可以归类为自变量和因变量。让我们通过下述例图来更好区分这七个Hooks

image.png

光看这个好像也不能很好的解释useState是什么?让我们通过下面的代码来继续学习一下useState的使用和说明吧❗❗❗

import { useState } from 'react'
import './App.css'

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

const ChangeCount = ()=>{
  useCount(count + 1)
}

  return (
    <div>
      <button onClick={ChangeCount}>{count}</button>
    </div>
  )
}

export default App

可以看到上述函数组件的行为是:首先我们使用useState定义了count,count的变化会导致button的变化。所以我们可以说useState是自变量,自变量的变化对应了视图的变化。当我们现在加入因变量看看代码会发生哪些变化:

import { useState } from 'react'
import './App.css'

function App() {
const [x,setX] = useState(0)
const y = 2*x + 5

const ChangeX = ()=>{
  setX(x + 1)
}

  return (
    <ul onClick={ChangeX}>
      <li>X is{x}</li>
      <li>Y is{y}</li>
    </ul>
  )
}

export default App

可以看到x变化y也跟着变化,最终视图产生变化对于useMemouseCallback的使用等到使用性能优化的时候我们再来写文章聊聊如何正确使用这两个Hooks。让我们看看上述代码的动态效果如何吧!!!

useState使用效果.gif



useEffect基本语法

React类组件里,有明确的生命周期函数(如 componentDidMountcomponentDidUpdatecomponentWillUnmount )来处理组件挂载、更新和卸载时的逻辑。而在函数组件中, useEffect Hook起到了类似生命周期函数的作用,下面解释其意义和对应关系:

useEffect(() => {
  // 副作用代码
  return () => {
    // 清理函数
  };
}, [dependencies]);
  • 第一个参数 :是一个回调函数,包含副作用逻辑。

  • 返回函数 :可选的清理函数,在组件卸载或下一次副作用执行前调用。

  • 第二个参数 :可选的依赖项数组,控制副作用何时执行。

组件的功能不仅局限于描述视图还能产生副作用,副作用是函数式编程中的概念,对于一个函数,如果固定的输入一定会产生固定的输出:这里x一定时输出的结果就是一定的,就可以说这是一个纯函数。

image.png

当我们在函数中添加一个随机数z时,x一定时输出是不固定的。这时我们就可以说函数是包含副作用的,在函数中,可以通过useEffect来定义有副作用的因变量。当x变化后,将当前页面的标题修改为x的值,改变标题的这一行为就属于副作用。还有常见的副作用有请求数据,操作DOM这些都属于副作用,都可以使用useEffect来处理。

functiion calc(x){
    const z = Math.random();
    return 2 * X + 1 + z;
}

可以看到下面这段代码的使用效果,当点击视图调用changeX时,不仅视图变化,title也跟着变化。可见自变量导致因变量变化后,因变量不仅可以作用于视图也能触发副作用。

useEffect(()=>{
  document.title = x
},[x])

useEffect副作用.gif



组件挂载时执行

当依赖项数组为空 [] 时, useEffect 回调函数仅在组件首次渲染完成后执行一次,模拟了类组件的 componentDidMount 生命周期。

  console.log('组件开始执行');
  
  useEffect(()=>{
    console.log('组件渲染完成');
  })

  console.log('组件的模版编译');

意义 :适用于初始化操作,如获取数据、订阅事件等,避免在每次渲染时重复执行。 可以看到浏览器打印顺序效果:

image.png



组件更新时执行

当依赖项数组包含变量时, useEffect 会在这些变量发生变化时执行回调函数,模拟了类组件的 componentDidUpdate 生命周期。

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    console.log(`count 值已更新为 ${count}`);
  }, [count]);

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

意义 :可以在特定状态或属性变化时执行副作用,实现数据更新后的逻辑处理。



组件卸载时执行

在 useEffect 中返回一个清理函数,该函数会在组件卸载前执行,模拟了类组件的 componentWillUnmount 生命周期。

import{
  useState,
  useEffect
}from 'react';
const Timer = ()=>{
  const [time,setTime] = useState(0);

  console.log('组件函数');
  console.log('JSX编译');
  useEffect(()=>{
    console.log('组件渲染完了');
    const interval = setInterval(()=>{
      setTime(prevTime => prevTime + 1)
    },1000)
    return()=>{
      console.log('组件卸载了');
      clearInterval(interval)
    }
  },[]);
  return(
    <div>已经运行{time}秒</div>
  )
}

export default Timer

意义 :用于清理副作用,如取消定时器、取消订阅等,防止内存泄漏。

useEffect通过依赖项数组和清理函数,灵活地组合了类组件多个生命周期函数的功能,让函数组件能处理各种副作用逻辑。这种设计使得代码逻辑更加集中,开发者可以根据不同的依赖项组合,在合适的时机执行副作用和清理操作。



useEffect的一段demo

import { 
  useState,
  useEffect
} from 'react'
import './App.css'
import Timer from './components/Timer'

function App() {
  const [count, setCount] = useState(0)
  const [num, setNumber] = useState(0)
  const [repos, setRepos] = useState([])
  const [isTimerOn, setIsTimerOn] = useState(true)


  // useEffect需要干净的函数
    useEffect(() => {
      // api 数据 动态的 
      console.log('只在组件挂载时运行一次!!!');
      const fetchRepos = async () => {
        const response = await fetch('https://api.github.com/users/shunwuyu/repos')
        const data = await response.json()
        console.log(data);
        setRepos(data)
      }
      fetchRepos();
    }, [])

    // 组件的模版编译
    // 挂载到#root节点上
    return (
    <>
      {count}
      <button onClick={() => {
        setCount(count + 1)
      }}>点我</button>
      <br />
      {num}
      <button onClick={() => {
        setNumber(num + 1)
      }}>点我</button>
      <ul id="repos">
      {
        repos.map(repo => <li key={repo.id}>{repo.full_name}</li>)
      }
      </ul>
      {isTimerOn && <Timer />}
      <button onClick={()=>{
        setIsTimerOn(!isTimerOn)
      }}>toggle timer</button>
    </>
  )
}

export default App

让我们看看上述代码在控制台当中的执行过程吧:

卸载组件效果.gif



总结

React Hooks的本质是自变量与因变量,其中useState用来定义自变量,useMemouseCallback定义无副作用的因变量。useEffect定义有副作用的因变量,为了方便操作更多自变量有了useReducer,为了跨组件层级的操作自变量,有了useContext,最后为了让组件逻辑更灵活有了useRef