diff --git a/src/renderer/src/components/EmojiPicker/index.tsx b/src/renderer/src/components/EmojiPicker/index.tsx index 406d6d1865..69ac7ccdaa 100644 --- a/src/renderer/src/components/EmojiPicker/index.tsx +++ b/src/renderer/src/components/EmojiPicker/index.tsx @@ -16,12 +16,22 @@ const EmojiPicker: FC = ({ onEmojiClick }) => { }, []) useEffect(() => { - if (ref.current) { - ref.current.addEventListener('emoji-click', (event: any) => { + const refValue = ref.current + + if (refValue) { + const handleEmojiClick = (event: any) => { event.stopPropagation() onEmojiClick(event.detail.unicode || event.detail.emoji.unicode) - }) + } + // 添加事件监听器 + refValue.addEventListener('emoji-click', handleEmojiClick) + + // 清理事件监听器 + return () => { + refValue.removeEventListener('emoji-click', handleEmojiClick) + } } + return }, [onEmojiClick]) // @ts-ignore next-line diff --git a/src/renderer/src/components/MinApp/WebviewContainer.tsx b/src/renderer/src/components/MinApp/WebviewContainer.tsx index 844f5c8e43..361bd39696 100644 --- a/src/renderer/src/components/MinApp/WebviewContainer.tsx +++ b/src/renderer/src/components/MinApp/WebviewContainer.tsx @@ -64,9 +64,9 @@ const WebviewContainer = memo( webviewRef.current.src = url return () => { + webviewRef.current?.removeEventListener('dom-ready', handleDomReady) webviewRef.current?.removeEventListener('did-finish-load', handleLoaded) webviewRef.current?.removeEventListener('did-navigate-in-page', handleNavigate) - webviewRef.current?.removeEventListener('dom-ready', handleDomReady) } // because the appid and url are enough, no need to add onLoadedCallback // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/renderer/src/components/ModelList/EditModelsPopup.tsx b/src/renderer/src/components/ModelList/EditModelsPopup.tsx index 01c3ea3a70..531d26eb74 100644 --- a/src/renderer/src/components/ModelList/EditModelsPopup.tsx +++ b/src/renderer/src/components/ModelList/EditModelsPopup.tsx @@ -162,6 +162,9 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { const onRemoveModel = useCallback((model: Model) => removeModel(model), [removeModel]) useEffect(() => { + let timer: NodeJS.Timeout + let mounted = true + runAsyncFunction(async () => { try { setLoading(true) @@ -188,18 +191,32 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { } catch (error) { logger.error('Failed to fetch models', error as Error) } finally { - setTimeout(() => setLoading(false), 300) + if (mounted) { + timer = setTimeout(() => setLoading(false), 300) + } } }) + + return () => { + mounted = false + if (timer) { + clearTimeout(timer) + } + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { if (open && searchInputRef.current) { - setTimeout(() => { + const timer = setTimeout(() => { searchInputRef.current?.focus() }, 350) + + return () => { + clearTimeout(timer) + } } + return }, [open]) const ModalHeader = () => { diff --git a/src/renderer/src/components/Popups/AddAssistantPopup.tsx b/src/renderer/src/components/Popups/AddAssistantPopup.tsx index dace8e44b5..c8bfdb0276 100644 --- a/src/renderer/src/components/Popups/AddAssistantPopup.tsx +++ b/src/renderer/src/components/Popups/AddAssistantPopup.tsx @@ -141,7 +141,10 @@ const PopupContainer: React.FC = ({ resolve }) => { } useEffect(() => { - open && setTimeout(() => inputRef.current?.focus(), 0) + if (!open) return + + const timer = setTimeout(() => inputRef.current?.focus(), 0) + return () => clearTimeout(timer) }, [open]) return ( diff --git a/src/renderer/src/components/Popups/SelectModelPopup/popup.tsx b/src/renderer/src/components/Popups/SelectModelPopup/popup.tsx index cad5daf1c5..c1b04de5df 100644 --- a/src/renderer/src/components/Popups/SelectModelPopup/popup.tsx +++ b/src/renderer/src/components/Popups/SelectModelPopup/popup.tsx @@ -346,7 +346,8 @@ const PopupContainer: React.FC = ({ model, resolve, modelFilter }) => { // 初始化焦点和滚动位置 useEffect(() => { if (!open) return - setTimeout(() => inputRef.current?.focus(), 0) + const timer = setTimeout(() => inputRef.current?.focus(), 0) + return () => clearTimeout(timer) }, [open]) const togglePin = useCallback( diff --git a/src/renderer/src/components/Popups/TextEditPopup.tsx b/src/renderer/src/components/Popups/TextEditPopup.tsx index 1a57205ed0..d5cd04a1c3 100644 --- a/src/renderer/src/components/Popups/TextEditPopup.tsx +++ b/src/renderer/src/components/Popups/TextEditPopup.tsx @@ -76,7 +76,8 @@ const PopupContainer: React.FC = ({ } useEffect(() => { - setTimeout(resizeTextArea, 0) + const timer = setTimeout(resizeTextArea, 0) + return () => clearTimeout(timer) }, []) const handleAfterOpenChange = (visible: boolean) => { diff --git a/src/renderer/src/components/Selector.tsx b/src/renderer/src/components/Selector.tsx index 5c064c19d8..d18c76dbff 100644 --- a/src/renderer/src/components/Selector.tsx +++ b/src/renderer/src/components/Selector.tsx @@ -51,11 +51,15 @@ const Selector = ({ const inputRef = useRef(null) useEffect(() => { + let timer: NodeJS.Timeout if (open) { - setTimeout(() => { + timer = setTimeout(() => { inputRef.current?.focus() }, 1) } + return () => { + clearTimeout(timer) + } }, [open]) const selectedValues = useMemo(() => { diff --git a/src/renderer/src/components/app/PinnedMinapps.tsx b/src/renderer/src/components/app/PinnedMinapps.tsx index bd13c4b8ac..2d7e30f76f 100644 --- a/src/renderer/src/components/app/PinnedMinapps.tsx +++ b/src/renderer/src/components/app/PinnedMinapps.tsx @@ -22,7 +22,8 @@ export const TopNavbarOpenedMinappTabs: FC = () => { const [keepAliveMinapps, setKeepAliveMinapps] = useState(openedKeepAliveMinapps) useEffect(() => { - setTimeout(() => setKeepAliveMinapps(openedKeepAliveMinapps), 300) + const timer = setTimeout(() => setKeepAliveMinapps(openedKeepAliveMinapps), 300) + return () => clearTimeout(timer) }, [openedKeepAliveMinapps]) const handleOnClick = (app) => { diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index ec67d2cd3e..db82793781 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -709,7 +709,8 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = }, [assistant, topic]) useEffect(() => { - setTimeout(() => resizeTextArea(), 0) + const timerId = requestAnimationFrame(() => resizeTextArea()) + return () => cancelAnimationFrame(timerId) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) diff --git a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx index 6b53773c6e..44fe80cbec 100644 --- a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx @@ -31,6 +31,7 @@ const WebSearchButton: FC = ({ ref, assistant, ToolbarButton }) => { const updateSelectedWebSearchProvider = useCallback( (providerId?: WebSearchProvider['id']) => { // TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿 + // NOTE: 也许可以用startTransition优化卡顿问题 setTimeout(() => { const currentWebSearchProviderId = assistant.webSearchProviderId const newWebSearchProviderId = currentWebSearchProviderId === providerId ? undefined : providerId diff --git a/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx b/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx index 1acf5cd284..25801bdeba 100644 --- a/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx +++ b/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx @@ -446,12 +446,16 @@ const ChatFlowHistory: FC = ({ conversationId }) => { useEffect(() => { setLoading(true) - setTimeout(() => { + const timer = setTimeout(() => { const { nodes: flowNodes, edges: flowEdges } = buildConversationFlowData() setNodes([...flowNodes]) setEdges([...flowEdges]) setLoading(false) }, 500) + + return () => { + clearTimeout(timer) + } }, [buildConversationFlowData, setNodes, setEdges]) return ( diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index 874ad25231..2b6dd59d1e 100644 --- a/src/renderer/src/pages/home/Messages/MessageEditor.tsx +++ b/src/renderer/src/pages/home/Messages/MessageEditor.tsx @@ -97,11 +97,13 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC }, [couldAddImageFile, couldAddTextFile]) useEffect(() => { - setTimeout(() => { + const timer = setTimeout(() => { if (textareaRef.current) { textareaRef.current.focus({ cursor: 'end' }) } }, 0) + + return () => clearTimeout(timer) }, []) // 仅在打开时执行一次 diff --git a/src/renderer/src/pages/home/Messages/MessageTools.tsx b/src/renderer/src/pages/home/Messages/MessageTools.tsx index 41c26e7bda..1f53e64fb8 100644 --- a/src/renderer/src/pages/home/Messages/MessageTools.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTools.tsx @@ -494,12 +494,18 @@ const CollapsedContent: FC<{ isExpanded: boolean; resultString: string }> = ({ i const [styledResult, setStyledResult] = useState('') useEffect(() => { + if (!isExpanded) { + return + } + const highlight = async () => { - const result = await highlightCode(isExpanded ? resultString : '', 'json') + const result = await highlightCode(resultString, 'json') setStyledResult(result) } - setTimeout(highlight, 0) + const timer = setTimeout(highlight, 0) + + return () => clearTimeout(timer) }, [isExpanded, resultString, highlightCode]) if (!isExpanded) {