typescript
复制代码
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>;
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: boolean[] = [];
/**导航栏TabNav */
export function TabNav({
tabs,
css,
iconCss,
labelCss,
listCss,
lineCss,
active,
onChange,
children,
}: Props) {
const [activeIndex, setActiveIndex] = useState<number>(active ? active : 0);
// 加载状态初始化
if (childrenLoaded.length == 0 && children?.length) {
for (let i = 0; i < children.length; i++) childrenLoaded[i] = active == i;
}
const setActive = (index: number) => {
setActiveIndex(index);
// 加载状态更新(只需处理为加载过的子组件)
if (childrenLoaded[index] == false) childrenLoaded[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",
}}>
{/* icon显示 */}
{typeof item != "string" && item.icon && (
<Image
style={[{ width: 45, height: 45 }, ...(iconCss || [])]}
source={item.icon || require("@/assets/images/logo.png")}
/>
)}
{/* label显示 */}
{(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[i] ? child : null}
</View>
);
})
: null}
</View>
);
}
const styles = StyleSheet.create({
active: {
color: "green",
},
line: {
height: 2,
width: 20,
backgroundColor: "green",
marginTop: 8,
},
});