我正在尝试学习钩子和useState方法使我困惑。我以数组的形式将初始值赋给一个状态。useState中的set方法不适合我,无论是否使用扩展语法。
我已经在另一台PC上做了一个API,我正在调用并获取我想设置为状态的数据。
这是我的代码:
<div id="root"></div>
<script type="text/babel" defer>
// import React, { useState, useEffect } from "react";
// import ReactDOM from "react-dom";
const { useState, useEffect } = React; // web-browser variant
const StateSelector = () => {
const initialValue = [
{
category: "",
photo: "",
description: "",
id: 0,
name: "",
rating: 0
}
];
const [movies, setMovies] = useState(initialValue);
useEffect(() => {
(async function() {
try {
// const response = await fetch("http://192.168.1.164:5000/movies/display");
// const json = await response.json();
// const result = json.data.result;
const result = [
{
category: "cat1",
description: "desc1",
id: "1546514491119",
name: "randomname2",
photo: null,
rating: "3"
},
{
category: "cat2",
description: "desc1",
id: "1546837819818",
name: "randomname1",
rating: "5"
}
];
console.log("result =", result);
setMovies(result);
console.log("movies =", movies);
} catch (e) {
console.error(e);
}
})();
}, []);
return <p>hello</p>;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
</script>
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
setMovies(result)和setMovies(…result)都不起作用。
我希望结果变量被推入movies数组。
很像扩展React创建的类组件中的. setstate()。组件或React。使用useState钩子提供的更新器的PureComponent状态更新也是异步的,并且不会立即反映出来。
此外,这里的主要问题不仅是异步性质,而且是函数基于当前闭包使用状态值,状态更新将在下一次重新呈现中反映出来,这样现有的闭包不会受到影响,但会创建新的闭包。现在,在当前状态下,钩子中的值是由现有的闭包获得的,当重新呈现发生时,闭包将根据是否再次重新创建函数而更新。
即使您添加了一个setTimeout函数,尽管超时将在重新呈现发生的一段时间后运行,但setTimeout仍将使用前一个闭包的值,而不是更新的值。
setMovies(result);
console.log(movies) // movies here will not be updated
如果你想在状态更新上执行一个动作,你需要使用useEffect钩子,就像在类组件中使用componentDidUpdate一样,因为useState返回的setter没有回调模式
useEffect(() => {
// action on update of movies
}, [movies]);
就更新状态的语法而言,setMovies(result)将用异步请求中可用的movies值替换状态中先前的movies值。
但是,如果希望将响应与先前存在的值合并,则必须使用状态更新的回调语法以及正确使用扩展语法,如
setMovies(prevMovies => ([...prevMovies, ...result]));
并不是说要这样做,但是没有useEffect做OP要求的事情并不难。
使用promise来解析setter函数体中的新状态:
const getState = <T>(
setState: React.Dispatch<React.SetStateAction<T>>
): Promise<T> => {
return new Promise((resolve) => {
setState((currentState: T) => {
resolve(currentState);
return currentState;
});
});
};
这就是你如何使用它(示例显示了count和outOfSyncCount/syncCount在UI渲染中的比较):
const App: React.FC = () => {
const [count, setCount] = useState(0);
const [outOfSyncCount, setOutOfSyncCount] = useState(0);
const [syncCount, setSyncCount] = useState(0);
const handleOnClick = async () => {
setCount(count + 1);
// Doesn't work
setOutOfSyncCount(count);
// Works
const newCount = await getState(setCount);
setSyncCount(newCount);
};
return (
<>
<h2>Count = {count}</h2>
<h2>Synced count = {syncCount}</h2>
<h2>Out of sync count = {outOfSyncCount}</h2>
<button onClick={handleOnClick}>Increment</button>
</>
);
};
之前答案的附加细节:
虽然React的setState是异步的(类和钩子都是异步的),而且很容易用这个事实来解释观察到的行为,但这并不是它发生的原因。
TLDR:原因是围绕不可变const值的闭包作用域。
解决方案:
read the value in render function (not inside nested functions):
useEffect(() => { setMovies(result) }, [])
console.log(movies)
add the variable into dependencies (and use the react-hooks/exhaustive-deps eslint rule):
useEffect(() => { setMovies(result) }, [])
useEffect(() => { console.log(movies) }, [movies])
use a temporary variable:
useEffect(() => {
const newMovies = result
console.log(newMovies)
setMovies(newMovies)
}, [])
use a mutable reference (if we don't need a state and only want to remember the value - updating a ref doesn't trigger re-render):
const moviesRef = useRef(initialValue)
useEffect(() => {
moviesRef.current = result
console.log(moviesRef.current)
}, [])
解释为什么会这样:
如果async是唯一的原因,那么可以等待setState()。
然而,道具和状态都假定在1渲染期间不变。
对待这个问题。状态,仿佛它是不可变的。
对于钩子,通过使用常量值和const关键字来加强这个假设:
const [state, setState] = useState('initial')
该值在2个渲染之间可能不同,但在渲染本身和任何闭包(即使在渲染完成后仍然存在更长的函数,例如useEffect,事件处理程序,在任何Promise或setTimeout内)中保持不变。
考虑以下伪的,但是同步的,类似react的实现:
// sync implementation:
let internalState
let renderAgain
const setState = (updateFn) => {
internalState = updateFn(internalState)
renderAgain()
}
const useState = (defaultState) => {
if (!internalState) {
internalState = defaultState
}
return [internalState, setState]
}
const render = (component, node) => {
const {html, handleClick} = component()
node.innerHTML = html
renderAgain = () => render(component, node)
return handleClick
}
// test:
const MyComponent = () => {
const [x, setX] = useState(1)
console.log('in render:', x) // ✅
const handleClick = () => {
setX(current => current + 1)
console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
}
return {
html: `<button>${x}</button>`,
handleClick
}
}
const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
<div id="root"></div>