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>;
}>;
// 子组件是否加载过(用于缓存处理,加载过将不再重新渲染); 一个页面同时使用多个tab, 需要用key区分记录加载状态
const childrenLoaded: { [key: string | number]: boolean[] } = {};
/**导航栏TabNav */
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",
}}>
{/* 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[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"]}>
{/* TabNavItem 00 */}
<View>
<Text>0</Text>
</View>
{/* TabNavItem 11 */}
<View>
<Text>1</Text>
</View>
{/* TabNavItem 22 */}
<View>
<Text>2</Text>
</View>
</TabNav>
TabNavItem 可以是自定义组件,注意:如果是自定义组件,切换到对应的组件才会触发组件内部所有逻辑,且第二次切换后会直接使用缓存渲染(不再重新初始化组件);TabNavItem 需要对应 tabs 数组
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » react、react-native 实现 tab 切换(实现缓存加载)
发表评论 取消回复