refactor(Company): 提取共享的 useStockSearch Hook

- 新增 useStockSearch.ts:统一股票模糊搜索逻辑
  - 支持按代码或名称搜索
  - 支持排除指定股票(用于对比场景)
  - 使用 useMemo 优化性能
- 重构 SearchBar.js:使用共享 Hook,减少 15 行代码
- 重构 CompareStockInput.tsx:使用共享 Hook,减少 20 行代码

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-17 15:34:36 +08:00
parent c49dee72eb
commit 3b352be1a8
3 changed files with 76 additions and 39 deletions

View File

@@ -13,6 +13,7 @@ import {
VStack, VStack,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { SearchIcon } from '@chakra-ui/icons'; import { SearchIcon } from '@chakra-ui/icons';
import { useStockSearch } from '../../hooks/useStockSearch';
/** /**
* 股票搜索栏组件(带模糊搜索下拉) * 股票搜索栏组件(带模糊搜索下拉)
@@ -31,27 +32,18 @@ const SearchBar = ({
}) => { }) => {
// 下拉状态 // 下拉状态
const [showDropdown, setShowDropdown] = useState(false); const [showDropdown, setShowDropdown] = useState(false);
const [filteredStocks, setFilteredStocks] = useState([]);
const containerRef = useRef(null); const containerRef = useRef(null);
// 从 Redux 获取全部股票列表 // 从 Redux 获取全部股票列表
const allStocks = useSelector(state => state.stock.allStocks); const allStocks = useSelector(state => state.stock.allStocks);
// 模糊搜索过滤 // 使用共享的搜索 Hook
const filteredStocks = useStockSearch(allStocks, inputCode, { limit: 10 });
// 根据搜索结果更新下拉显示状态
useEffect(() => { useEffect(() => {
if (inputCode && inputCode.trim()) { setShowDropdown(filteredStocks.length > 0 && !!inputCode?.trim());
const searchTerm = inputCode.trim().toLowerCase(); }, [filteredStocks, inputCode]);
const filtered = allStocks.filter(stock =>
stock.code.toLowerCase().includes(searchTerm) ||
stock.name.includes(inputCode.trim())
).slice(0, 10); // 限制显示10条
setFilteredStocks(filtered);
setShowDropdown(filtered.length > 0);
} else {
setFilteredStocks([]);
setShowDropdown(false);
}
}, [inputCode, allStocks]);
// 点击外部关闭下拉 // 点击外部关闭下拉
useEffect(() => { useEffect(() => {

View File

@@ -18,6 +18,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { SearchIcon } from '@chakra-ui/icons'; import { SearchIcon } from '@chakra-ui/icons';
import { BarChart2 } from 'lucide-react'; import { BarChart2 } from 'lucide-react';
import { useStockSearch, type Stock } from '../../../hooks/useStockSearch';
interface CompareStockInputProps { interface CompareStockInputProps {
onCompare: (stockCode: string) => void; onCompare: (stockCode: string) => void;
@@ -25,11 +26,6 @@ interface CompareStockInputProps {
currentStockCode?: string; currentStockCode?: string;
} }
interface Stock {
code: string;
name: string;
}
interface RootState { interface RootState {
stock: { stock: {
allStocks: Stock[]; allStocks: Stock[];
@@ -43,7 +39,6 @@ const CompareStockInput: React.FC<CompareStockInputProps> = ({
}) => { }) => {
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [showDropdown, setShowDropdown] = useState(false); const [showDropdown, setShowDropdown] = useState(false);
const [filteredStocks, setFilteredStocks] = useState<Stock[]>([]);
const [selectedStock, setSelectedStock] = useState<Stock | null>(null); const [selectedStock, setSelectedStock] = useState<Stock | null>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -55,25 +50,16 @@ const CompareStockInput: React.FC<CompareStockInputProps> = ({
const goldColor = '#F4D03F'; const goldColor = '#F4D03F';
const bgColor = '#1A202C'; const bgColor = '#1A202C';
// 模糊搜索过滤 // 使用共享的搜索 Hook排除当前股票
const filteredStocks = useStockSearch(allStocks, inputValue, {
excludeCode: currentStockCode,
limit: 8,
});
// 根据搜索结果更新下拉显示状态
useEffect(() => { useEffect(() => {
if (inputValue && inputValue.trim()) { setShowDropdown(filteredStocks.length > 0 && !!inputValue?.trim());
const searchTerm = inputValue.trim().toLowerCase(); }, [filteredStocks, inputValue]);
const filtered = allStocks
.filter(
(stock) =>
stock.code !== currentStockCode && // 排除当前股票
(stock.code.toLowerCase().includes(searchTerm) ||
stock.name.includes(inputValue.trim()))
)
.slice(0, 8); // 限制显示8条
setFilteredStocks(filtered);
setShowDropdown(filtered.length > 0);
} else {
setFilteredStocks([]);
setShowDropdown(false);
}
}, [inputValue, allStocks, currentStockCode]);
// 点击外部关闭下拉 // 点击外部关闭下拉
useEffect(() => { useEffect(() => {

View File

@@ -0,0 +1,59 @@
/**
* useStockSearch - 股票模糊搜索 Hook
*
* 提取自 SearchBar.js 和 CompareStockInput.tsx 的共享搜索逻辑
* 支持按代码或名称搜索,可选排除指定股票
*/
import { useMemo } from 'react';
export interface Stock {
code: string;
name: string;
}
interface UseStockSearchOptions {
excludeCode?: string;
limit?: number;
}
/**
* 股票模糊搜索 Hook
*
* @param allStocks - 全部股票列表
* @param searchTerm - 搜索关键词
* @param options - 可选配置
* @param options.excludeCode - 排除的股票代码(用于对比场景)
* @param options.limit - 返回结果数量限制,默认 10
* @returns 过滤后的股票列表
*/
export const useStockSearch = (
allStocks: Stock[],
searchTerm: string,
options: UseStockSearchOptions = {}
): Stock[] => {
const { excludeCode, limit = 10 } = options;
return useMemo(() => {
const trimmed = searchTerm?.trim();
if (!trimmed) return [];
const term = trimmed.toLowerCase();
return allStocks
.filter((stock) => {
// 排除指定股票
if (excludeCode && stock.code === excludeCode) {
return false;
}
// 按代码或名称匹配
return (
stock.code.toLowerCase().includes(term) ||
stock.name.includes(trimmed)
);
})
.slice(0, limit);
}, [allStocks, searchTerm, excludeCode, limit]);
};
export default useStockSearch;