react、react-native 实现 tab 切换(实现缓存加载)

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,
  },
});

使用举例

typescript 复制代码
<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 数组

相关推荐
风向决定发型丶5 小时前
redis集群搭建
数据库·redis·缓存
宠友信息9 小时前
多端数据互通场景下Spring Boot仿小红书源码结构设计
数据库·spring boot·redis·缓存·架构
长不胖的路人甲9 小时前
Redis 缓存的数据持久化方案讲解
数据库·redis·缓存
长不胖的路人甲10 小时前
Redis 单线程为什么速度很快
数据库·redis·缓存
CCPC不拿奖不改名15 小时前
Redis 工程化部署深度解析
linux·服务器·数据库·redis·深度学习·缓存·rag
谢尔登16 小时前
【React】 状态管理方案
前端·react.js·前端框架
Eiceblue21 小时前
使用 JavaScript 在 React 中实现 Word 转 PDF
javascript·react.js·word
想吃火锅10051 天前
【leetcode】146.LRU缓存js
算法·leetcode·缓存
光影少年2 天前
react navite 跨端核心原理
前端·react native·react.js
明哥聊AI2 天前
【推理与部署篇14】Prefix Caching深度解析:从自动前缀缓存到语义缓存的推理加速实战
java·开发语言·缓存