perf(Company): 优化渲染性能和 API 请求

- StockQuoteCard: 添加 memo 包装减少重渲染
- Company/index: componentProps 使用 useMemo 缓存
- useCompanyEvents: 页面浏览事件只触发一次,避免重复追踪
- useCompanyData: 自选股状态改用单股票查询接口,减少数据传输
- CompanyHeader: inputCode 状态下移到 SearchActions,减少父组件重渲染
- CompanyHeader: 移除重复环境光效果,由全局 AmbientGlow 统一处理

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-19 10:14:07 +08:00
parent c979e775a5
commit 51721ce9bf
5 changed files with 96 additions and 94 deletions

View File

@@ -137,25 +137,29 @@ const StockInfoDisplay = memo<{
StockInfoDisplay.displayName = 'StockInfoDisplay';
/**
* 搜索操作区组件
* 搜索操作区组件(状态自管理,减少父组件重渲染)
*/
const SearchActions = memo<{
inputCode: string;
onInputChange: (value: string) => void;
onSearch: () => void;
onSelect: (value: string) => void;
stockCode: string;
onStockChange: (value: string) => void;
isInWatchlist: boolean;
watchlistLoading: boolean;
onWatchlistToggle: () => void;
}>(({
inputCode,
onInputChange,
onSearch,
onSelect,
stockCode,
onStockChange,
isInWatchlist,
watchlistLoading,
onWatchlistToggle,
}) => {
// 输入状态自管理(避免父组件重渲染)
const [inputCode, setInputCode] = useState(stockCode);
// 同步外部 stockCode 变化
React.useEffect(() => {
setInputCode(stockCode);
}, [stockCode]);
// 股票搜索 Hook
const searchHook = useStockSearch({
limit: 10,
@@ -190,18 +194,28 @@ const SearchActions = memo<{
}));
}, [searchResults]);
// 处理搜索按钮点击
const handleSearch = useCallback(() => {
if (inputCode && inputCode !== stockCode) {
onStockChange(inputCode);
}
}, [inputCode, stockCode, onStockChange]);
// 选中股票
const handleSelect = useCallback((value: string) => {
clearSearch();
onSelect(value);
}, [clearSearch, onSelect]);
setInputCode(value);
if (value !== stockCode) {
onStockChange(value);
}
}, [clearSearch, stockCode, onStockChange]);
// 键盘事件
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
onSearch();
handleSearch();
}
}, [onSearch]);
}, [handleSearch]);
return (
<HStack spacing={3}>
@@ -241,7 +255,7 @@ const SearchActions = memo<{
options={stockOptions}
onSearch={doSearch}
onSelect={handleSelect}
onChange={onInputChange}
onChange={setInputCode}
placeholder="输入代码、名称或拼音"
style={{ width: 240 }}
dropdownStyle={{
@@ -271,7 +285,7 @@ const SearchActions = memo<{
size="md"
h="42px"
px={5}
onClick={onSearch}
onClick={handleSearch}
leftIcon={<Icon as={Search} boxSize={4} />}
fontWeight="bold"
borderRadius="10px"
@@ -335,28 +349,6 @@ const CompanyHeader: React.FC<CompanyHeaderProps> = memo(({
onStockChange,
onWatchlistToggle,
}) => {
const [inputCode, setInputCode] = useState(stockCode);
// 处理搜索
const handleSearch = useCallback(() => {
if (inputCode && inputCode !== stockCode) {
onStockChange(inputCode);
}
}, [inputCode, stockCode, onStockChange]);
// 处理选中
const handleSelect = useCallback((value: string) => {
setInputCode(value);
if (value !== stockCode) {
onStockChange(value);
}
}, [stockCode, onStockChange]);
// 同步 stockCode 变化
React.useEffect(() => {
setInputCode(stockCode);
}, [stockCode]);
return (
<Box
position="relative"
@@ -368,20 +360,7 @@ const CompanyHeader: React.FC<CompanyHeaderProps> = memo(({
backdropFilter={FUI_GLASS.blur.md}
overflow="hidden"
>
{/* 环境光效果 - James Turrell 风格 */}
<Box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
pointerEvents="none"
bg={`radial-gradient(ellipse 80% 50% at 20% 40%, ${FUI_COLORS.ambient.warm}, transparent),
radial-gradient(ellipse 60% 40% at 80% 60%, ${FUI_COLORS.ambient.cool}, transparent)`}
opacity={0.6}
/>
{/* 顶部发光线 */}
{/* 顶部发光线(环境光效果由全局 AmbientGlow 提供) */}
<Box
position="absolute"
top={0}
@@ -413,10 +392,8 @@ const CompanyHeader: React.FC<CompanyHeaderProps> = memo(({
{/* 右侧:搜索和操作 */}
<SearchActions
inputCode={inputCode}
onInputChange={setInputCode}
onSearch={handleSearch}
onSelect={handleSelect}
stockCode={stockCode}
onStockChange={onStockChange}
isInWatchlist={isInWatchlist}
watchlistLoading={watchlistLoading}
onWatchlistToggle={onWatchlistToggle}