BaseTable Architecture Design
Table of Contents
- Component Overview
- Architecture Design
- Core Modules
- Data Flow Design
- State Management
- Hook System
- Event System
- Rendering Mechanism
- Performance Optimization
- Extensibility Design
Component Overview
BaseTable is a high-level data table component encapsulated based on Arco Design's Table component, offering rich functional features and flexible extension capabilities. This component adopts a modular design, achieving decoupling and reuse of functions through Composition API and Hook system.
Core Features
- Data Binding: Supports two-way binding of data sources, automatically syncing data changes
- Column Configuration: Flexible column definitions supporting custom rendering, slots, styles, etc.
- Pagination: Built-in pagination controls supporting remote data loading
- Row Selection: Supports single/multi-selection with selection state management
- Virtual Scrolling: Performance optimization for large dataset scenarios
- Drag and Drop Sorting: Supports row dragging to adjust order
- Auto Height: Adapts to container height
- Internationalization: Complete multi-language support
- Custom Rendering: Supports both Render functions and slots
Architecture Design
BaseTable adopts a layered architecture design, dividing functionality into multiple independent modules, achieving collaboration between modules through dependency injection and Composition API.
Module Responsibilities
| Module | File | Responsibility |
|---|---|---|
| Main Component | BaseTable.tsx | Component entry, combining module functions |
| Props Definition | Props.ts | Defines component properties and events |
| Type Definition | BaseTable.d.ts | TypeScript type declarations |
| State Management | useState.ts | Component state initialization and sharing |
| Event Handling | useEvent.ts | User interaction event handling |
| Lifecycle | useHook.ts | Component Hooks and business logic |
| Column Rendering | useColumns.tsx | Column definitions and slot handling |
| Utils | useUtils.ts | General utilities and internationalization |
| API Interface | api.ts | Data request and sorting interfaces |
| Auto Tooltip | AutoTooltip.tsx | Text overflow tooltip component |
| Event Definition | events.ts | List of custom events |
Core Modules
1. Props System (Props.ts)
The Props module defines all input properties and events of the component, using Vue 3's PropType for type constraints.
Core Property Categories:
// Data Related
- columns: Column configuration array
- dataSource: Data source
- url: Remote data URL
- dataTransformer: Data transformation function
// Display Control
- isSeq: Whether to show sequence number
- isHidePagination: Whether to hide pagination
- isVScroll: Whether to enable virtual scrolling
- autoHeight: Whether to enable auto height
// Interaction
- isChecked: Whether to support selection
- isDrag: Whether to support drag and drop
- radio: Whether in single selection mode
- columnResizable: Whether column width is resizable
// Style Customization
- cellStyle: Cell style
- rowStyle: Row style
- rowClassName: Row class name
- hightLightRow: Highlight row color
// Internationalization
- LanguageScopeKey: Language scope key
2. State Management (useState.ts)
The state management module uses Vue 3's provide/inject mechanism to implement state sharing, ensuring all sub-modules can access global state.
State Structure:
Dependency Injection Pattern:
// Create State
export const createState = (props, ctx) => {
const store = useStore(props, ctx)
provide(KEY, store) // Inject into child components
return store
}
// Use State
export const injectState = () => {
return inject(KEY) as StoreType
}
3. Event Handling (useEvent.ts)
The event module is responsible for handling user interactions, including row clicks, column resizing, row selection, etc.
Event Handling Flow:
Core Events:
| Event Name | Trigger Timing | Parameters |
|---|---|---|
| onRowClick | Row Click | record: TableData |
| onColumnResize | Column Resize | dataIndex: string, width: number |
| onSelect | Single Row Select | selectedRowKeys, selectedRows |
| onSelectAll | Select All/Deselect All | isCheck: boolean |
4. Lifecycle Hook (useHook.ts)
The Hook module manages component lifecycle logic and business functionality.
Core Functions:
- Auto Height Calculation: Use ResizeObserver to listen for container size changes
- Data Loading: Remote data fetching and local data updates
- Row Highlight: Visual feedback for the current row
- Pagination Handling: Pagination parameter management
// Auto Height Calculation
onMounted(() => {
const resizeObserver = new ResizeObserver(debounce((entries) => {
const height = entries[0].borderBoxSize[0].blockSize
currentHeight.value = height
}, 150))
resizeObserver.observe(baseTableRef.value)
})
// Data Loading
const getList = async (query = {}) => {
const params = { ...baseParams.value, ...query }
const res = await getListData(params, props.url)
dataSource.value = props.dataTransformer ?
props.dataTransformer(res.items) : res.items
totalCount.value = res.totalCount
}
5. Column Rendering System (useColumns.tsx)
The column rendering module handles the rendering logic of table columns, supporting both slots and Render functions.
Rendering Priority:
Rendering Logic:
const RenderSlot = ({ column, record, rowIndex }) => {
const cellStyle = getCellStyle({ column, record, rowIndex })
// Priority 1: Slot Rendering
if (ctx.slots[column.field]) {
return <div class={styles.bodyCell} style={cellStyle}>
{ctx.slots[column.field]({ column, record, rowIndex })}
</div>
}
// Priority 2: Render Function
const render = column.customRender ||
(() => <AutoTooltip text={getValue(record, column.field)} />)
return <div class={styles.bodyCell} style={cellStyle}>
{render({ column, record, rowIndex })}
</div>
}
Data Flow Design
BaseTable's data flow combines unidirectional data flow with two-way binding.
Data Loading Process
- Initialization: Props pass initial data source
- Reactive Binding: Use
useVModelsfor two-way binding - Remote Loading: Auto load configured via
urlproperty - Data Transformation:
dataTransformerpreprocesses data - State Update: Update
dataSourceandtotalCount - Trigger Event: Emit
reloadevent to notify parent component
Performance Optimized Data Mapping
// Use Map to improve lookup performance
const dataSourceMap = computed(() => {
const map = new Map<string, any>()
for (const item of dataSource.value) {
map.set(item[rowId.value], item)
}
return map
})
// O(1) Time complexity to get records
const getRecordsByKeys = (keys: string[]) => {
return keys.map(key => dataSourceMap.value.get(key)).filter(Boolean)
}
State Management
State Categories
BaseTable states can be categorized as follows:
State Synchronization Mechanism
// 1. Parent-Child Sync - useVModels
const { dataSource } = useVModels(props, ctx.emit)
// 2. Cross-Module Sync - provide/inject
provide(KEY, store)
const store = inject(KEY)
// 3. Selection State Sync
watch(selectedKeys, async () => {
await nextTick()
const records = getRecordsByKeys(selectedKeys.value)
emit('check', records)
})
Hook System
BaseTable's Hook system handles component side effects and business logic.
Core Hook Functions
1. Auto Height Hook
const autoHeight = computed(() => {
if (baseTableRef.value) {
const rect = baseTableRef.value.getBoundingClientRect()
const rectHeight = currentHeight.value || rect.height
return rectHeight > 42 ? rectHeight - 42 : rectHeight
}
})
Working Principle:
- Use
ResizeObserverto listen for container size changes - Debounce to avoid frequent calculations
- Subtract header height (42px) to get content area height
- Used in conjunction with virtual scrolling
2. Data Loading Hook
const getList = async (query = {}) => {
if (!props.url) return
const params = { ...baseParams.value, ...query }
const res = await getListData(params, props.url)
dataSource.value = props.dataTransformer ?
props.dataTransformer(res.items || res) :
res.items || res
totalCount.value = res.totalCount || res.length
ctx.emit('reload')
await nextTick()
return dataSource.value
}
3. Row Highlight Hook
const getRowClassName = (record: TableData, rowIndex: number) => {
if (record === currentRow.value) {
return styles.baseTableRow
}
}
const setCurrentRow = (key: string) => {
const row = dataSource.value.find(item => item[rowId.value] === key)
if (row) {
currentRow.value = row
}
}
Event System
Event Categories
BaseTable supports two types of events:
-
Standard Events (from Arco Table)
- select: Row selection
- selectAll: Select all
- rowClick: Row click
- columnResize: Column width resize
-
Custom Events (BaseTable extensions)
- update:dataSource: Data source update
- load: Before data load
- beforeLoad: During data load
- reload: After data load
- check: Selection state change
Event Handling Chain
Event Debouncing
// Debounce column resize
const onColumnResize = debounce((dataIndex: string, width: number) => {
emit('columnResize', dataIndex, width)
}, 200)
Rendering Mechanism
Rendering Process
Slot System
BaseTable provides a flexible slot system:
const slotMap = {
// Sequence column
seq: columnSeq.value.seq,
// Dynamic column slots
...slots,
// Empty data slot
empty: () => (
<div class={styles.nullData}>{_t('No Data')}</div>
)
}
Usage Example:
<BaseTable :columns="columns" :dataSource="data">
<template #name="{ record }">
<a-link>{{ record.name }}</a-link>
</template>
</BaseTable>
AutoTooltip Component
Automatic text overflow tooltip component, used for default rendering scenarios.
Working Principle:
- Detect text overflow on mount (
scrollWidth > clientWidth) - Listen for window resize events to re-detect
- Use Arco Tooltip to show full text when overflow occurs
- Show text directly when no overflow
const checkOverflow = () => {
if (containerRef.value) {
isOverflow.value =
containerRef.value.scrollWidth > containerRef.value.clientWidth
}
}
onMounted(() => {
checkOverflow()
window.addEventListener('resize', checkOverflow)
})
Performance Optimization
1. Virtual Scrolling
Enable virtual scrolling via isVScroll, rendering only visible rows.
virtual-list-props={
props.isVScroll ? { height: autoHeight.value } : undefined
}
Applicable Scenario: Data volume > 1000 rows
2. Data Mapping Optimization
Use Map structure to store data, improving lookup efficiency:
// O(n) -> O(1)
const dataSourceMap = computed(() => {
const map = new Map()
for (const item of dataSource.value) {
map.set(item[rowId.value], item)
}
return map
})
3. Event Debouncing
Debounce high-frequency events:
// Column resize
const onColumnResize = debounce((dataIndex, width) => {
emit('columnResize', dataIndex, width)
}, 200)
// Container resize listener
const fn = debounce((entries) => {
currentHeight.value = entries[0].borderBoxSize[0].blockSize
}, 150)
4. Computed Property Caching
Use computed to cache calculation results:
const columns = computed(() => {
return props.columns.map(item => ({
...item,
title: _t(item.title)
}))
})
5. Conditional Rendering
Render functions on demand based on configuration:
// Pagination
pagination={props.isHidePagination ? false : {...}}
// Selection
row-selection={props.isChecked ? rowSelection : undefined}
// Drag and Drop
draggable={props.isDrag ? { type: 'handle', width: 20 } : undefined}
Extensibility Design
1. Plugin-based Architecture
BaseTable uses modular design, with independent pluggable functional modules:
// Core modules
const { columns, dataSource, selectedKeys } = createState(props, ctx)
const events = useEvent(props, ctx)
const { getRowClassName, getTableStyle } = useHook(props, ctx)
const slots = ColumnSlots(props, ctx, columns)
2. Render Function Extension
Supports custom render functions:
columns: [
{
field: 'status',
title: 'Status',
customRender: ({ record }) => (
<a-tag color={record.status === 1 ? 'green' : 'red'}>
{record.status === 1 ? 'Enabled' : 'Disabled'}
</a-tag>
)
}
]
3. Style Customization
Multi-level style customization capabilities:
// 1. Row style
rowStyle: (record) => ({ background: record.isNew ? '#f0f9ff' : '' })
// 2. Cell style
cellStyle: ({ record, column }) => ({
color: record.amount > 1000 ? 'red' : 'inherit'
})
// 3. Row class name
rowClassName: (record) => record.disabled ? 'disabled-row' : ''
// 4. Highlight row color
hightLightRow: '#e6f7ff'
4. Data Transformer
Supports data preprocessing:
<BaseTable
url="/api/users"
:dataTransformer="(data) => data.map(item => ({
...item,
fullName: `${item.firstName} ${item.lastName}`
}))"
/>
5. Internationalization Support
Complete multi-language support:
// Auto translate column titles
columns: [
{ field: 'name', title: 'Name' } // Auto translated
]
// Support scoped translation
<BaseTable LanguageScopeKey="ProductManagement" />
6. Event Extension
Supports custom event listening:
// events.ts
export const events = [
'update:dataSource',
'load',
'beforeLoad',
'reload'
]
// Usage
<BaseTable
@reload="handleReload"
@check="handleCheck"
@select="handleSelect"
/>
Best Practices
1. Basic Usage
<template>
<BaseTable
:columns="columns"
:dataSource="dataSource"
:isChecked="true"
@check="handleCheck"
/>
</template>
<script setup>
const columns = [
{ field: 'id', title: 'ID', width: 80 },
{ field: 'name', title: 'Name', width: 120 },
{ field: 'age', title: 'Age', width: 80 }
]
const dataSource = ref([
{ id: 1, name: 'John Doe', age: 25 },
{ id: 2, name: 'Jane Doe', age: 30 }
])
const handleCheck = (records) => {
console.log('Selected records:', records)
}
</script>
2. Remote Data Loading
<template>
<BaseTable
url="/api/users"
:columns="columns"
:pageSize="20"
:dataTransformer="transformData"
/>
</template>
<script setup>
const transformData = (data) => {
return data.map(item => ({
...item,
statusText: item.status === 1 ? 'Enabled' : 'Disabled'
}))
}
</script>
3. Custom Rendering
<template>
<BaseTable :columns="columns" :dataSource="dataSource">
<template #status="{ record }">
<a-tag :color="record.status === 1 ? 'green' : 'red'">
{{ record.status === 1 ? 'Enabled' : 'Disabled' }}
</a-tag>
</template>
<template #actions="{ record }">
<a-space>
<a-button size="mini" @click="handleEdit(record)">Edit</a-button>
<a-button size="mini" status="danger" @click="handleDelete(record)">
Delete
</a-button>
</a-space>
</template>
</BaseTable>
</template>
4. Virtual Scrolling + Auto Height
<template>
<BaseTable
:columns="columns"
:dataSource="largeDataSource"
:isVScroll="true"
:autoHeight="true"
style="height: 500px"
/>
</template>
5. Drag and Drop Sorting
<template>
<BaseTable
:columns="columns"
:dataSource="dataSource"
:isDrag="true"
@change="handleDragSort"
/>
</template>
<script setup>
const handleDragSort = (data) => {
// Update order
console.log('New order:', data)
}
</script>
Summary
BaseTable is a powerful, highly extensible table component with the following characteristics:
Design Advantages
- Modular Architecture: Functionality decoupled, easy to maintain and extend
- Performance Optimization: Multiple optimizations including virtual scrolling, data mapping, event debouncing
- Type Safety: Complete TypeScript type definitions
- Flexible Rendering: Supports slots, Render functions, default rendering
- State Management: Uses provide/inject for cross-module state sharing
- Internationalization: Complete multi-language support
- Responsive: Auto height, container size listening, etc.
Technical Highlights
- Composition API: Fully utilizes Vue 3 Composition API
- Hook System: Logic reuse and separation of concerns
- Dependency Injection: Elegant state sharing mechanism
- Performance Optimization: Virtual scrolling, computed caching, event debouncing
- Extensibility: Plugin-based design, easy to extend functions
BaseTable provides a unified, efficient, and easy-to-use table solution for the project and is an indispensable core UI component in the entire system.