React Hook useCallback 在调用时未获得更新状态
React Hook useCallback not getting updated state when invoked
我正在开发这个应用程序,我在其中双击页面并创建一个新项目。然后我通知 websockets 服务器将这个创建广播给所有其他收听它的用户。在另一个用户端,我必须添加这个新项目,这样每个人都会同步。
我正在使用 React Hooks,这是我从头开始使用它的第一个应用程序。我在这个应用程序中遇到了几个这样的问题,但我选择这个流程来说明基本问题。它必须涵盖大多数问题。
当我调用 useCallback
在“其他用户”端添加项目时,items
状态为旧(空)。所以无论我创建多少次项目,我在页面上只会有一个,我刚创建的那个。
这是代码示例:
import React, { useEffect, useState, useCallback } from 'react';
import io from 'socket.io-client';
let socket;
function App() {
const [items, setItems] = useState({});
const createItem = useCallback((data) => {
const id = generateId();
const newItem = { id, ...data };
const newItems = { ...items, [id]: newItem };
setItems(newItems)
// When I create a new item, I notify the server to broadcast it
socket.emit('itemCreated', newItem)
}, [items, setItems])
const doCreateItem = useCallback((item) => {
// When someone else creates an item, it should be invoked be invoked
const newItems = { ...items, [item.id]: item };
setItems(newItems)
}, [items]);
useEffect(() => {
// Connects to the server
socket = io('ws://localhost:8090', {
transports: ['websocket'],
path: '/ws'
});
// Listens to event
socket.on('createitem', (data) => {
doCreateItem(data)
});
// I cannot add doCreateItem here, otherwise it will call this effect everytime doCreateItem is executed
}, []);
return (
<div
onDoubleClick={
(e) => {
createItem({});
}
}
>
{
Object.values(items).map(item => {
return (
<div>{item.id}</div>
)
})
}
</div>
);
}
export default App;
doCreateItem
具有项目依赖性。另一方面,useEffect
依赖项列表不包含 doCreateItem
主要是因为每次我从服务器收到新项目时它都会重新调用 useEffect
回调。也因为我希望这个效果只执行一次(在组件安装时)。我尝试将 useEffect
一分为二,一个用于连接,另一个用于听众,但同样的事情发生了。
我实际上有几个这样的问题。回调的执行次数超出了我的预期,或者状态未在回调中更新。
有人可以阐明我们应该如何解决这个问题吗?
提前致谢!
编辑
根据 @Ross 的建议,我更改了 setItems
调用以接收回调和 return 新状态。这有助于 setItems
依赖。但是我在doCreateItem
函数中还是读不到items
。它总是空的。
我注意到的一件有趣的事情是,如果我停止使用 useCallback
并在每个渲染器上重新创建函数,它在该函数上没有更新状态,但它确实有外部函数:
function App() {
const [items, setItems] = useState({});
console.log('Rendered again and knows the state >', items)
const doCreatePostIt = (data) => {
console.log('Empty >', items)
// ...
};
// ...
}
当下一个状态依赖于当前状态时,使用 useState
的 functional update 形式消除了依赖数组中的依赖并解决了“陈旧状态”问题。
- 从
useState
钩子返回的 setX
函数是稳定的,不会在渲染之间改变(参见 useState
doc);从依赖数组中省略是安全的
const [items, setItems] = useState({});
const createItem = useCallback((data) => {
const id = generateId();
const newItem = { id, ...data };
setItems((currItems) => ({
...currItems,
[id]: newItem,
}))
socket.emit('itemCreated', newItem)
}, [])
useEffect(() => {
function doCreateItem(item) {
setItems((currItems) => ({
...currItems,
[item.id]: item,
}))
}
socket = io('ws://localhost:8090', {
transports: ['websocket'],
path: '/ws'
});
socket.on('createitem', doCreateItem);
// Return a clean up function so the component will stop listening to the
// socket when the component is unmounted.
return () => {
socket.off('createItem', doCreateItem)
}
}, []);
我正在开发这个应用程序,我在其中双击页面并创建一个新项目。然后我通知 websockets 服务器将这个创建广播给所有其他收听它的用户。在另一个用户端,我必须添加这个新项目,这样每个人都会同步。
我正在使用 React Hooks,这是我从头开始使用它的第一个应用程序。我在这个应用程序中遇到了几个这样的问题,但我选择这个流程来说明基本问题。它必须涵盖大多数问题。
当我调用 useCallback
在“其他用户”端添加项目时,items
状态为旧(空)。所以无论我创建多少次项目,我在页面上只会有一个,我刚创建的那个。
这是代码示例:
import React, { useEffect, useState, useCallback } from 'react';
import io from 'socket.io-client';
let socket;
function App() {
const [items, setItems] = useState({});
const createItem = useCallback((data) => {
const id = generateId();
const newItem = { id, ...data };
const newItems = { ...items, [id]: newItem };
setItems(newItems)
// When I create a new item, I notify the server to broadcast it
socket.emit('itemCreated', newItem)
}, [items, setItems])
const doCreateItem = useCallback((item) => {
// When someone else creates an item, it should be invoked be invoked
const newItems = { ...items, [item.id]: item };
setItems(newItems)
}, [items]);
useEffect(() => {
// Connects to the server
socket = io('ws://localhost:8090', {
transports: ['websocket'],
path: '/ws'
});
// Listens to event
socket.on('createitem', (data) => {
doCreateItem(data)
});
// I cannot add doCreateItem here, otherwise it will call this effect everytime doCreateItem is executed
}, []);
return (
<div
onDoubleClick={
(e) => {
createItem({});
}
}
>
{
Object.values(items).map(item => {
return (
<div>{item.id}</div>
)
})
}
</div>
);
}
export default App;
doCreateItem
具有项目依赖性。另一方面,useEffect
依赖项列表不包含 doCreateItem
主要是因为每次我从服务器收到新项目时它都会重新调用 useEffect
回调。也因为我希望这个效果只执行一次(在组件安装时)。我尝试将 useEffect
一分为二,一个用于连接,另一个用于听众,但同样的事情发生了。
我实际上有几个这样的问题。回调的执行次数超出了我的预期,或者状态未在回调中更新。
有人可以阐明我们应该如何解决这个问题吗?
提前致谢!
编辑
根据 @Ross 的建议,我更改了 setItems
调用以接收回调和 return 新状态。这有助于 setItems
依赖。但是我在doCreateItem
函数中还是读不到items
。它总是空的。
我注意到的一件有趣的事情是,如果我停止使用 useCallback
并在每个渲染器上重新创建函数,它在该函数上没有更新状态,但它确实有外部函数:
function App() {
const [items, setItems] = useState({});
console.log('Rendered again and knows the state >', items)
const doCreatePostIt = (data) => {
console.log('Empty >', items)
// ...
};
// ...
}
当下一个状态依赖于当前状态时,使用 useState
的 functional update 形式消除了依赖数组中的依赖并解决了“陈旧状态”问题。
- 从
useState
钩子返回的setX
函数是稳定的,不会在渲染之间改变(参见useState
doc);从依赖数组中省略是安全的
const [items, setItems] = useState({});
const createItem = useCallback((data) => {
const id = generateId();
const newItem = { id, ...data };
setItems((currItems) => ({
...currItems,
[id]: newItem,
}))
socket.emit('itemCreated', newItem)
}, [])
useEffect(() => {
function doCreateItem(item) {
setItems((currItems) => ({
...currItems,
[item.id]: item,
}))
}
socket = io('ws://localhost:8090', {
transports: ['websocket'],
path: '/ws'
});
socket.on('createitem', doCreateItem);
// Return a clean up function so the component will stop listening to the
// socket when the component is unmounted.
return () => {
socket.off('createItem', doCreateItem)
}
}, []);