From ad971796ba357201af818674237954942982b4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Sun, 16 Nov 2025 09:53:24 +0800 Subject: [PATCH] 1 --- src/app.config.ts | 8 ++- src/components/GameManagePopup/index.tsx | 9 ++- src/game_pages/list/index.tsx | 31 +++++++--- src/main_pages/components/ListPageContent.tsx | 48 ++++++++++------ .../components/MyselfPageContent.tsx | 24 ++++---- src/main_pages/index.tsx | 3 +- src/store/keyboardStore.ts | 56 +++++++++++++++---- src/user_pages/myself/index.tsx | 28 +++++----- 8 files changed, 143 insertions(+), 64 deletions(-) diff --git a/src/app.config.ts b/src/app.config.ts index f3e65d0..369173a 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -64,9 +64,11 @@ export default defineAppConfig({ ], "preloadRule": { - "home_pages/index": { - "packages": ["publish_pages", 'order_pages', 'user_pages', 'other_pages'], - "network": "all" // wifi/all + // 移除首屏预加载,所有子包改为按需加载,提升启动速度 + // 如果需要,可以在 main_pages/index 加载后再预加载常用包 + "main_pages/index": { + "packages": ["publish_pages"], // 只预加载最常用的发布页面 + "network": "all" } }, diff --git a/src/components/GameManagePopup/index.tsx b/src/components/GameManagePopup/index.tsx index 4d98074..f03f290 100644 --- a/src/components/GameManagePopup/index.tsx +++ b/src/components/GameManagePopup/index.tsx @@ -32,9 +32,12 @@ const CancelPopup = forwardRef((props, ref) => { show: (onAct) => { onFinish.current = onAct; setVisible(true); - setTimeout(() => { - inputRef.current && inputRef.current.focus(); - }, 0); + // 使用 requestAnimationFrame 替代 setTimeout(0),性能更好 + requestAnimationFrame(() => { + requestAnimationFrame(() => { + inputRef.current && inputRef.current.focus(); + }); + }); }, })); diff --git a/src/game_pages/list/index.tsx b/src/game_pages/list/index.tsx index 1acc70e..18ca82c 100644 --- a/src/game_pages/list/index.tsx +++ b/src/game_pages/list/index.tsx @@ -123,6 +123,15 @@ const ListPage = () => { isCityPickerVisible, ]); + // 使用 ref 保存最新的状态值,避免依赖项变化导致函数重新创建 + const showSearchBarRef = useRef(showSearchBar); + const isShowInputCustomerNavBarRef = useRef(isShowInputCustomerNavBar); + + useEffect(() => { + showSearchBarRef.current = showSearchBar; + isShowInputCustomerNavBarRef.current = isShowInputCustomerNavBar; + }, [showSearchBar, isShowInputCustomerNavBar]); + // ScrollView 滚动处理函数 const handleScrollViewScroll = useCallback( (e: any) => { @@ -165,13 +174,17 @@ const ListPage = () => { const positionThreshold = 120; // 需要滚动到距离顶部120px const distanceThreshold = 80; // 需要连续滚动80px才触发 + // 使用 ref 获取最新值,避免依赖项变化 + const currentShowSearchBar = showSearchBarRef.current; + const currentIsShowInputCustomerNavBar = isShowInputCustomerNavBarRef.current; + if ( newDirection === "up" && currentScrollTop > positionThreshold && totalScrollDistance > distanceThreshold ) { // 上滑超过阈值,且连续滚动距离足够,隐藏搜索框 - if (showSearchBar || !isShowInputCustomerNavBar) { + if (currentShowSearchBar || !currentIsShowInputCustomerNavBar) { setShowSearchBar(false); updateListPageState({ isShowInputCustomerNavBar: true, @@ -184,7 +197,7 @@ const ListPage = () => { currentScrollTop <= positionThreshold ) { // 下滑且连续滚动距离足够,或者回到顶部附近,显示搜索框 - if (!showSearchBar || isShowInputCustomerNavBar) { + if (!currentShowSearchBar || currentIsShowInputCustomerNavBar) { setShowSearchBar(true); updateListPageState({ isShowInputCustomerNavBar: false, @@ -197,7 +210,8 @@ const ListPage = () => { lastScrollTopRef.current = currentScrollTop; lastScrollTimeRef.current = currentTime; }, - [showSearchBar, isShowInputCustomerNavBar, updateListPageState] + [updateListPageState] + // 移除 showSearchBar 和 isShowInputCustomerNavBar 依赖,使用 ref 获取最新值 ); useEffect(() => { @@ -216,7 +230,10 @@ const ListPage = () => { isShowInputCustomerNavBar: false, }); } - }, [matches, pageOption?.page, updateListPageState]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [matches?.length, pageOption?.page]); + // 注意:updateListPageState 是稳定的函数引用,不需要加入依赖项 + // 只依赖实际会变化的数据:matches 的长度和 pageOption.page // 清理定时器 useEffect(() => { @@ -280,10 +297,10 @@ const ListPage = () => { duration: 1000, }); } finally { - // 使用 setTimeout 确保状态更新在下一个事件循环中执行,让 ScrollView 能正确响应 - setTimeout(() => { + // 使用 requestAnimationFrame 替代 setTimeout(0),性能更好 + requestAnimationFrame(() => { setRefreshing(false); - }, 0); + }); } }; diff --git a/src/main_pages/components/ListPageContent.tsx b/src/main_pages/components/ListPageContent.tsx index dd0a10a..a941395 100644 --- a/src/main_pages/components/ListPageContent.tsx +++ b/src/main_pages/components/ListPageContent.tsx @@ -110,6 +110,15 @@ const ListPageContent: React.FC = ({ } }, [scrollToTopTrigger, scrollToTopInternal]); + // 使用 ref 保存最新的状态值,避免依赖项变化导致函数重新创建 + const showSearchBarRef = useRef(showSearchBar); + const isShowInputCustomerNavBarRef = useRef(isShowInputCustomerNavBar); + + useEffect(() => { + showSearchBarRef.current = showSearchBar; + isShowInputCustomerNavBarRef.current = isShowInputCustomerNavBar; + }, [showSearchBar, isShowInputCustomerNavBar]); + // ScrollView 滚动处理 const handleScrollViewScroll = useCallback( (e: any) => { @@ -143,12 +152,16 @@ const ListPageContent: React.FC = ({ const positionThreshold = 120; const distanceThreshold = 80; + // 使用 ref 获取最新值,避免依赖项变化 + const currentShowSearchBar = showSearchBarRef.current; + const currentIsShowInputCustomerNavBar = isShowInputCustomerNavBarRef.current; + if ( newDirection === "up" && currentScrollTop > positionThreshold && totalScrollDistance > distanceThreshold ) { - if (showSearchBar || !isShowInputCustomerNavBar) { + if (currentShowSearchBar || !currentIsShowInputCustomerNavBar) { setShowSearchBar(false); updateListPageState({ isShowInputCustomerNavBar: true, @@ -160,7 +173,7 @@ const ListPageContent: React.FC = ({ (newDirection === "down" && totalScrollDistance > distanceThreshold) || currentScrollTop <= positionThreshold ) { - if (!showSearchBar || isShowInputCustomerNavBar) { + if (!currentShowSearchBar || currentIsShowInputCustomerNavBar) { setShowSearchBar(true); updateListPageState({ isShowInputCustomerNavBar: false, @@ -173,12 +186,8 @@ const ListPageContent: React.FC = ({ lastScrollTopRef.current = currentScrollTop; lastScrollTimeRef.current = currentTime; }, - [ - showSearchBar, - isShowInputCustomerNavBar, - updateListPageState, - onNavStateChange, - ] + [updateListPageState, onNavStateChange] + // 移除 showSearchBar 和 isShowInputCustomerNavBar 依赖,使用 ref 获取最新值 ); useEffect(() => { @@ -196,7 +205,10 @@ const ListPageContent: React.FC = ({ }); onNavStateChange?.({ isShowInputCustomerNavBar: false }); } - }, [matches, pageOption?.page, updateListPageState, onNavStateChange]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [matches?.length, pageOption?.page]); + // 注意:updateListPageState 和 onNavStateChange 是稳定的函数引用,不需要加入依赖项 + // 只依赖实际会变化的数据:matches 的长度和 pageOption.page useEffect(() => { return () => { @@ -236,9 +248,10 @@ const ListPageContent: React.FC = ({ duration: 1000, }); } finally { - setTimeout(() => { + // 使用 requestAnimationFrame 替代 setTimeout(0),性能更好 + requestAnimationFrame(() => { setRefreshing(false); - }, 0); + }); } }; @@ -252,13 +265,16 @@ const ListPageContent: React.FC = ({ // 先通知父组件筛选弹窗状态变化(设置 z-index) onFilterPopupVisibleChange?.(newVisible); // 然后更新本地状态显示/隐藏弹窗 - // 使用 setTimeout 确保 z-index 先设置,再显示弹窗 + // 使用 requestAnimationFrame 确保 z-index 先设置,再显示弹窗 if (newVisible) { - setTimeout(() => { - updateListPageState({ - isShowFilterPopup: newVisible, + // 使用双帧延迟确保 z-index 已生效 + requestAnimationFrame(() => { + requestAnimationFrame(() => { + updateListPageState({ + isShowFilterPopup: newVisible, + }); }); - }, 50); + }); } else { // 关闭时直接更新状态 updateListPageState({ diff --git a/src/main_pages/components/MyselfPageContent.tsx b/src/main_pages/components/MyselfPageContent.tsx index 3e52662..108f631 100644 --- a/src/main_pages/components/MyselfPageContent.tsx +++ b/src/main_pages/components/MyselfPageContent.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { View, Text, Image, ScrollView } from "@tarojs/components"; import Taro from "@tarojs/taro"; import "@/user_pages/myself/index.scss"; @@ -40,13 +40,8 @@ const MyselfPageContent: React.FC = () => { // 确保从编辑页面返回时刷新数据 }); - useEffect(() => { - if (!loading) { - load_game_data(); - } - }, [active_tab]); - - const classifyGameRecords = ( + // 分类球局数据(使用 useCallback 包装,避免每次渲染都创建新函数) + const classifyGameRecords = useCallback(( game_records: TennisMatch[] ): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => { const now = new Date().getTime(); @@ -64,9 +59,10 @@ const MyselfPageContent: React.FC = () => { finishedGames: [] as TennisMatch[], } ); - }; + }, []); - const load_game_data = async () => { + // 使用 useCallback 包装 load_game_data,避免每次渲染都创建新函数 + const load_game_data = useCallback(async () => { try { if (!user_info || !("id" in user_info)) { return; @@ -89,7 +85,13 @@ const MyselfPageContent: React.FC = () => { } catch (error) { console.error("加载球局数据失败:", error); } - }; + }, [active_tab, user_info, classifyGameRecords]); + + useEffect(() => { + if (!loading) { + load_game_data(); + } + }, [loading, load_game_data]); const handle_follow = async () => { try { diff --git a/src/main_pages/index.tsx b/src/main_pages/index.tsx index 9337f12..04acd6e 100644 --- a/src/main_pages/index.tsx +++ b/src/main_pages/index.tsx @@ -38,7 +38,8 @@ const MainPage: React.FC = () => { } }; init(); - }, [fetchUserInfo]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // fetchUserInfo 是稳定的函数引用,不需要加入依赖项 // 处理标签切换 const handleTabChange = useCallback((code: string) => { diff --git a/src/store/keyboardStore.ts b/src/store/keyboardStore.ts index e5bf20b..8b24e36 100644 --- a/src/store/keyboardStore.ts +++ b/src/store/keyboardStore.ts @@ -27,13 +27,31 @@ export const useKeyboardStore = create((set, get) => ({ setKeyboardHeight: (height: number) => { set({ keyboardHeight: height }) const { listeners } = get() - listeners.forEach(listener => listener(height, get().isKeyboardVisible)) + // 使用 requestAnimationFrame 异步执行监听器,避免阻塞主线程 + requestAnimationFrame(() => { + listeners.forEach(listener => { + try { + listener(height, get().isKeyboardVisible) + } catch (error) { + console.error('键盘监听器执行错误:', error) + } + }) + }) }, setKeyboardVisible: (visible: boolean) => { set({ isKeyboardVisible: visible }) const { listeners } = get() - listeners.forEach(listener => listener(get().keyboardHeight, visible)) + // 使用 requestAnimationFrame 异步执行监听器,避免阻塞主线程 + requestAnimationFrame(() => { + listeners.forEach(listener => { + try { + listener(get().keyboardHeight, visible) + } catch (error) { + console.error('键盘监听器执行错误:', error) + } + }) + }) }, addListener: (listener: (height: number, visible: boolean) => void) => { @@ -52,18 +70,36 @@ export const useKeyboardStore = create((set, get) => ({ console.log('初始化全局键盘监听器') + // 使用防抖优化,避免频繁触发 + let lastHeight = 0 + let lastTime = 0 + const debounceDelay = 16 // 约一帧的时间 + Taro.onKeyboardHeightChange?.((res: any) => { const height = Number(res?.height || 0) + const now = Date.now() + + // 防抖:如果高度没变化或时间间隔太短,忽略 + if (height === lastHeight && (now - lastTime) < debounceDelay) { + return + } + + lastHeight = height + lastTime = now + console.log('全局键盘高度变化:', height) - const store = get() - if (height > 0) { - store.setKeyboardVisible(true) - store.setKeyboardHeight(height) - } else { - store.setKeyboardVisible(false) - store.setKeyboardHeight(0) - } + // 使用 requestAnimationFrame 延迟执行,避免阻塞消息处理 + requestAnimationFrame(() => { + const store = get() + if (height > 0) { + store.setKeyboardVisible(true) + store.setKeyboardHeight(height) + } else { + store.setKeyboardVisible(false) + store.setKeyboardHeight(0) + } + }) }) set({ isInitialized: true }) diff --git a/src/user_pages/myself/index.tsx b/src/user_pages/myself/index.tsx index 4686a46..1726d30 100644 --- a/src/user_pages/myself/index.tsx +++ b/src/user_pages/myself/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { View, Text, Image, ScrollView } from "@tarojs/components"; import Taro, { useDidShow } from "@tarojs/taro"; import "./index.scss"; @@ -79,14 +79,8 @@ const MyselfPage: React.FC = () => { // set_user_info(useUserInfo()); // 确保从编辑页面返回时刷新数据 }); - // 切换标签页时重新加载球局数据 - useEffect(() => { - if (!loading) { - load_game_data(); - } - }, [active_tab]); - // 分类球局数据 - const classifyGameRecords = ( + // 分类球局数据(使用 useCallback 包装,避免每次渲染都创建新函数) + const classifyGameRecords = useCallback(( game_records: TennisMatch[] ): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => { const now = new Date().getTime(); @@ -104,9 +98,10 @@ const MyselfPage: React.FC = () => { finishedGames: [] as TennisMatch[], } ); - }; - // 加载球局数据 - const load_game_data = async () => { + }, []); + + // 加载球局数据(使用 useCallback 包装,避免每次渲染都创建新函数) + const load_game_data = useCallback(async () => { try { let games_data; if (active_tab === "hosted") { @@ -127,7 +122,14 @@ const MyselfPage: React.FC = () => { } catch (error) { console.error("加载球局数据失败:", error); } - }; + }, [active_tab, user_info, classifyGameRecords]); + + // 切换标签页时重新加载球局数据 + useEffect(() => { + if (!loading) { + load_game_data(); + } + }, [loading, load_game_data]); // 处理关注/取消关注 const handle_follow = async () => {