90%的前端面试者回答错误:React Hooks

520 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情

1.问题1

如下,我们点击修改num的按钮3次,请问控制台输出什么?

import {useEffect,useState} from "react"
export default function Home() {
  const [num,setNum] = useState(1)
  useEffect(()=>{
    setInterval(()=>{
      console.log(num)
    },1000)
  },[])
   
  //修改num
  const addNum = () => {
    setNum(num+1)
  }
  return <div>
    <div>{num}</div>
    <div onClick={addNum}>修改num</div>
  </div>
}

粗心大意的我第一次看就自信的回答控制台输出1,2,3。这就上了面试官的大当了。这是一道十分经典的hooks陷阱问题。

hooks陷阱

react数据发生改变后,函数组件会重新执行一遍。此时这里面就有闭包陷阱问题,react每次更新都是一份独立的函数组件,学过闭包的都清楚,函数里面的函数返回所依赖的变量会被延迟释放,因此每次组件更新后,每份组件都对自己的数据保持单独的引用。

题目解答

  1. 首先useEffect依赖是空数组,我们知道定时器只会在组件初始化后执行一次,组件更新不会产生新的定时器。
  2. 当页面挂载完成后,此时定时器依赖的num是1。然后我们点击修改num三次,虽然组件更新了,但是定时器一直是第一次的,它引用的变量也一直是1。所以无论我们点击多少次修改Num.控制台输出一直是1

页面效果

1.gif

2.问题2

如下,点击修改Num后,请问控制台输出什么?页面最终显示什么?

import {useState} from "react"
export default function Home() {
  const [num,setNum] = useState(1)

  const addNum = () => {
    setNum(num+1)
    setNum(num+2)
    console.log(num)
    setNum(num+3)
    setNum(num+4)
    console.log(num)
  }
  return <div>
    <div>{num}</div>
    <div onClick={addNum}>修改num</div>
  </div>
}

这个题不清楚useState的数据更新可能就给出各种答案,首先我们聊聊useState的核心

usetate特点

  1. 异步

useState的更新是异步的

  1. 合并更新

useState多次对同一份数据更新时,会出现数据合并现象,这个现象vue也是存在。并且会取批量更新的最后一个去更新数据

  1. 特殊性

useState存在一个特殊机制可以破解合并更新,在useState修改数据时传递一个回调函数即可

题目解答

首先开始页面挂载后,页面显示的是1。当点击修改num后,根据事件循环队列机制,首选执行同步代码,控制台输出1,1.然后多份useState对一个数据进行更新,因此产生更新合并,选择最后的数据作为更新因此num+4 最终页面会显示5

页面效果

2.gif

3.问题3

如下,点击修改num,请问控制台输出什么?页面最终显示什么?

import {useEffect,useState} from "react"
export default function Home() {
  const [num,setNum] = useState(1)

  const addNum = () => {
    setNum(num+1)
    setNum(num+2)
    console.log(num)
    setNum(num=>num+1)
    setNum(num=>num+1)
    console.log(num)
  }
  return <div>
    <div>{num}</div>
    <div onClick={addNum}>修改num</div>
  </div>
}

问题解答

这个是对问题2的拓展,其实问题2已经给出了useState的特点,首次页面显示1.当我们点击修改按钮后,首先执行同步代码,控制台输出1,1。然后自己看上面对useState的特点总结,其中两份setNum会产生合并,因此num+2。但是下面的setState两份传入的是回调函数,不会对批量更新进行合并,二者都会执行。因此最终num+4,页面显示5

页面效果

3.gif

4.问题4

使用useState修改数据后,如何获取最新修改的数据?

方案一:setState传入回调函数

setState的参数其实是可以传递一个回调函数,回调函数的参数是更新前的数据,我们在执行修改后可以直接获取其最新数据。

import {useState} from "react";
export default () => {
const [age,setAge] = useState(0);
    return (
        <div>
            <div onClick={()=>setAge((age)=>{
                age = age+1;
                console.log("最新的age",age)//1
                return age
              })}>修改age
            </div>
        </div>
    )
}

方案二:useEffect

useEffect监听数据,当数据更新后会自动触发useEffect的回调函数,由于useEffect的回调函数执行时,最新的数据已经挂载到dom上,因此我们可以获取最新的数据。

import {useState,useEffect} from "react";
export default () => {
    const [age,setAge] = useState(0);
    useEffect(()=>{
        console.log("最新的age",age);
    },[age])
    
    return (
        <div>
            <div onClick={()=>setAge(age+1)}>
              修改age
            </div>
        </div>
    )

}

总结

希望大家点赞支持,没懂的请大家评论区见