React中的State和setState | 豆包MarsCode AI刷题

94 阅读7分钟

React 中的 State

因为局部变量无法在多次渲染中持久保存,所以当 React 再次渲染这个组件的时候,他会从头开始渲染,不会保留之前的状态,但是我们想要更新数据,所以可以使用 Hook 中的useState来实现。

State 变量用来保存渲染间的数据,当数据发生变化的时候可以触发重新渲染,State setter函数可以用来更新 State 变量。

import { useState } from "react";

通过添加useState来引入State

基本使用

import React, { useState } from "react";
const [count, setCount] = useState(0);
//这里的useState(0)表示初始化count的值为0
//[]包裹的是一个解构数组,它允许你从这个数组中读取值

这是一个使用 useState 的例子const [thing,setThing]是约定,第一个是变量名,第二个是变量更新的函数,后面的useState(0)中的0是 thing 的初始值。

State 的特性

State 是完全独立的,如果我渲染了两个组件,他们的 State 是完全独立的,一个组件的 State 不会影响另一个组件的 State。

设置State的时候不会更改已有的State会重新渲染,一个State永远不会在一次渲染中改变。

State 的更新

State的更新是异步的,所以如果你想要在State更新后执行一些操作,你可以使用useEffect

import React, { useState, useEffect } from "react";
const [count, setCount] = useState(0);
useEffect(() => {
  console.log("count has changed");
}, [count]);

使用一系列 State

来看官方文档上的例子

import { useState } from "react";

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 1);
          setNumber(number + 1);
          setNumber(number + 1);
        }}
      >
        +3
      </button>
    </>
  );
}

这个例子中,当点击一次并没有出现想要的+3 结果,这是因为setNumber是异步的,所以在这里setNumber并不会立即更新number的值,而是在下一次渲染的时候才会更新。

要使用这样的方式更新StatesetNumber((prevNumber) => prevNumber + 1)

下面来分析几个setNumber的使用方式:

setNumber(number + 1);
//未使用n直接更新
setNumber((n) => n + 1);
//使用n,在n的基础上更新
setNumber(22);
//直接更新为22

命名的规范

通常使用 State 变量中的第一个字母来命名新的更新函数的参数

更新对象 State

const [person, setPerson] = useState({ name: "fanceir", age: 18 });
function handlePerson(e) {
  setPerson({ ...person, name: e.target.value });
}

更新数组 State

let nextId = 0;
const [name, setName] = useState("");
const [author, setAuthor] = useState([]);

function handleAuthor() {
  setAuthor((prevAuthor) => [
    ...prevAuthor,
    {
      id: nextId++,
      name: name,
    },
  ]);
  setName(""); // 清空输入框
}

return (
  <div>
    <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
    <button onClick={handleAuthor}>add</button>
    <ul>
      {author.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  </div>
);

这里的更新数组 State 是通过setAuthor的参数函数来实现的,这样可以保证在更新数组的时候不会丢失之前的数据。

useState 的介绍

useState是 react 的一个 hook,可以用作在函数组件中添加状态 调用 useState 可以给他传入一个初始的值,一般使用 use+变量名来命名

import React, { useState } from "react";
export default function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>你点击了{count}次</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  );
}

这里一般使用小驼峰进行命名 useState 返回一个由两个值组成的数组, 当前的 state,在首次渲染的时候,它的值和你传入的 useState 的第一个参数一样,set 函数,它可以将 state 更新为不同的值并且重新触发渲染

useState 是一个 hook 所以只能在组件的顶层或者自己的 hook 中使用它

useState 的渲染

useState 首先肯定会在创建这个组件的时候执行一次,然后在每次调用 set 函数的时候会重新渲染这个组件,set 函数仅仅更新下一次渲染的状态变量所以在调用 set 函数后读取状态,仍然得到调用之前的旧的值。

import React from "react";

export const Count: React.FC = () => {
  console.log("导入了Count组件");
  const [count, setCount] = React.useState(0);
  //调用useState可以给他一个初始值
  //z只要状态发生变化就会有一个新的值
  //已经渲染过一遍了之后会修改状态,不会重新赋初值
  console.log("触发了useState");
  const add = () => {
    setCount(count + 1);
  };
  const reset = () => {
    setCount(0);
  };
  //第一个是状态值,第二个要以set开始使用小驼峰来进行命名
  return (
    <>
      <h1>Count的值是{count}</h1>
      <button onClick={add}>+1</button>
      <button onClick={reset}>重置</button>
    </>
  );
};

useState 存储对象

useState 想要存储对象的时候可以使用这样的方式

import React, { useState } from "react";

