├── .npmrc
├── .babelrc
├── example1.gif
├── src
├── index.js
└── virtualTable
│ ├── BaseTable.jsx
│ └── VirtualTable.jsx
├── .npmignore
├── .travis.yml
├── .gitignore
├── examples
├── fixed
│ ├── index.html
│ └── index.js
├── src
│ ├── index.html
│ └── index.js
└── onSelectAll
│ ├── index.html
│ └── index.js
├── dist
├── index.js
└── virtualTable
│ └── VirtualTable.js
├── webpack.config.js
├── package.json
├── README.md
└── README-en_EN.md
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env","stage-0","react"
4 | ]
5 | }
--------------------------------------------------------------------------------
/example1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ctq123/ant-virtual-table/HEAD/example1.gif
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as VirtualTable } from './virtualTable/VirtualTable'
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # .npmignore
2 | src
3 | examples
4 | .babelrc
5 | .gitignore
6 | webpack.config.js
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | sudo: false
4 |
5 | notifications:
6 | email:
7 | - chengtianqing123@sina.cn
8 |
9 | node_js:
10 | - "4"
11 |
12 | install:
13 | - npm install -g codecov
14 |
15 | script:
16 | - codecov
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist
3 | .DS_Store
4 |
5 | # Log files
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Editor directories and files
11 | .idea
12 | .vscode
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 | *.sw*
18 | .git
19 |
--------------------------------------------------------------------------------
/examples/fixed/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | ant-virtual-table
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | ant-virtual-table
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/onSelectAll/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | ant-virtual-table
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _VirtualTable = require('./virtualTable/VirtualTable');
8 |
9 | Object.defineProperty(exports, 'VirtualTable', {
10 | enumerable: true,
11 | get: function get() {
12 | return _interopRequireDefault(_VirtualTable).default;
13 | }
14 | });
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const htmlWebpackPlugin = require('html-webpack-plugin')
3 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
4 | const hwp = new htmlWebpackPlugin({
5 | template: path.join(__dirname, 'examples/src/index.html'),
6 | filename: './index.html'
7 | })
8 |
9 | module.exports = {
10 | entry: path.join(__dirname, 'examples/src/index.js'),
11 | module: {
12 | rules: [{
13 | test: /\.(js|jsx)$/,
14 | use: 'babel-loader',
15 | exclude: /node_modules/
16 | }, {
17 | test: /\.css$/,
18 | use: ['style-loader', 'css-loader']
19 | }]
20 | },
21 | plugins: [hwp, new BundleAnalyzerPlugin()],
22 | resolve: {
23 | extensions: ['.js', '.jsx']
24 | },
25 | devtool: 'source-map',
26 | stats: {
27 | entrypoints: false,
28 | children: false,
29 | modules: false,
30 | errors: true,
31 | errorDetails: true,
32 | warnings: true
33 | },
34 | devServer: {
35 | stats: {
36 | assets: false,
37 | entrypoints: false,
38 | children: false,
39 | modules: false,
40 | errors: true,
41 | errorDetails: true,
42 | warnings: true
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ant-virtual-table",
3 | "version": "0.1.11",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "directories": {
7 | "example": "examples"
8 | },
9 | "scripts": {
10 | "test": "jest",
11 | "coverage": "jest --coverage",
12 | "start": "webpack-dev-server --mode development --open --port 3001",
13 | "analyzer": "webpack --mode production",
14 | "prepublish": "babel src -d dist --copy-files"
15 | },
16 | "jest": {
17 | "setupFiles": [
18 | "./tests/setup.js"
19 | ],
20 | "collectCoverageFrom": [
21 | "**/src/virtualTable/**"
22 | ],
23 | "transform": {
24 | "^.+\\.(js|jsx)$": "babel-jest",
25 | "^.+\\.css$": "./tests/css-transform.js"
26 | }
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/ctq123/ant-virtual-table.git"
31 | },
32 | "keywords": [
33 | "virtual-table",
34 | "ant",
35 | "ant-table",
36 | "virtualied",
37 | "虚拟渲染",
38 | "虚拟表格",
39 | "antd",
40 | "virtual"
41 | ],
42 | "author": "tqcheng",
43 | "license": "ISC",
44 | "bugs": {
45 | "url": "https://github.com/ctq123/ant-virtual-table/issues"
46 | },
47 | "homepage": "https://github.com/ctq123/ant-virtual-table#readme",
48 | "files": [
49 | "dist"
50 | ],
51 | "devDependencies": {
52 | "babel-cli": "6.26.0",
53 | "babel-core": "6.26.3",
54 | "babel-jest": "23.6.0",
55 | "babel-loader": "7.1.5",
56 | "babel-plugin-import": "^1.12.0",
57 | "babel-preset-env": "1.7.0",
58 | "babel-preset-react": "6.24.1",
59 | "babel-preset-stage-0": "6.24.1",
60 | "css-loader": "1.0.0",
61 | "html-webpack-plugin": "3.2.0",
62 | "style-loader": "0.23.1",
63 | "webpack": "4.22.0",
64 | "webpack-bundle-analyzer": "^3.8.0",
65 | "webpack-cli": "3.1.2",
66 | "webpack-dev-server": "3.1.10"
67 | },
68 | "dependencies": {
69 | "antd": "^3.26.2",
70 | "enzyme": "^3.1.0",
71 | "enzyme-adapter-react-16": "^1.0.2",
72 | "lodash.throttle": "^4.1.1",
73 | "react": "^16.12.0",
74 | "react-dom": "^16.12.0",
75 | "jest": "^21.2.1",
76 | "sinon": "^7.3.2"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/virtualTable/BaseTable.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import Table from 'antd/es/table';
3 |
4 | class BaseTable extends PureComponent {
5 | constructor(props) {
6 | super(props)
7 | const { rowSelection } = props
8 | const newRowSelection = rowSelection ? { ...rowSelection } : null
9 | if (rowSelection) {
10 | const { onSelectAll, onChange } = rowSelection
11 | if (onSelectAll) {
12 | newRowSelection.onSelectAll = this.reOnSelectAll
13 | // 原方法保存下来
14 | this.onSelectAllCB = onSelectAll
15 | }
16 | if (onChange) {
17 | newRowSelection.onChange = this.reOnChange
18 | // 原方法保存下来
19 | this.onChangeCB = onChange
20 | }
21 | }
22 | this.state = {
23 | selectedRowKeys: [],
24 | newRowSelection,
25 | }
26 | }
27 |
28 | reOnSelectAll = (selected, selectedRows, changeRows) => {// 处理全选函数
29 | const { dataSource, rowKey } = this.props
30 | if (selected) {
31 | const selectedRowKeys = dataSource.map(item => item[rowKey])
32 | this.setState({
33 | selectedRowKeys
34 | }, () => {
35 | this.onSelectAllCB(selected, dataSource, changeRows)
36 | })
37 | } else {
38 | this.setState({
39 | selectedRowKeys: []
40 | }, () => {
41 | this.onSelectAllCB(selected, [], dataSource)
42 | })
43 | }
44 | }
45 |
46 | reOnChange = (selectedRowKeys, selectedRows) => {// 处理onChange函数
47 | if (selectedRowKeys) {
48 | this.setState({
49 | selectedRowKeys
50 | }, () => {
51 | this.onChangeCB(selectedRowKeys, selectedRows)
52 | })
53 | }
54 | }
55 |
56 | render() {
57 | const { renderSource, dataSource, rowSelection, ...rest } = this.props
58 | const { selectedRowKeys, newRowSelection } = this.state
59 |
60 | if (newRowSelection) {
61 | /**需要兼容用户传递进来的selectedRowKeys,
62 | * 用户selectedRowKeys优先级高于内部selectedRowKeys */
63 | newRowSelection.selectedRowKeys = rowSelection['selectedRowKeys'] || selectedRowKeys
64 | }
65 |
66 | return (
67 |
72 | )
73 | }
74 |
75 | }
76 |
77 | export default BaseTable
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ant-virtual-table
2 |
3 | ## 提示:已不再维护,antd4已有替代方案
4 |
5 | [English](./README-en_EN.md) | 简体中文
6 |
7 | 这是一个ant的虚拟表格,用于解决大数据渲染时页面卡顿的问题,本组件是对ant.desigin中Table组件进行一层封装,属性完全与原组件Table保持一致 [AntDesign Table](https://ant.design/components/table-cn/),可以让你像使用普通table一样使用虚拟table。例子中处理渲染1000万条数据,页面也非常流畅。[在线demo](https://codesandbox.io/s/antdxunibiao-demo-rj5qc?file=/index.js)
8 |
9 | ## 设计说明
10 | 考虑到兼容性问题,内部通过监听Table的滚动事件判断滑动行的位置,没有采用H5新特性IntersectionObserver。因此兼容性问题是比较好的。另外组件引入loash的throttle处理抖动问题,目前没有采用raf
11 |
12 | ## React ant-virtual-table
13 | [](https://travis-ci.org/ctq123/ant-virtual-table)
14 | [](https://www.npmjs.com/package/ant-virtual-table)
15 |
16 | # install
17 | npm install ant-virtual-table --save-dev
18 | # Usage
19 |
20 | ## 例子
21 | 
22 | ```
23 | import React, { Component, Fragment } from 'react'
24 | import { VirtualTable } from 'ant-virtual-table'
25 | import 'antd/dist/antd.css'
26 |
27 | const columns = [
28 | {
29 | title: '序号',
30 | dataIndex: 'id',
31 | width: 100
32 | },
33 | {
34 | title: '姓名',
35 | dataIndex: 'name',
36 | width: 150
37 | },
38 | {
39 | title: '年龄',
40 | dataIndex: 'age',
41 | width: 100
42 | },
43 | {
44 | title: '性别',
45 | dataIndex: 'sex',
46 | width: 100,
47 | render: (text) => {
48 | return text === 'male' ? '男' : '女'
49 | }
50 | },
51 | {
52 | title: '地址',
53 | dataIndex: 'address',
54 | key: 'address'
55 | }
56 | ]
57 |
58 | function generateData () {
59 | const res = []
60 | const names = ['Tom', 'Marry', 'Jack', 'Lorry', 'Tanken', 'Salla']
61 | const sexs = ['male', 'female']
62 | for (let i = 0; i < 10000000; i++) {
63 | let obj = {
64 | id: i,
65 | name: names[i % names.length] + i,
66 | sex: sexs[i % sexs.length],
67 | age: 15 + Math.round(10 * Math.random()),
68 | address: '浙江省杭州市西湖区华星时代广场2号楼'
69 | }
70 | res.push(obj)
71 | }
72 | return res
73 | }
74 |
75 | const dataSource = generateData()
76 |
77 | class App extends Component {
78 | render () {
79 | return (
80 |
81 |
89 |
90 | )
91 | }
92 | }
93 | ```
94 |
95 | # Prop Types
96 |
97 | 为降低迁移成本,属性与antd的Table完全保持一致,暂时没有自身独特的属性
98 |
101 |
102 | # 注意
103 | **目前暂不支持内嵌tree等复杂的表单结构,任何复杂的表单结构都不建议使用,后续跟进当中...**
104 |
--------------------------------------------------------------------------------
/examples/fixed/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { render } from 'react-dom'
3 | import { VirtualTable } from '../../src'
4 | import { Pagination } from 'antd'
5 | import 'antd/dist/antd.css'
6 |
7 | const columns = [
8 | {
9 | title: '序号',
10 | dataIndex: 'id',
11 | fixed: 'left',
12 | width: 100
13 | },
14 | {
15 | title: '姓名',
16 | dataIndex: 'name',
17 | width: 150
18 | },
19 | {
20 | title: '年龄',
21 | dataIndex: 'age',
22 | width: 100
23 | },
24 | {
25 | title: '性别',
26 | dataIndex: 'sex',
27 | width: 100,
28 | render: (text) => {
29 | return text === 'male' ? '男' : '女'
30 | }
31 | },
32 | {
33 | title: '地址',
34 | dataIndex: 'address',
35 | key: 'address'
36 | }
37 | ]
38 |
39 | function generateData (count) {
40 | const res = []
41 | const names = ['Tom', 'Marry', 'Jack', 'Lorry', 'Tanken', 'Salla']
42 | const sexs = ['male', 'female']
43 | for (let i = 0; i < count; i++) {
44 | let obj = {
45 | id: i,
46 | name: names[i % names.length] + i,
47 | sex: sexs[i % sexs.length],
48 | age: 15 + Math.round(10 * Math.random()),
49 | address: '浙江省杭州市西湖区华星时代广场2号楼'
50 | }
51 | res.push(obj)
52 | }
53 | return res
54 | }
55 |
56 | const dataSource = generateData(100)
57 |
58 | class App extends Component {
59 | constructor (props) {
60 | super(props)
61 | this.state = {
62 | pageNumber: 1,
63 | objectsPerPage: 10,
64 | list: dataSource
65 | }
66 | }
67 |
68 | // 改变页面数字第几页发起的请求
69 | onPageChange (pageNumber) {
70 | this.setState({
71 | pageNumber
72 | })
73 | }
74 |
75 | // 改变页面显示条数发起的请求
76 | onShowSizeChange (current, objectsPerPage) {
77 | const list = dataSource.slice((current - 1) * objectsPerPage, objectsPerPage)
78 | this.setState({
79 | list,
80 | pageNumber: current,
81 | objectsPerPage
82 | })
83 | }
84 |
85 | render () {
86 | const { list = [] } = this.state
87 | return (
88 |
89 |
90 |
98 | `共 ${list.length} 条`}
108 | />
109 |
110 | )
111 | }
112 | }
113 |
114 | render(, document.getElementById('root'))
115 |
--------------------------------------------------------------------------------
/examples/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { render } from 'react-dom'
3 | import { VirtualTable } from '../../src'
4 | import { Pagination } from 'antd'
5 | import 'antd/dist/antd.css'
6 |
7 | const columns = [
8 | {
9 | title: '序号',
10 | dataIndex: 'id',
11 | // fixed: 'left',
12 | width: 100
13 | },
14 | {
15 | title: '姓名',
16 | dataIndex: 'name',
17 | width: 150
18 | },
19 | {
20 | title: '年龄',
21 | dataIndex: 'age',
22 | width: 100
23 | },
24 | {
25 | title: '性别',
26 | dataIndex: 'sex',
27 | width: 100,
28 | render: (text) => {
29 | return text === 'male' ? '男' : '女'
30 | }
31 | },
32 | {
33 | title: '地址',
34 | dataIndex: 'address',
35 | key: 'address'
36 | }
37 | ]
38 |
39 | function generateData (count) {
40 | const res = []
41 | const names = ['Tom', 'Marry', 'Jack', 'Lorry', 'Tanken', 'Salla']
42 | const sexs = ['male', 'female']
43 | for (let i = 0; i < count; i++) {
44 | let obj = {
45 | id: i,
46 | name: names[i % names.length] + i,
47 | sex: sexs[i % sexs.length],
48 | age: 15 + Math.round(10 * Math.random()),
49 | address: '浙江省杭州市西湖区华星时代广场2号楼'
50 | }
51 | res.push(obj)
52 | }
53 | return res
54 | }
55 |
56 | const dataSource = generateData(1000000)
57 |
58 | class App extends Component {
59 | constructor (props) {
60 | super(props)
61 | this.state = {
62 | pageNumber: 1,
63 | objectsPerPage: 10,
64 | list: dataSource
65 | }
66 | }
67 |
68 | // 改变页面数字第几页发起的请求
69 | onPageChange (pageNumber) {
70 | this.setState({
71 | pageNumber
72 | })
73 | }
74 |
75 | // 改变页面显示条数发起的请求
76 | onShowSizeChange (current, objectsPerPage) {
77 | const list = dataSource.slice((current - 1) * objectsPerPage, objectsPerPage)
78 | this.setState({
79 | list,
80 | pageNumber: current,
81 | objectsPerPage
82 | })
83 | }
84 |
85 | render () {
86 | const { list = [] } = this.state
87 | return (
88 |
89 |
90 |
98 | `共 ${list.length} 条`}
108 | />
109 |
110 | )
111 | }
112 | }
113 |
114 | render(, document.getElementById('root'))
115 |
--------------------------------------------------------------------------------
/README-en_EN.md:
--------------------------------------------------------------------------------
1 | # ant-virtual-table
2 |
3 | ## Tip: No longer maintained, antd4 has an alternative
4 |
5 | English | [简体中文](./README.md)
6 |
7 | This is an ant.design virtual table, which is used to solve the problem of page jamming during big data rendering. This component encapsulates the Table component in ant.desigin and its properties are completely consistent with the original component Table [AntDesign Table](https://ant.design/components/table-cn/), it allows you to use a virtual table like a normal table. The example handles rendering 10 million pieces of data, and the page is very smooth. [online demo](https://codesandbox.io/s/antdxunibiao-demo-rj5qc?file=/index.js)
8 |
9 | ## Design Notes
10 | Considering the compatibility issue, the internal scrolling event of the Listening Table determines the position of the sliding line, and does not adopt the new H5 feature IntersectionObserver. Therefore the compatibility issue is better. In addition, the component introduces the loose handle of the loash to deal with the jitter problem. Currently, raf is not used.
11 |
12 | ## React ant-virtual-table
13 | [](https://travis-ci.org/ctq123/ant-virtual-table)
14 | [](https://www.npmjs.com/package/ant-virtual-table)
15 |
16 | # install
17 | npm install ant-virtual-table --save-dev
18 | # Usage
19 |
20 | ## demo
21 | 
22 | ```
23 | import React, { Component, Fragment } from 'react'
24 | import { VirtualTable } from 'ant-virtual-table'
25 | import 'antd/dist/antd.css'
26 |
27 | const columns = [
28 | {
29 | title: '序号',
30 | dataIndex: 'id',
31 | width: 100
32 | },
33 | {
34 | title: '姓名',
35 | dataIndex: 'name',
36 | width: 150
37 | },
38 | {
39 | title: '年龄',
40 | dataIndex: 'age',
41 | width: 100
42 | },
43 | {
44 | title: '性别',
45 | dataIndex: 'sex',
46 | width: 100,
47 | render: (text) => {
48 | return text === 'male' ? '男' : '女'
49 | }
50 | },
51 | {
52 | title: '地址',
53 | dataIndex: 'address',
54 | key: 'address'
55 | }
56 | ]
57 |
58 | function generateData () {
59 | const res = []
60 | const names = ['Tom', 'Marry', 'Jack', 'Lorry', 'Tanken', 'Salla']
61 | const sexs = ['male', 'female']
62 | for (let i = 0; i < 10000000; i++) {
63 | let obj = {
64 | id: i,
65 | name: names[i % names.length] + i,
66 | sex: sexs[i % sexs.length],
67 | age: 15 + Math.round(10 * Math.random()),
68 | address: '浙江省杭州市西湖区华星时代广场2号楼'
69 | }
70 | res.push(obj)
71 | }
72 | return res
73 | }
74 |
75 | const dataSource = generateData()
76 |
77 | class App extends Component {
78 | render () {
79 | return (
80 |
81 |
89 |
90 | )
91 | }
92 | }
93 | ```
94 |
95 | # Prop Types
96 |
97 | The attribute is consistent with the ant.design Table, and there are no unique attributes for the time being.
98 |
--------------------------------------------------------------------------------
/examples/onSelectAll/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { render } from 'react-dom'
3 | import { VirtualTable } from '../../src'
4 | import { Pagination } from 'antd'
5 | import 'antd/dist/antd.css'
6 |
7 | const columns = [
8 | {
9 | title: '序号',
10 | dataIndex: 'id',
11 | // fixed: 'left',
12 | width: 100
13 | },
14 | {
15 | title: '姓名',
16 | dataIndex: 'name',
17 | width: 150
18 | },
19 | {
20 | title: '年龄',
21 | dataIndex: 'age',
22 | width: 100
23 | },
24 | {
25 | title: '性别',
26 | dataIndex: 'sex',
27 | width: 100,
28 | render: (text) => {
29 | return text === 'male' ? '男' : '女'
30 | }
31 | },
32 | {
33 | title: '地址',
34 | dataIndex: 'address',
35 | key: 'address'
36 | }
37 | ]
38 |
39 | function generateData (count) {
40 | const res = []
41 | const names = ['Tom', 'Marry', 'Jack', 'Lorry', 'Tanken', 'Salla']
42 | const sexs = ['male', 'female']
43 | for (let i = 0; i < count; i++) {
44 | let obj = {
45 | id: i,
46 | name: names[i % names.length] + i,
47 | sex: sexs[i % sexs.length],
48 | age: 15 + Math.round(10 * Math.random()),
49 | address: '浙江省杭州市西湖区华星时代广场2号楼'
50 | }
51 | res.push(obj)
52 | }
53 | return res
54 | }
55 |
56 | const dataSource = generateData(20)
57 |
58 | class App extends Component {
59 | constructor (props) {
60 | super(props)
61 | this.state = {
62 | pageNumber: 1,
63 | objectsPerPage: 10,
64 | list: dataSource
65 | }
66 | }
67 |
68 | // 改变页面数字第几页发起的请求
69 | onPageChange (pageNumber) {
70 | this.setState({
71 | pageNumber
72 | })
73 | }
74 |
75 | // 改变页面显示条数发起的请求
76 | onShowSizeChange (current, objectsPerPage) {
77 | const list = dataSource.slice((current - 1) * objectsPerPage, objectsPerPage)
78 | this.setState({
79 | list,
80 | pageNumber: current,
81 | objectsPerPage
82 | })
83 | }
84 |
85 | onChange = (selectedRowKeys, selectedRows) => {
86 | console.log(`onChange selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
87 | }
88 |
89 | onSelect = (record, selected, selectedRows, nativeEvent) => {
90 | console.log(`onSelect record: ${record} selected: ${selected}`, 'selectedRows: ', selectedRows)
91 | }
92 |
93 | onSelectAll = (selected, selectedRows) => {
94 | console.log(`onSelectAll selected: ${selected}`, 'selectedRows: ', selectedRows)
95 | }
96 |
97 | render () {
98 | const { list = [] } = this.state
99 | const rowSelection = {
100 | onChange: this.onChange,
101 | onSelect: this.onSelect,
102 | onSelectAll: this.onSelectAll,
103 | }
104 | return (
105 |
106 |
107 |
116 | `共 ${list.length} 条`}
126 | />
127 |
128 | )
129 | }
130 | }
131 |
132 | render(, document.getElementById('root'))
133 |
--------------------------------------------------------------------------------
/src/virtualTable/VirtualTable.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent, Fragment } from 'react'
2 | import ReactDOM from 'react-dom'
3 | import throttle from 'lodash/throttle';
4 | import BaseTable from './BaseTable'
5 |
6 | class VirtualTable extends PureComponent {
7 | static FillNode ({ height, node, marginTop, marginBottom }) {
8 | if (node) {
9 | marginTop = marginTop || 0
10 | marginBottom = marginBottom || 0
11 | height = height || 0
12 | return ReactDOM.createPortal(
13 | ,
14 | node
15 | )
16 | }
17 | return null
18 | }
19 | constructor (props) {
20 | super(props)
21 | this.state = {
22 | startIndex: 0,
23 | visibleRowCount: 0,
24 | thresholdCount: 40,
25 | rowHeight: 0,
26 | topBlankHeight: 0,
27 | bottomBlankHeight: 0,
28 | maxTotalHeight: 15000000
29 | }
30 | }
31 |
32 | componentDidMount () {
33 | // 普通table布局
34 | this.refScroll = ReactDOM.findDOMNode(this).getElementsByClassName('ant-table-body')[0]
35 | // 固定列的布局
36 | const fixedEles = ReactDOM.findDOMNode(this).getElementsByClassName('ant-table-body-inner')
37 | this.refFixedLeftScroll = fixedEles && fixedEles.length ? fixedEles[0] : null
38 | this.refFixedRightScroll = fixedEles && fixedEles.length > 1 ? fixedEles[1] : null
39 |
40 | this.listenEvent = throttle(this.handleScrollEvent, 50)
41 |
42 | if (this.refScroll) {
43 | this.refScroll.addEventListener('scroll', this.listenEvent)
44 | }
45 |
46 | this.createTopFillNode()
47 | this.createBottomFillNode()
48 | // 初始化设置滚动条
49 | this.setRowHeight()
50 | this.handleScrollEvent()
51 | }
52 |
53 | componentDidUpdate(prevProps) {
54 | const { dataSource } = prevProps
55 | const { dataSource: tdataSource } = this.props
56 | if (dataSource && dataSource.length != tdataSource.length) {
57 | this.refScroll.scrollTop = 0
58 | this.handleScroll(dataSource.length)
59 | }
60 | }
61 |
62 | componentWillUnmount () {
63 | if (this.refScroll) {
64 | this.refScroll.removeEventListener('scroll', this.listenEvent)
65 | }
66 | }
67 |
68 | createTopFillNode () {
69 | if (this.refScroll) {
70 | const ele = document.createElement('div')
71 | this.refScroll.insertBefore(ele, this.refScroll.firstChild)
72 | this.refTopNode = ele
73 | }
74 | if (this.refFixedLeftScroll) {
75 | const ele = document.createElement('div')
76 | this.refFixedLeftScroll.insertBefore(ele, this.refFixedLeftScroll.firstChild)
77 | this.refFixedLeftTopNode = ele
78 | }
79 | if (this.refFixedRightScroll) {
80 | const ele = document.createElement('div')
81 | this.refFixedRightScroll.insertBefore(ele, this.refFixedRightScroll.firstChild)
82 | this.refFixedRightTopNode = ele
83 | }
84 | }
85 |
86 | createBottomFillNode () {
87 | if (this.refScroll) {
88 | const ele = document.createElement('div')
89 | this.refScroll.appendChild(ele)
90 | this.refBottomNode = ele
91 | }
92 | if (this.refFixedLeftScroll) {
93 | const ele = document.createElement('div')
94 | this.refFixedLeftScroll.appendChild(ele)
95 | this.refFixedLeftBottomNode = ele
96 | }
97 | if (this.refFixedRightScroll) {
98 | const ele = document.createElement('div')
99 | this.refFixedRightScroll.appendChild(ele)
100 | this.refFixedRightBottomNode = ele
101 | }
102 | }
103 |
104 | setRowHeight () {
105 | this.refTable = this.refScroll.getElementsByTagName('table')[0]
106 | // this.refFixedLeftTable = this.refFixedLeftScroll.getElementsByTagName('table')[0]
107 | if (this.refTable) {
108 | const tr = this.refTable.getElementsByTagName('tr')[0]
109 | const rowHeight = (tr && tr.clientHeight) || 0
110 | this.state.rowHeight = rowHeight
111 | }
112 | }
113 |
114 | handleScrollEvent = (e) => {
115 | const { dataSource } = this.props
116 | this.handleScroll((dataSource || []).length)
117 | }
118 |
119 | handleScroll = (length) => {
120 | const { rowHeight, maxTotalHeight } = this.state
121 | if (rowHeight && length) {
122 | const visibleHeight = this.refScroll.clientHeight // 显示的高度
123 | const scrollTop = this.refScroll.scrollTop // 滑动的距离
124 | this.handleBlankHeight(length, rowHeight, maxTotalHeight, visibleHeight, scrollTop)
125 | } else {
126 | this.setRowHeight()
127 | }
128 | }
129 |
130 | getIndexByScrollTop(rowHeight, scrollTop) {
131 | const index = (scrollTop - scrollTop % rowHeight) / rowHeight
132 | return index
133 | }
134 |
135 | handleBlankHeight(length, rowHeight, maxTotalHeight, visibleHeight, scrollTop) {
136 | let oriRowHeight = rowHeight
137 | let totalHeight = length * rowHeight
138 | let isBigData = false
139 | if (totalHeight > maxTotalHeight) {
140 | isBigData = true
141 | totalHeight = maxTotalHeight
142 | rowHeight = totalHeight / length
143 | scrollTop = scrollTop > maxTotalHeight ? maxTotalHeight : scrollTop
144 | }
145 | if (length >= 10000) {
146 | isBigData = true
147 | }
148 | let topBlankHeight, bottomBlankHeight, startIndex, visibleRowCount
149 | startIndex = this.getIndexByScrollTop(rowHeight, scrollTop)
150 | visibleRowCount = Math.ceil(visibleHeight / oriRowHeight)
151 | topBlankHeight = rowHeight * startIndex
152 | topBlankHeight = this.getValidValue(topBlankHeight, 0, totalHeight)
153 | bottomBlankHeight = totalHeight - topBlankHeight - visibleHeight
154 | bottomBlankHeight = bottomBlankHeight > 0 ? bottomBlankHeight : 0
155 |
156 | const slideUpHeight = Math.abs(topBlankHeight - this.state.topBlankHeight)
157 | const slideDownHeight = Math.abs(bottomBlankHeight - this.state.bottomBlankHeight)
158 |
159 | if (!this.lastSlideUpHeight) {
160 | this.sameSlideHeightCount = 0
161 | this.lastSlideUpHeight = slideUpHeight
162 | } else if (this.lastSlideUpHeight === slideUpHeight) {
163 | this.sameSlideHeightCount++
164 | } else {
165 | this.lastSlideUpHeight = slideUpHeight
166 | this.sameSlideHeightCount = 0
167 | }
168 |
169 | let isValid = slideUpHeight >= rowHeight
170 | isValid = isValid || slideDownHeight >= rowHeight
171 | isValid = isValid || startIndex === 0
172 | if (isValid) {
173 | startIndex = startIndex - 5
174 | visibleRowCount = visibleRowCount + 5
175 | this.setState({
176 | startIndex,
177 | visibleRowCount,
178 | topBlankHeight,
179 | bottomBlankHeight
180 | })
181 |
182 | if (isBigData && this.sameSlideHeightCount >= 1) { // 防止大数据持续滚动期间出现空白的问题
183 | this.refScroll.scrollTop = scrollTop
184 | this.sameSlideHeightCount = 0
185 | }
186 | }
187 | }
188 |
189 | getValidValue (val, min = 0, max = 40) {
190 | if (val < min) {
191 | return min
192 | } else if (val > max) {
193 | return max
194 | }
195 | return val
196 | }
197 |
198 | render () {
199 | const { dataSource, ...rest } = this.props
200 | const { topBlankHeight, bottomBlankHeight, startIndex, visibleRowCount, rowHeight, thresholdCount } = this.state
201 | const { length } = dataSource || []
202 | let startCount = length - visibleRowCount
203 | startCount = startCount > 0 ? startCount : length
204 | let startIn = this.getValidValue(startIndex, 0, startCount)
205 | let endIn = startIndex + visibleRowCount
206 | if (!endIn) { // 初始化渲染数据
207 | endIn = length > thresholdCount ? thresholdCount : length
208 | }
209 | endIn = this.getValidValue(endIn, startIndex, length)
210 | const renderSource = (dataSource || []).slice(startIn, endIn)
211 |
212 | return (
213 |
214 |
218 |
223 |
227 |
228 | {
229 | /**fixed 针对固定列的*/
230 |
231 |
235 |
239 |
243 |
247 |
248 | }
249 |
250 | )
251 | }
252 | }
253 |
254 | export default VirtualTable
255 |
--------------------------------------------------------------------------------
/dist/virtualTable/VirtualTable.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8 |
9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
10 |
11 | var _react = require('react');
12 |
13 | var _react2 = _interopRequireDefault(_react);
14 |
15 | var _reactDom = require('react-dom');
16 |
17 | var _reactDom2 = _interopRequireDefault(_reactDom);
18 |
19 | var _throttle = require('lodash/throttle');
20 |
21 | var _throttle2 = _interopRequireDefault(_throttle);
22 |
23 | var _BaseTable = require('./BaseTable');
24 |
25 | var _BaseTable2 = _interopRequireDefault(_BaseTable);
26 |
27 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
28 |
29 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
30 |
31 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
32 |
33 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
34 |
35 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
36 |
37 | var VirtualTable = function (_PureComponent) {
38 | _inherits(VirtualTable, _PureComponent);
39 |
40 | _createClass(VirtualTable, null, [{
41 | key: 'FillNode',
42 | value: function FillNode(_ref) {
43 | var height = _ref.height,
44 | node = _ref.node,
45 | marginTop = _ref.marginTop,
46 | marginBottom = _ref.marginBottom;
47 |
48 | if (node) {
49 | marginTop = marginTop || 0;
50 | marginBottom = marginBottom || 0;
51 | height = height || 0;
52 | return _reactDom2.default.createPortal(_react2.default.createElement('div', { style: { height: height + 'px', marginTop: marginTop + 'px', marginBottom: marginBottom + 'px' } }), node);
53 | }
54 | return null;
55 | }
56 | }]);
57 |
58 | function VirtualTable(props) {
59 | _classCallCheck(this, VirtualTable);
60 |
61 | var _this = _possibleConstructorReturn(this, (VirtualTable.__proto__ || Object.getPrototypeOf(VirtualTable)).call(this, props));
62 |
63 | _this.handleScrollEvent = function (e) {
64 | var dataSource = _this.props.dataSource;
65 |
66 | _this.handleScroll((dataSource || []).length);
67 | };
68 |
69 | _this.handleScroll = function (length) {
70 | var _this$state = _this.state,
71 | rowHeight = _this$state.rowHeight,
72 | maxTotalHeight = _this$state.maxTotalHeight;
73 |
74 | if (rowHeight && length) {
75 | var visibleHeight = _this.refScroll.clientHeight; // 显示的高度
76 | var scrollTop = _this.refScroll.scrollTop; // 滑动的距离
77 | _this.handleBlankHeight(length, rowHeight, maxTotalHeight, visibleHeight, scrollTop);
78 | } else {
79 | _this.setRowHeight();
80 | }
81 | };
82 |
83 | _this.state = {
84 | startIndex: 0,
85 | visibleRowCount: 0,
86 | thresholdCount: 40,
87 | rowHeight: 0,
88 | topBlankHeight: 0,
89 | bottomBlankHeight: 0,
90 | maxTotalHeight: 15000000
91 | };
92 | return _this;
93 | }
94 |
95 | _createClass(VirtualTable, [{
96 | key: 'componentDidMount',
97 | value: function componentDidMount() {
98 | // 普通table布局
99 | this.refScroll = _reactDom2.default.findDOMNode(this).getElementsByClassName('ant-table-body')[0];
100 | // 固定列的布局
101 | var fixedEles = _reactDom2.default.findDOMNode(this).getElementsByClassName('ant-table-body-inner');
102 | this.refFixedLeftScroll = fixedEles && fixedEles.length ? fixedEles[0] : null;
103 | this.refFixedRightScroll = fixedEles && fixedEles.length > 1 ? fixedEles[1] : null;
104 |
105 | this.listenEvent = (0, _throttle2.default)(this.handleScrollEvent, 50);
106 |
107 | if (this.refScroll) {
108 | this.refScroll.addEventListener('scroll', this.listenEvent);
109 | }
110 |
111 | this.createTopFillNode();
112 | this.createBottomFillNode();
113 | // 初始化设置滚动条
114 | this.setRowHeight();
115 | this.handleScrollEvent();
116 | }
117 | }, {
118 | key: 'componentDidUpdate',
119 | value: function componentDidUpdate(prevProps) {
120 | var dataSource = prevProps.dataSource;
121 | var tdataSource = this.props.dataSource;
122 |
123 | if (dataSource && dataSource.length != tdataSource.length) {
124 | this.refScroll.scrollTop = 0;
125 | this.handleScroll(dataSource.length);
126 | }
127 | }
128 | }, {
129 | key: 'componentWillUnmount',
130 | value: function componentWillUnmount() {
131 | if (this.refScroll) {
132 | this.refScroll.removeEventListener('scroll', this.listenEvent);
133 | }
134 | }
135 | }, {
136 | key: 'createTopFillNode',
137 | value: function createTopFillNode() {
138 | if (this.refScroll) {
139 | var ele = document.createElement('div');
140 | this.refScroll.insertBefore(ele, this.refScroll.firstChild);
141 | this.refTopNode = ele;
142 | }
143 | if (this.refFixedLeftScroll) {
144 | var _ele = document.createElement('div');
145 | this.refFixedLeftScroll.insertBefore(_ele, this.refFixedLeftScroll.firstChild);
146 | this.refFixedLeftTopNode = _ele;
147 | }
148 | if (this.refFixedRightScroll) {
149 | var _ele2 = document.createElement('div');
150 | this.refFixedRightScroll.insertBefore(_ele2, this.refFixedRightScroll.firstChild);
151 | this.refFixedRightTopNode = _ele2;
152 | }
153 | }
154 | }, {
155 | key: 'createBottomFillNode',
156 | value: function createBottomFillNode() {
157 | if (this.refScroll) {
158 | var ele = document.createElement('div');
159 | this.refScroll.appendChild(ele);
160 | this.refBottomNode = ele;
161 | }
162 | if (this.refFixedLeftScroll) {
163 | var _ele3 = document.createElement('div');
164 | this.refFixedLeftScroll.appendChild(_ele3);
165 | this.refFixedLeftBottomNode = _ele3;
166 | }
167 | if (this.refFixedRightScroll) {
168 | var _ele4 = document.createElement('div');
169 | this.refFixedRightScroll.appendChild(_ele4);
170 | this.refFixedRightBottomNode = _ele4;
171 | }
172 | }
173 | }, {
174 | key: 'setRowHeight',
175 | value: function setRowHeight() {
176 | this.refTable = this.refScroll.getElementsByTagName('table')[0];
177 | // this.refFixedLeftTable = this.refFixedLeftScroll.getElementsByTagName('table')[0]
178 | if (this.refTable) {
179 | var tr = this.refTable.getElementsByTagName('tr')[0];
180 | var rowHeight = tr && tr.clientHeight || 0;
181 | this.state.rowHeight = rowHeight;
182 | }
183 | }
184 | }, {
185 | key: 'getIndexByScrollTop',
186 | value: function getIndexByScrollTop(rowHeight, scrollTop) {
187 | var index = (scrollTop - scrollTop % rowHeight) / rowHeight;
188 | return index;
189 | }
190 | }, {
191 | key: 'handleBlankHeight',
192 | value: function handleBlankHeight(length, rowHeight, maxTotalHeight, visibleHeight, scrollTop) {
193 | var oriRowHeight = rowHeight;
194 | var totalHeight = length * rowHeight;
195 | var isBigData = false;
196 | if (totalHeight > maxTotalHeight) {
197 | isBigData = true;
198 | totalHeight = maxTotalHeight;
199 | rowHeight = totalHeight / length;
200 | scrollTop = scrollTop > maxTotalHeight ? maxTotalHeight : scrollTop;
201 | }
202 | if (length >= 10000) {
203 | isBigData = true;
204 | }
205 | var topBlankHeight = void 0,
206 | bottomBlankHeight = void 0,
207 | startIndex = void 0,
208 | visibleRowCount = void 0;
209 | startIndex = this.getIndexByScrollTop(rowHeight, scrollTop);
210 | visibleRowCount = Math.ceil(visibleHeight / oriRowHeight);
211 | topBlankHeight = rowHeight * startIndex;
212 | topBlankHeight = this.getValidValue(topBlankHeight, 0, totalHeight);
213 | bottomBlankHeight = totalHeight - topBlankHeight - visibleHeight;
214 | bottomBlankHeight = bottomBlankHeight > 0 ? bottomBlankHeight : 0;
215 |
216 | var slideUpHeight = Math.abs(topBlankHeight - this.state.topBlankHeight);
217 | var slideDownHeight = Math.abs(bottomBlankHeight - this.state.bottomBlankHeight);
218 |
219 | if (!this.lastSlideUpHeight) {
220 | this.sameSlideHeightCount = 0;
221 | this.lastSlideUpHeight = slideUpHeight;
222 | } else if (this.lastSlideUpHeight === slideUpHeight) {
223 | this.sameSlideHeightCount++;
224 | } else {
225 | this.lastSlideUpHeight = slideUpHeight;
226 | this.sameSlideHeightCount = 0;
227 | }
228 |
229 | var isValid = slideUpHeight >= rowHeight;
230 | isValid = isValid || slideDownHeight >= rowHeight;
231 | isValid = isValid || startIndex === 0;
232 | if (isValid) {
233 | startIndex = startIndex - 5;
234 | visibleRowCount = visibleRowCount + 5;
235 | this.setState({
236 | startIndex: startIndex,
237 | visibleRowCount: visibleRowCount,
238 | topBlankHeight: topBlankHeight,
239 | bottomBlankHeight: bottomBlankHeight
240 | });
241 |
242 | if (isBigData && this.sameSlideHeightCount >= 1) {
243 | // 防止大数据持续滚动期间出现空白的问题
244 | this.refScroll.scrollTop = scrollTop;
245 | this.sameSlideHeightCount = 0;
246 | }
247 | }
248 | }
249 | }, {
250 | key: 'getValidValue',
251 | value: function getValidValue(val) {
252 | var min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
253 | var max = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 40;
254 |
255 | if (val < min) {
256 | return min;
257 | } else if (val > max) {
258 | return max;
259 | }
260 | return val;
261 | }
262 | }, {
263 | key: 'render',
264 | value: function render() {
265 | var _props = this.props,
266 | dataSource = _props.dataSource,
267 | rest = _objectWithoutProperties(_props, ['dataSource']);
268 |
269 | var _state = this.state,
270 | topBlankHeight = _state.topBlankHeight,
271 | bottomBlankHeight = _state.bottomBlankHeight,
272 | startIndex = _state.startIndex,
273 | visibleRowCount = _state.visibleRowCount,
274 | rowHeight = _state.rowHeight,
275 | thresholdCount = _state.thresholdCount;
276 |
277 | var _ref2 = dataSource || [],
278 | length = _ref2.length;
279 |
280 | var startCount = length - visibleRowCount;
281 | startCount = startCount > 0 ? startCount : length;
282 | var startIn = this.getValidValue(startIndex, 0, startCount);
283 | var endIn = startIndex + visibleRowCount;
284 | if (!endIn) {
285 | // 初始化渲染数据
286 | endIn = length > thresholdCount ? thresholdCount : length;
287 | }
288 | endIn = this.getValidValue(endIn, startIndex, length);
289 | var renderSource = (dataSource || []).slice(startIn, endIn);
290 |
291 | return _react2.default.createElement(
292 | _react.Fragment,
293 | null,
294 | _react2.default.createElement(VirtualTable.FillNode, {
295 | height: topBlankHeight,
296 | node: this.refTopNode
297 | }),
298 | _react2.default.createElement(_BaseTable2.default, _extends({}, rest, {
299 | dataSource: dataSource,
300 | renderSource: renderSource
301 | })),
302 | _react2.default.createElement(VirtualTable.FillNode, {
303 | height: bottomBlankHeight,
304 | node: this.refBottomNode
305 | }),
306 |
307 | /**fixed 针对固定列的*/
308 | _react2.default.createElement(
309 | _react.Fragment,
310 | null,
311 | _react2.default.createElement(VirtualTable.FillNode, {
312 | height: topBlankHeight,
313 | node: this.refFixedLeftTopNode
314 | }),
315 | _react2.default.createElement(VirtualTable.FillNode, {
316 | height: bottomBlankHeight,
317 | node: this.refFixedLeftBottomNode
318 | }),
319 | _react2.default.createElement(VirtualTable.FillNode, {
320 | height: topBlankHeight,
321 | node: this.refFixedRightTopNode
322 | }),
323 | _react2.default.createElement(VirtualTable.FillNode, {
324 | height: bottomBlankHeight,
325 | node: this.refFixedRightBottomNode
326 | })
327 | )
328 | );
329 | }
330 | }]);
331 |
332 | return VirtualTable;
333 | }(_react.PureComponent);
334 |
335 | exports.default = VirtualTable;
--------------------------------------------------------------------------------