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

相关推荐
?abc!3 小时前
缓存(5):常见 缓存数据淘汰算法/缓存清空策略
java·算法·缓存
郝开5 小时前
扩展:React 项目执行 yarn eject 后的 scripts 目录结构详解
react.js·前端框架·react
悟空打码6 小时前
MyBatis源码解读5(3.1、缓存简介)
缓存·mybatis
weifont7 小时前
React中的useSyncExternalStore使用
前端·javascript·react.js
初遇你时动了情7 小时前
js fetch流式请求 AI动态生成文本,实现逐字生成渲染效果
前端·javascript·react.js
几何心凉8 小时前
如何使用 React Hooks 替代类组件的生命周期方法?
前端·javascript·react.js
码农飞哥8 小时前
互联网大厂Java面试实战:Spring Boot到微服务的技术问答解析
java·数据库·spring boot·缓存·微服务·消息队列·面试技巧
scdifsn9 小时前
动手学深度学习12.4.硬件-笔记&练习(PyTorch)
pytorch·笔记·深度学习·缓存·内存·硬盘·深度学习硬件
寻找沙漠的人12 小时前
Redis 缓存
数据库·redis·缓存
LLLLLindream14 小时前
Redis——达人探店
数据库·redis·缓存