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 数组

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部