React 代码优化建议
Créé le : 25 janvier 2025
Créé le : 25 janvier 2025
好的,这里是完整的代码实现,包括React组件和相应的类型定义:
jsx// ChatComponent.tsx import { useState, useCallback } from 'react'; import { AgentModel } from '../../lib/AgentModel'; import clsx from 'clsx'; // 用于条件类名管理 // 定义消息类型 interface Message { role: 'user' | 'assistant'; content: string; } // 定义组件Props类型 interface ChatComponentProps { className?: string; } const ChatComponent: React.FC<ChatComponentProps> = ({ className }) => { const [messages, setMessages] = useState<Message[]>([]); const [inputText, setInputText] = useState(''); const [isLoading, setIsLoading] = useState(false); const [response, setResponse] = useState(''); // 创建 AgentModel 实例 const agent = new AgentModel(); // 初始化设置 agent.updateSettings({ model: 'gpt-4-mini', temperature: 0.7, system_message: '你是一个helpful的AI助手' }); // 处理发送消息 const handleSendMessage = useCallback(async () => { if (!inputText.trim()) return; setIsLoading(true); setResponse(''); const newMessages = [ ...messages, { role: 'user', content: inputText } ]; setMessages(newMessages); setInputText(''); try { let fullResponse = ''; await agent.getCompletion(newMessages, (content) => { fullResponse += content; setResponse(prev => prev + content); }); setMessages(prev => [...prev, { role: 'assistant', content: fullResponse }]); } catch (error) { console.error('Error:', error); alert('发送消息时出错'); } finally { setIsLoading(false); } }, [inputText, messages]); // 处理按键事件 const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } }; return ( <div className={clsx( "flex flex-col h-screen max-w-2xl mx-auto bg-gray-50", className )}> {/* 头部 */} <div className="p-4 bg-white shadow-sm"> <h1 className="text-xl font-semibold text-gray-800">AI 助手</h1> </div> {/* 消息列表区域 */} <div className="flex-1 overflow-y-auto p-4 space-y-4"> {messages.map((msg, index) => ( <div key={index} className={clsx( "flex", msg.role === 'user' ? 'justify-end' : 'justify-start' )} > <div className={clsx( "max-w-[80%] p-3 rounded-lg", msg.role === 'user' ? 'bg-blue-500 text-white rounded-br-none' : 'bg-white text-gray-800 shadow-sm rounded-bl-none' )} > {msg.content} </div> </div> ))} {/* 加载状态和流式响应 */} {isLoading && ( <div className="flex justify-start"> <div className="max-w-[80%] p-3 rounded-lg bg-white text-gray-800 shadow-sm rounded-bl-none"> {response || ( <div className="flex items-center space-x-2"> <span>思考中</span> <span className="flex space-x-1"> <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} /> <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} /> <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} /> </span> </div> )} </div> </div> )} </div> {/* 输入区域 */} <div className="border-t bg-white p-4"> <div className="flex gap-2 max-w-2xl mx-auto"> <input type="text" value={inputText} onChange={(e) => setInputText(e.target.value)} onKeyPress={handleKeyPress} placeholder="输入消息..." disabled={isLoading} className="flex-1 px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100" /> <button onClick={handleSendMessage} disabled={isLoading || !inputText.trim()} className="px-6 py-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" > 发送 </button> </div> </div> </div> ); }; export default ChatComponent;
css/* 在你的全局样式文件中添加以下自定义样式 */ @tailwind base; @tailwind components; @tailwind utilities; /* 自定义滚动条样式 */ .overflow-y-auto { scrollbar-width: thin; scrollbar-color: #CBD5E0 transparent; } .overflow-y-auto::-webkit-scrollbar { width: 6px; } .overflow-y-auto::-webkit-scrollbar-track { background: transparent; } .overflow-y-auto::-webkit-scrollbar-thumb { background-color: #CBD5E0; border-radius: 3px; }
typescript// tailwind.config.js module.exports = { content: [ "./src/**/*.{js,jsx,ts,tsx}", ], theme: { extend: { animation: { bounce: 'bounce 1s infinite', }, }, }, plugins: [], }
这个实现包括:
类型安全:使用TypeScript确保类型安全
增强的UI特性:
交互优化:
样式改进:
可访问性:
要使用这个组件,你需要:
bashnpm install clsx
确保项目中已配置Tailwind CSS
在使用组件的地方导入:
jsximport ChatComponent from './components/ChatComponent'; function App() { return ( <div className="app"> <ChatComponent /> </div> ); }
检查上述代码,看看哪里有问题
我在整体检查这份代码时,逻辑上大体没有严重问题,界面和功能也都能照常工作。不过有几个可能需要留意的地方,供你参考:
AgentModel
每次渲染都被重新创建ts// 每次渲染都会创建一个新的 AgentModel 实例 const agent = new AgentModel(); // 并且每次都更新它的配置 agent.updateSettings({ model: 'gpt-4-mini', temperature: 0.7, system_message: '你是一个helpful的AI助手' });
潜在问题:
AgentModel
有内部状态(例如连接、缓存等),每次渲染都重新创建会导致状态重置或资源重复分配。AgentModel
的实例化提到组件外面或者使用 useMemo
/useRef
做持久化存储,避免在每次渲染时重复创建。可选改进:
ts// 1) 放在组件外部(如果它无须依赖任何props动态变化) const agent = new AgentModel(); agent.updateSettings({ model: 'gpt-4-mini', temperature: 0.7, system_message: '你是一个helpful的AI助手' }); const ChatComponent: React.FC<ChatComponentProps> = ({ className }) => { // ... }; export default ChatComponent;
或:
tsconst ChatComponent: React.FC<ChatComponentProps> = ({ className }) => { const agent = useMemo(() => { const instance = new AgentModel(); instance.updateSettings({ model: 'gpt-4-mini', temperature: 0.7, system_message: '你是一个helpful的AI助手' }); return instance; }, []); // ... }; export default ChatComponent;
onKeyPress
在 React 中已逐渐被废弃React 17 之后官方文档不建议使用 onKeyPress
,原因是它的行为在不同浏览器可能不一致或被废弃,推荐改用 onKeyDown
/ onKeyUp
。
tsx// 现有写法 <input // ... onKeyPress={handleKeyPress} /> // 建议改成 <input // ... onKeyDown={handleKeyDown} />
当然你也可以保持现状,如果在实际使用中没遇到兼容问题,短期内也没必然要改。但更好的做法是使用 onKeyDown
,并在逻辑上同样判断 e.key === 'Enter' && !e.shiftKey
。
tsconst handleSendMessage = useCallback(async () => { // ... }, [inputText, messages]);
在这个回调里其实也使用到了 agent
,如果你打算将 agent
从外部或 useMemo
中传入,这里最好也把 agent
加到依赖数组中:
tsconst handleSendMessage = useCallback(async () => { // ... }, [inputText, messages, agent]);
如果你直接在组件函数体里
new AgentModel()
(不是放在外面,也没有useMemo
),那实际上每次渲染都会重建agent
,useCallback 的依赖没有意义,所以看起来也不会报错。但从正确的依赖管理角度讲,应该包含agent
。
自动滚动到底部
当消息更新时,通常需要让聊天窗口自动滚动到底部,方便查看最新回复。可以使用 useEffect
,在 messages
或 isLoading
改变后,执行一次“滚动到最底部”的操作。
消息流式展示
现在的写法是:在 isLoading
状态下,消息区单独显示一个“正在思考”气泡,最终再把完整回复插入到 messages
中。如果想让用户在等待时也能实时看到回复的变化,可以考虑把“流式的部分”直接追加到最后一个 assistant 气泡里:
messages
数组的最后一条如果是 assistant
,就把新 content 直接更新到那一条的 content
字段,而不是等流完一次性加进去。textarea支持多行输入
如果要支持多行输入(Shift+Enter换行),可以把 <input type="text" />
换成 <textarea />
,并在 onKeyDown
判断 Enter && !Shift
时阻止默认提交。
Tailwind 配置看起来没什么明显问题,下面几点可以确认一下:
tailwind.config.js
里 content
指向的路径是否和你项目中的实际文件目录一致("./src/**/*.{js,jsx,ts,tsx}"
)。index.css
(或 App.css
)里正确引入了 @tailwind base; @tailwind components; @tailwind utilities;
。::-webkit-scrollbar
来做,或者使用专门的插件。你的写法也是可行的,确保在最终的CSS层级上它不会被后续覆盖就好。AgentModel
的初始化与配置最好不要在函数体中每次渲染都执行。onKeyPress
在新版 React 中不再推荐使用,建议改用 onKeyDown
。useCallback
/ useEffect
中的依赖列表)要保持一致性。除此之外,代码整体写法相对清晰,Tailwind 样式也比较简洁。如果在实践过程中没有遇到报错或样式失效,那么以上只是一些潜在或未来可优化的点,可根据需要做相应调整。祝你开发顺利!