import { PropsWithChildren, ReactElement, useState } from "react" ;
import {
View,
Text,
Image,
ViewStyle,
StyleSheet,
ScrollView,
TouchableWithoutFeedback,
ImageStyle,
} from "react-native" ;
interface Tab {
icon? : string ;
label? : string ;
value? : string | number ;
}
type Props = PropsWithChildren< {
tabs: Array < string > | Array < Tab> ;
tabKey? : number | string ;
css? : Array < ViewStyle> ;
iconCss? : Array < ImageStyle> ;
labelCss? : Array < ViewStyle> ;
listCss? : Array < ViewStyle> ;
lineCss? : Array < ViewStyle> ;
active? : number ;
onChange? : ( value: number ) => void ;
children: Array < ReactElement> ;
} > ;
const childrenLoaded: { [ key: string | number ] : boolean [ ] } = { } ;
export function TabNav ( {
tabs,
tabKey,
css,
iconCss,
labelCss,
listCss,
lineCss,
active,
onChange,
children,
} : Props) {
const [ key] = useState < number | string > ( tabKey ? tabKey : 0 ) ;
const [ activeIndex, setActiveIndex] = useState < number > ( active ? active : 0 ) ;
if ( childrenLoaded[ key] == undefined ) childrenLoaded[ key] = [ ] ;
if ( childrenLoaded[ key] . length == 0 && children?. length) {
for ( let i = 0 ; i < children. length; i++ ) childrenLoaded[ key] [ i] = active == i;
}
const setActive = ( index: number ) => {
setActiveIndex ( index) ;
if ( childrenLoaded[ key] [ index] == false ) childrenLoaded[ key] [ index] = true ;
if ( onChange) onChange ( index) ;
} ;
return (
< View style= { { width: "100%" , display: "flex" , flexDirection: "column" } } >
< ScrollView
horizontal= { true }
showsHorizontalScrollIndicator= { false }
style= { [ { width: "100%" , minHeight: 45 } , ... ( css || [ ] ) ] }
contentContainerStyle= { { width: "100%" , height: "100%" } } >
< View
style= { [
{
height: "100%" ,
minWidth: "100%" ,
display: "flex" ,
flexDirection: "row" ,
flexWrap: "nowrap" ,
} ,
... ( listCss || [ ] ) ,
] } >
{ tabs. map ( ( item, index) => (
< TouchableWithoutFeedback
key= { index}
onPress= { ( ) => setActive ( index) } >
< View
style= { {
height: "100%" ,
padding: 15 ,
display: "flex" ,
flexDirection: "column" ,
alignItems: "center" ,
justifyContent: "center" ,
} } >
{ }
{ typeof item != "string" && item. icon && (
< Image
style= { [ { width: 45 , height: 45 } , ... ( iconCss || [ ] ) ] }
source= { item. icon || require ( "@/assets/images/logo.png" ) }
/ >
) }
{ }
{ ( typeof item == "string" ||
( typeof item != "string" && item. label) ) && (
< Text
style= { [
{ fontWeight: "bold" } ,
... ( labelCss || [ ] ) ,
activeIndex == index ? styles. active : null ,
] } >
{ typeof item == "string" ? item : ( item as Tab) . label}
< / Text>
) }
{ }
< View
style= { [
styles. line,
{ opacity: activeIndex == index ? 1 : 0 } ,
... ( lineCss || [ ] ) ,
] }
/ >
< / View>
< / TouchableWithoutFeedback>
) ) }
< / View>
< / ScrollView>
{ }
{ children
? children. map ( ( child, i) => {
return (
< View
style= { { display: i == activeIndex ? "flex" : "none" } }
key= { i} >
{ childrenLoaded[ key] [ i] ? child : null }
< / View>
) ;
} )
: null }
< / View>
) ;
}
const styles = StyleSheet. create ( {
active: {
color: "green" ,
} ,
line: {
height: 2 ,
width: 20 ,
backgroundColor: "green" ,
marginTop: 8 ,
} ,
} ) ;
使用举例
< TabNav tabs= { [ "00" , "11" , "22" ] } >
{ }
< View>
< Text> 0 < / Text>
< / View>
{ }
< View>
< Text> 1 < / Text>
< / View>
{ }
< View>
< Text> 2 < / Text>
< / View>
< / TabNav>
TabNavItem 可以是自定义组件,注意:如果是自定义组件,切换到对应的组件才会触发组件内部所有逻辑,且第二次切换后会直接使用缓存渲染(不再重新初始化组件);TabNavItem 需要对应 tabs 数组