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的值,而是在下一次渲染的时候才会更新。
要使用这样的方式更新State:
setNumber((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 函数会导致循环调用,所以要注意这一点