React中botChat数组更新时如何避免聊天记录重复触发动画?
问题:React聊天组件历史消息打字动画重复触发的修复方案
原组件代码
import React, { useState, useEffect, useMemo } from 'react'; import axios from 'axios'; import { ChatLog } from './ChatLog.jsx'; import { GoArrowUp } from "react-icons/go"; export function QueryBar() { const [searchInput, setSearchInput] = useState(""); const [botChat, setBotChat] = useState([]); const [userChat, setUserChat] = useState([]); const handleKeyDown = (e) => { if (e.key === 'Enter') { e.preventDefault(); handleSubmit(); } } const handleSubmit = () => { const params = { prompt: searchInput } axios.get("http://localhost:5555/chat", { params }) .then((response) => { setBotChat(botChat => [...botChat, response.data.result]); setSearchInput(""); }) .catch((error) => { alert('Something bad happened while getting API response'); console.log(error); }) } useEffect(() => { setSearchInput(searchInput); }, [searchInput]); const chat = useMemo(() => botChat.map(chat => { return ( <> <React.Fragment> <h4 className="pl-7 pt-2">Bot: </h4> <div className="pl-24 before:absolute before:inset-0 before:animate-typewriter before:bg-white after:absolute after:inset-0 after:w-[0.125em] after:animate-caret after:bg-black">{chat}</div> </React.Fragment> </> ) }), [botChat]) return ( <> <div className="h-screen"> <div>{chat}</div> <div className="p-4"> <div className="w-screen absolute bottom-0 flex justify-center mb-8"> <form className="w-1/2 rounded-lg outline"> <input className="w-full placeholder:text-gray-500 pl-[8px] outline-none" type="text" placeholder='Talk to Bot' onChange={(e) => setSearchInput(e.target.value)} onKeyDown={handleKeyDown} value={searchInput}/> </form> <button className="mx-4 h-6 w-6 inline-block rounded-full align-center outline items-center" onClick={handleSubmit}> <div className='flex items-center justify-center'> <GoArrowUp/> </div> </button> </div> </div> </div> </> ) } export default QueryBar
问题分析
原组件中,每次botChat数组更新时,useMemo会重新生成整个聊天日志的JSX数组,导致所有历史消息的DOM元素被重新创建,进而重复触发打字动画。此外,代码中存在一个无效的useEffect,会导致searchInput无限循环更新。
修复方案
1. 拆分独立消息组件,隔离渲染逻辑
将单条机器人消息抽成独立组件,并用React.memo包裹,确保只有当组件的props变化时才会重新渲染。
新建BotMessage.jsx组件:
import React from 'react'; // React.memo 让组件仅在props变化时重渲染 const BotMessage = React.memo(({ content }) => { return ( <div> <h4 className="pl-7 pt-2">Bot: </h4> <div className="pl-24 before:absolute before:inset-0 before:animate-typewriter before:bg-white after:absolute after:inset-0 after:w-[0.125em] after:animate-caret after:bg-black"> {content} </div> </div> ); }); export default BotMessage;
2. 优化主组件代码
- 删除无效的
useEffect - 使用拆分后的
BotMessage组件渲染消息,避免整组重渲染 - 为列表元素添加唯一
key,帮助React优化DOM更新
修改后的QueryBar.jsx:
import React, { useState } from 'react'; import axios from 'axios'; import { GoArrowUp } from "react-icons/go"; import BotMessage from './BotMessage'; export function QueryBar() { const [searchInput, setSearchInput] = useState(""); const [botChat, setBotChat] = useState([]); const [userChat, setUserChat] = useState([]); const handleKeyDown = (e) => { if (e.key === 'Enter') { e.preventDefault(); handleSubmit(); } } const handleSubmit = () => { const params = { prompt: searchInput } axios.get("http://localhost:5555/chat", { params }) .then((response) => { // 存储用户消息,完善聊天逻辑 setUserChat(prev => [...prev, searchInput]); setBotChat(prev => [...prev, response.data.result]); setSearchInput(""); }) .catch((error) => { alert('获取API响应时出错'); console.log(error); }) } return ( <div className="h-screen"> <div> {/* 渲染用户消息 */} {userChat.map((msg, index) => ( <div key={index} className="pl-7 pt-2"> <h4>You: </h4> <div className="pl-24">{msg}</div> </div> ))} {/* 渲染机器人消息,仅新增消息会触发重渲染 */} {botChat.map((msg, index) => ( <BotMessage key={index} content={msg} /> ))} </div> <div className="p-4"> <div className="w-screen absolute bottom-0 flex justify-center mb-8"> <form className="w-1/2 rounded-lg outline"> <input className="w-full placeholder:text-gray-500 pl-[8px] outline-none" type="text" placeholder='和机器人聊天' onChange={(e) => setSearchInput(e.target.value)} onKeyDown={handleKeyDown} value={searchInput} /> </form> <button className="mx-4 h-6 w-6 inline-block rounded-full align-center outline items-center" onClick={handleSubmit} > <div className='flex items-center justify-center'> <GoArrowUp/> </div> </button> </div> </div> </div> ) } export default QueryBar
核心修复点说明
React.memo的作用:BotMessage组件只有在contentprops变化时才会重新渲染,历史消息的content不会改变,因此不会重复触发打字动画。- 组件拆分:将单条消息的渲染逻辑独立后,父组件更新时仅会创建并渲染新增的
BotMessage实例,历史消息的DOM不会被重新创建。 - 移除无效
useEffect:原代码中监听searchInput并调用setSearchInput的逻辑会导致无限循环,直接删除即可。
内容的提问来源于stack exchange,提问作者Kyle Lee
相关产品推荐
相关产品推荐

