From 565d530d85a659798d50106ac89cb9dff54a6147 Mon Sep 17 00:00:00 2001 From: realcwang Date: Fri, 16 Aug 2024 10:37:37 +0800 Subject: [PATCH 1/3] feat(list): add list component re #394 --- site/mobile/components/style/index.less | 2 + site/mobile/mobile.config.js | 5 ++ site/web/site.config.js | 12 +-- src/index.ts | 1 + src/list/_example/base.jsx | 59 ++++++++++++++ src/list/_example/err-tip.jsx | 56 +++++++++++++ src/list/_example/index.jsx | 53 ++++++++++++ src/list/_example/pull-refresh.jsx | 70 ++++++++++++++++ src/list/_example/style/index.less | 30 +++++++ src/list/index.ts | 8 ++ src/list/list.en-US.md | 13 +++ src/list/list.md | 13 +++ src/list/list.tsx | 104 ++++++++++++++++++++++++ src/list/style/css.js | 1 + src/list/style/index.js | 1 + src/list/type.ts | 34 ++++++++ 16 files changed, 456 insertions(+), 6 deletions(-) create mode 100644 src/list/_example/base.jsx create mode 100644 src/list/_example/err-tip.jsx create mode 100644 src/list/_example/index.jsx create mode 100644 src/list/_example/pull-refresh.jsx create mode 100644 src/list/_example/style/index.less create mode 100644 src/list/index.ts create mode 100644 src/list/list.en-US.md create mode 100644 src/list/list.md create mode 100644 src/list/list.tsx create mode 100644 src/list/style/css.js create mode 100644 src/list/style/index.js create mode 100644 src/list/type.ts diff --git a/site/mobile/components/style/index.less b/site/mobile/components/style/index.less index 10e578a0..fbe4680f 100644 --- a/site/mobile/components/style/index.less +++ b/site/mobile/components/style/index.less @@ -6,10 +6,12 @@ #app { min-height: 100vh; + height: 100vh; display: flex; flex-direction: column; } .tdesign-mobile-demo { flex: 1; + overflow-y: scroll; } \ No newline at end of file diff --git a/site/mobile/mobile.config.js b/site/mobile/mobile.config.js index 4bb613b7..1ff08d71 100644 --- a/site/mobile/mobile.config.js +++ b/site/mobile/mobile.config.js @@ -30,6 +30,11 @@ export default { name: 'grid', component: () => import('tdesign-mobile-react/grid/_example/base.jsx'), }, + { + title: 'List 列表', + name: 'list', + component: () => import('tdesign-mobile-react/list/_example/index.jsx'), + }, { title: 'Image 图片', name: 'image', diff --git a/site/web/site.config.js b/site/web/site.config.js index 928e5823..bafc2379 100644 --- a/site/web/site.config.js +++ b/site/web/site.config.js @@ -239,12 +239,12 @@ export default { path: '/mobile-react/components/image', component: () => import('tdesign-mobile-react/image/image.md'), }, - // { - // title: 'List 列表', - // name: 'list', - // path: '/mobile-react/components/list', - // component: () => import('tdesign-mobile-react/list/list.md'), - // }, + { + title: 'List 列表', + name: 'list', + path: '/mobile-react/components/list', + component: () => import('tdesign-mobile-react/list/list.md'), + }, // { // title: 'ImageViewer 图片预览', // name: 'image-viewer', diff --git a/src/index.ts b/src/index.ts index 8688946d..6d5fb20d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,6 +45,7 @@ export * from './sticky'; export * from './swiper'; export * from './swipe-cell'; export * from './tag'; +export * from './list'; /** * 消息提醒(7个) diff --git a/src/list/_example/base.jsx b/src/list/_example/base.jsx new file mode 100644 index 00000000..ef69d28b --- /dev/null +++ b/src/list/_example/base.jsx @@ -0,0 +1,59 @@ +import React, { useEffect, useState, useRef} from 'react'; +import './style/index.less'; +import { Cell, List } from 'tdesign-mobile-react'; + +export default function ListDemo() { + const [isLoading, setIsLoading] = useState(false); + const pageSize = 20; + const stateRef = useRef([]); + const pageRef = useRef(1); + const dataSource = []; + const total = 100; + for (let i = 0; i < total; i++) { + dataSource.push({ + id: i, + content: '列表内容列表内容列表内容', + icon: 'https://tdesign.gtimg.com/list-icon.png', + title: '列表主内容', + }); + } + + // 模拟请求 + const fetchData = async (pageInfo) => { + if (isLoading) return; + setIsLoading(true); + try { + setTimeout(() => { + const { pageNum, pageSize } = pageInfo; + const newDataSource = dataSource.slice((pageNum - 1) * pageSize, pageNum * pageSize); + const newListData = stateRef.current.concat(newDataSource); + pageRef.current = pageNum + stateRef.current = newListData + setIsLoading(false); + }, 0); + } catch (err) { + stateRef.current = [] + } + }; + + const onScroll = (scrollBottom) => { + if (!scrollBottom && stateRef.current.length < total) { + fetchData({ pageNum: pageRef.current + 1, pageSize }); + } + } + + useEffect(() => { + fetchData({ pageNum: pageRef.current, pageSize }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + { + stateRef.current.map((item) => + {item.id} + ) + } + + ); +} diff --git a/src/list/_example/err-tip.jsx b/src/list/_example/err-tip.jsx new file mode 100644 index 00000000..f9b65453 --- /dev/null +++ b/src/list/_example/err-tip.jsx @@ -0,0 +1,56 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Cell, List, Loading } from 'tdesign-mobile-react'; + +export default function ListDemo() { + const listError = useRef([]) + const [loading, setLoading] = useState('') + const [showError, setShowError] = useState(false) + + const onLoadError = () => { + setLoading('loading') + + setTimeout(() => { + const newVal = [...listError.current] + for (let i = listError.current.length; i < 8; i++) { + newVal.push(`${i}`); + } + listError.current = newVal; + + setShowError(true) + setLoading('') + }, 1000); + }; + + const onLoadMore = () => { + setShowError(false) + if (listError.current.length >= 60 || loading) { + return; + } + setLoading('loading') + + setTimeout(() => { + for (let i = 0; i < 15; i++) { + listError.current.push(`${listError.current.length + 1}`) + } + setLoading('') + }, 1000); + }; + + useEffect(()=>{ + onLoadError() + }, []); + + return ( + +
请求失败,点击重新加载
+ + }> + { + listError.current.map((item) => + {item} + ) + } +
+ ); +} diff --git a/src/list/_example/index.jsx b/src/list/_example/index.jsx new file mode 100644 index 00000000..e2e11111 --- /dev/null +++ b/src/list/_example/index.jsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { Button } from 'tdesign-mobile-react'; +import TDemoBlock from '../../../site/mobile/components/DemoBlock'; +// import TDemoHeader from '../../../site/mobile/components/DemoHeader'; +import './style/index.less' + +import BaseList from './base.jsx'; +import ErrTipDemo from './err-tip.jsx'; +import PullRefreshDemo from './pull-refresh.jsx'; + + +export default function ListDemo() { + + const [currentTab, setCurrentTab] = useState('info') + + const onChangeTab = (val) => { + setCurrentTab(val); + history.pushState({}, '', '?tab=demo'); + }; + + return ( +
+
+ { currentTab === 'info' &&
+

List 列表

+

+ 瀑布流滚动加载,用于展示同一类型信息的长列表。当列表即将滚动到底部时,会触发事件并加载更多列表项。 +

+ + + + + +
} + { + currentTab === 'base' && + } + { + currentTab === 'error-tip' && + } + { + currentTab === 'pull-refresh' &&
+ +
+ } +
+
+ ); +} diff --git a/src/list/_example/pull-refresh.jsx b/src/list/_example/pull-refresh.jsx new file mode 100644 index 00000000..94846129 --- /dev/null +++ b/src/list/_example/pull-refresh.jsx @@ -0,0 +1,70 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Cell, List, PullDownRefresh } from 'tdesign-mobile-react'; + +export default function ListDemo() { + const [loading, setLoading] = useState('') + const [refreshing, setRefreshing] = useState(false) + + const listData = useRef([]) + + const MAX_DATA_LEN = 60; + + const loadData = (isRefresh) => { + const ONCE_LOAD_NUM = 20; + return new Promise((resolve) => { + setTimeout(() => { + const temp = []; + for (let i = 0; i < ONCE_LOAD_NUM; i++) { + if (isRefresh) { + temp.push(`${i + 1}`); + } else { + temp.push(`${listData.current.length + 1 + i}`); + } + } + + if (isRefresh) { + listData.current = temp + } else { + listData.current= [...listData.current, ...temp ] + } + setLoading(''); + setRefreshing(false); + }, 1000); + }); + }; + + const onLoadData = (isRefresh) => { + if ((listData.current.length >= MAX_DATA_LEN && !isRefresh) || loading.value) { + return; + } + setLoading('loading'); + loadData(isRefresh) + }; + + const onScroll = (scrollBottom) => { + if (scrollBottom < 50) { + onLoadData(); + } + }; + + const onRefresh = () => { + setRefreshing(true); + onLoadData(true); + }; + + useEffect(()=>{ + onLoadData(); + }, []); + + return ( + setRefreshing(val)} onRefresh={onRefresh}> + + { + listData.current.map((item) => + {item} + ) + } + + + ); +} diff --git a/src/list/_example/style/index.less b/src/list/_example/style/index.less new file mode 100644 index 00000000..e1a5a2b8 --- /dev/null +++ b/src/list/_example/style/index.less @@ -0,0 +1,30 @@ +.list-demo { + .t-list { + .cell { + width: 100%; + text-align: center; + } + .error { + text-align: center; + color: #969799; + font-size: 14px; + margin-top: 8px; + } + } + .custom-error { + font-size: 14px; + color: #969799; + text-align: center; + padding-top: 16px; + cursor: default; + + span { + color: #0052d9; + cursor: pointer; + } + } + .t-button { + margin: 0 16px 16px 16px; + width: calc(100% - 32px); + } +} \ No newline at end of file diff --git a/src/list/index.ts b/src/list/index.ts new file mode 100644 index 00000000..26d5e2a0 --- /dev/null +++ b/src/list/index.ts @@ -0,0 +1,8 @@ +import _List from './list'; + +import './style/index.js'; + +export * from './type'; + +export const List = _List; +export default List; diff --git a/src/list/list.en-US.md b/src/list/list.en-US.md new file mode 100644 index 00000000..ca82cafa --- /dev/null +++ b/src/list/list.en-US.md @@ -0,0 +1,13 @@ +:: BASE_DOC :: + +## API + +### List Props + +name | type | default | description | required +-- | -- | -- | -- | -- +asyncLoading | TNode / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N +footer | TNode / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N +header | TNode / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N +onLoadMore | Function | | Typescript:`() => void`
| N +onScroll | Function | | Typescript:`(bottomDistance: number, scrollTop: number) => void`
| N \ No newline at end of file diff --git a/src/list/list.md b/src/list/list.md new file mode 100644 index 00000000..62ed0905 --- /dev/null +++ b/src/list/list.md @@ -0,0 +1,13 @@ +:: BASE_DOC :: + +## API + +### List Props + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +asyncLoading | String / TNode | - | 自定义加载中。值为空不显示加载中,值为 'loading' 显示加载中状态,值为 'load-more' 显示加载更多状态。值类型为函数,则表示自定义加载状态呈现内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N +footer | String / TNode | - | 底部。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N +header | String / TNode | - | 头部。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N +onLoadMore | Function | | TS 类型:`() => void`
点击加载更多时触发 | N +onScroll | Function | | TS 类型:`(bottomDistance: number, scrollTop: number) => void`
列表滚动时触发,bottomDistance 表示底部距离;scrollTop 表示顶部滚动距离 | N diff --git a/src/list/list.tsx b/src/list/list.tsx new file mode 100644 index 00000000..dbc52de9 --- /dev/null +++ b/src/list/list.tsx @@ -0,0 +1,104 @@ + + +import React, { useState, useRef, useEffect, useCallback } from 'react'; +import { TdListProps } from './type'; +import useConfig from '../_util/useConfig'; + +import TLoading from '../loading'; + +export interface ListProps extends TdListProps { + required?: boolean; + readonly?: boolean; +} + +function isElement(node: Element) { + const ELEMENT_NODE_TYPE = 1; + return node.tagName !== 'HTML' && node.tagName !== 'BODY' && node.nodeType === ELEMENT_NODE_TYPE; +} + +const overflowScrollReg = /scroll|auto/i; + +function getScrollParent(el: Element, root = window ) { + let node = el; + + while (node && node !== root && isElement(node)) { + const { overflowY } = window.getComputedStyle(node); + if (overflowScrollReg.test(overflowY)) { + return node; + } + node = node.parentNode as Element; + } + + return root; +} + +const List: React.FC = (props: ListProps) => { + const { classPrefix } = useConfig(); + const { header, footer, children} = props; + const name = classPrefix; + + const LOADING_TEXT_MAP = { + loading: '加载中', // TODO: i18n + 'load-more': '加载更多', + }; + + const root = useRef(null); + + const useWindowHeight = ()=>{ + const [height, setHeight] = useState(window.innerHeight); + window.onresize = ()=>{ + const height = window.innerHeight + setHeight(height) + } + return height + } + const height = useWindowHeight(); + + const onLoadMore = () => { + if (props.asyncLoading === 'load-more') { + props.onLoadMore?.(); + } + }; + + const handleScroll = useCallback((e: WheelEvent | Event):void => { + const scrollHeight = + (e.target as HTMLElement).scrollHeight || + Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); + + const scrollTop = + (e.target as HTMLElement).scrollTop || document.documentElement.scrollTop || document.body.scrollTop; + + const offsetHeight = (e.target as HTMLElement).offsetHeight || height; + const bottomDistance = scrollHeight - (scrollTop + offsetHeight) + props.onScroll?.(bottomDistance, scrollTop); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const scorllParent = getScrollParent(root.current) + + scorllParent.addEventListener('scroll', handleScroll); + return () => { + removeEventListener('scroll', handleScroll) + } + }, [height, handleScroll]) + + return ( +
handleScroll(e)}> + {header} + {children} +
onLoadMore()}> + {typeof props.asyncLoading === 'string' && ['loading', 'load-more'].includes(props.asyncLoading) && ( + + )} +
+ {footer} +
+ ); +}; + +export default List; diff --git a/src/list/style/css.js b/src/list/style/css.js new file mode 100644 index 00000000..6a9a4b13 --- /dev/null +++ b/src/list/style/css.js @@ -0,0 +1 @@ +import './index.css'; diff --git a/src/list/style/index.js b/src/list/style/index.js new file mode 100644 index 00000000..fdbefc78 --- /dev/null +++ b/src/list/style/index.js @@ -0,0 +1 @@ +import '../../_common/style/mobile/components/list/_index.less'; diff --git a/src/list/type.ts b/src/list/type.ts new file mode 100644 index 00000000..52be6029 --- /dev/null +++ b/src/list/type.ts @@ -0,0 +1,34 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TNode } from '../common'; + +export interface TdListProps { + /** + * 自定义加载中。值为空不显示加载中,值为 'loading' 显示加载中状态,值为 'load-more' 显示加载更多状态。值类型为函数,则表示自定义加载状态呈现内容 + */ + asyncLoading?: string | TNode; + /** + * 底部 + */ + footer?: string | TNode; + /** + * 子元素 + */ + children?: string | TNode; + /** + * 头部 + */ + header?: string | TNode; + /** + * 点击加载更多时触发 + */ + onLoadMore?: () => void; + /** + * 列表滚动时触发,bottomDistance 表示底部距离;scrollTop 表示顶部滚动距离 + */ + onScroll?: (bottomDistance: number, scrollTop: number) => void; +} From 40d71801916131f9e80741460e1e03f499dd5e6e Mon Sep 17 00:00:00 2001 From: feaswcy <708853816@qq.com> Date: Mon, 26 Aug 2024 17:37:34 +0800 Subject: [PATCH 2/3] feat(list): fix list review qa fix #463 --- src/list/_example/{base.jsx => base.tsx} | 29 +++++---- src/list/_example/err-tip.jsx | 56 ---------------- src/list/_example/err-tip.tsx | 64 +++++++++++++++++++ src/list/_example/index.jsx | 53 --------------- src/list/_example/index.tsx | 52 +++++++++++++++ .../{pull-refresh.jsx => pull-refresh.tsx} | 33 +++++----- src/list/list.tsx | 51 ++++++++------- 7 files changed, 176 insertions(+), 162 deletions(-) rename src/list/_example/{base.jsx => base.tsx} (74%) delete mode 100644 src/list/_example/err-tip.jsx create mode 100644 src/list/_example/err-tip.tsx delete mode 100644 src/list/_example/index.jsx create mode 100644 src/list/_example/index.tsx rename src/list/_example/{pull-refresh.jsx => pull-refresh.tsx} (60%) diff --git a/src/list/_example/base.jsx b/src/list/_example/base.tsx similarity index 74% rename from src/list/_example/base.jsx rename to src/list/_example/base.tsx index ef69d28b..30f3cec9 100644 --- a/src/list/_example/base.jsx +++ b/src/list/_example/base.tsx @@ -1,13 +1,20 @@ -import React, { useEffect, useState, useRef} from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import './style/index.less'; import { Cell, List } from 'tdesign-mobile-react'; +interface ListItem { + id: number; + content: string; + icon: string; + title: string; +} + export default function ListDemo() { const [isLoading, setIsLoading] = useState(false); const pageSize = 20; const stateRef = useRef([]); const pageRef = useRef(1); - const dataSource = []; + const dataSource: ListItem[] = []; const total = 100; for (let i = 0; i < total; i++) { dataSource.push({ @@ -27,12 +34,12 @@ export default function ListDemo() { const { pageNum, pageSize } = pageInfo; const newDataSource = dataSource.slice((pageNum - 1) * pageSize, pageNum * pageSize); const newListData = stateRef.current.concat(newDataSource); - pageRef.current = pageNum - stateRef.current = newListData + pageRef.current = pageNum; + stateRef.current = newListData; setIsLoading(false); }, 0); } catch (err) { - stateRef.current = [] + stateRef.current = []; } }; @@ -40,7 +47,7 @@ export default function ListDemo() { if (!scrollBottom && stateRef.current.length < total) { fetchData({ pageNum: pageRef.current + 1, pageSize }); } - } + }; useEffect(() => { fetchData({ pageNum: pageRef.current, pageSize }); @@ -49,11 +56,11 @@ export default function ListDemo() { return ( - { - stateRef.current.map((item) => - {item.id} - ) - } + {stateRef.current.map((item) => ( + + {item.id} + + ))} ); } diff --git a/src/list/_example/err-tip.jsx b/src/list/_example/err-tip.jsx deleted file mode 100644 index f9b65453..00000000 --- a/src/list/_example/err-tip.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { Cell, List, Loading } from 'tdesign-mobile-react'; - -export default function ListDemo() { - const listError = useRef([]) - const [loading, setLoading] = useState('') - const [showError, setShowError] = useState(false) - - const onLoadError = () => { - setLoading('loading') - - setTimeout(() => { - const newVal = [...listError.current] - for (let i = listError.current.length; i < 8; i++) { - newVal.push(`${i}`); - } - listError.current = newVal; - - setShowError(true) - setLoading('') - }, 1000); - }; - - const onLoadMore = () => { - setShowError(false) - if (listError.current.length >= 60 || loading) { - return; - } - setLoading('loading') - - setTimeout(() => { - for (let i = 0; i < 15; i++) { - listError.current.push(`${listError.current.length + 1}`) - } - setLoading('') - }, 1000); - }; - - useEffect(()=>{ - onLoadError() - }, []); - - return ( - -
请求失败,点击重新加载
- - }> - { - listError.current.map((item) => - {item} - ) - } -
- ); -} diff --git a/src/list/_example/err-tip.tsx b/src/list/_example/err-tip.tsx new file mode 100644 index 00000000..4a30637c --- /dev/null +++ b/src/list/_example/err-tip.tsx @@ -0,0 +1,64 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Cell, List, Loading } from 'tdesign-mobile-react'; + +export default function ListDemo() { + const listError = useRef([]); + const [loading, setLoading] = useState(''); + const [showError, setShowError] = useState(false); + + const onLoadError = () => { + setLoading('loading'); + + setTimeout(() => { + const newVal: string[] = [...listError.current]; + for (let i = listError.current.length; i < 8; i++) { + newVal.push(`${i}`); + } + listError.current = newVal; + + setShowError(true); + setLoading(''); + }, 1000); + }; + + const onLoadMore = () => { + setShowError(false); + if (listError.current.length >= 60 || loading) { + return; + } + setLoading('loading'); + + setTimeout(() => { + for (let i = 0; i < 15; i++) { + listError.current.push(`${listError.current.length + 1}`); + } + setLoading(''); + }, 1000); + }; + + useEffect(() => { + onLoadError(); + }, []); + + return ( + +
+ 请求失败,点击重新加载 +
+ + ) + } + > + {listError.current.map((item) => ( + + {item} + + ))} +
+ ); +} diff --git a/src/list/_example/index.jsx b/src/list/_example/index.jsx deleted file mode 100644 index e2e11111..00000000 --- a/src/list/_example/index.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState } from 'react'; -import { Button } from 'tdesign-mobile-react'; -import TDemoBlock from '../../../site/mobile/components/DemoBlock'; -// import TDemoHeader from '../../../site/mobile/components/DemoHeader'; -import './style/index.less' - -import BaseList from './base.jsx'; -import ErrTipDemo from './err-tip.jsx'; -import PullRefreshDemo from './pull-refresh.jsx'; - - -export default function ListDemo() { - - const [currentTab, setCurrentTab] = useState('info') - - const onChangeTab = (val) => { - setCurrentTab(val); - history.pushState({}, '', '?tab=demo'); - }; - - return ( -
-
- { currentTab === 'info' &&
-

List 列表

-

- 瀑布流滚动加载,用于展示同一类型信息的长列表。当列表即将滚动到底部时,会触发事件并加载更多列表项。 -

- - - - - -
} - { - currentTab === 'base' && - } - { - currentTab === 'error-tip' && - } - { - currentTab === 'pull-refresh' &&
- -
- } -
-
- ); -} diff --git a/src/list/_example/index.tsx b/src/list/_example/index.tsx new file mode 100644 index 00000000..a7ed93ce --- /dev/null +++ b/src/list/_example/index.tsx @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; +import { Button } from 'tdesign-mobile-react'; +import TDemoBlock from '../../../site/mobile/components/DemoBlock'; +// import TDemoHeader from '../../../site/mobile/components/DemoHeader'; +import './style/index.less'; + +import BaseList from './base.jsx'; +import ErrTipDemo from './err-tip.jsx'; +import PullRefreshDemo from './pull-refresh.jsx'; + +export default function ListDemo() { + const [currentTab, setCurrentTab] = useState('info'); + + const onChangeTab = (val) => { + setCurrentTab(val); + history.pushState({}, '', '?tab=demo'); + }; + + return ( +
+
+ {currentTab === 'info' && ( +
+

List 列表

+

+ 瀑布流滚动加载,用于展示同一类型信息的长列表。当列表即将滚动到底部时,会触发事件并加载更多列表项。 +

+ + + + + +
+ )} + {currentTab === 'base' && } + {currentTab === 'error-tip' && } + {currentTab === 'pull-refresh' && ( +
+ +
+ )} +
+
+ ); +} diff --git a/src/list/_example/pull-refresh.jsx b/src/list/_example/pull-refresh.tsx similarity index 60% rename from src/list/_example/pull-refresh.jsx rename to src/list/_example/pull-refresh.tsx index 94846129..5ff17987 100644 --- a/src/list/_example/pull-refresh.jsx +++ b/src/list/_example/pull-refresh.tsx @@ -2,18 +2,18 @@ import React, { useState, useEffect, useRef } from 'react'; import { Cell, List, PullDownRefresh } from 'tdesign-mobile-react'; export default function ListDemo() { - const [loading, setLoading] = useState('') - const [refreshing, setRefreshing] = useState(false) + const [loading, setLoading] = useState(''); + const [refreshing, setRefreshing] = useState(false); - const listData = useRef([]) + const listData = useRef([]); const MAX_DATA_LEN = 60; const loadData = (isRefresh) => { const ONCE_LOAD_NUM = 20; - return new Promise((resolve) => { + return new Promise(() => { setTimeout(() => { - const temp = []; + const temp: string[] = []; for (let i = 0; i < ONCE_LOAD_NUM; i++) { if (isRefresh) { temp.push(`${i + 1}`); @@ -23,9 +23,9 @@ export default function ListDemo() { } if (isRefresh) { - listData.current = temp + listData.current = temp; } else { - listData.current= [...listData.current, ...temp ] + listData.current = [...listData.current, ...temp]; } setLoading(''); setRefreshing(false); @@ -33,12 +33,12 @@ export default function ListDemo() { }); }; - const onLoadData = (isRefresh) => { + const onLoadData = (isRefresh?) => { if ((listData.current.length >= MAX_DATA_LEN && !isRefresh) || loading.value) { return; } setLoading('loading'); - loadData(isRefresh) + loadData(isRefresh); }; const onScroll = (scrollBottom) => { @@ -52,18 +52,19 @@ export default function ListDemo() { onLoadData(true); }; - useEffect(()=>{ + useEffect(() => { onLoadData(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( - setRefreshing(val)} onRefresh={onRefresh}> + setRefreshing(val)} onRefresh={onRefresh}> - { - listData.current.map((item) => - {item} - ) - } + {listData.current.map((item) => ( + + {item} + + ))} ); diff --git a/src/list/list.tsx b/src/list/list.tsx index dbc52de9..c45a1f44 100644 --- a/src/list/list.tsx +++ b/src/list/list.tsx @@ -1,10 +1,9 @@ - - import React, { useState, useRef, useEffect, useCallback } from 'react'; import { TdListProps } from './type'; import useConfig from '../_util/useConfig'; import TLoading from '../loading'; +import parseTNode from '../_util/parseTNode'; export interface ListProps extends TdListProps { required?: boolean; @@ -18,7 +17,7 @@ function isElement(node: Element) { const overflowScrollReg = /scroll|auto/i; -function getScrollParent(el: Element, root = window ) { +function getScrollParent(el: Element, root = window) { let node = el; while (node && node !== root && isElement(node)) { @@ -32,9 +31,9 @@ function getScrollParent(el: Element, root = window ) { return root; } -const List: React.FC = (props: ListProps) => { +const List: React.FC = (props) => { const { classPrefix } = useConfig(); - const { header, footer, children} = props; + const { asyncLoading, header, footer, children } = props; const name = classPrefix; const LOADING_TEXT_MAP = { @@ -44,23 +43,23 @@ const List: React.FC = (props: ListProps) => { const root = useRef(null); - const useWindowHeight = ()=>{ - const [height, setHeight] = useState(window.innerHeight); - window.onresize = ()=>{ - const height = window.innerHeight - setHeight(height) - } - return height - } + const useWindowHeight = () => { + const [height, setHeight] = useState(window.innerHeight); + window.onresize = () => { + const height = window.innerHeight; + setHeight(height); + }; + return height; + }; const height = useWindowHeight(); const onLoadMore = () => { - if (props.asyncLoading === 'load-more') { + if (asyncLoading === 'load-more') { props.onLoadMore?.(); } }; - const handleScroll = useCallback((e: WheelEvent | Event):void => { + const handleScroll = useCallback((e: WheelEvent | Event): void => { const scrollHeight = (e.target as HTMLElement).scrollHeight || Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); @@ -69,25 +68,25 @@ const List: React.FC = (props: ListProps) => { (e.target as HTMLElement).scrollTop || document.documentElement.scrollTop || document.body.scrollTop; const offsetHeight = (e.target as HTMLElement).offsetHeight || height; - const bottomDistance = scrollHeight - (scrollTop + offsetHeight) + const bottomDistance = scrollHeight - (scrollTop + offsetHeight); props.onScroll?.(bottomDistance, scrollTop); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { - const scorllParent = getScrollParent(root.current) - + const scorllParent = getScrollParent(root.current); + if (scorllParent === root.current) return; scorllParent.addEventListener('scroll', handleScroll); return () => { - removeEventListener('scroll', handleScroll) - } - }, [height, handleScroll]) + removeEventListener('scroll', handleScroll); + }; + }, [height, handleScroll]); return ( -
handleScroll(e)}> - {header} - {children} -
onLoadMore()}> +
handleScroll(e)}> + {parseTNode(header)} + {parseTNode(children)} +
onLoadMore()}> {typeof props.asyncLoading === 'string' && ['loading', 'load-more'].includes(props.asyncLoading) && ( = (props: ListProps) => { /> )}
- {footer} + {parseTNode(footer)}
); }; From fb655da8e717822a39f6daa1a4af20c315c53f85 Mon Sep 17 00:00:00 2001 From: feaswcy <708853816@qq.com> Date: Tue, 24 Sep 2024 10:48:26 +0800 Subject: [PATCH 3/3] style(list): fix code review comment fix #463 --- src/hooks/useWindowHeight.ts | 12 ++++++++++++ src/list/list.tsx | 33 +++------------------------------ 2 files changed, 15 insertions(+), 30 deletions(-) create mode 100644 src/hooks/useWindowHeight.ts diff --git a/src/hooks/useWindowHeight.ts b/src/hooks/useWindowHeight.ts new file mode 100644 index 00000000..3cc85cdb --- /dev/null +++ b/src/hooks/useWindowHeight.ts @@ -0,0 +1,12 @@ +import { useState } from 'react'; + +const useWindowHeight = () => { + const [height, setHeight] = useState(window.innerHeight); + window.onresize = () => { + const height = window.innerHeight; + setHeight(height); + }; + return height; +}; + +export default useWindowHeight; diff --git a/src/list/list.tsx b/src/list/list.tsx index c45a1f44..63802f8a 100644 --- a/src/list/list.tsx +++ b/src/list/list.tsx @@ -1,36 +1,17 @@ -import React, { useState, useRef, useEffect, useCallback } from 'react'; +import React, { useRef, useEffect, useCallback } from 'react'; import { TdListProps } from './type'; import useConfig from '../_util/useConfig'; import TLoading from '../loading'; import parseTNode from '../_util/parseTNode'; +import getScrollParent from '../_util/getScrollParent'; +import useWindowHeight from '../hooks/useWindowHeight'; export interface ListProps extends TdListProps { required?: boolean; readonly?: boolean; } -function isElement(node: Element) { - const ELEMENT_NODE_TYPE = 1; - return node.tagName !== 'HTML' && node.tagName !== 'BODY' && node.nodeType === ELEMENT_NODE_TYPE; -} - -const overflowScrollReg = /scroll|auto/i; - -function getScrollParent(el: Element, root = window) { - let node = el; - - while (node && node !== root && isElement(node)) { - const { overflowY } = window.getComputedStyle(node); - if (overflowScrollReg.test(overflowY)) { - return node; - } - node = node.parentNode as Element; - } - - return root; -} - const List: React.FC = (props) => { const { classPrefix } = useConfig(); const { asyncLoading, header, footer, children } = props; @@ -43,14 +24,6 @@ const List: React.FC = (props) => { const root = useRef(null); - const useWindowHeight = () => { - const [height, setHeight] = useState(window.innerHeight); - window.onresize = () => { - const height = window.innerHeight; - setHeight(height); - }; - return height; - }; const height = useWindowHeight(); const onLoadMore = () => {