581 lines
18 KiB
Markdown
581 lines
18 KiB
Markdown
# 前端开发规范\-React篇
|
||
|
||
React 开发实践指南
|
||
|
||
V1\.0 \| 2026年5月 \
|
||
|
||
# 一、概述与总则
|
||
|
||
本文档旨在统一前端开发团队的技术实践标准,确保代码质量、可维护性和团队协作效率。规范覆盖 React 技术栈的核心开发场景,同时涵盖 HTML/CSS、JavaScript/TypeScript 等基础层面的通用准则。
|
||
|
||
## 1\.1 适用范围
|
||
|
||
本规范适用于所有使用 React 技术栈的前端项目,包括但不限于:
|
||
|
||
1. 使用 React 18\+ 的 Web 应用项目
|
||
|
||
2. 基于 Next\.js、Remix 等元框架的服务端渲染项目
|
||
|
||
3. 使用 React Native 的移动端跨平台项目(部分适用)
|
||
|
||
## 1\.2 规范层级
|
||
|
||
规范条目按强制程度分为三个层级,开发者应根据项目实际情况合理遵循:
|
||
|
||
|**层级**|**标识**|**说明**|
|
||
|---|---|---|
|
||
|必须(Must)|\[M\]|所有项目必须严格遵守,Code Review 中必检项|
|
||
|推荐(Should)|\[S\]|强烈建议遵循,特殊场景经评估后可调整|
|
||
|可选(May)|\[O\]|根据项目实际情况选择性采纳|
|
||
|
||
## 1\.3 技术栈版本要求
|
||
|
||
新项目应优先采用以下技术栈版本,已有项目应在迭代周期内逐步升级:
|
||
|
||
|**技术项**|**推荐版本**|**说明**|
|
||
|---|---|---|
|
||
|React|18\.x / 19\.x|使用最新稳定版|
|
||
|TypeScript|5\.5\+|strict 模式启用|
|
||
|Vite|6\.x|构建工具首选|
|
||
|Next\.js|15\.x|SSR/SSG 场景|
|
||
|Tailwind CSS|4\.x|原子化 CSS 方案|
|
||
|ESLint|9\.x|Flat Config 格式|
|
||
|
||
# 二、React 开发规范
|
||
|
||
React 是本规范的核心关注领域。本章从组件设计、Hooks 使用、状态管理、TypeScript 类型约束和性能优化五个维度,系统性地定义 React 开发的最佳实践。
|
||
|
||
## 2\.1 组件设计规范
|
||
|
||
### 2\.1\.1 组件分类与组织
|
||
|
||
React 组件应按职责明确划分为以下类别,并在项目目录中保持清晰的组织结构:
|
||
|
||
|**组件类型**|**存放路径**|**职责说明**|
|
||
|---|---|---|
|
||
|Page 组件|app/ 或 pages/|路由级别的页面组件,负责数据获取和页面级布局|
|
||
|Layout 组件|components/layout/|页面布局框架,如 Header、Sidebar、Footer|
|
||
|UI 组件|components/ui/|基础 UI 元素,Button、Input、Modal 等纯展示组件|
|
||
|Feature 组件|features/\*/components/|业务功能组件,与特定功能域紧耦合|
|
||
|HOC / 工具|components/hoc/|高阶组件和渲染工具(render props)|
|
||
|
||
### 2\.1\.2 函数组件优先
|
||
|
||
自 React 16\.8 引入 Hooks 以来,函数组件已成为官方推荐的标准写法。所有新开发组件必须使用函数组件,类组件仅在维护遗留代码时允许存在。
|
||
|
||
```TypeScript
|
||
// 推荐:函数组件 + Hooks
|
||
import { useState, useCallback } from 'react';
|
||
|
||
interface UserCardProps {
|
||
user: User;
|
||
onSelect: (id: string) => void;
|
||
}
|
||
|
||
export function UserCard({ user, onSelect }: UserCardProps) {
|
||
const [expanded, setExpanded] = useState(false);
|
||
|
||
const handleClick = useCallback(() => {
|
||
onSelect(user.id);
|
||
setExpanded(prev => !prev);
|
||
}, [onSelect, user.id]);
|
||
|
||
return (
|
||
<Card onClick={handleClick}>
|
||
<Avatar src={user.avatar} />
|
||
<UserName>{user.name}</UserName>
|
||
{expanded && <UserDetail user={user} />}
|
||
</Card>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 2\.1\.3 Props 设计原则
|
||
|
||
组件的 Props 接口设计直接影响组件的可复用性和可维护性。遵循以下原则:
|
||
|
||
4. 单一职责:每个组件只接收其渲染所需的最小数据集合,避免传递冗余数据
|
||
|
||
5. 显式接口:使用 TypeScript interface 定义 Props,禁止隐式 any 类型
|
||
|
||
6. 默认值策略:对可选 Props 提供合理的默认值,或使用解构赋值简化处理
|
||
|
||
7. 事件命名:自定义事件处理器以 on 为前缀(如 onSelect、onValueChange),遵循 React 原生事件命名惯例
|
||
|
||
8. 避免过度透传:不要简单地将父组件的 Props 全部展开传递给子组件,应显式声明所需属性
|
||
|
||
### 2\.1\.4 组件文件结构
|
||
|
||
每个组件应按以下结构组织,确保关注点分离和可测试性:
|
||
|
||
```TypeScript
|
||
// components/UserCard/index.tsx
|
||
export { UserCard } from './UserCard';
|
||
export type { UserCardProps } from './types';
|
||
|
||
// components/UserCard/UserCard.tsx
|
||
import { useState } from 'react';
|
||
import type { UserCardProps } from './types';
|
||
import { useUserCard } from './useUserCard';
|
||
import * as S from './styles';
|
||
|
||
export function UserCard({ user, onSelect }: UserCardProps) {
|
||
const { expanded, handleClick } = useUserCard(user, onSelect);
|
||
return (
|
||
<S.Card onClick={handleClick}>...</S.Card>
|
||
);
|
||
}
|
||
|
||
// components/UserCard/types.ts
|
||
export interface UserCardProps {
|
||
user: User;
|
||
onSelect: (id: string) => void;
|
||
}
|
||
|
||
// components/UserCard/useUserCard.ts
|
||
export function useUserCard(user: User, onSelect: (id: string) => void) {
|
||
// 业务逻辑抽离到自定义 Hook
|
||
}
|
||
|
||
// components/UserCard/styles.ts (styled-components / CSS Modules)
|
||
```
|
||
|
||
## 2\.2 Hooks 使用规范
|
||
|
||
### 2\.2\.1 Hooks 基础规则
|
||
|
||
Hooks 是 React 16\.8 引入的革命性特性,必须严格遵循以下使用规则,否则可能导致不可预期的行为:
|
||
|
||
9. 只在最顶层调用 Hooks:不要在循环、条件判断或嵌套函数中调用 Hooks
|
||
|
||
10. 只在 React 函数中调用 Hooks:在函数组件或自定义 Hook 中调用,不要在普通 JavaScript 函数中调用
|
||
|
||
11. 以 use 开头命名:自定义 Hook 必须以 use 开头命名,以便 ESLint 插件识别
|
||
|
||
12. 依赖数组诚实原则:useEffect、useMemo、useCallback 的依赖数组必须完整列出所有依赖项
|
||
|
||
### 2\.2\.2 useEffect 最佳实践
|
||
|
||
useEffect 是最常用的 Hook 之一,也是最容易滥用的。遵循以下实践:
|
||
|
||
```JavaScript
|
||
// 推荐:单一职责的 Effect
|
||
useEffect(() => {
|
||
const controller = new AbortController();
|
||
fetchUser(userId, { signal: controller.signal })
|
||
.then(setUser)
|
||
.catch(setError);
|
||
return () => controller.abort();
|
||
}, [userId]); // 依赖数组必须完整
|
||
|
||
// 推荐:逻辑拆分到独立 Effect
|
||
useEffect(() => {
|
||
// 数据获取逻辑
|
||
}, [params]);
|
||
|
||
useEffect(() => {
|
||
// DOM 操作或订阅逻辑
|
||
return () => { /* 清理逻辑 */ };
|
||
}, []);
|
||
|
||
// 禁止:缺失依赖项
|
||
useEffect(() => {
|
||
fetchData(page); // page 未在依赖数组中!
|
||
}, []); // eslint-disable-line 是临时方案,应尽快修复
|
||
```
|
||
|
||
### 2\.2\.3 useMemo 与 useCallback
|
||
|
||
性能优化 Hooks 应在有明确性能问题时使用,避免过早优化。遵循以下准则:
|
||
|
||
13. useMemo:用于缓存昂贵的计算结果,仅在计算成本显著高于缓存开销时使用
|
||
|
||
14. useCallback:用于缓存事件处理函数,主要配合 React\.memo 使用,避免子组件不必要的重渲染
|
||
|
||
15. 避免滥用:简单的计算和事件处理不需要 memoization,React 的渲染性能通常优于预期
|
||
|
||
16. 依赖数组完整性:与 useEffect 同样,必须确保依赖数组的完整性
|
||
|
||
```JavaScript
|
||
// 推荐:复杂数据转换使用 useMemo
|
||
const filteredUsers = useMemo(() => {
|
||
return users
|
||
.filter(u => u.active)
|
||
.sort((a, b) => b.score - a.score)
|
||
.slice(0, 100);
|
||
}, [users]);
|
||
|
||
// 推荐:配合 React.memo 使用 useCallback
|
||
const handleSubmit = useCallback((values: FormData) => {
|
||
api.submit(values).then(onSuccess);
|
||
}, [onSuccess]);
|
||
|
||
// 禁止:对简单值使用 useMemo
|
||
const fullName = useMemo(
|
||
() => `$${firstName} $${lastName}`,
|
||
[firstName, lastName] // 字符串拼接成本极低,无需缓存
|
||
);
|
||
```
|
||
|
||
## 2\.3 状态管理规范
|
||
|
||
### 2\.3\.1 状态管理策略
|
||
|
||
React 应用的状态管理应按状态的作用域和复杂度选择适当的方案,避免过度工程化:
|
||
|
||
|**状态类型**|**管理方案**|**适用场景**|
|
||
|---|---|---|
|
||
|本地 UI 状态|useState|组件内部的临时状态,如表单输入、展开/收起|
|
||
|派生状态|useMemo / 计算|可从已有状态计算得出的值|
|
||
|共享状态|Context / Props|跨 2\-3 层组件传递的状态|
|
||
|全局状态|Zustand / Jotai|应用级共享状态,如用户信息、主题设置|
|
||
|服务端状态|TanStack Query|服务器数据缓存和同步|
|
||
|表单状态|React Hook Form|复杂表单的状态和验证管理|
|
||
|
||
### 2\.3\.2 Context 使用规范
|
||
|
||
React Context 适用于跨组件层级的数据传递,但不当使用会导致性能问题:
|
||
|
||
17. 拆分 Context:将高频变化和低频变化的状态拆分到独立的 Context,避免不必要的重渲染
|
||
|
||
18. 避免过度使用:仅在真正需要跨多级组件传递数据时使用,简单的父子组件通信仍应通过 Props
|
||
|
||
19. 结合 useReducer:对于复杂状态逻辑,Context 配合 useReducer 可以实现轻量级的 Redux\-like 方案
|
||
|
||
```TypeScript
|
||
// 推荐:拆分 Context 避免重渲染
|
||
const ThemeContext = createContext<Theme>('light');
|
||
const UserContext = createContext<User | null>(null);
|
||
|
||
// ThemeProvider 更新时,只消费 ThemeContext 的组件重渲染
|
||
// UserProvider 更新时,只消费 UserContext 的组件重渲染
|
||
|
||
// 推荐:Context + useReducer 组合
|
||
type Action = { type: 'increment' } | { type: 'decrement' };
|
||
const CounterContext = createContext<{
|
||
state: number;
|
||
dispatch: React.Dispatch<Action>;
|
||
} | null>(null);
|
||
```
|
||
|
||
### 2\.3\.3 外部状态管理(Zustand/Jotai)
|
||
|
||
对于中大型企业级应用,推荐使用轻量级的原子化状态管理方案,如 Zustand 或 Jotai:
|
||
|
||
20. Zustand:适合模块化的 Store 架构,API 极简,无 Provider 包裹问题
|
||
|
||
21. Jotai:适合原子化的细粒度状态管理,状态依赖自动追踪
|
||
|
||
22. 避免 Redux 过度使用:仅在需要 Redux DevTools 时间旅行调试、复杂中间件链时考虑 Redux Toolkit
|
||
|
||
## 2\.4 TypeScript 类型规范
|
||
|
||
### 2\.4\.1 组件 Props 类型
|
||
|
||
所有组件 Props 必须使用 TypeScript 接口显式定义,禁止使用 any 类型绕过类型检查:
|
||
|
||
```TypeScript
|
||
// 推荐:显式 Props 接口
|
||
interface ButtonProps {
|
||
variant?: 'primary' | 'secondary' | 'ghost';
|
||
size?: 'sm' | 'md' | 'lg';
|
||
disabled?: boolean;
|
||
loading?: boolean;
|
||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||
children: React.ReactNode;
|
||
}
|
||
|
||
export function Button({
|
||
variant = 'primary',
|
||
size = 'md',
|
||
disabled = false,
|
||
loading = false,
|
||
onClick,
|
||
children,
|
||
}: ButtonProps) {
|
||
// 实现...
|
||
}
|
||
|
||
// 禁止:隐式 any 或缺少类型
|
||
// function Button(props) { // 错误!props 为 any
|
||
// return <button>{props.label}</button>;
|
||
// }
|
||
```
|
||
|
||
### 2\.4\.2 泛型组件
|
||
|
||
对于数据展示类组件,使用泛型实现类型安全的通用组件:
|
||
|
||
```TypeScript
|
||
// 推荐:泛型表格组件
|
||
interface DataTableProps<T> {
|
||
data: T[];
|
||
columns: ColumnDef<T>[];
|
||
keyExtractor: (item: T) => string;
|
||
onRowClick?: (item: T) => void;
|
||
}
|
||
|
||
export function DataTable<T>({
|
||
data, columns, keyExtractor, onRowClick,
|
||
}: DataTableProps<T>) {
|
||
return (
|
||
<table>
|
||
<tbody>
|
||
{data.map(item => (
|
||
<tr key={keyExtractor(item)}
|
||
onClick={() => onRowClick?.(item)}>
|
||
{columns.map(col => (
|
||
<td key={col.key}>{col.render(item)}</td>
|
||
))}
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 2\.4\.3 事件类型
|
||
|
||
React 事件处理函数应使用 React 提供的泛型事件类型,而非原生 DOM 事件类型:
|
||
|
||
|**事件类型**|**React 类型**|
|
||
|---|---|
|
||
|点击事件|React\.MouseEvent\<HTMLButtonElement\>|
|
||
|输入事件|React\.ChangeEvent\<HTMLInputElement\>|
|
||
|表单提交|React\.FormEvent\<HTMLFormElement\>|
|
||
|键盘事件|React\.KeyboardEvent\<HTMLInputElement\>|
|
||
|拖拽事件|React\.DragEvent\<HTMLDivElement\>|
|
||
|触摸事件|React\.TouchEvent\<HTMLDivElement\>|
|
||
|通用事件|React\.SyntheticEvent|
|
||
|
||
## 2\.5 性能优化规范
|
||
|
||
### 2\.5\.1 渲染优化
|
||
|
||
React 的渲染优化应从以下维度系统化地进行:
|
||
|
||
23. React\.memo:对纯展示组件使用 React\.memo 进行浅比较优化,避免不必要的重渲染
|
||
|
||
24. useMemo / useCallback:对昂贵的计算和传递给子组件的回调进行缓存
|
||
|
||
25. 虚拟列表:长列表使用 react\-window 或 react\-virtualized 实现虚拟滚动
|
||
|
||
26. 代码分割:使用 React\.lazy \+ Suspense 实现路由级别和组件级别的懒加载
|
||
|
||
```JavaScript
|
||
// 推荐:React.memo + 自定义比较函数
|
||
export const UserList = React.memo(function UserList({
|
||
users,
|
||
onSelect,
|
||
}: UserListProps) {
|
||
return (
|
||
<ul>
|
||
{users.map(user => (
|
||
<UserItem key={user.id} user={user} onSelect={onSelect} />
|
||
))}
|
||
</ul>
|
||
);
|
||
}, (prev, next) => prev.users === next.users);
|
||
|
||
// 推荐:React.lazy 代码分割
|
||
const Dashboard = React.lazy(() => import('./Dashboard'));
|
||
const Settings = React.lazy(() => import('./Settings'));
|
||
|
||
function App() {
|
||
return (
|
||
<Suspense fallback={<LoadingSpinner />}>
|
||
<Routes>
|
||
<Route path="/dashboard" element={<Dashboard />} />
|
||
<Route path="/settings" element={<Settings />} />
|
||
</Routes>
|
||
</Suspense>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 2\.5\.2 状态更新优化
|
||
|
||
状态更新方式直接影响渲染性能,应遵循以下最佳实践:
|
||
|
||
27. 批量更新:React 18 自动批处理所有状态更新,无需手动合并
|
||
|
||
28. 不可变数据:始终使用不可变更新模式,配合 useMemo/React\.memo 进行引用比较
|
||
|
||
29. 状态拆分:将独立变化的状态拆分为多个 useState,避免不必要的联合更新
|
||
|
||
30. 派生状态:优先使用 useMemo 计算派生状态,避免在状态中存储可计算的值
|
||
|
||
# 三、HTML/CSS 规范
|
||
|
||
HTML 和 CSS 是前端开发的基础,良好的标记和样式实践是构建可维护应用的前提。
|
||
|
||
## 3\.1 HTML 语义化
|
||
|
||
语义化的 HTML 不仅有利于可访问性(A11y),也有助于 SEO 和代码的可读性:
|
||
|
||
31. 使用恰当的语义化标签:header、nav、main、article、section、aside、footer
|
||
|
||
32. 表单元素必须关联 label,使用 aria\-label 或 aria\-labelledby 补充描述
|
||
|
||
33. 图片必须提供有意义的 alt 文本,装饰性图片使用 alt=""
|
||
|
||
34. 遵循标题层级顺序(h1 → h2 → h3),不要跳级使用
|
||
|
||
## 3\.2 CSS 架构
|
||
|
||
推荐采用CSS Modules,避免全局命名空间污染:
|
||
|
||
```JavaScript
|
||
// 推荐:CSS Modules
|
||
import styles from './Button.module.css';
|
||
|
||
export function Button({ children }) {
|
||
return <button className={styles.button}>{children}</button>;
|
||
}
|
||
|
||
/* Button.module.css */
|
||
.button {
|
||
padding: 8px 16px;
|
||
border-radius: 4px;
|
||
background: var(--color-primary);
|
||
color: white;
|
||
}
|
||
|
||
.button:hover {
|
||
background: var(--color-primary-dark);
|
||
}
|
||
```
|
||
|
||
## 3\.3 响应式设计
|
||
|
||
所有界面必须适配至少三种断点,采用移动优先的设计策略:
|
||
|
||
|**断点名**|**尺寸范围**|**适配策略**|
|
||
|---|---|---|
|
||
|Mobile|\< 768px|单列布局、触摸友好的交互、简化导航|
|
||
|Tablet|768px \- 1024px|双列布局、侧边栏可收起、适配触控|
|
||
|Desktop|\> 1024px|完整多列布局、 hover 交互、固定侧边栏|
|
||
|
||
# 四、JavaScript/TypeScript 通用规范
|
||
|
||
除 React 特定规范外,团队应遵循以下 JavaScript/TypeScript 通用编码规范。
|
||
|
||
## 4\.1 命名规范
|
||
|
||
35. 文件名:PascalCase 用于组件文件(UserCard\.tsx),camelCase 用于工具文件(formatDate\.ts)
|
||
|
||
36. 组件名:PascalCase,与文件名保持一致
|
||
|
||
37. Hook 名:以 use 开头,后跟 PascalCase(useUserData)
|
||
|
||
38. 常量:UPPER\_SNAKE\_CASE(MAX\_RETRY\_COUNT)
|
||
|
||
39. 布尔变量:使用 is、has、should 等前缀(isLoading、hasError)
|
||
|
||
## 4\.2 代码风格
|
||
|
||
统一使用 ESLint \+ Prettier 进行代码格式化和质量检查,配置文件纳入版本控制:
|
||
|
||
```Java
|
||
// .eslintrc.cjs
|
||
module.exports = {
|
||
extends: [
|
||
'eslint:recommended',
|
||
'@typescript-eslint/recommended',
|
||
'plugin:react-hooks/recommended',
|
||
],
|
||
rules: {
|
||
'@typescript-eslint/no-explicit-any': 'error',
|
||
'@typescript-eslint/explicit-function-return-type': 'warn',
|
||
'react-hooks/exhaustive-deps': 'error',
|
||
},
|
||
};
|
||
|
||
// .prettierrc
|
||
{
|
||
"semi": true,
|
||
"singleQuote": true,
|
||
"tabWidth": 2,
|
||
"trailingComma": "all"
|
||
}
|
||
```
|
||
|
||
## 4\.3 类型安全
|
||
|
||
40. strict 模式:TypeScript 配置必须启用 strict: true
|
||
|
||
41. 禁止 any:原则上禁止使用 any 类型,必要时应使用 unknown 并配合类型收窄
|
||
|
||
42. 返回值类型:公共函数应显式声明返回值类型,利用类型推断的边界情况除外
|
||
|
||
43. 类型导出:组件 Props 接口应随组件一起导出,便于复用
|
||
|
||
# 五、工程化与项目结构
|
||
|
||
良好的项目结构和工程化配置是团队协作的基石。
|
||
|
||
## 5\.1 目录结构
|
||
|
||
推荐采用以下目录组织方式,Feature\-based 结构优先:
|
||
|
||
```Python
|
||
src/
|
||
├── app/ # 路由页面(Next.js / React Router)
|
||
│ ├── layout.tsx
|
||
│ ├── page.tsx
|
||
│ └── dashboard/
|
||
│ └── page.tsx
|
||
├── components/ # 共享组件
|
||
│ ├── ui/ # 基础 UI 组件(Button, Input, Modal)
|
||
│ └── layout/ # 布局组件(Header, Sidebar, Footer)
|
||
├── features/ # 功能模块
|
||
│ └── auth/ # 认证功能
|
||
│ ├── api/ # API 请求
|
||
│ ├── components/ # 功能组件
|
||
│ ├── hooks/ # 功能 Hooks
|
||
│ ├── stores/ # 状态管理
|
||
│ └── types.ts # 功能类型
|
||
├── hooks/ # 全局共享 Hooks
|
||
├── lib/ # 工具库和配置
|
||
│ ├── api.ts # Axios 实例配置
|
||
│ └── utils.ts # 通用工具函数
|
||
├── types/ # 全局类型定义
|
||
└── styles/ # 全局样式和主题配置
|
||
```
|
||
|
||
## 5\.2 开发工作流
|
||
|
||
44. Git 分支策略:采用 Git Flow 或 Trunk\-based 开发,功能分支命名格式 feature/描述 或 fix/描述
|
||
|
||
45. 代码审查:所有代码变更必须通过 Pull Request 审查,至少 1 人 approving 后方可合并
|
||
|
||
46. 提交规范:遵循 Conventional Commits 规范(feat:、fix:、docs:、refactor:、test: 等前缀)
|
||
|
||
47. CI/CD:集成自动化测试、代码质量检查(ESLint、TypeScript 编译检查)到 CI 流水线
|
||
|
||
# 六、性能优化与最佳实践
|
||
|
||
性能优化是前端开发的重要环节,应贯穿整个开发周期。
|
||
|
||
## 6\.1 加载性能
|
||
|
||
48. 资源压缩:启用 Gzip/Brotli 压缩,图片使用 WebP/AVIF 格式
|
||
|
||
49. 懒加载:路由、图片、非首屏组件均使用懒加载策略
|
||
|
||
50. 预加载:对关键资源使用 rel=preload,对后续路由使用 rel=prefetch
|
||
|
||
51. Bundle 分析:定期使用 @next/bundle\-analyzer 或 webpack\-bundle\-analyzer 分析打包体积
|
||
|
||
## 6\.2 运行时性能
|
||
|
||
52. 避免频繁的状态更新:使用防抖(debounce)和节流(throttle)控制高频事件
|
||
|
||
53. Web Workers:将复杂计算 offload 到 Web Worker,避免阻塞主线程
|
||
|
||
54. 内存管理:及时清理定时器、事件监听器和订阅,防止内存泄漏
|
||
|
||
55. 虚拟化:长列表使用虚拟滚动,大数据集使用分页或虚拟表格
|
||
|