mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 14:29:15 +08:00
perf: part of memory leak (#8619)
* fix: 修复多个组件中的内存泄漏问题 清理setTimeout和事件监听器以避免内存泄漏 优化useEffect中的异步操作清理逻辑 * fix: review comments
This commit is contained in:
parent
27af64f2bd
commit
b716a7446a
@ -16,12 +16,22 @@ const EmojiPicker: FC<Props> = ({ onEmojiClick }) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current) {
|
const refValue = ref.current
|
||||||
ref.current.addEventListener('emoji-click', (event: any) => {
|
|
||||||
|
if (refValue) {
|
||||||
|
const handleEmojiClick = (event: any) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
onEmojiClick(event.detail.unicode || event.detail.emoji.unicode)
|
onEmojiClick(event.detail.unicode || event.detail.emoji.unicode)
|
||||||
})
|
}
|
||||||
|
// 添加事件监听器
|
||||||
|
refValue.addEventListener('emoji-click', handleEmojiClick)
|
||||||
|
|
||||||
|
// 清理事件监听器
|
||||||
|
return () => {
|
||||||
|
refValue.removeEventListener('emoji-click', handleEmojiClick)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}, [onEmojiClick])
|
}, [onEmojiClick])
|
||||||
|
|
||||||
// @ts-ignore next-line
|
// @ts-ignore next-line
|
||||||
|
|||||||
@ -64,9 +64,9 @@ const WebviewContainer = memo(
|
|||||||
webviewRef.current.src = url
|
webviewRef.current.src = url
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
webviewRef.current?.removeEventListener('dom-ready', handleDomReady)
|
||||||
webviewRef.current?.removeEventListener('did-finish-load', handleLoaded)
|
webviewRef.current?.removeEventListener('did-finish-load', handleLoaded)
|
||||||
webviewRef.current?.removeEventListener('did-navigate-in-page', handleNavigate)
|
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
|
// because the appid and url are enough, no need to add onLoadedCallback
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@ -162,6 +162,9 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
|||||||
const onRemoveModel = useCallback((model: Model) => removeModel(model), [removeModel])
|
const onRemoveModel = useCallback((model: Model) => removeModel(model), [removeModel])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let timer: NodeJS.Timeout
|
||||||
|
let mounted = true
|
||||||
|
|
||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@ -188,18 +191,32 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to fetch models', error as Error)
|
logger.error('Failed to fetch models', error as Error)
|
||||||
} finally {
|
} 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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && searchInputRef.current) {
|
if (open && searchInputRef.current) {
|
||||||
setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
searchInputRef.current?.focus()
|
searchInputRef.current?.focus()
|
||||||
}, 350)
|
}, 350)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
const ModalHeader = () => {
|
const ModalHeader = () => {
|
||||||
|
|||||||
@ -141,7 +141,10 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
open && setTimeout(() => inputRef.current?.focus(), 0)
|
if (!open) return
|
||||||
|
|
||||||
|
const timer = setTimeout(() => inputRef.current?.focus(), 0)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -346,7 +346,8 @@ const PopupContainer: React.FC<Props> = ({ model, resolve, modelFilter }) => {
|
|||||||
// 初始化焦点和滚动位置
|
// 初始化焦点和滚动位置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return
|
if (!open) return
|
||||||
setTimeout(() => inputRef.current?.focus(), 0)
|
const timer = setTimeout(() => inputRef.current?.focus(), 0)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
const togglePin = useCallback(
|
const togglePin = useCallback(
|
||||||
|
|||||||
@ -76,7 +76,8 @@ const PopupContainer: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(resizeTextArea, 0)
|
const timer = setTimeout(resizeTextArea, 0)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleAfterOpenChange = (visible: boolean) => {
|
const handleAfterOpenChange = (visible: boolean) => {
|
||||||
|
|||||||
@ -51,11 +51,15 @@ const Selector = <V extends string | number>({
|
|||||||
const inputRef = useRef<any>(null)
|
const inputRef = useRef<any>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let timer: NodeJS.Timeout
|
||||||
if (open) {
|
if (open) {
|
||||||
setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
}, 1)
|
}, 1)
|
||||||
}
|
}
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
const selectedValues = useMemo(() => {
|
const selectedValues = useMemo(() => {
|
||||||
|
|||||||
@ -22,7 +22,8 @@ export const TopNavbarOpenedMinappTabs: FC = () => {
|
|||||||
const [keepAliveMinapps, setKeepAliveMinapps] = useState(openedKeepAliveMinapps)
|
const [keepAliveMinapps, setKeepAliveMinapps] = useState(openedKeepAliveMinapps)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => setKeepAliveMinapps(openedKeepAliveMinapps), 300)
|
const timer = setTimeout(() => setKeepAliveMinapps(openedKeepAliveMinapps), 300)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
}, [openedKeepAliveMinapps])
|
}, [openedKeepAliveMinapps])
|
||||||
|
|
||||||
const handleOnClick = (app) => {
|
const handleOnClick = (app) => {
|
||||||
|
|||||||
@ -709,7 +709,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
}, [assistant, topic])
|
}, [assistant, topic])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => resizeTextArea(), 0)
|
const timerId = requestAnimationFrame(() => resizeTextArea())
|
||||||
|
return () => cancelAnimationFrame(timerId)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
|||||||
const updateSelectedWebSearchProvider = useCallback(
|
const updateSelectedWebSearchProvider = useCallback(
|
||||||
(providerId?: WebSearchProvider['id']) => {
|
(providerId?: WebSearchProvider['id']) => {
|
||||||
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
||||||
|
// NOTE: 也许可以用startTransition优化卡顿问题
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const currentWebSearchProviderId = assistant.webSearchProviderId
|
const currentWebSearchProviderId = assistant.webSearchProviderId
|
||||||
const newWebSearchProviderId = currentWebSearchProviderId === providerId ? undefined : providerId
|
const newWebSearchProviderId = currentWebSearchProviderId === providerId ? undefined : providerId
|
||||||
|
|||||||
@ -446,12 +446,16 @@ const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
const { nodes: flowNodes, edges: flowEdges } = buildConversationFlowData()
|
const { nodes: flowNodes, edges: flowEdges } = buildConversationFlowData()
|
||||||
setNodes([...flowNodes])
|
setNodes([...flowNodes])
|
||||||
setEdges([...flowEdges])
|
setEdges([...flowEdges])
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
}, [buildConversationFlowData, setNodes, setEdges])
|
}, [buildConversationFlowData, setNodes, setEdges])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -97,11 +97,13 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
|
|||||||
}, [couldAddImageFile, couldAddTextFile])
|
}, [couldAddImageFile, couldAddTextFile])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
if (textareaRef.current) {
|
if (textareaRef.current) {
|
||||||
textareaRef.current.focus({ cursor: 'end' })
|
textareaRef.current.focus({ cursor: 'end' })
|
||||||
}
|
}
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
|
return () => clearTimeout(timer)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 仅在打开时执行一次
|
// 仅在打开时执行一次
|
||||||
|
|||||||
@ -494,12 +494,18 @@ const CollapsedContent: FC<{ isExpanded: boolean; resultString: string }> = ({ i
|
|||||||
const [styledResult, setStyledResult] = useState<string>('')
|
const [styledResult, setStyledResult] = useState<string>('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isExpanded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const highlight = async () => {
|
const highlight = async () => {
|
||||||
const result = await highlightCode(isExpanded ? resultString : '', 'json')
|
const result = await highlightCode(resultString, 'json')
|
||||||
setStyledResult(result)
|
setStyledResult(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(highlight, 0)
|
const timer = setTimeout(highlight, 0)
|
||||||
|
|
||||||
|
return () => clearTimeout(timer)
|
||||||
}, [isExpanded, resultString, highlightCode])
|
}, [isExpanded, resultString, highlightCode])
|
||||||
|
|
||||||
if (!isExpanded) {
|
if (!isExpanded) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user