dev*_*opp 5 socket.io reactjs react-hooks react-state
我正在使用 React 和 socket.io 开发一个聊天应用程序。后端是express/node。相关组件是: Room.js --> Chat.js --> Messages.js --> Message.js
从服务器接收到的消息数据以状态存储在 Room.js 中。然后它通过 Chat.js 传递到 Messages.js,并在其中映射到一系列 Message.js 组件。
当收到消息时,它们才会出现,但只有在我再次开始在表单中输入并触发 messageChangeHandler() 后才会出现。有什么想法为什么当收到新消息并将其添加到 Room.js 中的状态时消息不会重新呈现?我已经确认状态和道具正在更新它们应该在的所有地方——它们只是在 messageChangeHandler() 触发它自己的重新渲染之前不会出现/重新渲染。
这是组件。
Room.js
export default function Room(props) {
const [messagesData, setMessagesData] = useState([])
useEffect(() => {
console.log('the use effect ')
socket.on('broadcast', data => {
console.log(messagesData)
let previousData = messagesData
previousData.push(data)
// buildMessages(previousData)
setMessagesData(previousData)
})
}, [socket])
console.log('this is messagesData in queue.js', messagesData)
return(
// queue counter will go up here
// <QueueDisplay />
// chat goes here
<Chat
profile={props.profile}
messagesData={messagesData}
/>
)
}
Run Code Online (Sandbox Code Playgroud)
聊天.js
export default function Chat(props) {
// state
const [newPayload, setNewPayload] = useState({
message: '',
sender: props.profile.name
})
// const [messagesData, setMessagesData] = useState([])
const [updateToggle, setUpdateToggle] = useState(true)
const messageChangeHandler = (e) => {
setNewPayload({... newPayload, [e.target.name]: e.target.value})
}
const messageSend = (e) => {
e.preventDefault()
if (newPayload.message) {
socket.emit('chat message', newPayload)
setNewPayload({
message: '',
sender: props.profile.name
})
}
}
return(
<div id='chatbox'>
<div id='messages'>
<Messages messagesData={props.messagesData} />
</div>
<form onSubmit={messageSend}>
<input
type="text"
name="message"
id="message"
placeholder="Start a new message"
onChange={messageChangeHandler}
value={newPayload.message}
autoComplete='off'
/>
<input type="submit" value="Send" />
</form>
</div>
)
}
Run Code Online (Sandbox Code Playgroud)
消息.js
export default function Messages(props) {
return(
<>
{props.messagesData.map((data, i) => {
return <Message key={i} sender={data.sender} message={data.message} />
})}
</>
)
}
Run Code Online (Sandbox Code Playgroud)
消息.js
export default function Message(props) {
return(
<div key={props.key}>
<p>{props.sender}</p>
<p>{props.message}</p>
</div>
)
}
Run Code Online (Sandbox Code Playgroud)
预先感谢您的任何帮助!
我不认为你的useEffect()函数做了你认为它做的事情。
如果您看到一个useEffect()函数使用在封闭范围(在闭包中)中声明的变量,但这些变量未在useEffect()\ 的依赖项中列出([]在 的末尾useEffect()) ,您的大脑应该立即产生一个危险信号
在这种情况下,messagesData在内部使用useEffect()但未声明为依赖项。发生的情况是,在第一个broadcast被接收并被setMessagesData调用之后,messagesData在 内不再有效useEffect()。它引用了一个数组,来自上次运行时的闭包,该数组不再被分配messageData。当你调用 时setMessagesData,React 知道该值已更新,并重新渲染。它运行该useState()线路并获得一个新的messagesData. useEffect(),这是一个记忆函数,不会重新创建,因此它仍然使用messagesData之前运行的函数。
useEffect()在开始之前,让我们消除函数中的一些噪音:
\n useEffect(() => {\n socket.on(\'broadcast\', data => {\n setMessagesData([...messagesData, data])\n })\n }, [socket])\nRun Code Online (Sandbox Code Playgroud)\n这在功能上等同于您的代码,减去console.log()消息和额外的变量。
让我们更进一步,将处理程序变成单行代码:
\n useEffect(() => {\n socket.on(\'broadcast\', data => setMessagesData([...messagesData, data]));\n }, [socket])\nRun Code Online (Sandbox Code Playgroud)\n现在,让我们添加缺少的依赖项!
\n useEffect(() => {\n socket.on(\'broadcast\', data => setMessagesData([...messagesData, data]));\n }, [socket, messagesData])\nRun Code Online (Sandbox Code Playgroud)\n从技术上讲,我们也依赖于setMessagesData(),但是React 对于函数有这样的说法setState():
\n\nReact 保证 setState 函数身份是稳定的,并且在重新渲染时不会改变。这就是为什么从 useEffect 或 useCallback 依赖项列表中省略它\xe2\x80\x99 是安全的。
\n
该useEffect()功能看起来不错,但我们仍然依赖messagesData. 这是一个问题,因为每次socket收到一个broadcast,messagesData都会发生变化,所以useEffect()要重新运行。每次重新运行时,它都会为broadcast消息添加一个新的处理程序/侦听器,这意味着当收到下一条消息时,每个处理程序/侦听器都会调用setMessagesData(). 代码可能仍然会意外地工作,至少在逻辑上是这样,因为侦听器通常按照注册的顺序同步调用,而且我相信如果在setState()同一个渲染期间进行多次调用,React 只会重新渲染一次使用最后的setState()调用。但这肯定会发生内存泄漏,因为我们无法注销所有这些侦听器。
这个小问题通常会导致解决起来非常痛苦,因为要解决这个问题,我们需要在每次注册新侦听器时取消注册旧侦听器。为了取消注册侦听器,我们使用removeListener()我们注册的相同函数调用函数 - 但我们不再有该函数了。这意味着我们需要将旧函数保存为状态或将其记忆化,但现在我们的useEffect()函数还有另一个依赖项。事实证明,避免无限重新渲染的连续循环并非易事。
事实证明,我们不必跨越所有这些障碍。如果仔细观察我们的函数,我们会发现除了设置新值之外,useEffect()我们实际上并没有使用。messagesData我们正在获取旧值并附加到它上面。
React 开发人员知道这是一个常见的场景,因此实际上有一个内置的帮助器。setState()可以接受一个函数,该函数将立即以先前的值作为参数进行调用。该函数的结果将是新的状态。听起来比实际更复杂,但它看起来像这样:
setState(previous => previous + 1);\nRun Code Online (Sandbox Code Playgroud)\n或者在我们的具体情况下:
\nsetMessagesData(oldMessagesData => [...oldMessagesData, data]);\nRun Code Online (Sandbox Code Playgroud)\n现在我们不再依赖于messagesData:
useEffect(() => {\n socket.on(\'broadcast\', data => setMessagesData(oldMessagesData => [...oldMessagesData, data]);\n }, [socket])\nRun Code Online (Sandbox Code Playgroud)\n还记得之前我们讨论过内存泄漏吗?事实证明,使用我们最新的代码仍然会发生这种情况。该组件可能会多次安装和卸载(例如,在单页应用程序中,当用户切换页面时)。每次发生这种情况时,都会注册一个新的侦听器。礼貌的做法是返回useEffect()一个可以清理的函数。在我们的例子中,这意味着取消注册/删除监听器。
首先,在注册之前保存侦听器,然后返回一个函数将其删除
\n useEffect(() => {\n const listener = data => setMessagesData(oldMessagesData => [...oldMessagesData, data];\n socket.on(\'broadcast\', listener);\n return () => socket.removeListener(\'broadcast\', listener);\n }, [socket])\nRun Code Online (Sandbox Code Playgroud)\n请注意,如果发生更改,我们的侦听器仍然会悬空socket,并且由于代码中不清楚其socket来源,因此无论发生什么更改,都必须删除所有旧侦听器,例如socket.removeAllListeners()或socket.removeAllListeners(\'broadcast\')。
| 归档时间: |
|
| 查看次数: |
1864 次 |
| 最近记录: |