perf: table

refactor-table
tangjinzhou 2021-09-10 17:01:33 +08:00
parent d3acab2d55
commit 6e85fd92e3
17 changed files with 119 additions and 94 deletions

View File

@ -3,7 +3,6 @@ import PropTypes, { withUndefined } from '../../_util/vue-types';
import TableCell from './TableCell';
import { initDefaultProps, findDOMNode } from '../../_util/props-util';
import BaseMixin from '../../_util/BaseMixin';
import warning from '../../_util/warning';
import { computed, inject } from 'vue';
function noop() {}
const TableRow = {

View File

@ -35,7 +35,8 @@ import defaultLocale from '../locale/en_US';
import type { SizeType } from '../config-provider';
import devWarning from '../vc-util/devWarning';
import type { PropType } from 'vue';
import { computed, defineComponent, ref, toRef, watchEffect } from 'vue';
import { reactive } from 'vue';
import { computed, defineComponent, toRef, watchEffect } from 'vue';
import type { DefaultRecordType } from '../vc-table/interface';
import useBreakpoint from '../_util/hooks/useBreakpoint';
import useConfigInject from '../_util/hooks/useConfigInject';
@ -43,7 +44,8 @@ import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
import classNames from '../_util/classNames';
import omit from '../_util/omit';
import { initDefaultProps } from '../_util/props-util';
import { ContextSlots, useProvideSlots } from './context';
import { useProvideSlots } from './context';
import type { ContextSlots } from './context';
import useColumns from './hooks/useColumns';
import { convertChildrenToColumns } from './util';
@ -77,6 +79,7 @@ export interface TableProps<RecordType = DefaultRecordType>
| 'scroll'
| 'emptyText'
| 'canExpandable'
| 'onUpdateInternalRefs'
> {
dropdownPrefixCls?: string;
dataSource?: RcTableProps<RecordType>['data'];
@ -217,11 +220,11 @@ const InteralTable = defineComponent<
}
>({
name: 'InteralTable',
inheritAttrs: false,
props: initDefaultProps(tableProps(), {
rowKey: 'key',
}) as any,
inheritAttrs: false,
emits: [],
// emits: ['expandedRowsChange', 'change', 'expand'],
slots: [
'emptyText',
'expandIcon',
@ -282,8 +285,12 @@ const InteralTable = defineComponent<
return null;
});
const internalRefs = {
body: ref<HTMLDivElement>(),
const internalRefs = reactive({
body: null,
});
const updateInternalRefs = refs => {
Object.assign(internalRefs, refs);
};
// ============================ RowKey ============================
@ -325,9 +332,9 @@ const InteralTable = defineComponent<
}
}
if (scroll && scroll.scrollToFirstRowOnChange !== false && internalRefs.body.value) {
if (scroll && scroll.scrollToFirstRowOnChange !== false && internalRefs.body) {
scrollTo(0, {
getContainer: () => internalRefs.body.value!,
getContainer: () => internalRefs.body,
});
}
@ -606,7 +613,8 @@ const InteralTable = defineComponent<
rowClassName={internalRowClassName}
// Internal
internalHooks={INTERNAL_HOOKS}
internalRefs={internalRefs as any}
internalRefs={internalRefs}
onUpdateInternalRefs={updateInternalRefs}
transformColumns={transformColumns}
v-slots={{
...slots,

View File

@ -25,7 +25,7 @@ To fix some columns and scroll inside other columns, and you must set `scroll.x`
</docs>
<template>
<a-table :columns="columns" :data-source="data" :scroll="{ x: 1300 }">
<a-table :columns="columns" :data-source="data" :scroll="{ x: 1300, y: 1000 }">
<template #bodyCell="{ column, text }">
<template v-if="column.key === 'operation'">
<a>action</a>

View File

@ -38,6 +38,7 @@ Set summary content by `summary` prop. Sync column fixed status with `a-table-su
</a-table-summary-row>
</template>
</a-table>
<br />
<a-table
:columns="fixedColumns"
:data-source="fixedData"

View File

@ -1,6 +1,6 @@
import devWarning from '../../vc-util/devWarning';
import type { Ref } from 'vue';
import { ContextSlots } from '../context';
import type { ContextSlots } from '../context';
import type { TransformColumns, ColumnsType } from '../interface';
function fillSlots<RecordType>(columns: ColumnsType<RecordType>, contextSlots: Ref<ContextSlots>) {

View File

@ -2,7 +2,8 @@ import Table, { tableProps } from './Table';
import Column from './Column';
import ColumnGroup from './ColumnGroup';
import type { TableProps, TablePaginationConfig } from './Table';
import { App, defineComponent } from 'vue';
import { defineComponent } from 'vue';
import type { App } from 'vue';
import { Summary, SummaryCell, SummaryRow } from '../vc-table';
import { SELECTION_ALL, SELECTION_INVERT, SELECTION_NONE } from './hooks/useSelection';

View File

@ -24,6 +24,8 @@ export interface BodyRowProps<RecordType> {
}
export default defineComponent<BodyRowProps<unknown>>({
name: 'BodyRow',
inheritAttrs: false,
props: [
'record',
'index',
@ -38,8 +40,6 @@ export default defineComponent<BodyRowProps<unknown>>({
'getRowKey',
'childrenColumnName',
] as any,
name: 'BodyRow',
inheritAttrs: false,
setup(props, { attrs }) {
const tableContext = useInjectTable();
const bodyContext = useInjectBody();
@ -92,6 +92,7 @@ export default defineComponent<BodyRowProps<unknown>>({
} else if (typeof rowClassName === 'function') {
return rowClassName(record, index, indent);
}
return '';
});
const columnsKey = computed(() => getColumnsKey(bodyContext.flattenColumns));

View File

@ -17,6 +17,7 @@ export interface ExpandedRowProps {
export default defineComponent<ExpandedRowProps>({
name: 'ExpandedRow',
inheritAttrs: false,
props: [
'prefixCls',
'component',
@ -28,7 +29,6 @@ export default defineComponent<ExpandedRowProps>({
'expanded',
'colSpan',
] as any,
inheritAttrs: false,
setup(props, { slots, attrs }) {
const tableContext = useInjectTable();
return () => {

View File

@ -1,3 +1,4 @@
import { defineComponent, onMounted, ref } from 'vue';
import VCResizeObserver from '../../vc-resize-observer';
import type { Key } from '../interface';
@ -6,16 +7,28 @@ export interface MeasureCellProps {
onColumnResize: (key: Key, width: number) => void;
}
export default function MeasureCell({ columnKey }: MeasureCellProps, { emit }) {
return (
<VCResizeObserver
onResize={({ offsetWidth }) => {
emit('columnResize', columnKey, offsetWidth);
}}
>
<td style={{ padding: 0, border: 0, height: 0 }}>
<div style={{ height: 0, overflow: 'hidden' }}>&nbsp;</div>
</td>
</VCResizeObserver>
);
}
export default defineComponent<MeasureCellProps>({
name: 'MeasureCell',
props: ['columnKey'] as any,
setup(props, { emit }) {
const tdRef = ref();
onMounted(() => {
if (tdRef.value) {
emit('columnResize', props.columnKey, tdRef.value.offsetWidth);
}
});
return () => {
return (
<VCResizeObserver
onResize={({ offsetWidth }) => {
emit('columnResize', props.columnKey, offsetWidth);
}}
>
<td ref={tdRef} style={{ padding: 0, border: 0, height: 0 }}>
<div style={{ height: 0, overflow: 'hidden' }}>&nbsp;</div>
</td>
</VCResizeObserver>
);
};
},
});

View File

@ -107,7 +107,7 @@ export default defineComponent<BodyProps<any>>({
const columnsKey = getColumnsKey(flattenColumns);
return (
<WrapperComponent className={`${prefixCls}-tbody`}>
<WrapperComponent class={`${prefixCls}-tbody`}>
{/* Measure body column width with additional hidden col */}
{measureColumnWidth && (
<tr

View File

@ -48,6 +48,7 @@ export interface FixedHeaderProps<RecordType> extends HeaderProps<RecordType> {
export default defineComponent<FixedHeaderProps<DefaultRecordType>>({
name: 'FixedHolder',
inheritAttrs: false,
props: [
'columns',
'flattenColumns',
@ -64,7 +65,6 @@ export default defineComponent<FixedHeaderProps<DefaultRecordType>>({
'stickyClassName',
] as any,
emits: ['scroll'],
inheritAttrs: false,
setup(props, { attrs, slots, emit }) {
const tableContext = useInjectTable();
const combinationScrollBarSize = computed(() =>

View File

@ -14,8 +14,8 @@ export interface SummaryCellProps {
export default defineComponent<SummaryCellProps>({
name: 'SummaryCell',
props: ['index', 'colSpan', 'rowSpan', 'align'] as any,
inheritAttrs: false,
props: ['index', 'colSpan', 'rowSpan', 'align'] as any,
setup(props, { attrs, slots }) {
const tableContext = useInjectTable();
const summaryContext = useInjectSummary();

View File

@ -7,8 +7,8 @@ export interface SummaryProps {
let indexGuid = 0;
const Summary = defineComponent<SummaryProps>({
props: ['fixed'] as any,
name: 'Summary',
props: ['fixed'] as any,
setup(props, { slots }) {
const tableContext = useInjectTable();
const uniKey = `table-summary-uni-key-${++indexGuid}`;

View File

@ -14,8 +14,8 @@ export interface FooterProps<RecordType = DefaultRecordType> {
export default defineComponent({
name: 'Footer',
props: ['stickyOffsets', 'flattenColumns'],
inheritAttrs: false,
props: ['stickyOffsets', 'flattenColumns'],
setup(props, { slots }) {
const tableContext = useInjectTable();
useProvideSummary(

View File

@ -29,7 +29,7 @@ import { getCellFixedInfo } from './utils/fixUtil';
import StickyScrollBar from './stickyScrollBar';
import useSticky from './hooks/useSticky';
import FixedHolder from './FixedHolder';
import type { CSSProperties, Ref } from 'vue';
import type { CSSProperties } from 'vue';
import {
computed,
defineComponent,
@ -130,18 +130,19 @@ export interface TableProps<RecordType = unknown> {
* !!! DO NOT USE IN PRODUCTION ENVIRONMENT !!!
*/
internalRefs?: {
body: Ref<HTMLDivElement>;
body: HTMLDivElement;
};
sticky?: boolean | TableSticky;
canExpandable?: boolean;
onUpdateInternalRefs?: (refs: Record<string, any>) => void;
}
export default defineComponent<TableProps>({
name: 'Table',
slots: ['title', 'footer', 'summary', 'emptyText'],
emits: ['expand', 'expandedRowsChange'],
inheritAttrs: false,
props: [
'prefixCls',
'data',
@ -178,8 +179,10 @@ export default defineComponent<TableProps>({
'internalHooks',
'internalRefs',
'canExpandable',
'onUpdateInternalRefs',
] as any,
inheritAttrs: false,
slots: ['title', 'footer', 'summary', 'emptyText'],
emits: ['expand', 'expandedRowsChange', 'updateInternalRefs'],
setup(props, { attrs, slots, emit }) {
const mergedData = computed(() => props.data || EMPTY_DATA);
const hasData = computed(() => !!mergedData.value.length);
@ -466,7 +469,11 @@ export default defineComponent<TableProps>({
watchEffect(
() => {
if (props.internalHooks === INTERNAL_HOOKS && props.internalRefs) {
props.internalRefs.body.value = scrollBodyRef.value;
props.onUpdateInternalRefs({
body: scrollBodyRef.value
? (scrollBodyRef.value as any).$el || scrollBodyRef.value
: null,
});
}
},
{ flush: 'post' },
@ -545,6 +552,26 @@ export default defineComponent<TableProps>({
onColumnResize,
});
// Body
const bodyTable = () => (
<Body
data={mergedData.value}
measureColumnWidth={fixHeader.value || horizonScroll.value || stickyState.value.isSticky}
expandedKeys={mergedExpandedKeys.value}
rowExpandable={props.rowExpandable}
getRowKey={getRowKey.value}
customRow={props.customRow}
childrenColumnName={mergedChildrenColumnName.value}
v-slots={{ emptyText: emptyNode }}
/>
);
const bodyColGroup = () => (
<ColGroup
colWidths={flattenColumns.value.map(({ width }) => width)}
columns={flattenColumns.value}
/>
);
return () => {
const {
prefixCls,
@ -560,17 +587,14 @@ export default defineComponent<TableProps>({
id,
showHeader,
customHeaderRow,
rowExpandable,
customRow,
} = props;
const { isSticky, offsetHeader, offsetSummary, offsetScroll, stickyClassName, container } =
stickyState.value;
const TableComponent = getComponent(['table'], 'table');
const customizeScrollBody = getComponent(['body']) as unknown as CustomizeScrollBody<any>;
const summaryNode = slots.summary?.({ pageData: mergedData.value });
let groupTableNode;
let groupTableNode = () => null;
// Header props
const headerProps = {
@ -582,29 +606,6 @@ export default defineComponent<TableProps>({
scroll,
};
// Body
const bodyTable = (
<Body
data={mergedData.value}
measureColumnWidth={fixHeader.value || horizonScroll.value || isSticky}
expandedKeys={mergedExpandedKeys.value}
rowExpandable={rowExpandable}
getRowKey={getRowKey.value}
customRow={customRow}
childrenColumnName={mergedChildrenColumnName.value}
v-slots={{ emptyText: emptyNode }}
/>
);
const bodyColGroup = (
<ColGroup
colWidths={flattenColumns.value.map(({ width }) => width)}
columns={flattenColumns.value}
/>
);
const customizeScrollBody = getComponent(['body']) as unknown as CustomizeScrollBody<any>;
if (
process.env.NODE_ENV !== 'production' &&
typeof customizeScrollBody === 'function' &&
@ -615,14 +616,15 @@ export default defineComponent<TableProps>({
}
if (fixHeader.value || isSticky) {
// >>>>>> Fixed Header
let bodyContent;
let bodyContent = () => null;
if (typeof customizeScrollBody === 'function') {
bodyContent = customizeScrollBody(mergedData.value, {
scrollbarSize: scrollbarSize.value,
ref: scrollBodyRef,
onScroll,
});
bodyContent = () =>
customizeScrollBody(mergedData.value, {
scrollbarSize: scrollbarSize.value,
ref: scrollBodyRef,
onScroll,
});
headerProps.colWidths = flattenColumns.value.map(({ width }, index) => {
const colWidth =
@ -638,7 +640,7 @@ export default defineComponent<TableProps>({
return 0;
}) as number[];
} else {
bodyContent = (
bodyContent = () => (
<div
style={{
...scrollXStyle.value,
@ -654,8 +656,8 @@ export default defineComponent<TableProps>({
tableLayout: mergedTableLayout.value,
}}
>
{bodyColGroup}
{bodyTable}
{bodyColGroup()}
{bodyTable()}
{!fixFooter.value && summaryNode && (
<Footer stickyOffsets={stickyOffsets} flattenColumns={flattenColumns.value}>
{summaryNode}
@ -677,7 +679,7 @@ export default defineComponent<TableProps>({
onScroll,
};
groupTableNode = (
groupTableNode = () => (
<>
{/* Header Table */}
{showHeader !== false && (
@ -700,7 +702,7 @@ export default defineComponent<TableProps>({
)}
{/* Body Table */}
{bodyContent}
{bodyContent()}
{/* Summary Table */}
{fixFooter.value && fixFooter.value !== 'top' && (
@ -730,7 +732,7 @@ export default defineComponent<TableProps>({
);
} else {
// >>>>>> Unique table
groupTableNode = (
groupTableNode = () => (
<div
style={{
...scrollXStyle.value,
@ -743,9 +745,9 @@ export default defineComponent<TableProps>({
<TableComponent
style={{ ...scrollTableStyle.value, tableLayout: mergedTableLayout.value }}
>
{bodyColGroup}
{bodyColGroup()}
{showHeader !== false && <Header {...headerProps} {...columnContext.value} />}
{bodyTable}
{bodyTable()}
{summaryNode && (
<Footer stickyOffsets={stickyOffsets.value} flattenColumns={flattenColumns.value}>
{summaryNode}
@ -756,7 +758,7 @@ export default defineComponent<TableProps>({
);
}
const ariaProps = getDataAndAriaProps(attrs);
let fullTable = (
const fullTable = () => (
<div
{...ariaProps}
class={classNames(prefixCls, {
@ -779,15 +781,20 @@ export default defineComponent<TableProps>({
ref={fullTableRef}
>
{title && <Panel class={`${prefixCls}-title`}>{title(mergedData.value)}</Panel>}
<div class={`${prefixCls}-container`}>{groupTableNode}</div>
<div class={`${prefixCls}-container`}>{groupTableNode()}</div>
{footer && <Panel class={`${prefixCls}-footer`}>{footer(mergedData.value)}</Panel>}
</div>
);
if (horizonScroll.value) {
fullTable = <VCResizeObserver onResize={onFullTableResize}>{fullTable}</VCResizeObserver>;
return (
<VCResizeObserver
onResize={onFullTableResize}
v-slots={{ default: fullTable }}
></VCResizeObserver>
);
}
return fullTable;
return fullTable();
};
},
});

View File

@ -1,5 +1,5 @@
import type { Ref, UnwrapRef } from 'vue';
import { getCurrentInstance, onBeforeUnmount, ref } from 'vue';
import { onBeforeUnmount, ref } from 'vue';
export type Updater<State> = (prev: State) => State;
@ -14,7 +14,6 @@ export function useLayoutState<State>(
const lastPromiseRef = ref<Promise<void>>(null);
const updateBatchRef = ref<Updater<State>[]>([]);
const instance = getCurrentInstance();
function setFrameState(updater: Updater<State>) {
updateBatchRef.value.push(updater);
@ -24,7 +23,7 @@ export function useLayoutState<State>(
promise.then(() => {
if (lastPromiseRef.value === promise) {
const prevBatch = updateBatchRef.value;
const prevState = stateRef.value;
// const prevState = stateRef.value;
updateBatchRef.value = [];
prevBatch.forEach(batchUpdater => {
@ -32,10 +31,6 @@ export function useLayoutState<State>(
});
lastPromiseRef.value = null;
if (prevState !== stateRef.value) {
instance.update();
}
}
});
}

View File

@ -17,9 +17,9 @@ interface StickyScrollBarProps {
export default defineComponent<StickyScrollBarProps>({
name: 'StickyScrollBar',
inheritAttrs: false,
props: ['offsetScroll', 'container', 'scrollBodyRef'] as any,
emits: ['scroll'],
inheritAttrs: false,
setup(props, { emit, expose }) {
const tableContext = useInjectTable();
const bodyScrollWidth = computed(() => props.scrollBodyRef.value.scrollWidth || 0);