如何使用 useState 在反应功能组件的循环中正确设置状态
How to correctly set state in a loop in react functional component using useState
考虑一下我正在上传一堆文件,我正在尝试跟踪正在进行的文件,这样每当上传完成时,我都会递减我的状态,直到所有文件都完成,最后我更改我的上传状态至 false
:
(这里我做了一个sleep
函数来模拟上传过程)
import React, { useState} from "react";
const sleep = (ms, dev =1 ) => {
const msWithDev = (Math.random() * dev + 1) * ms
return new Promise(resolve => setTimeout(resolve, msWithDev))
}
function Counter() {
const [, setSortEnabled] = useState(true)
const [count, setCount] = useState(0)
const arr = [1, 2, 3, 4, 5]
const upload = async () => {
await sleep(1000)
setCount(c => c - 1)
console.log(`count state: ${count}`)
if (count === 0) {
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
}
const onClick = () => {
console.warn('Sort set to disabled.')
arr.forEach(item => {
setCount(c => c + 1)
console.error(count)
upload()
})
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;
问题出在upload
,
function `console.log(\`count state: ${count}\`)`
总是记录 0
,这意味着状态从未更新过。
我做错了什么?
Which means the state has never been updated
它确实得到了更新,你只是在记录过时的值 because of closures。
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function.
要修复它,请使用 setState
functional updates:
const onClick = () => {
console.warn("Sort set to disabled.");
arr.forEach((item) => {
setCount((c) => {
console.error(c);
return c + 1;
});
upload();
});
};
不能依赖计数,必须使用Promise.all
等待所有文件上传。在您的情况下,计数是从闭包中接收到的,因此即使状态已更新,计数变量也不会受到影响。
你可以用Promise.all like
实现上面的逻辑
function Counter() {
const [, setSortEnabled] = useState(true)
const arr = [1, 2, 3, 4, 5]
const upload = async () => {
await sleep(1000);
}
const onClick = async () => {
console.warn('Sort set to disabled.')
const promises = arr.map(item => {
console.error(count)
return upload()
});
await Promise.all(promises);
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;
更新:
要解决计数状态的关闭问题,您可以使用 refs 和 useEffect。然而,它是一种解决方法,不应成为首选
import React, { useState} from "react";
const sleep = (ms, dev =1 ) => {
const msWithDev = (Math.random() * dev + 1) * ms
return new Promise(resolve => setTimeout(resolve, msWithDev))
}
function Counter() {
const [, setSortEnabled] = useState(true)
const [count, setCount] = useState(0)
const arr = [1, 2, 3, 4, 5]
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count ]);
const upload = async () => {
await sleep(1000)
setCount(c => c - 1)
console.log(`count state: ${count}`)
if (countRef.current === 0) {
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
}
const onClick = () => {
console.warn('Sort set to disabled.')
arr.forEach(item => {
setCount(c => c + 1)
upload();
})
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;
考虑一下我正在上传一堆文件,我正在尝试跟踪正在进行的文件,这样每当上传完成时,我都会递减我的状态,直到所有文件都完成,最后我更改我的上传状态至 false
:
(这里我做了一个sleep
函数来模拟上传过程)
import React, { useState} from "react";
const sleep = (ms, dev =1 ) => {
const msWithDev = (Math.random() * dev + 1) * ms
return new Promise(resolve => setTimeout(resolve, msWithDev))
}
function Counter() {
const [, setSortEnabled] = useState(true)
const [count, setCount] = useState(0)
const arr = [1, 2, 3, 4, 5]
const upload = async () => {
await sleep(1000)
setCount(c => c - 1)
console.log(`count state: ${count}`)
if (count === 0) {
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
}
const onClick = () => {
console.warn('Sort set to disabled.')
arr.forEach(item => {
setCount(c => c + 1)
console.error(count)
upload()
})
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;
问题出在upload
,
function `console.log(\`count state: ${count}\`)`
总是记录 0
,这意味着状态从未更新过。
我做错了什么?
Which means the state has never been updated
它确实得到了更新,你只是在记录过时的值 because of closures。
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function.
要修复它,请使用 setState
functional updates:
const onClick = () => {
console.warn("Sort set to disabled.");
arr.forEach((item) => {
setCount((c) => {
console.error(c);
return c + 1;
});
upload();
});
};
不能依赖计数,必须使用Promise.all
等待所有文件上传。在您的情况下,计数是从闭包中接收到的,因此即使状态已更新,计数变量也不会受到影响。
你可以用Promise.all like
实现上面的逻辑function Counter() {
const [, setSortEnabled] = useState(true)
const arr = [1, 2, 3, 4, 5]
const upload = async () => {
await sleep(1000);
}
const onClick = async () => {
console.warn('Sort set to disabled.')
const promises = arr.map(item => {
console.error(count)
return upload()
});
await Promise.all(promises);
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;
更新:
要解决计数状态的关闭问题,您可以使用 refs 和 useEffect。然而,它是一种解决方法,不应成为首选
import React, { useState} from "react";
const sleep = (ms, dev =1 ) => {
const msWithDev = (Math.random() * dev + 1) * ms
return new Promise(resolve => setTimeout(resolve, msWithDev))
}
function Counter() {
const [, setSortEnabled] = useState(true)
const [count, setCount] = useState(0)
const arr = [1, 2, 3, 4, 5]
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count ]);
const upload = async () => {
await sleep(1000)
setCount(c => c - 1)
console.log(`count state: ${count}`)
if (countRef.current === 0) {
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
}
const onClick = () => {
console.warn('Sort set to disabled.')
arr.forEach(item => {
setCount(c => c + 1)
upload();
})
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;