Skip to content

Commit 149d36e

Browse files
committed
refactor: Optimize ChatInput, ChatWindow, MessageItem, and MessageList components with useCallback and memoization to enhance performance and reduce unnecessary re-renders
1 parent e42436a commit 149d36e

File tree

4 files changed

+84
-33
lines changed

4 files changed

+84
-33
lines changed

src/renderer/src/components/pages/chat/ChatInput.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef, forwardRef, useImperativeHandle, useState } from 'react'
1+
import React, { useRef, forwardRef, useImperativeHandle, useState, useCallback } from 'react'
22
import { Input, Button, Alert, Switch, Tooltip, Space, Select, Dropdown, Flex } from 'antd'
33
import {
44
SendOutlined,
@@ -174,7 +174,7 @@ export interface ChatInputRef {
174174
focus: () => void
175175
}
176176

177-
const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
177+
const ChatInput = React.memo(forwardRef<ChatInputRef, ChatInputProps>(
178178
(
179179
{
180180
value,
@@ -205,12 +205,16 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
205205
}
206206
}))
207207

208-
const handleKeyDown = (e: React.KeyboardEvent) => {
208+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
209209
if (e.key === 'Enter' && !e.shiftKey) {
210210
e.preventDefault()
211211
onSend()
212212
}
213-
}
213+
}, [onSend])
214+
215+
const handleTextChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
216+
onChange(e.target.value)
217+
}, [onChange])
214218

215219
const hasNoModels = !llmConfigs || llmConfigs.length === 0
216220
const hasNoSelectedModel = !selectedModel
@@ -272,7 +276,7 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
272276
: '输入消息... (Enter发送,Shift+Enter换行)'
273277
}
274278
value={value}
275-
onChange={(e) => onChange(e.target.value)}
279+
onChange={handleTextChange}
276280
onKeyDown={handleKeyDown}
277281
autoSize={{ minRows: 1, maxRows: 10 }}
278282
disabled={hasNoModels}
@@ -348,6 +352,6 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
348352
</div>
349353
)
350354
}
351-
)
355+
))
352356

353357
export default ChatInput

src/renderer/src/components/pages/chat/ChatWindow.tsx

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useMemo, useRef, forwardRef, useImperativeHandle } from 'react'
1+
import React, { useState, useMemo, useRef, forwardRef, useImperativeHandle, useCallback } from 'react'
22
import { usePagesStore } from '../../../stores/pagesStore'
33
import { useTabsStore } from '../../../stores/tabsStore'
44
import { useUIStore } from '../../../stores/uiStore'
@@ -63,36 +63,36 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
6363
}, [chat?.messages])
6464

6565
// 处理分支切换(所有消息都使用兄弟分支切换)
66-
const handleSwitchBranch = (messageId: string, branchIndex: number) => {
66+
const handleSwitchBranch = useCallback((messageId: string, branchIndex: number) => {
6767
const newPath = messageTree.switchToSiblingBranch(messageId, branchIndex)
6868
updateCurrentPath(chatId, newPath)
69-
}
69+
}, [messageTree, updateCurrentPath, chatId])
7070

71-
const handleToggleMessageCollapse = (messageId: string) => {
71+
const handleToggleMessageCollapse = useCallback((messageId: string) => {
7272
toggleMessageCollapse(chatId, messageId)
73-
}
73+
}, [toggleMessageCollapse, chatId])
7474

75-
const handleCollapseAll = () => {
75+
const handleCollapseAll = useCallback(() => {
7676
collapseAllMessages(
7777
chatId,
7878
chat.messages.map((msg) => msg.id)
7979
)
80-
}
80+
}, [collapseAllMessages, chatId, chat?.messages])
8181

82-
const handleExpandAll = () => {
82+
const handleExpandAll = useCallback(() => {
8383
expandAllMessages(chatId)
84-
}
84+
}, [expandAllMessages, chatId])
8585

86-
const handleOpenSettings = () => {
86+
const handleOpenSettings = useCallback(() => {
8787
const settingsPageId = createAndOpenSettingsPage('llm') // 从聊天窗口点击设置通常是想配置模型
8888
setActiveTab(settingsPageId)
89-
}
89+
}, [createAndOpenSettingsPage, setActiveTab])
9090

91-
const handleToggleMessageTree = () => {
91+
const handleToggleMessageTree = useCallback(() => {
9292
setMessageTreeCollapsed(!messageTreeCollapsed)
93-
}
93+
}, [messageTreeCollapsed])
9494

95-
const handleMessageTreeNodeSelect = (messageId: string) => {
95+
const handleMessageTreeNodeSelect = useCallback((messageId: string) => {
9696
// 构建到选中消息的路径
9797
const path: string[] = []
9898
let currentMsg = chat?.messages.find((msg) => msg.id === messageId)
@@ -108,19 +108,19 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
108108

109109
// 更新当前路径
110110
updateCurrentPath(chatId, path)
111-
}
111+
}, [chat?.messages, updateCurrentPath, chatId])
112112

113-
const handleMessageTreePathChange = (path: string[]) => {
113+
const handleMessageTreePathChange = useCallback((path: string[]) => {
114114
// 更新当前路径
115115
updateCurrentPath(chatId, path)
116-
}
116+
}, [updateCurrentPath, chatId])
117117

