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组件 [![github](https://img.shields.io/github/package-json/v/DavidWong9785/react-virtualized-scroll?logo=github)](https://github.com/DavidWong9785/react-virtualized-scroll) [![issues](https://img.shields.io/github/issues/DavidWong9785/react-virtualized-scroll)](https://github.com/DavidWong9785/react-virtualized-scroll/issues) [![forks](https://img.shields.io/github/forks/DavidWong9785/react-virtualized-scroll)](https://github.com/DavidWong9785/react-virtualized-scroll) [![stars](https://img.shields.io/github/stars/DavidWong9785/react-virtualized-scroll)](https://github.com/DavidWong9785/react-virtualized-scroll)

[![npm](https://img.shields.io/npm/v/react-virtualized-scroll?label=versoin&logo=npm)](https://www.npmjs.com/package/react-virtualized-scroll) [![downloads](https://img.shields.io/npm/dm/react-virtualized-scroll?logo=npm)](https://www.npmjs.com/package/react-virtualized-scroll) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-virtualized-scroll)](https://www.npmjs.com/package/react-virtualized-scroll) ![license](https://img.shields.io/github/license/DavidWong9785/react-virtualized-scroll) ![github last commit](https://img.shields.io/github/last-commit/DavidWong9785/react-virtualized-scroll) ![avatar](./test.gif) ### 简介 - 搭配 typescriptreact-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组件 [![github](https://img.shields.io/github/package-json/v/DavidWong9785/react-virtualized-scroll?logo=github)](https://github.com/DavidWong9785/react-virtualized-scroll) [![issues](https://img.shields.io/github/issues/DavidWong9785/react-virtualized-scroll)](https://github.com/DavidWong9785/react-virtualized-scroll/issues) [![forks](https://img.shields.io/github/forks/DavidWong9785/react-virtualized-scroll)](https://github.com/DavidWong9785/react-virtualized-scroll) [![stars](https://img.shields.io/github/stars/DavidWong9785/react-virtualized-scroll)](https://github.com/DavidWong9785/react-virtualized-scroll)

[![npm](https://img.shields.io/npm/v/react-virtualized-scroll?label=versoin&logo=npm)](https://www.npmjs.com/package/react-virtualized-scroll) [![downloads](https://img.shields.io/npm/dm/react-virtualized-scroll?logo=npm)](https://www.npmjs.com/package/react-virtualized-scroll) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-virtualized-scroll)](https://www.npmjs.com/package/react-virtualized-scroll) ![license](https://img.shields.io/github/license/DavidWong9785/react-virtualized-scroll) ![github last commit](https://img.shields.io/github/last-commit/DavidWong9785/react-virtualized-scroll) ![avatar](./test.gif) ### 简介 - 搭配 typescriptreact-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 (
{ STATS === 'success' || STATS === 'init' ? logo.pulldown_success ? logo.pulldown_success : : logo.pulldown_loading ? logo.pulldown_loading : }
{ VirtualScroll }
); }; 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, }, }