Repository: DavidWong9785/react-virtualized-scroll
Branch: master
Commit: d3e82595ed87
Files: 40
Total size: 71.6 KB
Directory structure:
gitextract_0u698uid/
├── .babelrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .npmignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── example/
│ └── src/
│ ├── app.tsx
│ ├── index.html
│ ├── index.tsx
│ └── utils.ts
├── jestconfig.json
├── lib/
│ ├── README.md
│ ├── __tests__/
│ │ └── demo.test.js
│ ├── assets/
│ │ ├── iconfont.css
│ │ ├── iconfont.js
│ │ └── iconfont.ts
│ ├── component/
│ │ └── ReactVirtualizedScroll/
│ │ ├── ReactVirtualizedScroll.js
│ │ ├── Row.js
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── style.css
│ │ ├── svg.js
│ │ └── utils.js
│ └── package.json
├── package.json
├── src/
│ ├── __tests__/
│ │ └── demo.test.tsx
│ ├── assets/
│ │ ├── iconfont.css
│ │ └── iconfont.js
│ ├── component/
│ │ └── ReactVirtualizedScroll/
│ │ ├── ReactVirtualizedScroll.tsx
│ │ ├── index.tsx
│ │ ├── style.css
│ │ └── svg.ts
│ └── types/
│ ├── demo/
│ │ └── index.d.ts
│ └── global.d.ts
├── tsconfig.json
├── tslint.json
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"]
}
================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
[{*.json,*.md,*.yml,*.*rc}]
indent_style = space
indent_size = 2
================================================
FILE: .gitattributes
================================================
*.html linguist-language=JavaScript
================================================
FILE: .gitignore
================================================
/node_modules/
/dist/
/coverage/
*.log
================================================
FILE: .npmignore
================================================
/.git/
/.vscode/
/node_modules/
.gitignore
.npmignore
.prettierrc
.editorconfig
tslint.json
tsconfig.json
note.md
*.log
================================================
FILE: .prettierrc
================================================
{
"trailingComma": "all",
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"endOfLine": "lf",
"printWidth": 120,
"overrides": [
{
"files": ["*.md", "*.json", "*.yml", "*.yaml"],
"options": {
"tabWidth": 2
}
}
]
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- '12'
install:
- npm install
script:
- npm run test
- npm run lint
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2013-present, DavidWong9785(Zhaokang Huang)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# react-virtualized-scroll
虚拟滚动搭配上拉下滑加载的scroll组件
[](https://github.com/DavidWong9785/react-virtualized-scroll)
[](https://github.com/DavidWong9785/react-virtualized-scroll/issues)
[](https://github.com/DavidWong9785/react-virtualized-scroll)
[](https://github.com/DavidWong9785/react-virtualized-scroll)
[](https://www.npmjs.com/package/react-virtualized-scroll)
[](https://www.npmjs.com/package/react-virtualized-scroll)
[](https://www.npmjs.com/package/react-virtualized-scroll)



### 简介
- 搭配 typescript 和 react-hooks 编写的虚拟滚动组件
- 基于 react-virtualized 进行再封装。
- 暴露了 react-virtualized 的 ref,可调用 react-virtualized 的方法
- 除了渲染列表,你还可以传入其他的子组件(如悬浮球~等),只需要把定位设为 fixed
### 安装导入
> cnpm i react-virtualized react-virtualized-scroll --save
> import ReactVirtualizedScroll from 'react-virtualized-scroll'
### 使用
```
fixed element
```
### 属性
| 名称 |
类型 |
说明 |
| width、height |
string |
列表宽高,带单位 |
可选
默认100vw/vh
|
| hasMore |
boolean |
判断是否还可以下滑加载 |
必传,默认true |
| data |
array |
用于渲染列表的目标数 |
必传,默认 [] |
| info |
object |
需要传入 row 渲染函数作为参数的数据 |
可选 |
| logo |
object |
加载时展示的 loading 图案,四个属性 |
可选
有默认logo
|
| onPullDown |
function |
下拉加载回调
该方法必须返回一个 promise 对象 ( 用于控制下拉 loading 状态 )
可以使用 async 方法或者直接返回 promise 对象
当 promise 状态完成之后 ( resolve/reject ),下拉加载状态结束
|
可选 |
| onPullUp |
function |
上滑加载回调,目的同上,该方法需要返回一个 promise 对象
|
可选 |
| onScroll |
function |
滑动回调
参数1: clientHeight
参数2: scrollTop
参数3: scrollHeight
|
可选 |
| row |
function |
列表每一行的渲染函数
参数1:类型为object,属性包含该行索引 index 和自定义传入的 info 属性
参数2:用于渲染列表的目标数组data
|
必传 |
### logo属性(字符串,图片等等,只要是JSX.Element即可)
| 属性 | 说明 |
| ---------------- | --------------------- |
| pulldown_loading | 下拉加载 loading 的 logo |
| pulldown_success | 下拉加载 成功 的 logo |
| pullup_loading | 上滑加载 loading 的 logo |
| pullup_success | 上滑加载 成功 的 logo |
================================================
FILE: example/src/app.tsx
================================================
/* tslint:disable */
import React, { useState, useCallback } from 'react'
import ReactVirtualizedScroll from '../../lib/component/ReactVirtualizedScroll/index.js'
import useStateAndRef from './utils'
// import ReactVirtualizedScroll from '../../src/component/ReactVirtualizedScroll/index.tsx'
import { loading_pullup } from '../../src/component/ReactVirtualizedScroll/svg'
const initState = [{
key: 1,
value: 1
}, {
key: 2,
value: 2
}, {
key: 3,
value: 3
}, {
key: 4,
value: 4
}, {
key: 5,
value: 5
}]
const example = () => {
const [data, setData, dataRef] = useStateAndRef(initState)
const [hasMore, setHasMore] = useState(true)
const info = {
title: 'VirtualizedScroll',
desc: '虚拟滚动搭配上拉下滑加载的scroll组件'
}
const requestPullDown = () => {
return new Promise(resolve => {
setTimeout(() => {
setData(initState)
setHasMore(true)
resolve()
}, 2000);
})
}
const requestPullUp = useCallback(
() => {
return new Promise(resolve => {
if (data.length === 100) {
setHasMore(false)
resolve()
}
else {
setTimeout(() => {
const target: any = []
for (let i = data.length + 1; i < data.length + 6; i++) {
target.push({
key: i,
value: i
})
}
setData(data.concat(target))
resolve()
}, 1000);
}
})
},
[data],
)
const handlePullDown = async () => {
await requestPullDown()
}
const handlePullUp = async () => {
await requestPullUp()
}
const onScroll = (clientHeight: number, scrollTop: number, scrollHeight: number) => {
// console.log('onScroll')
}
const logo = {
pulldown_loading: ,
pulldown_success: ,
pullup_loading:
,
pullup_success: '没有更多了',
}
const countAdd = ((index: number) => {
const newData = dataRef.current
newData[index].value += 100
setData(JSON.parse(JSON.stringify(newData)))
})
const Row = ((args: any) => {
const {index, info, data} = args
return countAdd(index)}>
{ info.title } - { data[index].value }
{ info.desc }
})
return (
)
}
export default example
================================================
FILE: example/src/index.html
================================================
NPMPluginTemplate
================================================
FILE: example/src/index.tsx
================================================
import React from 'react'
import { render } from 'react-dom'
import App from './app'
render(, document.getElementById('root'))
================================================
FILE: example/src/utils.ts
================================================
import React, { useState, useEffect, useRef } from 'react'
export default (initState) => {
const [data, setData] = useState(initState)
const dataRef = useRef(data)
useEffect(() => {
dataRef.current = data
}, [data])
return [data, setData, dataRef]
}
================================================
FILE: jestconfig.json
================================================
{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest",
"^.+\\.jsx?$": "babel-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["tsx", "ts", "js", "jsx", "json", "node"],
"moduleNameMapper": {
"\\.(s?css|less)$": "identity-obj-proxy"
}
}
================================================
FILE: lib/README.md
================================================
# react-virtualized-scroll
虚拟滚动搭配上拉下滑加载的scroll组件
[](https://github.com/DavidWong9785/react-virtualized-scroll)
[](https://github.com/DavidWong9785/react-virtualized-scroll/issues)
[](https://github.com/DavidWong9785/react-virtualized-scroll)
[](https://github.com/DavidWong9785/react-virtualized-scroll)
[](https://www.npmjs.com/package/react-virtualized-scroll)
[](https://www.npmjs.com/package/react-virtualized-scroll)
[](https://www.npmjs.com/package/react-virtualized-scroll)



### 简介
- 搭配 typescript 和 react-hooks 编写的虚拟滚动组件
- 基于 react-virtualized 进行再封装。
- 暴露了 react-virtualized 的 ref,可调用 react-virtualized 的方法
- 除了渲染列表,你还可以传入其他的子组件(如悬浮球~等),只需要把定位设为 fixed
### 安装导入
> cnpm i react-virtualized react-virtualized-scroll --save
> import ReactVirtualizedScroll from 'react-virtualized-scroll'
### 使用
```
fixed element
```
### 属性
| 名称 |
类型 |
说明 |
| width、height |
string |
列表宽高,带单位 |
可选
默认100vw/vh
|
| hasMore |
boolean |
判断是否还可以下滑加载 |
必传,默认true |
| data |
array |
用于渲染列表的目标数 |
必传,默认 [] |
| info |
object |
需要传入 row 渲染函数作为参数的数据 |
可选 |
| logo |
object |
加载时展示的 loading 图案,四个属性 |
可选
有默认logo
|
| onPullDown |
function |
下拉加载回调
该方法必须返回一个 promise 对象 ( 用于控制下拉 loading 状态 )
可以使用 async 方法或者直接返回 promise 对象
当 promise 状态完成之后 ( resolve/reject ),下拉加载状态结束
|
可选 |
| onPullUp |
function |
上滑加载回调,目的同上,该方法需要返回一个 promise 对象
|
可选 |
| onScroll |
function |
滑动回调
参数1: clientHeight
参数2: scrollTop
参数3: scrollHeight
|
可选 |
| row |
function |
列表每一行的渲染函数
参数1:类型为object,属性包含该行索引 index 和自定义传入的 info 属性
参数2:用于渲染列表的目标数组data
|
必传 |
### logo属性(字符串,图片等等,只要是JSX.Element即可)
| 属性 | 说明 |
| ---------------- | --------------------- |
| pulldown_loading | 下拉加载 loading 的 logo |
| pulldown_success | 下拉加载 成功 的 logo |
| pullup_loading | 上滑加载 loading 的 logo |
| pullup_success | 上滑加载 成功 的 logo |
================================================
FILE: lib/__tests__/demo.test.js
================================================
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importDefault(require("react"));
var enzyme_1 = require("enzyme");
var enzyme_adapter_react_16_1 = __importDefault(require("enzyme-adapter-react-16"));
var index_1 = __importDefault(require("../component/ReactVirtualizedScroll/index"));
enzyme_1.configure({ adapter: new enzyme_adapter_react_16_1.default() });
var setup = function () {
// 模拟 props
var props = {
height: '100vh',
row: function () { },
hasMore: true
};
// 通过 enzyme 提供的 shallow(浅渲染) 创建组件
var wrapper = enzyme_1.shallow(react_1.default.createElement(index_1.default, __assign({}, props)));
return {
props: props,
wrapper: wrapper,
};
};
describe('测试height属性', function () {
var _a = setup(), wrapper = _a.wrapper, props = _a.props;
it('props', function () {
expect(props.height).toEqual('100vh');
});
});
================================================
FILE: lib/assets/iconfont.css
================================================
@font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1578637319034'); /* IE9 */
src: url('iconfont.eot?t=1578637319034#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAATYAAsAAAAACVwAAASMAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDSAqFRIRoATYCJAMYCw4ABCAFhG0HYhsoCFFUTppkf4U4RBYobL8q1RqS2WPnlCFBo7GKFHDAYghuPVikPTPGSvDG5xkqd+fb7TfWfzouIB7+f79v+1wblWaOScQTnliEhDdLM/2TaIXkoZHMmmfyGzjnonwDm65BowSjLG08nE1kzfkfpDGBft9vrrIERkSYRbmKUO0Rzpv+EqTpwcC6PI12AhiN9IK2NtffElXBPp0cfBcOAs798xgziFvb89w8Bzxk4ZqNpuRg+pv/wrmxywtwTVzLA0w86gIa6/PvCxzBcfCfO8BDunlbQhnWPJBEw7a29N878nqgwANVvaLX13vRdP7/905/6/gDa13Lcplj08IA44ACGmPbIiuQTpigpqLOyC4BXh0eJjBoHgM8t7/eU1fhjQrEvcAz1K1plREU+hWtZ2FZAIla+tPjNHiSvj/+ORzWJDXzJl69sXNo+XNYD+rkPcbw9jNDWUbGXoBC7PRaH2nk0b1gg9YvNJ1HhvVLys/hD/5sV8+mJvScYdj/yyNURZKJBtJhJl2MVX4Oh2phyQ6C2ye7DgaFX7tqiWU9yDy6su04YAjviL2AJ0il8Zw9Qt1PDu13ZTQHuseSdB/wVk3Qktl6ytKuH0SgiqMfxD6g973BG2xHUEQzq1UyZ4umsha6ZzKm9shpGK+WQe8YssH8DuyqW1qqirD/2ukZ090542X17dYJqd9G2u+m7g3T7ItPSqVt9H0MsHWDPjhS1Nxa0MvQ2DlhmnWlrSO6bSPkRdJK3Ql6u4RRAU3piFaDVhtdN23dPaPKG7C9SgYm8+URXWQQXlOr1yCfFVuhdYgu7IMm/qTwREjDp7bq8VDb+oqV2Mod5Psb/uAdaHYQsKIbEC6epTtzuXeVjfObxOyVJS036DgL07KOgv/I/0uIDogNIZf4pA0+O4Mh4jEbQr4p4BW5NarmYeDj4xN54e3f5zaCAs/o3GzZsNUf2cPr6bn7fWa5y4A18BKxubb53a/jQ0ku1scaTQjw1C2ge4x/+uZ3fQLascKZ5+wGl8dJD1BaTn1cQEr57y0PlNXkq+VOkNMAG1c2k8WagH18TBWQ26vD8d0AsAOgm1HCVanqhC82cdybFQ6HTPKie2qCI/9Nn/FjV76jU/5/L9Yu4POJ2lB38zyrTvSXkJnN++9UlBVR7qgC1rVi1gllz9dlaU5rcThh0HfSwTnpu5d8ZnY+od9MCUmfOcj6reIKaS9UQ45C0+8kDNpDXz5kSqohygC2lQLCuAmSUa8hG3fGFdIHqll/aMYDhkHXgtxwyHqM92WBUThs/3Nks9QNDNrPCm/wqthIVZCSHyFlEGBxPG0mDkghe0zJfrx5ri77kiWsieMQxxnHkoWwOg5V8wU/cfueNHayhPrOEmAocDDbPxaxMik3bq76rdffgKcSM2SkrsX8CEQpaJ+JOMIM3EGazqp7l+6ZH565nHJ5mE9kEkyDGWI/mmFxf78QWGosXBHOLfDUyJ8rHZ8vT17lFhjkPbJFihwlarTo03ffr8xfkA4dHzYaOYHYGO0UcWac3rN0GeUote8Hsars59IQAQ==') format('woff2'),
url('iconfont.woff?t=1578637319034') format('woff'),
url('iconfont.ttf?t=1578637319034') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('iconfont.svg?t=1578637319034#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconshuaxin:before {
content: "\e60c";
}
.iconcheck-circle:before {
content: "\e77d";
}
.iconreload:before {
content: "\e788";
}
.iconicon-test:before {
content: "\e658";
}
.iconfiltration:before {
content: "\e630";
}
================================================
FILE: lib/assets/iconfont.js
================================================
"use strict";
!function (l) { var e, c = '', t = (e = document.getElementsByTagName("script"))[e.length - 1].getAttribute("data-injectcss"); if (t && !l.__iconfont__svg__cssinject__) {
l.__iconfont__svg__cssinject__ = !0;
try {
document.write("");
}
catch (e) {
console && console.log(e);
}
} !function (e) { if (document.addEventListener)
if (~["complete", "loaded", "interactive"].indexOf(document.readyState))
setTimeout(e, 0);
else {
var t = function () { document.removeEventListener("DOMContentLoaded", t, !1), e(); };
document.addEventListener("DOMContentLoaded", t, !1);
}
else
document.attachEvent && (o = e, i = l.document, a = !1, (c = function () { try {
i.documentElement.doScroll("left");
}
catch (e) {
return void setTimeout(c, 50);
} n(); })(), i.onreadystatechange = function () { "complete" == i.readyState && (i.onreadystatechange = null, n()); }); function n() { a || (a = !0, o()); } var o, i, a, c; }(function () { var e, t, n, o, i, a; (e = document.createElement("div")).innerHTML = c, c = null, (t = e.getElementsByTagName("svg")[0]) && (t.setAttribute("aria-hidden", "true"), t.style.position = "absolute", t.style.width = 0, t.style.height = 0, t.style.overflow = "hidden", n = t, (o = document.body).firstChild ? (i = n, (a = o.firstChild).parentNode.insertBefore(i, a)) : o.appendChild(n)); }); }(window);
================================================
FILE: lib/assets/iconfont.ts
================================================
"use strict";
!function (l) { var e, c = '', t = (e = document.getElementsByTagName("script"))[e.length - 1].getAttribute("data-injectcss"); if (t && !l.__iconfont__svg__cssinject__) {
l.__iconfont__svg__cssinject__ = !0;
try {
document.write("");
}
catch (e) {
console && console.log(e);
}
} !function (e) { if (document.addEventListener)
if (~["complete", "loaded", "interactive"].indexOf(document.readyState))
setTimeout(e, 0);
else {
var t = function () { document.removeEventListener("DOMContentLoaded", t, !1), e(); };
document.addEventListener("DOMContentLoaded", t, !1);
}
else
document.attachEvent && (o = e, i = l.document, a = !1, (c = function () { try {
i.documentElement.doScroll("left");
}
catch (e) {
return void setTimeout(c, 50);
} n(); })(), i.onreadystatechange = function () { "complete" == i.readyState && (i.onreadystatechange = null, n()); }); function n() { a || (a = !0, o()); } var o, i, a, c; }(function () { var e, t, n, o, i, a; (e = document.createElement("div")).innerHTML = c, c = null, (t = e.getElementsByTagName("svg")[0]) && (t.setAttribute("aria-hidden", "true"), t.style.position = "absolute", t.style.width = 0, t.style.height = 0, t.style.overflow = "hidden", n = t, (o = document.body).firstChild ? (i = n, (a = o.firstChild).parentNode.insertBefore(i, a)) : o.appendChild(n)); }); }(window);
================================================
FILE: lib/component/ReactVirtualizedScroll/ReactVirtualizedScroll.js
================================================
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importStar(require("react"));
var react_virtualized_1 = require("react-virtualized");
var svg_1 = require("./svg");
require("../../assets/iconfont.css");
require("./style.css");
var Row = function (_a) {
var row = _a.row, data = _a.data, index = _a.index, info = _a.info;
return react_1.useMemo(function () {
return row({ data: data, index: index, info: info });
}, [JSON.stringify(data[index])]);
};
var VirtualizedScroll = function (_a, ref) {
var children = _a.children, _b = _a.hasMore, hasMore = _b === void 0 ? true : _b, _c = _a.refreshDistance, refreshDistance = _c === void 0 ? 120 : _c, _d = _a.loading, loading = _d === void 0 ? false : _d, _e = _a.data, data = _e === void 0 ? [] : _e, info = _a.info, _f = _a.height, height = _f === void 0 ? '100vh' : _f, _g = _a.width, width = _g === void 0 ? '100vw' : _g, noDataRow = _a.noDataRow, _h = _a.onPullDown, onPullDown = _h === void 0 ? null : _h, _j = _a.onPullUp, onPullUp = _j === void 0 ? null : _j, _k = _a.onScroll, onScroll = _k === void 0 ? null : _k, row = _a.row, _l = _a.logo, logo = _l === void 0 ? {} : _l;
// 下拉状态
var _m = react_1.useState('init'), STATS = _m[0], setSTATS = _m[1];
// touch数据
var _o = react_1.useState(0), startY = _o[0], setStartY = _o[1];
var _p = react_1.useState(0), offset = _p[0], setOffset = _p[1];
// vList容器
var vListRef = react_1.useRef();
var timer = react_1.useRef();
// 上滑时,scrollTop为0禁止滑动
var scrollingDisable = react_1.useRef(false);
var hasMoreRef = react_1.useRef(true);
var canNotDrag = react_1.useRef(false);
var isPullUpLoading = react_1.useRef(false);
react_1.useEffect(function () {
hasMoreRef.current = hasMore;
}, [hasMore]);
react_1.useEffect(function () {
canNotDrag.current = STATS === 'refreshing' || STATS === 'success' || STATS == 'scroll' || !onPullDown;
}, [STATS]);
var initState = function () {
setOffset(0);
setStartY(0);
};
var onTouchStart = function ($event) {
if (canNotDrag.current || scrollingDisable.current)
return;
setStartY($event.nativeEvent.touches[0].pageY);
};
var onTouchMove = react_1.useCallback(function ($event) {
if (canNotDrag.current || scrollingDisable.current)
return;
var offsetComputed = $event.nativeEvent.touches[0].pageY - startY;
if (0 < offsetComputed && offsetComputed <= refreshDistance)
setOffset(offsetComputed);
else if (offsetComputed < 0)
setOffset(0);
else if (offsetComputed > refreshDistance)
setOffset(refreshDistance);
if (offset < refreshDistance && STATS !== 'dragging')
setSTATS('dragging');
else if (offset >= refreshDistance)
setSTATS('pre-refresh');
}, [startY, canNotDrag, offset, STATS]);
var onTouchEnd = react_1.useCallback(function ($event) {
if (canNotDrag.current)
return;
if (scrollingDisable.current) {
initState();
return;
}
if (STATS === 'pre-refresh') {
setSTATS('refreshing');
if (!onPullDown)
return;
onPullDown().then(function () {
setSTATS('success');
if (vListRef.current)
vListRef.current.scrollToRow(0);
if (timer.current)
clearTimeout(timer.current);
timer.current = setTimeout(function () {
setSTATS('init');
initState();
}, 500);
});
}
else {
initState();
setSTATS('not-enough');
if (timer.current)
clearTimeout(timer.current);
timer.current = setTimeout(function () {
setSTATS('init');
}, 500);
}
}, [timer, STATS, canNotDrag]);
var onScrollCallback = function (_a) {
var clientHeight = _a.clientHeight, scrollTop = _a.scrollTop, scrollHeight = _a.scrollHeight;
if (scrollTop === 0)
scrollingDisable.current = false;
else
scrollingDisable.current = true;
if ((clientHeight + scrollTop === scrollHeight) && onPullUp && !isPullUpLoading.current) {
isPullUpLoading.current = true;
onPullUp().then(function () {
isPullUpLoading.current = false;
if (vListRef.current)
vListRef.current.scrollToRow(data.length + 1);
}).catch(function () {
isPullUpLoading.current = false;
});
}
if (onScroll)
onScroll(clientHeight, scrollTop, scrollHeight);
};
var cache = new react_virtualized_1.CellMeasurerCache({ defaultHeight: 400, fixedWidth: true });
react_1.useImperativeHandle(ref, function () { return ({
vListRef: vListRef.current
}); }, [vListRef]);
var renderRow = react_1.useCallback(function (_a) {
var index = _a.index, key = _a.key, parent = _a.parent, style = _a.style;
return (react_1.default.createElement(react_virtualized_1.CellMeasurer, { cache: cache, columnIndex: 0, key: key, parent: parent, rowIndex: index },
react_1.default.createElement("div", { className: "card-content", style: __assign({}, style), key: key },
react_1.default.createElement(Row, { row: row, data: data, index: index, info: info }),
index === data.length - 1 ? !hasMoreRef.current ?
(react_1.default.createElement("div", { className: "load-item", style: { height: '50px', width: '100%' } }, logo.pullup_success ? logo.pullup_success : '没有更多了')) :
(react_1.default.createElement("div", { className: "load-item", style: { height: '50px', width: '100%' } }, logo.pullup_loading ? logo.pullup_loading : react_1.default.createElement("img", { src: svg_1.loading_pullup, alt: "" }))) : '')));
}, [data, hasMoreRef]);
var VirtualScroll = react_1.useMemo(function () {
return (react_1.default.createElement("div", { className: "content-wrap", style: {
width: width,
height: height,
position: 'relative'
} },
children,
!data.length ? noDataRow ? noDataRow : '' :
(react_1.default.createElement(react_virtualized_1.AutoSizer, null, function (_a) {
var height = _a.height, width = _a.width;
return (react_1.default.createElement(react_virtualized_1.List, { ref: function (ref) { return vListRef.current = ref; }, onScroll: onScrollCallback, height: height, width: width, rowHeight: cache.rowHeight, rowCount: data.length, rowRenderer: renderRow }));
}))));
}, [data]);
return (react_1.default.createElement("div", { id: "virtualized-scroll-panel", onTouchStart: onTouchStart, onTouchMove: onTouchMove, onTouchEnd: onTouchEnd },
react_1.default.createElement("div", { className: "list-box" },
react_1.default.createElement("div", { className: "pull-down-box", style: {
top: offset - 45 + "px",
transition: STATS === 'dragging' ? 'none' : 'all .3s',
zIndex: 999
} },
react_1.default.createElement("div", { className: "pull-down-round" },
react_1.default.createElement("div", { className: "inner", style: {
transform: "rotate(" + (offset / refreshDistance) * 720 + "deg)",
transition: STATS === 'refreshing' || STATS === 'dragging' ? 'none' : 'all .3s',
animation: STATS === 'refreshing' ? 'self_rotate 1s infinite linear' : ''
} }, STATS === 'success' || STATS === 'init' ?
logo.pulldown_success ? logo.pulldown_success : react_1.default.createElement("i", { className: "icon iconfont iconcheck-circle" })
: logo.pulldown_loading ? logo.pulldown_loading : react_1.default.createElement("i", { className: "icon iconfont iconshuaxin" })))),
VirtualScroll)));
};
exports.default = react_1.forwardRef(VirtualizedScroll);
================================================
FILE: lib/component/ReactVirtualizedScroll/Row.js
================================================
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = require("react");
var Row = function (_a) {
var row = _a.row, data = _a.data, index = _a.index, info = _a.info;
var Render = row({ data: data, index: index, info: info });
return react_1.useMemo(function () {
console.log('data[index].value]', index, data[index].value);
return Render;
}, [JSON.stringify(data[index])]);
};
exports.default = Row;
================================================
FILE: lib/component/ReactVirtualizedScroll/index.d.ts
================================================
declare module 'react-virtualized-scroll'
================================================
FILE: lib/component/ReactVirtualizedScroll/index.js
================================================
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var ReactVirtualizedScroll_1 = __importDefault(require("./ReactVirtualizedScroll"));
exports.default = ReactVirtualizedScroll_1.default;
================================================
FILE: lib/component/ReactVirtualizedScroll/style.css
================================================
*{
margin: 0;
padding: 0;
}
#virtualized-scroll-panel, .list-box{
width: 100%;
height: 100%;
}
#virtualized-scroll-panel .list-box{
transition: all .3s;
position: absolute;
}
#virtualized-scroll-panel{
position: relative;
background-color: #f5f5f9;
/* overflow: hidden; */
}
#virtualized-scroll-panel .pull-down-box{
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
#virtualized-scroll-panel .pull-down-box .pull-down-round .iconcheck-circle{
font-size: 28px;
color: #43cd80;
}
#virtualized-scroll-panel .pull-down-box .pull-down-round .iconshuaxin{
font-size: 28px;
color: #ff8080;
}
#virtualized-scroll-panel .pull-down-box .pull-down-round{
width: 40px;
height: 40px;
background: #fff;
box-shadow: 1px 1px 5px rgba(0,0,0,0.5);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes self_rotate{
0%{ transform: rotate(0deg); }
100%{ transform: rotate(360deg); }
}
#virtualized-scroll-panel .card-content .load-item{
height: 50px;
text-align: center;
line-height: 50px;
background-color: #f5f5f9;
width: 100%;
}
================================================
FILE: lib/component/ReactVirtualizedScroll/svg.js
================================================
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loading_pullup = void 0;
exports.loading_pullup = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiAjZjVmNWY5OyBkaXNwbGF5OiBibG9jazsiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjUwcHgiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+DQo8Y2lyY2xlIGN4PSI4NCIgY3k9IjUwIiByPSIxLjA2NTE3IiBmaWxsPSIjZmFjZDllIj4NCiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJyIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgZHVyPSIwLjM2MjMxODg0MDU3OTcxMDFzIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsxIiB2YWx1ZXM9IjEwOzAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxIiBiZWdpbj0iMHMiPjwvYW5pbWF0ZT4NCiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJmaWxsIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgZHVyPSIxLjQ0OTI3NTM2MjMxODg0MDRzIiBjYWxjTW9kZT0iZGlzY3JldGUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIjZmFjZDllOyNmMTlkM2I7IzQ1OTQ0ODsjMzg5Nzk4OyNmYWNkOWUiIGJlZ2luPSIwcyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+PGNpcmNsZSBjeD0iMTYiIGN5PSI1MCIgcj0iOC45MzQ4MyIgZmlsbD0iI2ZhY2Q5ZSI+DQogIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIwOzA7MTA7MTA7MTAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxIiBiZWdpbj0iMHMiPjwvYW5pbWF0ZT4NCiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIxNjsxNjsxNjs1MDs4NCIga2V5U3BsaW5lcz0iMCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDEiIGJlZ2luPSIwcyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+PGNpcmNsZSBjeD0iNDYuMzc4NCIgY3k9IjUwIiByPSIxMCIgZmlsbD0iIzM4OTc5OCI+DQogIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIwOzA7MTA7MTA7MTAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxIiBiZWdpbj0iLTAuMzYyMzE4ODQwNTc5NzEwMXMiPjwvYW5pbWF0ZT4NCiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIxNjsxNjsxNjs1MDs4NCIga2V5U3BsaW5lcz0iMCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDEiIGJlZ2luPSItMC4zNjIzMTg4NDA1Nzk3MTAxcyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+PGNpcmNsZSBjeD0iODAuMzc4NCIgY3k9IjUwIiByPSIxMCIgZmlsbD0iIzQ1OTQ0OCI+DQogIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIwOzA7MTA7MTA7MTAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxIiBiZWdpbj0iLTAuNzI0NjM3NjgxMTU5NDIwMnMiPjwvYW5pbWF0ZT4NCiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIxNjsxNjsxNjs1MDs4NCIga2V5U3BsaW5lcz0iMCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDEiIGJlZ2luPSItMC43MjQ2Mzc2ODExNTk0MjAycyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+PGNpcmNsZSBjeD0iMTYiIGN5PSI1MCIgcj0iMCIgZmlsbD0iI2YxOWQzYiI+DQogIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIwOzA7MTA7MTA7MTAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxIiBiZWdpbj0iLTEuMDg2OTU2NTIxNzM5MTMwNHMiPjwvYW5pbWF0ZT4NCiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIxNjsxNjsxNjs1MDs4NCIga2V5U3BsaW5lcz0iMCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDEiIGJlZ2luPSItMS4wODY5NTY1MjE3MzkxMzA0cyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+DQo8L3N2Zz4=';
================================================
FILE: lib/component/ReactVirtualizedScroll/utils.js
================================================
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useStateAndRef = void 0;
var react_1 = require("react");
exports.useStateAndRef = function (data) {
var _a = react_1.useState(data), data = _a[0], setData = _a[1];
var dataRef = react_1.useRef(data);
react_1.useEffect(function () {
dataRef.current = data;
}, [data]);
return [data, setData, dataRef];
};
================================================
FILE: lib/package.json
================================================
{
"name": "react-virtualized-scroll",
"version": "0.0.8",
"description": "虚拟滚动搭配上拉下滑加载的scroll组件",
"main": "./component/ReactVirtualizedScroll/index.js",
"author": "DavidWong ",
"license": "MIT",
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"react-virtualized": "^9.21.2"
}
}
================================================
FILE: package.json
================================================
{
"name": "react-virtualized-scroll",
"version": "0.0.9",
"description": "虚拟滚动搭配上拉下滑加载的scroll组件",
"main": "lib/index.js",
"homepage": "https://github.com/DavidWong9785/react-virtualized-scroll",
"scripts": {
"start": "webpack-dev-server --open development",
"build": "tsc",
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
"lint": "tslint -p tsconfig.json",
"test": "jest --config jestconfig.json --coverage"
},
"keywords": [
"typescript",
"虚拟滚动",
"scroll",
"react"
],
"author": "DavidWong ",
"repository": {
"type": "git",
"url": "git+https://github.com/DavidWong9785/react-virtualized-scroll.git"
},
"license": "MIT",
"dependencies": {
"@types/html-webpack-plugin": "^3.2.1",
"@types/node": "^12.6.8",
"@types/react": "^16.8.20",
"@types/react-dom": "^16.8.4",
"@types/react-router-dom": "^4.3.4",
"@types/react-virtualized": "^9.21.7",
"@types/webpack": "^4.32.1",
"media-show-card": "^1.0.3",
"pre-commit": "^1.2.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"react-virtualized": "^9.21.2",
"ts-node": "^8.3.0"
},
"devDependencies": {
"@types/enzyme": "^3.10.3",
"@types/jest": "^24.0.16",
"babel-jest": "^24.9.0",
"css-loader": "^3.1.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"file-loader": "^4.1.0",
"html-webpack-plugin": "^3.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^24.8.0",
"prettier": "^1.18.2",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.1",
"ts-jest": "^24.0.2",
"ts-loader": "^6.0.3",
"tslint": "^5.18.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.5.2",
"url-loader": "^2.1.0",
"webpack": "^4.34.0",
"webpack-cli": "^3.3.4",
"webpack-dev-server": "^3.7.2"
}
}
================================================
FILE: src/__tests__/demo.test.tsx
================================================
import React from 'react'
import { shallow, configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import ReactVirtualizedScroll from '../component/ReactVirtualizedScroll/index'
configure({ adapter: new Adapter() })
const setup = () => {
// 模拟 props
const props = {
height: '100vh',
row: () => {},
hasMore: true
}
// 通过 enzyme 提供的 shallow(浅渲染) 创建组件
const wrapper = shallow()
return {
props,
wrapper,
}
}
describe('测试height属性', () => {
const { wrapper, props } = setup()
it('props', () => {
expect(props.height).toEqual('100vh')
})
})
================================================
FILE: src/assets/iconfont.css
================================================
@font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1578637319034'); /* IE9 */
src: url('iconfont.eot?t=1578637319034#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAATYAAsAAAAACVwAAASMAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDSAqFRIRoATYCJAMYCw4ABCAFhG0HYhsoCFFUTppkf4U4RBYobL8q1RqS2WPnlCFBo7GKFHDAYghuPVikPTPGSvDG5xkqd+fb7TfWfzouIB7+f79v+1wblWaOScQTnliEhDdLM/2TaIXkoZHMmmfyGzjnonwDm65BowSjLG08nE1kzfkfpDGBft9vrrIERkSYRbmKUO0Rzpv+EqTpwcC6PI12AhiN9IK2NtffElXBPp0cfBcOAs798xgziFvb89w8Bzxk4ZqNpuRg+pv/wrmxywtwTVzLA0w86gIa6/PvCxzBcfCfO8BDunlbQhnWPJBEw7a29N878nqgwANVvaLX13vRdP7/905/6/gDa13Lcplj08IA44ACGmPbIiuQTpigpqLOyC4BXh0eJjBoHgM8t7/eU1fhjQrEvcAz1K1plREU+hWtZ2FZAIla+tPjNHiSvj/+ORzWJDXzJl69sXNo+XNYD+rkPcbw9jNDWUbGXoBC7PRaH2nk0b1gg9YvNJ1HhvVLys/hD/5sV8+mJvScYdj/yyNURZKJBtJhJl2MVX4Oh2phyQ6C2ye7DgaFX7tqiWU9yDy6su04YAjviL2AJ0il8Zw9Qt1PDu13ZTQHuseSdB/wVk3Qktl6ytKuH0SgiqMfxD6g973BG2xHUEQzq1UyZ4umsha6ZzKm9shpGK+WQe8YssH8DuyqW1qqirD/2ukZ090542X17dYJqd9G2u+m7g3T7ItPSqVt9H0MsHWDPjhS1Nxa0MvQ2DlhmnWlrSO6bSPkRdJK3Ql6u4RRAU3piFaDVhtdN23dPaPKG7C9SgYm8+URXWQQXlOr1yCfFVuhdYgu7IMm/qTwREjDp7bq8VDb+oqV2Mod5Psb/uAdaHYQsKIbEC6epTtzuXeVjfObxOyVJS036DgL07KOgv/I/0uIDogNIZf4pA0+O4Mh4jEbQr4p4BW5NarmYeDj4xN54e3f5zaCAs/o3GzZsNUf2cPr6bn7fWa5y4A18BKxubb53a/jQ0ku1scaTQjw1C2ge4x/+uZ3fQLascKZ5+wGl8dJD1BaTn1cQEr57y0PlNXkq+VOkNMAG1c2k8WagH18TBWQ26vD8d0AsAOgm1HCVanqhC82cdybFQ6HTPKie2qCI/9Nn/FjV76jU/5/L9Yu4POJ2lB38zyrTvSXkJnN++9UlBVR7qgC1rVi1gllz9dlaU5rcThh0HfSwTnpu5d8ZnY+od9MCUmfOcj6reIKaS9UQ45C0+8kDNpDXz5kSqohygC2lQLCuAmSUa8hG3fGFdIHqll/aMYDhkHXgtxwyHqM92WBUThs/3Nks9QNDNrPCm/wqthIVZCSHyFlEGBxPG0mDkghe0zJfrx5ri77kiWsieMQxxnHkoWwOg5V8wU/cfueNHayhPrOEmAocDDbPxaxMik3bq76rdffgKcSM2SkrsX8CEQpaJ+JOMIM3EGazqp7l+6ZH565nHJ5mE9kEkyDGWI/mmFxf78QWGosXBHOLfDUyJ8rHZ8vT17lFhjkPbJFihwlarTo03ffr8xfkA4dHzYaOYHYGO0UcWac3rN0GeUote8Hsars59IQAQ==') format('woff2'),
url('iconfont.woff?t=1578637319034') format('woff'),
url('iconfont.ttf?t=1578637319034') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('iconfont.svg?t=1578637319034#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconshuaxin:before {
content: "\e60c";
}
.iconcheck-circle:before {
content: "\e77d";
}
.iconreload:before {
content: "\e788";
}
.iconicon-test:before {
content: "\e658";
}
.iconfiltration:before {
content: "\e630";
}
================================================
FILE: src/assets/iconfont.js
================================================
!function(l){var e,c='',t=(e=document.getElementsByTagName("script"))[e.length-1].getAttribute("data-injectcss");if(t&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(e){console&&console.log(e)}}!function(e){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(e,0);else{var t=function(){document.removeEventListener("DOMContentLoaded",t,!1),e()};document.addEventListener("DOMContentLoaded",t,!1)}else document.attachEvent&&(o=e,i=l.document,a=!1,(c=function(){try{i.documentElement.doScroll("left")}catch(e){return void setTimeout(c,50)}n()})(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,n())});function n(){a||(a=!0,o())}var o,i,a,c}(function(){var e,t,n,o,i,a;(e=document.createElement("div")).innerHTML=c,c=null,(t=e.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",n=t,(o=document.body).firstChild?(i=n,(a=o.firstChild).parentNode.insertBefore(i,a)):o.appendChild(n))})}(window);
================================================
FILE: src/component/ReactVirtualizedScroll/ReactVirtualizedScroll.tsx
================================================
import React, { useState, useMemo, TouchEvent, useRef, useCallback, forwardRef, useImperativeHandle, useEffect } from 'react';
import { List as VList, AutoSizer, CellMeasurer, CellMeasurerCache } from 'react-virtualized';
import { loading_pullup } from './svg';
import '../../assets/iconfont.css'
import './style.css'
interface ILogo {
pulldown_loading?: JSX.Element;
pulldown_success?: JSX.Element;
pullup_loading?: JSX.Element;
pullup_success?: JSX.Element;
}
interface IVirtualized {
children? : JSX.Element;
hasMore : boolean;
refreshDistance? : number;
loading? : boolean;
data? : any;
info? : any;
height? : string;
width? : string;
noDataRow? : JSX.Element;
logo? : ILogo;
onPullUp? : any;
onPullDown? : any;
onScroll?: any;
row({index, info}: {index: number, info: any, data: any}): any;
}
const Row = ({ row, data, index, info }:any) => {
return useMemo(() => {
return row({ data: data, index, info })
}, [JSON.stringify(data[index])])
}
const VirtualizedScroll = ({
children,
hasMore = true,
refreshDistance = 120,
loading = false,
data = [],
info,
height = '100vh',
width = '100vw',
noDataRow,
onPullDown = null,
onPullUp = null,
onScroll = null,
row,
logo = {}
}: IVirtualized, ref: any) => {
// 下拉状态
const [STATS, setSTATS] = useState('init')
// touch数据
const [startY, setStartY] = useState(0)
const [offset, setOffset] = useState(0)
// vList容器
const vListRef = useRef()
const timer = useRef()
// 上滑时,scrollTop为0禁止滑动
const scrollingDisable = useRef(false)
const hasMoreRef = useRef(true)
const canNotDrag = useRef(false)
const isPullUpLoading = useRef(false)
useEffect(() => {
hasMoreRef.current = hasMore
}, [hasMore])
useEffect(() => {
canNotDrag.current = STATS === 'refreshing' || STATS === 'success' || STATS == 'scroll' || !onPullDown
}, [STATS])
const initState = () => {
setOffset(0)
setStartY(0)
}
const onTouchStart = ($event: TouchEvent) => {
if (canNotDrag.current || scrollingDisable.current) return
setStartY($event.nativeEvent.touches[0].pageY)
}
const onTouchMove = useCallback(
($event: TouchEvent) => {
if (canNotDrag.current || scrollingDisable.current) return
const offsetComputed = $event.nativeEvent.touches[0].pageY - startY
if (0 < offsetComputed && offsetComputed <= refreshDistance) setOffset(offsetComputed)
else if (offsetComputed < 0) setOffset(0)
else if (offsetComputed > refreshDistance) setOffset(refreshDistance)
if (offset < refreshDistance && STATS !== 'dragging') setSTATS('dragging')
else if (offset >= refreshDistance) setSTATS('pre-refresh')
},
[startY, canNotDrag, offset, STATS],
)
const onTouchEnd = useCallback(
($event: TouchEvent) => {
if (canNotDrag.current) return
if (scrollingDisable.current) {
initState()
return
}
if (STATS === 'pre-refresh') {
setSTATS('refreshing')
if (!onPullDown) return
onPullDown().then(() => {
setSTATS('success')
if (vListRef.current) vListRef.current.scrollToRow(0)
if (timer.current) clearTimeout(timer.current)
timer.current = setTimeout(() => {
setSTATS('init')
initState()
}, 500)
})
} else {
initState()
setSTATS('not-enough')
if (timer.current) clearTimeout(timer.current)
timer.current = setTimeout(() => {
setSTATS('init')
}, 500)
}
},
[timer, STATS, canNotDrag],
)
const onScrollCallback = ({ clientHeight, scrollTop, scrollHeight }: any) => {
if (scrollTop === 0) scrollingDisable.current = false
else scrollingDisable.current = true
if ((clientHeight + scrollTop === scrollHeight) && onPullUp && !isPullUpLoading.current) {
isPullUpLoading.current = true
onPullUp().then(() => {
isPullUpLoading.current = false
if (vListRef.current) vListRef.current.scrollToRow(data.length + 1)
}).catch(() => {
isPullUpLoading.current = false
})
}
if (onScroll) onScroll(clientHeight, scrollTop, scrollHeight)
}
const cache = new CellMeasurerCache({ defaultHeight: 400,fixedWidth: true});
useImperativeHandle(
ref,
() => ({
vListRef: vListRef.current
}),
[vListRef],
)
const renderRow = useCallback(
({ index, key, parent, style }: any) => {
return (
{
|
}
{
index === data.length - 1 ? !hasMoreRef.current ?
(
{ logo.pullup_success ? logo.pullup_success : '没有更多了' }
) :
(
{ logo.pullup_loading ? logo.pullup_loading :

}
) : ''
}
)
}, [data, hasMoreRef]
)
const VirtualScroll = useMemo(() => {
return (
{ children }
{
!data.length ? noDataRow ? noDataRow : '' :
(
{
({ height, width }: any) => (
vListRef.current = ref}
onScroll={onScrollCallback}
height={height}
width={width}
rowHeight={cache.rowHeight}
rowCount={data.length}
rowRenderer={renderRow}
/>
)
}
)
}
)
}, [data])
return (
);
};
export default forwardRef(VirtualizedScroll);
================================================
FILE: src/component/ReactVirtualizedScroll/index.tsx
================================================
import ReactVirtualizedScroll from './ReactVirtualizedScroll'
export default ReactVirtualizedScroll
================================================
FILE: src/component/ReactVirtualizedScroll/style.css
================================================
*{
margin: 0;
padding: 0;
}
#virtualized-scroll-panel, .list-box{
width: 100%;
height: 100%;
}
#virtualized-scroll-panel .list-box{
transition: all .3s;
position: absolute;
}
#virtualized-scroll-panel{
position: relative;
background-color: #f5f5f9;
/* overflow: hidden; */
}
#virtualized-scroll-panel .pull-down-box{
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
#virtualized-scroll-panel .pull-down-box .pull-down-round .iconcheck-circle{
font-size: 28px;
color: #43cd80;
}
#virtualized-scroll-panel .pull-down-box .pull-down-round .iconshuaxin{
font-size: 28px;
color: #ff8080;
}
#virtualized-scroll-panel .pull-down-box .pull-down-round{
width: 40px;
height: 40px;
background: #fff;
box-shadow: 1px 1px 5px rgba(0,0,0,0.5);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes self_rotate{
0%{ transform: rotate(0deg); }
100%{ transform: rotate(360deg); }
}
#virtualized-scroll-panel .card-content .load-item{
height: 50px;
text-align: center;
line-height: 50px;
background-color: #f5f5f9;
width: 100%;
}
================================================
FILE: src/component/ReactVirtualizedScroll/svg.ts
================================================
export const loading_pullup = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiAjZjVmNWY5OyBkaXNwbGF5OiBibG9jazsiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjUwcHgiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+DQo8Y2lyY2xlIGN4PSI4NCIgY3k9IjUwIiByPSIxLjA2NTE3IiBmaWxsPSIjZmFjZDllIj4NCiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJyIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgZHVyPSIwLjM2MjMxODg0MDU3OTcxMDFzIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlUaW1lcz0iMDsxIiB2YWx1ZXM9IjEwOzAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxIiBiZWdpbj0iMHMiPjwvYW5pbWF0ZT4NCiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJmaWxsIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgZHVyPSIxLjQ0OTI3NTM2MjMxODg0MDRzIiBjYWxjTW9kZT0iZGlzY3JldGUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIjZmFjZDllOyNmMTlkM2I7IzQ1OTQ0ODsjMzg5Nzk4OyNmYWNkOWUiIGJlZ2luPSIwcyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+PGNpcmNsZSBjeD0iMTYiIGN5PSI1MCIgcj0iOC45MzQ4MyIgZmlsbD0iI2ZhY2Q5ZSI+DQogIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIwOzA7MTA7MTA7MTAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxIiBiZWdpbj0iMHMiPjwvYW5pbWF0ZT4NCiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIxNjsxNjsxNjs1MDs4NCIga2V5U3BsaW5lcz0iMCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDEiIGJlZ2luPSIwcyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+PGNpcmNsZSBjeD0iNDYuMzc4NCIgY3k9IjUwIiByPSIxMCIgZmlsbD0iIzM4OTc5OCI+DQogIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIwOzA7MTA7MTA7MTAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxIiBiZWdpbj0iLTAuMzYyMzE4ODQwNTc5NzEwMXMiPjwvYW5pbWF0ZT4NCiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIxNjsxNjsxNjs1MDs4NCIga2V5U3BsaW5lcz0iMCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDEiIGJlZ2luPSItMC4zNjIzMTg4NDA1Nzk3MTAxcyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+PGNpcmNsZSBjeD0iODAuMzc4NCIgY3k9IjUwIiByPSIxMCIgZmlsbD0iIzQ1OTQ0OCI+DQogIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIwOzA7MTA7MTA7MTAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxIiBiZWdpbj0iLTAuNzI0NjM3NjgxMTU5NDIwMnMiPjwvYW5pbWF0ZT4NCiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIxNjsxNjsxNjs1MDs4NCIga2V5U3BsaW5lcz0iMCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDEiIGJlZ2luPSItMC43MjQ2Mzc2ODExNTk0MjAycyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+PGNpcmNsZSBjeD0iMTYiIGN5PSI1MCIgcj0iMCIgZmlsbD0iI2YxOWQzYiI+DQogIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIwOzA7MTA7MTA7MTAiIGtleVNwbGluZXM9IjAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxOzAgMC41IDAuNSAxIiBiZWdpbj0iLTEuMDg2OTU2NTIxNzM5MTMwNHMiPjwvYW5pbWF0ZT4NCiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjEuNDQ5Mjc1MzYyMzE4ODQwNHMiIGNhbGNNb2RlPSJzcGxpbmUiIGtleVRpbWVzPSIwOzAuMjU7MC41OzAuNzU7MSIgdmFsdWVzPSIxNjsxNjsxNjs1MDs4NCIga2V5U3BsaW5lcz0iMCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDE7MCAwLjUgMC41IDEiIGJlZ2luPSItMS4wODY5NTY1MjE3MzkxMzA0cyI+PC9hbmltYXRlPg0KPC9jaXJjbGU+DQo8L3N2Zz4='
================================================
FILE: src/types/demo/index.d.ts
================================================
interface IDemo {
age: number
}
================================================
FILE: src/types/global.d.ts
================================================
declare module '*.png' {
const value: string
export = value
}
declare module '*.mp4' {
const content: string
export = content
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"outDir": "./lib",
// "sourceMap": true,
"noImplicitAny": false, //在表达式和声明上有隐含的 any类型时报错。
"target": "es5", //指定ECMAScript目标版本 "ES3"(默认)
"lib": ["dom", "dom.iterable", "esnext"], // 编译过程中需要引入的库文件的列表
"allowJs": true, //允许编译javascript文件。
"skipLibCheck": true, //忽略所有的声明文件( *.d.ts)的类型检查
"esModuleInterop": true, //允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查
"allowSyntheticDefaultImports": true, // 同上
"strict": true, //启用所有严格类型检查选项
"forceConsistentCasingInFileNames": true, //禁止对同一个文件的不一致的引用
"module": "commonjs", //指定生成哪个模块系统代码
"moduleResolution": "node", //决定如何处理模块。
"resolveJsonModule": true,
"isolatedModules": false, //将每个文件作为单独的模块(与“ts.transpileModule”类似)。
// "noEmit": true,//不生成输出文件
"jsx": "react",
"baseUrl": "./",
"paths": { "*": ["types/*"] }
// "suppressImplicitAnyIndexErrors": true, //阻止对缺少索引签名的索引对象报错
},
"exclude": ["node_modules", "./lib", "./example"],
"include": ["src/**/*"]
}
================================================
FILE: tslint.json
================================================
{
"extends": ["tslint:recommended", "tslint-config-prettier"],
"rules": {
"no-console": false,
"object-literal-sort-keys": false,
"member-access": false,
"ordered-imports": false
},
"linterOptions": {
"exclude": ["**/*.json", "node_modules", "./lib", "./example"]
}
}
================================================
FILE: webpack.config.js
================================================
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const htmlWebpackPlugin = new HtmlWebpackPlugin({
template: path.join(__dirname, './example/src/index.html'),
filename: './index.html',
})
module.exports = {
entry: path.join(__dirname, './example/src/index.tsx'),
output: {
path: path.join(__dirname, 'example/dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.tsx?/,
loader: 'ts-loader',
},
{
// pre/nomal/post - loader的执行顺序 - 前/中/后
enforce: 'pre',
test: /\.tsx?/,
loader: 'source-map-loader',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(gif|jpg|png|woff|svg|eot|ttf|otf|woff2)$/,
use: {
loader: 'url-loader',
options: {
limit: 20,
},
},
},
],
},
//映射工具
// devtool: 'source-map',
//处理路径解析
resolve: {
//extensions 拓展名
extensions: ['.tsx', '.ts', '.js', '.jsx', '.json'],
},
plugins: [htmlWebpackPlugin],
devServer: {
port: 3005,
},
}