export default function DateCom() {
  const [date] = useState(() => {
    const dt = new Date();
    return {
      year: dt.getFullYear(),
      month: dt.getMonth() + 1,
      day: dt.getDate(),
    };
  });
  return (
    <>
      <h1>当前的日期</h1>
      <p>当前的月份{date.year}</p>
      <p>当前的月份{date.month}</p>
      <p>当前的月份{date.day}</p>
    </>
  );
}

这里使用了一个函数来进行初始化,但是如何去更新对象呢?

export const Userinfo: React.FC = () => {
  const [user, setUser] = useState({
    name: "faa",
    age: 18,
    gender: "男",
  });
  const onChangeUser = () => {
    user.name = "bbb";
    user.age = 20;
    setUser({ ...user });
  };
  return (
    <>
      <h1>用户信息</h1>
      <p>姓名:{user.name}</p>
      <p>年龄: {user.age}</p>
      <p>性别: {user.gender}</p>
      <button onClick={onChangeUser}>修改信息</button>
    </>
  );
};

这里的setUser是一个浅拷贝,所以在修改对象的时候需要使用...来进行拷贝 实际上是因为判断是否是同一个对象主要是看访问的地址是否相同,所以在修改对象的时候要注意这一点,当使用 setUser({ ...user }) 时,它会创建一个新的对象,并将 user 的属性复制到这个新对象中。

更新完之后的值

之前已经说到过,由于异步的原因,set 函数只会在下一次渲染的时候更新状态,所以在调用 set 函数之后读取状态,仍然得到调用之前的旧值,使用 useEffect 可以解决这个问题

// import { useEffect } from "react";
import React, { useState, useEffect } from "react";
export const Count2: React.FC = () => {
  console.log("导入了Count组件");
  const [count, setCount] = useState(0);
  //调用useState可以给他一个初始值
  //只要状态发生变化就会有一个新的值
  //已经渲染过一遍了之后会修改状态,不会重新赋初值
  console.log("触发了useState");
  const add = () => {
    setCount(count + 1);
    // console.log(count);
  };

  //useEffect的fn属性首先被渲染的时候也会执行一次
  useEffect(() => {
    console.log("触发了useEffect");
    console.log(`count的值是${count}`);
  }, [count]);
  const reset = () => {
    setCount(0);
  };
  //第一个是状态值,第二个要以set开始使用小驼峰来进行命名
  return (
    <>
      <h1>Count的值是{count}</h1>
      <button onClick={add}>+1</button>
      <button onClick={reset}>重置</button>
    </>
  );
};

值的更新

之前只能加一,但是如何加多个呢?

import React from "react";
import { useState } from "react";
export default function Count3() {
  const [count, setCount] = useState(0);
  //   const add = () => {
  //     setCount(count + 1);
  //     setCount(count + 1);
  //   };//发现这种情况只能加1,因为setCount是异步的,所以第一个setCount执行完后,count还是0,所以第二个setCount还是加1
  const add = () => {
    setCount((count) => count + 2);
  };
  return (
    <>
      <h1>Count的值是{count}</h1>
      <button onClick={add}>+2</button>
    </>
  );
}

这里的 set 函数选择了调用一个函数,这样写可以保证每次都能进行操作,使用函数的写法可以改善代码的可读性,也可以避免一些问题

click 事件被异常调用

下面的代码会导致点击按钮的时候就会调用 setCount 函数,这是不对的,因为这里的 setCount 函数是在渲染的时候就会调用的,但是这样也会报错

import React, { useState } from "react";

export default function Count4() {
  const [count, setCount] = useState(0);

  const add = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h1>Count的值是 {count}</h1>
      <button onClick={add}>+1</button>
      {/* 这里的写法会导致按钮无法正常工作 */}
      <button onClick={setCount(count + 1)}>+1</button>
    </>
  );
}

正确的写法

import React, { useState } from "react";
export default function Count4() {
  const [count, setCount] = useState(0);

  const add = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h1>Count的值是 {count}</h1>
      <button onClick={add}>+1</button>
    </>
  );
}

出现循环调用

// 🚩 错误:在渲染过程中调用事件处理函数
return <button onClick={handleClick()}>Click me</button>;

// ✅ 正确:将事件处理函数传递下去
return <button onClick={handleClick}>Click me</button>;

// ✅ 正确:传递一个内联函数
return <button onClick={(e) => handleClick(e)}>Click me</button>;

总结

  • useState 是一种可以存储状态的 hook
  • useState 返回一个数组,第一个是状态值,第二个是更新状态的函数
  • useState 可以存储对象,但是在更新对象的时候需要使用浅拷贝
  • useState 是异步的,所以在调用 set 函数之后读取状态,仍然得到调用之前的旧值,使用 useEffect 可以解决这个问题
  • useState 可以使用函数的方式来进行更新,这样可以避免一些问题
  • 在渲染的时候调用 set 函数会导致循环调用,所以要注意这一点