useState
官方文档 useState
更新状态中的对象和数组
对象
const [form, setForm] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com',
});
<input
value={form.firstName}
onChange={e => {
setForm({
...form,
firstName: e.target.value
});
}}
/>
嵌套对象
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
setPerson({
...person,
name: e.target.value
});
}
function handleTitleChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
title: e.target.value
}
});
}
数组
import { useState } from 'react';
import AddTodo from './AddTodo.js';
import TaskList from './TaskList.js';
let nextId = 3;
const initialTodos = [
{ id: 0, title: 'Buy milk', done: true },
{ id: 1, title: 'Eat tacos', done: false },
{ id: 2, title: 'Brew tea', done: false },
];
export default function TaskApp() {
const [todos, setTodos] = useState(initialTodos);
function handleAddTodo(title) {
setTodos([
...todos,
{
id: nextId++,
title: title,
done: false
}
]);
}
function handleChangeTodo(nextTodo) {
setTodos(todos.map(t => {
if (t.id === nextTodo.id) {
return nextTodo;
} else {
return t;
}
}));
}
function handleDeleteTodo(todoId) {
setTodos(
todos.filter(t => t.id !== todoId)
);
}
return (
<>
<AddTodo
onAddTodo={handleAddTodo}
/>
<TaskList
todos={todos}
onChangeTodo={handleChangeTodo}
onDeleteTodo={handleDeleteTodo}
/>
</>
);
}
问题
更新了状态,但日志仍显示旧
function handleClick() {
console.log(count); // 0
setCount(count + 1); // 请求使用 1 重新渲染
console.log(count); // 仍然是 0!
setTimeout(() => {
console.log(count); // 还是 0!
}, 5000);
}
如果你需要使用下一个状态,你可以在将其传递给 set 函数之前将其保存在一个变量中:
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1
将 state 设置为一个函数,但它却被调用了
不能像这样把函数放入状态:
const [fn, setFn] = useState(someFunction);
function handleClick() {
setFn(someOtherFunction);
}
因为你传递了一个函数,React 认为 someFunction 是一个 初始化函数,而 someOtherFunction 是一个 更新函数,于是它尝试调用它们并存储结果。要实际 存储 一个函数,你必须在两种情况下在它们之前加上 () =>。然后 React 将存储你传递的函数。
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}
useEffect(setup, dependencies?)
用法
setup:处理 Effect 的函数。setup 函数选择性返回一个 清理(cleanup) 函数。当组件被添加到 DOM 的时候,React 将运行 setup 函数。在每次依赖项变更重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。在组件从 DOM 中移除后,React 将最后一次运行 cleanup 函数。
import { useEffect, useRef } from 'react';
export default function ModalDialog({ isOpen, children }) {
const ref = useRef();
useEffect(() => {
if (!isOpen) {
return;
}
const dialog = ref.current;
dialog.showModal();
return () => {
dialog.close();
};
}, [isOpen]);
return <dialog ref={ref}>{children}</dialog>;
}
指定响应式依赖项
注意,你无法“选择” Effect 的依赖项。Effect 代码中使用的每个 响应式值 都必须声明为依赖项。你的 Effect 依赖列表是由周围代码决定的:
function ChatRoom({ roomId }) { // 这是一个响应式值
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // 这也是一个响应式值
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // 这个 Effect 读取这些响应式值
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ 因此你必须指定它们作为 Effect 的依赖项
// ...
}
不需要依赖项的情况
现在 serverUrl 不再是一个响应式值(并且在重新渲染时也不会更改),它就不需要成为一个依赖项。如果 Effect 的代码不使用任何响应式值,则其依赖项列表应为空([]) :
const serverUrl = 'https://localhost:1234'; // 不再是响应式值
const roomId = 'music'; // 不再是响应式值
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ 所有声明的依赖项
// ...
}
传递响应式依赖
传递依赖项数组
如果指定了依赖项,则 Effect 在 初始渲染后以及依赖项变更的重新渲染后 运行。
useEffect(() => {
}, [a, b]); // 如果 a 或 b 不同则会再次运行
传递空依赖项数组
如果你的 Effect 确实没有使用任何响应式值,则它仅在 初始渲染后 运行。
useEffect(() => {
}, []); // 不会再次运行(开发环境下除外)
不传递依赖项数组
如果完全不传递依赖数组,则 Effect 会在组件的 每次单独渲染(和重新渲染)之后 运行。
useEffect(() => {
// ...
}); // 总是再次运行