diff --git a/.changeset/nice-pans-confess.md b/.changeset/nice-pans-confess.md new file mode 100644 index 0000000000..60afe186a2 --- /dev/null +++ b/.changeset/nice-pans-confess.md @@ -0,0 +1,5 @@ +--- +'@td-design/react-native-tabs': minor +--- + +feat: 为Tabs增加lazy功能 diff --git a/packages/react-native-tabs/src/SceneView.tsx b/packages/react-native-tabs/src/SceneView.tsx new file mode 100644 index 0000000000..6dba4d946a --- /dev/null +++ b/packages/react-native-tabs/src/SceneView.tsx @@ -0,0 +1,51 @@ +import React, { useEffect } from 'react'; +import { View } from 'react-native'; + +import { useSafeState } from '@td-design/rn-hooks'; + +export default function SceneView({ + lazy, + currentPage, + index, + children, +}: { + lazy: boolean; + currentPage: number; + index: number; + children: (props: { loading: boolean }) => React.ReactNode; +}) { + const [isLoading, setIsLoading] = useSafeState(lazy && Math.abs(currentPage - index) > 0); + + if (isLoading && Math.abs(currentPage - index) <= 0 && lazy) { + setIsLoading(false); + } + + useEffect(() => { + let timer: NodeJS.Timeout | undefined; + + if (!lazy && isLoading) { + timer = setTimeout(() => { + setIsLoading(false); + }, 0); + } + + return () => { + clearTimeout(timer); + }; + }, [lazy, isLoading]); + + const focused = currentPage === index; + + return ( + + {children({ loading: isLoading })} + + ); +} diff --git a/packages/react-native-tabs/src/index.md b/packages/react-native-tabs/src/index.md index 78109419fb..68be5104ea 100644 --- a/packages/react-native-tabs/src/index.md +++ b/packages/react-native-tabs/src/index.md @@ -197,6 +197,8 @@ return ( | tabItemStyle | `false` | 选项卡标签样式 | `ViewStyle` | | | labelStyle | `false` | 标签文字样式 | `TextStyle` | | | indicatorStyle | `false` | 指示器样式 | `ViewStyle` | | +| lazy | `false` | 是否启用懒加载模式 | `boolean` | | +| lazyPlaceholder | `false` | 懒加载时的placeholder组件 | `() => ReactNode` | | ```ts interface TabScene { diff --git a/packages/react-native-tabs/src/index.tsx b/packages/react-native-tabs/src/index.tsx index 01454fa727..96a52602f1 100644 --- a/packages/react-native-tabs/src/index.tsx +++ b/packages/react-native-tabs/src/index.tsx @@ -1,9 +1,10 @@ -import React, { cloneElement } from 'react'; +import { ComponentType, createElement, memo } from 'react'; import { Animated, StyleProp, TextStyle, ViewStyle } from 'react-native'; import PagerView from 'react-native-pager-view'; import { Box, helpers } from '@td-design/react-native'; +import SceneView from './SceneView'; import ScrollBar from './ScrollBar'; import TabBar from './TabBar'; import usePagerView from './usePagerView'; @@ -13,11 +14,13 @@ const AnimatedPagerView = Animated.createAnimatedComponent(Pag type Tab = { title: string; - component: JSX.Element; + component: ComponentType; }; export interface TabsProps { scenes: Tab[]; + lazy?: boolean; + renderLazyPlaceholder?: () => JSX.Element; /** 默认当前是第几个tab */ initialPage?: number; /** 当前是第几个tab */ @@ -42,6 +45,8 @@ export interface TabsProps { export default function Tabs({ initialPage = 0, + lazy = false, + renderLazyPlaceholder, page, onChange, scenes = [], @@ -103,8 +108,20 @@ export default function Tabs({ onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} > - {scenes.map(({ title, component }) => cloneElement(component, { key: title }))} + {scenes.map(({ component }, i) => ( + + {({ loading }) => { + if (loading) return renderLazyPlaceholder?.(); + + return ; + }} + + ))} ); } + +const SceneComponent = memo( }>({ component }: T) => { + return createElement(component); +}); diff --git a/packages/react-native-tabs/src/types.tsx b/packages/react-native-tabs/src/types.tsx new file mode 100644 index 0000000000..ccedee3489 --- /dev/null +++ b/packages/react-native-tabs/src/types.tsx @@ -0,0 +1,14 @@ +export type Route = { + key: string; + icon?: string; + title?: string; +}; + +export type Scene = { + route: T; +}; + +export type NavigationState = { + index: number; + routes: T[]; +};