mirror of
https://gh-proxy.org/https://github.com/tiajinsha/JKVideo
synced 2026-07-08 07:28:37 +08:00
feat: 性能优化 + Bug修复 + 搜索增强
- expo-image 替换 RN Image(VideoCard/LiveCard/BigVideoCard/CommentItem,recyclingKey) - DanmakuList Animated.Value 对象池,减少 GC 压力 - FlatList 性能参数:windowSize=7 / maxToRenderPerBatch=6 / removeClippedSubviews - bilibili.ts 请求去重(getVideoDetail/getPlayUrl) - SESSDATA 迁移至 expo-secure-store(utils/secureStorage.ts,启动自动迁移) - 主题系统扩展:新增 sheetBg/modalBg/modalText/placeholder/iconDefault/danger 等 token - 多组件深色模式适配:DownloadSheet/LivePlayer/NativeVideoPlayer/DownloadProgressBtn/CommentItem/LoginModal - 搜索页增强:搜索建议 + 热搜榜(hooks/useSearch.ts + app/search.tsx) - LoginModal 修复轮询竞态(cancelled flag + try-catch) - 修复 downloads.tsx 冗余三元表达式
This commit is contained in:
@@ -1,6 +1,25 @@
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { searchVideos } from '../services/bilibili';
|
||||
import type { VideoItem } from '../services/types';
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { searchVideos, getSearchSuggest, getHotSearch } from '../services/bilibili';
|
||||
import type { VideoItem, SearchSuggestItem, HotSearchItem } from '../services/types';
|
||||
|
||||
const HISTORY_KEY = 'search_history';
|
||||
const MAX_HISTORY = 20;
|
||||
|
||||
export type SearchSort = 'default' | 'pubdate' | 'view';
|
||||
|
||||
async function loadHistory(): Promise<string[]> {
|
||||
try {
|
||||
const raw = await AsyncStorage.getItem(HISTORY_KEY);
|
||||
return raw ? JSON.parse(raw) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHistory(history: string[]) {
|
||||
await AsyncStorage.setItem(HISTORY_KEY, JSON.stringify(history));
|
||||
}
|
||||
|
||||
export function useSearch() {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
@@ -8,18 +27,74 @@ export function useSearch() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [sort, setSort] = useState<SearchSort>('default');
|
||||
const [history, setHistory] = useState<string[]>([]);
|
||||
const [suggestions, setSuggestions] = useState<SearchSuggestItem[]>([]);
|
||||
const [hotSearches, setHotSearches] = useState<HotSearchItem[]>([]);
|
||||
const loadingRef = useRef(false);
|
||||
const suggestTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const currentSort = useRef<SearchSort>('default');
|
||||
|
||||
const search = useCallback(async (kw: string, reset = false) => {
|
||||
// Load history & hot searches on mount
|
||||
useEffect(() => {
|
||||
loadHistory().then(setHistory);
|
||||
getHotSearch().then(setHotSearches);
|
||||
}, []);
|
||||
|
||||
// Debounced suggestions
|
||||
useEffect(() => {
|
||||
if (suggestTimer.current) clearTimeout(suggestTimer.current);
|
||||
if (!keyword.trim() || keyword.trim().length < 1) {
|
||||
setSuggestions([]);
|
||||
return;
|
||||
}
|
||||
suggestTimer.current = setTimeout(async () => {
|
||||
const items = await getSearchSuggest(keyword.trim());
|
||||
setSuggestions(items);
|
||||
}, 300);
|
||||
return () => {
|
||||
if (suggestTimer.current) clearTimeout(suggestTimer.current);
|
||||
};
|
||||
}, [keyword]);
|
||||
|
||||
const addToHistory = useCallback(async (kw: string) => {
|
||||
const trimmed = kw.trim();
|
||||
if (!trimmed) return;
|
||||
setHistory(prev => {
|
||||
const filtered = prev.filter(h => h !== trimmed);
|
||||
const next = [trimmed, ...filtered].slice(0, MAX_HISTORY);
|
||||
saveHistory(next);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeFromHistory = useCallback(async (kw: string) => {
|
||||
setHistory(prev => {
|
||||
const next = prev.filter(h => h !== kw);
|
||||
saveHistory(next);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearHistory = useCallback(async () => {
|
||||
setHistory([]);
|
||||
await AsyncStorage.removeItem(HISTORY_KEY);
|
||||
}, []);
|
||||
|
||||
const search = useCallback(async (kw: string, reset = false, sortOverride?: SearchSort) => {
|
||||
if (!kw.trim() || loadingRef.current) return;
|
||||
loadingRef.current = true;
|
||||
setLoading(true);
|
||||
setSuggestions([]);
|
||||
const activeSort = sortOverride ?? currentSort.current;
|
||||
const currentPage = reset ? 1 : page;
|
||||
const orderParam = activeSort === 'pubdate' ? 'pubdate' : activeSort === 'view' ? 'click' : '';
|
||||
try {
|
||||
const items = await searchVideos(kw, currentPage);
|
||||
const items = await searchVideos(kw, currentPage, orderParam);
|
||||
if (reset) {
|
||||
setResults(items);
|
||||
setPage(2);
|
||||
addToHistory(kw);
|
||||
} else {
|
||||
setResults(prev => [...prev, ...items]);
|
||||
setPage(p => p + 1);
|
||||
@@ -31,12 +106,28 @@ export function useSearch() {
|
||||
loadingRef.current = false;
|
||||
setLoading(false);
|
||||
}
|
||||
}, [page]);
|
||||
}, [page, addToHistory]);
|
||||
|
||||
const changeSort = useCallback((newSort: SearchSort) => {
|
||||
setSort(newSort);
|
||||
currentSort.current = newSort;
|
||||
if (keyword.trim()) {
|
||||
search(keyword, true, newSort);
|
||||
}
|
||||
}, [keyword, search]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (!keyword.trim() || loadingRef.current || !hasMore) return;
|
||||
search(keyword, false);
|
||||
}, [keyword, hasMore, search]);
|
||||
|
||||
return { keyword, setKeyword, results, loading, hasMore, search, loadMore };
|
||||
return {
|
||||
keyword, setKeyword,
|
||||
results, loading, hasMore,
|
||||
search, loadMore,
|
||||
sort, changeSort,
|
||||
history, removeFromHistory, clearHistory,
|
||||
suggestions,
|
||||
hotSearches,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user