118-
const handleMessageTreeWidthChange = (width: number) => {
118+
const handleMessageTreeWidthChange = useCallback((width: number) => {
119119
setMessageTreeWidth(width)
120120
localStorage.setItem('messageTreeWidth', width.toString())
121-
}
121+
}, [])
122122

123-
const handleAutoQuestionChange = (enabled: boolean, mode: 'ai' | 'preset', listId?: string) => {
123+
const handleAutoQuestionChange = useCallback((enabled: boolean, mode: 'ai' | 'preset', listId?: string) => {
124124
console.log('ChatWindow handleAutoQuestionChange:', {
125125
enabled,
126126
mode,
@@ -140,7 +140,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
140140
setAutoQuestionListId(defaultListId)
141141
console.log('ChatWindow 自动设置 defaultListId:', defaultListId)
142142
}
143-
}
143+
}, [settings.promptLists, settings.defaultPromptListId])
144144

145145
if (!chat) {
146146
return <div className="chat-window-error">聊天不存在</div>
@@ -238,7 +238,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
238238
ref={chatInputRef}
239239
value={inputValue}
240240
onChange={setInputValue}
241-
onSend={async () => {
241+
onSend={useCallback(async () => {
242242
if (inputValue.trim()) {
243243
setInputValue('') // 立即清空输入框
244244
await onSendMessage(inputValue)
@@ -247,7 +247,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
247247
chatInputRef.current?.focus()
248248
}, 0)
249249
}
250-
}}
250+
}, [inputValue, onSendMessage])}
251251
onStop={onStopGeneration}
252252
disabled={isLoading}
253253
loading={isLoading}

src/renderer/src/components/pages/chat/MessageItem.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ interface MessageItemProps {
5151
onToggleCollapse?: (messageId: string) => void
5252
}
5353

54-
export default function MessageItem({
54+
const MessageItem = React.memo(function MessageItem({
5555
message,
5656
chatId,
5757
isLoading = false,
@@ -434,4 +434,30 @@ export default function MessageItem({
434434
</div>
435435
</div>
436436
)
437-
}
437+
}, (prevProps, nextProps) => {
438+
// 自定义比较函数,避免不必要的重渲染
439+
// 注意:对于正在流式输出的消息,我们需要重新渲染
440+
if (prevProps.message.id !== nextProps.message.id) return false
441+
442+
// 如果消息内容或流式状态发生变化,需要重新渲染
443+
if (prevProps.message.content !== nextProps.message.content ||
444+
prevProps.message.reasoning_content !== nextProps.message.reasoning_content ||
445+
prevProps.message.isStreaming !== nextProps.message.isStreaming ||
446+
prevProps.message.isFavorited !== nextProps.message.isFavorited ||
447+
prevProps.message.modelId !== nextProps.message.modelId) {
448+
return false
449+
}
450+
451+
// 检查其他重要属性
452+
return (
453+
prevProps.isLoading === nextProps.isLoading &&
454+
prevProps.isLastMessage === nextProps.isLastMessage &&
455+
prevProps.isCollapsed === nextProps.isCollapsed &&
456+
prevProps.hasChildBranches === nextProps.hasChildBranches &&
457+
prevProps.branchIndex === nextProps.branchIndex &&
458+
prevProps.branchCount === nextProps.branchCount &&
459+
prevProps.llmConfigs === nextProps.llmConfigs
460+
)
461+
})
462+
463+
export default MessageItem

src/renderer/src/components/pages/chat/MessageList.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ interface MessageListProps {
2828
onOpenSettings?: () => void
2929
}
3030

31-
export default function MessageList({
31+
const MessageList = React.memo(function MessageList({
3232
chatId,
3333
messages,
3434
currentPath = [],
@@ -229,4 +229,25 @@ export default function MessageList({
229229
</div>
230230
</div>
231231
)
232-
}
232+
}, (prevProps, nextProps) => {
233+
// 自定义比较函数,只在关键属性变化时重新渲染
234+
return (
235+
prevProps.messages === nextProps.messages &&
236+
prevProps.currentPath === nextProps.currentPath &&
237+
prevProps.isLoading === nextProps.isLoading &&
238+
prevProps.collapsedMessages === nextProps.collapsedMessages &&
239+
prevProps.llmConfigs === nextProps.llmConfigs &&
240+
// 对于回调函数,我们假设它们是稳定的(在父组件中使用 useCallback)
241+
prevProps.onRetryMessage === nextProps.onRetryMessage &&
242+
prevProps.onEditMessage === nextProps.onEditMessage &&
243+
prevProps.onEditAndResendMessage === nextProps.onEditAndResendMessage &&
244+
prevProps.onToggleFavorite === nextProps.onToggleFavorite &&
245+
prevProps.onModelChange === nextProps.onModelChange &&
246+
prevProps.onDeleteMessage === nextProps.onDeleteMessage &&
247+
prevProps.onSwitchBranch === nextProps.onSwitchBranch &&
248+
prevProps.onToggleMessageCollapse === nextProps.onToggleMessageCollapse &&
249+
prevProps.onOpenSettings === nextProps.onOpenSettings
250+
)
251+
})
252+
253+
export default MessageList

0 commit comments

Comments
 (0)