问题
我有一个反复出现的问题,每当我尝试使用钩子做任何远程复杂和异步的事情时,似乎都会发生这个问题。
导致问题的时间线如下所示:
- 在钩子
s
中定义一些状态 - 进行异步调用以获取一些数据
d
并使用它来设置状态s
- 根据
d
进行后续调用,并使用它来更新s
问题是在3.;当我想更新状态s
时,它此时是定义函数的范围内定义的s
,即它没有最新更新状态的概念,只有定义函数时它所知道的状态。
我的半解决方案
我已经能够使用一些方法解决这个问题,一些倾向于工作的事情:
- 非规范化状态(无论如何可能是一个好主意(,以便我们尽可能避免更新状态中的现有项目
- 超时后运行更新;使用
setTimeout
强制在下一个渲染周期进行更新 - 完全忘记使用状态钩子,在外部管理状态并将所有内容作为道具传递
这些并不总是(或永远(好的或理想的解决方案。我猜我在这里错过了一些基本的东西,但我无法在网上找到任何关于这个的东西,而且我的同事也没有任何可行的建议。
TL:DR
在React 功能组件主体中定义的函数将使用它们当时有权访问的范围进行定义。这并不总是最新状态。如何解决这个问题?
一个任意的例子来证明我在说什么:
const getUsers = () => Promise.resolve({
1: {
name: 'Mr 1',
favouriteColour: null,
},
2: {
name: 'Ms 2',
favouriteColour: null,
},
});
const getFavouriteColorForUser = (id) => Promise.resolve({ id, color: Math.random() > 0.5 ? 'red' : 'blue' });
const App = () => {
const [users, setUsers] = React.useState({});
const handleClick = () => {
getUsers()
.then(data => {
setUsers(data);
return Promise.resolve(data);
})
.then(data => {
return Promise.all(Object.keys(data).map(getFavouriteColorForUser));
})
.then(data => {
const updatedUsers = {
...users,
...data.reduce(
(p, c) => ({
...p,
[c.id]: {
...users[c.id],
favouriteColor: c.color
},
}),
{}
)
};
console.log('users:');
console.dir(users);
console.log('color data:');
console.dir(data);
console.log('updatedUsers:');
console.dir(updatedUsers);
setUsers(updatedUsers);
})
}
return (
<div>
{Object.keys(users).map(k => users[k]).map(user => (
<div>{user.name}'s favourite colour is {user.favouriteColour}</div>
))}
<button onClick={handleClick}>Get Users</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
setState 有一个变体,您可以在其中向其传递一个函数。保证使用状态中的最新值调用该函数。您可以使用它来计算下一个状态:
.then(data => {
setUsers(previousUsers => {
const updatedUsers = {
...previousUsers, // <--- using previousUsers, not users
...data.reduce(
(p, c) => ({
...p,
[c.id]: {
...previousUsers[c.id], // <--- using previousUsers, not users
favouriteColor: c.color
},
}), {})
};
return updatedUsers;
})
})