新手如何使用useState

69 阅读4分钟

useState(initialState)

  const [age, setAge] = useState(28);
  const [name, setName] = useState('Taylor');
  const [todos, setTodos] = useState(() => createTodos());
  1. initialState 可以接受任何类型的数据
  2. 如果接受一个函数,那么这个函数需要保证是一个纯函数(pure)。这个函数不接受参数,同时它需要返回任何类型的数据,作为初始值

1. 不可局部更新

import {useState} from "react"

function App() {
  const [user, setUser] = useState({
    name: "John",
    age: 20
  })
  const handleClick = () => {
    setUser({
      // 这里只更新age,会导致姓名消失
      age: user.age + 1
    })
  }
  return (
    <>
      <button onClick={handleClick}>addAge</button>
      <div>{user.name}</div>
      <div>{user.age}</div>
    </>
  )
}

export default App

上面的代码,当点击按钮之后,会导致 name 属性消失。其原因是React.useStatesetState 并不会去帮你合并对象。正确的写法如下:

 const handleClick = () => {
    setUser({
      ...user, // 先copy 之前的对象
      age: user.age + 1
    })
  }

2. 新的值和旧的值地址不相等

n  !== n  

import {useState} from "react"

function App() {
  const [user, setUser] = useState({
    name: "John",
    age: 20
  })
  const handleClick = () => {
    user.age = user.age + 1 
    setUser(user) // 新传入的user 和旧的 user相等,则不会触发更新
  }
  return (
    <>
      <button onClick={handleClick}>addAge</button>
      <div>{user.name}</div>
      <div>{user.age}</div>
    </>
  )
}

export default App

上面的代码代码在setUser的时候,传入的还是之前的user,不会触发更新。正确的做法是复制一份新的对象,并传入到setUser当中

 const handleClick = () => {
   const newUser = {
      ...user,
     age: user.age +1
   }
    console(user === newUser) // false
    setUser(newUser) // 新传入的user 和旧的 user相等,则不会触发更新
  }

3. 在大部分情况下,都推荐在setState中传入函数

import {useState} from "react"

function App() {
  const [user, setUser] = useState({
    name: "John",
    age: 20
  })
  const handleClick = () => {
    setUser({
      ...user,
      age: user.age + 1
    })
    setUser({
      ...user,
      age: user.age + 1
    })
    // 点击按钮会年龄会增加2 吗? 
  }
  return (
    <>
      <button onClick={handleClick}>addAge</button>
      <div>{user.name}</div>
      <div>{user.age}</div>
    </>
  )
}

export default App

如上代码,点击按钮的时候做了两次setState 的操作,那么年龄会增加 2 吗?答案是否定的。实际情况是,他永远都执行了第二个setUser。正确的做法是,在setState 里面传入一个函数:

 const handleClick = () => {
    setUser((a)=>({
      ...a,
      age: a.age + 1
    })
    )
    setUser((b)=>({
      ...b,
      age: b.age + 1
    })
    )
  }

setState(nextState)

setState 函数更新state为不同的值,并且触发一次重新渲染(re-render)

如果你传入一个函数作为nextState 。那么这个函数必须是纯函数(pure),关于纯函数,简单理解就是:函数接收什么就返回什么,想要深入理解去看下函数式编程,这也是React推崇的理念。

const add = (a,b) = > a+b  // 加法函数,接受两个值并永远返回两个值的和

另外如果传入一个函数作为nextState,那么这个函数只接收一个参数,这个参数就是当前的state,可以理解为旧的值。并且这个函数需要返回 新的state 。React 会把你的更新函数放入一个队列当中。当下一次渲染来临时 React 会根据队列里面的更新函数计算出最新的state。 

注意事项

1.setState 函数只会更新状态变量到下一次渲染。如果你在调用set函数后读取状态变量,你将仍然获取到调用之前屏幕上的旧值。如下代码两次打印 user.age 都将返回当前state中的值

 const handleClick = () => {
    setUser((a)=>({
      ...a,
      age: a.age + 1
    })
    )
    console.log(user.age) // 20
    setUser((b)=>({
      ...b,
      age: b.age + 1
    })
    )
    console.log(user.age) // 20
  }
  1. 如果提供的新的值和旧的值相同,那么会跳过重新渲染组件以及其子组件。在react中每次渲染都是从根组件往下渲染的,跳过相同值的渲染算是React的一种性能优化。
  2. React 会批处理状态更新。它会在所有事件处理程序运行并调用它们的set函数后才更新屏幕。这样可以防止在单个事件期间进行多次重新渲染。在极少数情况下,如果你需要强制React提早更新屏幕,你可以使用flushSync
  3. 在渲染过程中调用set函数只允许在当前正在渲染的组件内部进行。React将丢弃当前输出并立即尝试使用新状态重新渲染。这种模式很少需要使用,但你可以用它来存储来自先前渲染的信息
// App.js
import { useState } from 'react';
import CountLabel from './CountLabel.js';

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <button onClick={() => setCount(count - 1)}>
        Decrement
      </button>
      <CountLabel count={count} />
    </>
  );
}
//CountLabel.js
import { useState } from 'react';

export default function CountLabel({ count }) {
  const [prevCount, setPrevCount] = useState(count);
  const [trend, setTrend] = useState(null);
  if (prevCount !== count) {
    setPrevCount(count);
    setTrend(count > prevCount ? 'increasing' : 'decreasing');
  }
  return (
    <>
      <h1>{count}</h1>
      {trend && <p>The count is {trend}</p>}
    </>
  );
}
  1. 在严格模式下,为了帮助你发现意外的不纯性,React会调用更新函数两次。这只是开发过程中的行为,不会影响生产环境。如果你的更新函数是纯函数,这不会影响行为。其中一次调用的结果将被忽略。
import ReactDOM from 'react-dom/client'
import React from 'react'
import App from './App.jsx'

ReactDOM.createRoot(document.getElementById('root')).render(
  // 严格模式
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

更多useState的用法去React官网进行查看 useState – React

完。