旺仔小糖

react常见错误 a year ago

react
3829个字符
共有113人围观

setState数据不同步

现在我们有这样一个demo,当点击button时,我们期望count以4递增

可是当我们点击按钮后:

count并没有像我们预期的那样增加

这是因为所有的count还是上一次的count

当点击点1次时,count初始化为0,上面的add相当于:

    setCount(0 + 1)
    setCount(0 + 1)
    setCount(0 + 1)
    setCount(0 + 1)

当点击点2次时,上一次的count变成了1,上面的add相当于:

    setCount(1 + 1)
    setCount(1 + 1)
    setCount(1 + 1)
    setCount(1 + 1)

那么如何解决state不同步的问题呢?

答案是用回调函数, 用上一次的state更新下一次的state

我们更新add方法再来尝试一下:

    setCount(previous => previous + 1)
    setCount(previous => previous + 1)
    setCount(previous => previous + 1)
    setCount(previous => previous + 1)

setState: 普通值vs object

来看一个demo:

import { useState } from 'react'

type User = {
  name: string;
  age: number;
  sex: string;
}

function App() {
  console.log("component is rendering...")
  const [num, setNum] = useState<number>(0)
  const [user, setUser] = useState<User>({
    name: 'scott',
    age: 18,
    sex: ''
  })

  const changeNum = () => {
    setNum(0)
  }

  const changeUser = () => {
    setUser({
      name: 'scott',
      age: 18,
      sex: ''
    })
  }

  console.log("user", user)

  return (
    <>
      <button onClick={changeUser}>change user</button>
      <button onClick={changeNum}>change num</button>
      <div>number:{num}</div>
      <div>
        <ul>
          <li>name:{user.name}</li>
          <li>email:{user.age}</li>
          <li>address: {user.sex}</li>
        </ul>
      </div>
    </>
  )
}

export default App

我们来分别点击 change numchange user按钮:

分析:

  • num值不变:当我们点击change num时,由于set的value值没有改变,所以组件就没有重新渲染。
  • userset的值和初始值一样,按理说应该没变,但是奇怪的是每次都重新渲染了

到底是什么原因呢?

这就是js传值:普通值和对象的区别

来看看下面的演示 你就明白了

传递普通值是value值的比较,传递arry object的时候,是地址的比较

所以在setvalue的时候如果值是object那么组件会一直重新render

useEffect的依赖也是如此,将依赖转化为普通值可以大大提高性能

  useEffect(()=>{
    //do sth
  },[user.age]) 

setState更新object的正确姿势

当state为object时,更新value的正确方式是先copy再赋值,原因是地址变了, 之前的值无法保留

来看看下面的demo:

import React, { useState } from 'react'

type User = {
  name: string;
  age: number;
  sex: string;
}

function App() {
  console.log("component is rendering...")
  const [user, setUser] = useState<User>({
    name: 'scott',
    age: 18,
    sex: ''
  })

  const nameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setUser({
      ...user,
      name: e.target.value,
    })
  }

  console.log("user", user)

  return (
    <>
      <input type="text" onChange={nameChange} value={user.name} />
      <div>
        <ul>
          <li>name:{user.name}</li>
          <li>email:{user.age}</li>
          <li>address: {user.sex}</li>
        </ul>
      </div>
    </>
  )
}

export default App

当然我们可以进一步优化,写成下面这种方式更完美 😋

 const nameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setUser(prev => ({
      ...prev,
      name: e.target.value,
    }))
  }

清理Event Listener

1, 如果不清理

import { useEffect, useState } from 'react'

function App() {
  const [count, setCount] = useState<number>(0)

  useEffect(() => {
    setInterval(() => {
      console.log("interval function is running...")
      setCount(count + 1)
    }, 1000)
  }, [count])

  return (
    <>
      {count}
    </>
  )
}

export default App

刷新浏览器,我们来看看输出:

可以看到前几秒输出还正常 越往后页面刷新都感觉有点吃力了 为什么呢?看下控制台的输出 - 异常夸张,它并不是1s打印一次,而是1s 几千次甚至后面数万次

所以清理Event Listener非常有必要

2, 来看看清理后

import { useEffect, useState } from 'react'

function App() {
  const [count, setCount] = useState<number>(0)

  useEffect(() => {
    const i = setInterval(() => {
      console.log("interval function is running...")
      setCount(count + 1)
    }, 1000)
    return () => {
      //清理工作
      clearInterval(i)
    }
  }, [count])

  return (
    <>
      {count}
    </>
  )
}

export default App

刷新浏览器,我们来看看输出:

可以看到console的次数和页面显示的此时是一致的