Upgrade: react 18 && connection table scroll perf

This commit is contained in:
Dreamacro 2022-03-30 20:19:48 +08:00
parent fa98e692bd
commit 2866bafc6a
7 changed files with 324 additions and 484 deletions

View File

@ -33,10 +33,10 @@
"@types/react-table": "^7.7.10",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"@vitejs/plugin-react": "^1.2.0",
"eslint": "^8.11.0",
"eslint": "^8.12.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.4",
"eslint-config-standard-with-typescript": "^21.0.1",
@ -44,18 +44,19 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-react-hooks": "^4.4.0",
"sass": "^1.49.9",
"type-fest": "^2.12.1",
"typescript": "^4.6.3",
"vite": "^2.8.6",
"vite": "^2.9.0",
"vite-plugin-pwa": "^0.11.13",
"vite-plugin-windicss": "^1.8.3",
"vite-tsconfig-paths": "^3.4.1",
"windicss": "^3.5.1"
},
"dependencies": {
"@tanstack/react-table": "^8.0.0-alpha.8",
"@react-hookz/web": "^13.1.0",
"@tanstack/react-table": "^8.0.0-alpha.11",
"axios": "^0.26.1",
"classnames": "^2.3.1",
"dayjs": "^1.11.0",
@ -64,10 +65,9 @@
"jotai": "^1.6.1",
"lodash-es": "^4.17.21",
"neverthrow": "^4.3.1",
"react": "^18.0.0-rc.3",
"react-dom": "^18.0.0-rc.3",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.2.2",
"react-use": "^17.3.2",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.6",
"swr": "^1.2.2",

721
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,9 @@
import { columnFilterRowsFn, createTable, sortRowsFn } from '@tanstack/react-table'
import { type ColumnSort } from '@tanstack/react-table/build/types/features/Sorting'
import { useIntersectionObserver, useSyncedRef } from '@react-hookz/web/esm'
import { useTable, columnFilterRowsFn, createTable, sortRowsFn } from '@tanstack/react-table'
import classnames from 'classnames'
import produce from 'immer'
import { groupBy } from 'lodash-es'
import { useMemo, useLayoutEffect, useRef, useState, useEffect } from 'react'
import { useLatest, useScroll } from 'react-use'
import { Header, Checkbox, Modal, Icon, Drawer, Card, Button } from '@components'
import { fromNow } from '@lib/date'
@ -47,7 +46,7 @@ function formatSpeed (upload: number, download: number) {
}
}
const table = createTable().RowType<FormatConnection>()
const table = createTable<FormatConnection>()
export default function Connections () {
const { translation, lang } = useI18n()
@ -95,8 +94,8 @@ export default function Connections () {
}, [connections])
// table
const tableRef = useRef<HTMLDivElement>(null)
const { x: scrollX } = useScroll(tableRef)
const pinRef = useRef<HTMLTableCellElement>(null)
const intersection = useIntersectionObserver(pinRef, { threshold: [1] })
const columns = useMemo(
() => table.createColumns([
table.createDataColumn(Columns.Host, { minWidth: 260, width: 260, header: t(`columns.${Columns.Host}`) }),
@ -113,28 +112,28 @@ export default function Connections () {
width: 200,
sortDescFirst: true,
sortType (rowA, rowB) {
const speedA = rowA.original.speed
const speedB = rowB.original.speed
const speedA = rowA.original?.speed ?? { upload: 0, download: 0 }
const speedB = rowB.original?.speed ?? { upload: 0, download: 0 }
return speedA.download === speedB.download
? speedA.upload - speedB.upload
: speedA.download - speedB.download
},
cell: cell => formatSpeed(cell.value[0], cell.value[1]),
cell: (cell: { value: [number, number] }) => formatSpeed(cell.value[0], cell.value[1]),
},
),
table.createDataColumn(Columns.Upload, { minWidth: 100, width: 100, header: t(`columns.${Columns.Upload}`), cell: cell => formatTraffic(cell.value) }),
table.createDataColumn(Columns.Download, { minWidth: 100, width: 100, header: t(`columns.${Columns.Download}`), cell: cell => formatTraffic(cell.value) }),
table.createDataColumn(Columns.Upload, { minWidth: 100, width: 100, header: t(`columns.${Columns.Upload}`), cell: cell => formatTraffic(cell.value as number) }),
table.createDataColumn(Columns.Download, { minWidth: 100, width: 100, header: t(`columns.${Columns.Download}`), cell: cell => formatTraffic(cell.value as number) }),
table.createDataColumn(Columns.SourceIP, { minWidth: 140, width: 140, header: t(`columns.${Columns.SourceIP}`), filterType: 'equals' }),
table.createDataColumn(
Columns.Time,
{
minWidth:
120,
minWidth: 120,
width: 120,
header: t(`columns.${Columns.Time}`),
cell: cell => fromNow(new Date(cell.value), lang),
sortType: (rowA, rowB) => rowB.original.time - rowA.original.time,
}),
cell: cell => fromNow(new Date(cell.value as string), lang),
sortType: (rowA, rowB) => (rowB.original as FormatConnection).time - (rowA.original as FormatConnection).time,
},
),
]),
[lang, t],
)
@ -158,7 +157,7 @@ export default function Connections () {
}
}, [connStreamReader, feed, setTraffic])
const instance = table.useTable({
const instance = useTable(table, {
data,
columns,
sortRowsFn,
@ -191,7 +190,7 @@ export default function Connections () {
setDrawerState(d => { d.connection.completed = true })
client.closeConnection(drawerState.selectedID)
}
const latestConntion = useLatest(drawerState.connection)
const latestConntion = useSyncedRef(drawerState.connection)
useEffect(() => {
const conn = data.find(c => c.id === drawerState.selectedID)?.original
if (conn) {
@ -206,9 +205,9 @@ export default function Connections () {
}
}, [data, drawerState.selectedID, latestConntion, setDrawerState])
const scrolled = useMemo(() => scrollX > 0, [scrollX])
const scrolled = useMemo(() => (intersection?.intersectionRatio ?? 0) < 1, [intersection])
const headers = headerGroup.headers.map((header, idx) => {
const column = header.column // as unknown as TableColumn<FormatConnection>
const column = header.column
const id = column.id
return (
<th
@ -223,6 +222,7 @@ export default function Connections () {
props.style.width = header.getWidth()
}),
)}
ref={column.id === Columns.Host ? pinRef : undefined}
key={id}>
<div {...column.getToggleSortingProps()}>
{header.renderHeader()}
@ -233,13 +233,13 @@ export default function Connections () {
}
</div>
{ idx !== headerGroup.headers.length - 1 &&
<div {...column.getResizerProps()} className="connections-resizer" />
<div {...header.getResizerProps()} className="connections-resizer" />
}
</th>
)
})
const content = instance.getRows().map(row => {
const content = instance.getRowModel().rows.map(row => {
return (
<tr
{...row.getRowProps()}
@ -253,7 +253,7 @@ export default function Connections () {
{ 'text-center': shouldCenter.has(cell.column.id), completed: row.original?.completed },
{
fixed: cell.column.id === Columns.Host,
shadow: scrollX > 0 && cell.column.id === Columns.Host,
shadow: scrolled && cell.column.id === Columns.Host,
},
)
return (
@ -286,7 +286,7 @@ export default function Connections () {
</Header>
{ devices.length > 1 && <Devices devices={devices} selected={device} onChange={handleDeviceSelected} /> }
<Card ref={cardRef} className="connections-card relative">
<div className="overflow-auto min-h-full" ref={tableRef}>
<div className="overflow-auto min-h-full">
<table {...instance.getTableProps()} className="flex-1">
<thead>
<tr {...headerGroup.getHeaderGroupProps()} className="connections-header">

View File

@ -28,7 +28,7 @@
&.fixed {
position: sticky !important;
left: 0;
left: -0.1px;
z-index: 99;
&.shadow {
box-shadow: inset -9px 0 8px -14px $color-black;

View File

@ -1,4 +1,4 @@
import { Suspense } from 'react'
import { Suspense, StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { HashRouter } from 'react-router-dom'
@ -9,11 +9,13 @@ import 'virtual:windi.css'
export default function renderApp () {
const rootEl = document.getElementById('root')
const AppInstance = (
<HashRouter>
<Suspense fallback={<Loading visible />}>
<App />
</Suspense>
</HashRouter>
<StrictMode>
<HashRouter>
<Suspense fallback={<Loading visible />}>
<App />
</Suspense>
</HashRouter>
</StrictMode>
)
const root = createRoot(rootEl!)

View File

@ -1,6 +1,6 @@
import { atom, useAtom, useAtomValue } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
import { useLocation } from 'react-use'
import { useLocation } from 'react-router-dom'
import { isClashX, jsBridge } from '@lib/jsBridge'
import { Client } from '@lib/request'

View File

@ -1,5 +1,5 @@
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'
import windiCSS from 'vite-plugin-windicss'
import tsConfigPath from 'vite-tsconfig-paths'
@ -24,6 +24,7 @@ export default defineConfig(
name: 'Clash Dashboard',
},
}),
splitVendorChunkPlugin(),
],
base: './',
css: {