RN组件库 - Button 组件

news2024/11/28 20:50:31

从零构建 React Native 组件库,作为一个前端er~谁不想拥有一个自己的组件库呢

1、定义 Button 基本类型 type.ts

import type {StyleProp, TextStyle, ViewProps} from 'react-native';
import type {TouchableOpacityProps} from '../TouchableOpacity/type';
import type Loading from '../Loading';

// 五种按钮类型
export type ButtonType =
  | 'primary'
  | 'success'
  | 'warning'
  | 'danger'
  | 'default';
// 四种按钮大小
export type ButtonSize = 'large' | 'small' | 'mini' | 'normal';
// 加载中组件类型
type LoadingProps = React.ComponentProps<typeof Loading>;
// 按钮的基本属性
// extends Pick的作用是:
// 继承父类型的属性和方法:通过extends关键字,子类型可以继承父类型的所有属性和方法。
// 选取父类型的特定属性:通过Pick工具类型,从父类型中选取需要的属性,并将其添加到子类型中。
export interface ButtonProps
  extends Pick<ViewProps, 'style' | 'testID'>,
    Pick<
      TouchableOpacityProps,
      'onPress' | 'onLongPress' | 'onPressIn' | 'onPressOut'
    > {
  /**
   * 类型,可选值为 primary success warning danger
   * @default default
   */
  type?: ButtonType;
  /**
   * 尺寸,可选值为 large small mini
   * @default normal
   */
  size?: ButtonSize;
  /**
   * 按钮颜色,支持传入 linear-gradient 渐变色
   */
  color?: string;
  /**
   * 左侧图标名称或自定义图标组件
   */
  icon?: React.ReactNode;
  /**
   * 图标展示位置,可选值为 right
   * @default left
   */
  iconPosition?: 'left' | 'right';
  /**
   * 是否为朴素按钮
   */
  plain?: boolean;
  /**
   * 是否为方形按钮
   */
  square?: boolean;
  /**
   * 是否为圆形按钮
   */
  round?: boolean;
  /**
   * 是否禁用按钮
   */
  disabled?: boolean;
  /**
   * 是否显示为加载状态
   */
  loading?: boolean;
  /**
   * 加载状态提示文字
   */
  loadingText?: string;
  /**
   * 加载图标类型
   */
  loadingType?: LoadingProps['type'];
  /**
   * 加载图标大小
   */
  loadingSize?: number;
  textStyle?: StyleProp<TextStyle>;
  children?: React.ReactNode;
}

2、动态生成样式对象style.ts

import {StyleSheet} from 'react-native';
import type {ViewStyle, TextStyle} from 'react-native';
import type {ButtonType, ButtonSize} from './type';

type Params = {
  type: ButtonType;
  size: ButtonSize;
  plain?: boolean;
};

type Styles = {
  button: ViewStyle;
  disabled: ViewStyle;
  plain: ViewStyle;
  round: ViewStyle;
  square: ViewStyle;
  text: TextStyle;
};

const createStyle = (
  theme: DiceUI.Theme,
  {type, size, plain}: Params,
): Styles => {
  // Record 是一种高级类型操作,用于创建一个对象类型
  // 其中键的类型由第一个参数指定(ButtonType),值的类型由第二个参数指定(ViewStyle)
  const buttonTypeStyleMaps: Record<ButtonType, ViewStyle> = {
    default: {
      backgroundColor: theme.button_default_background_color,
      borderColor: theme.button_default_border_color,
      borderStyle: 'solid',
      borderWidth: theme.button_border_width,
    },
    danger: {
      backgroundColor: theme.button_danger_background_color,
      borderColor: theme.button_danger_border_color,
      borderStyle: 'solid',
      borderWidth: theme.button_border_width,
    },
    primary: {
      backgroundColor: theme.button_primary_background_color,
      borderColor: theme.button_primary_border_color,
      borderStyle: 'solid',
      borderWidth: theme.button_border_width,
    },
    success: {
      backgroundColor: theme.button_success_background_color,
      borderColor: theme.button_success_border_color,
      borderStyle: 'solid',
      borderWidth: theme.button_border_width,
    },
    warning: {
      backgroundColor: theme.button_warning_background_color,
      borderColor: theme.button_warning_border_color,
      borderStyle: 'solid',
      borderWidth: theme.button_border_width,
    },
  };

  const buttonSizeStyleMaps: Record<ButtonSize, ViewStyle> = {
    normal: {},
    small: {
      height: theme.button_small_height,
    },
    large: {
      height: theme.button_large_height,
      width: '100%',
    },
    mini: {
      height: theme.button_mini_height,
    },
  };

  const contentPadding: Record<ButtonSize, ViewStyle> = {
    normal: {
      paddingHorizontal: theme.button_normal_padding_horizontal,
    },
    small: {
      paddingHorizontal: theme.button_small_padding_horizontal,
    },
    large: {},
    mini: {
      paddingHorizontal: theme.button_mini_padding_horizontal,
    },
  };

  const textSizeStyleMaps: Record<ButtonSize, TextStyle> = {
    normal: {
      fontSize: theme.button_normal_font_size,
    },
    large: {
      fontSize: theme.button_default_font_size,
    },
    mini: {
      fontSize: theme.button_mini_font_size,
    },
    small: {
      fontSize: theme.button_small_font_size,
    },
  };

  const textTypeStyleMaps: Record<ButtonType, TextStyle> = {
    default: {
      color: theme.button_default_color,
    },
    danger: {
      color: plain
        ? theme.button_danger_background_color
        : theme.button_danger_color,
    },
    primary: {
      color: plain
        ? theme.button_primary_background_color
        : theme.button_primary_color,
    },
    success: {
      color: plain
        ? theme.button_success_background_color
        : theme.button_success_color,
    },
    warning: {
      color: plain
        ? theme.button_warning_background_color
        : theme.button_warning_color,
    },
  };

  return StyleSheet.create<Styles>({
    button: {
      alignItems: 'center',
      borderRadius: theme.button_border_radius,
      flexDirection: 'row',
      height: theme.button_default_height,
      justifyContent: 'center',
      overflow: 'hidden',
      position: 'relative',
      ...buttonTypeStyleMaps[type],
      ...buttonSizeStyleMaps[size],
      ...contentPadding[size],
    },
    disabled: {
      opacity: theme.button_disabled_opacity,
    },
    plain: {
      backgroundColor: theme.button_plain_background_color,
    },

    round: {
      borderRadius: theme.button_round_border_radius,
    },

    square: {
      borderRadius: 0,
    },

    text: {
      ...textTypeStyleMaps[type],
      ...textSizeStyleMaps[size],
    },
  });
};

export default createStyle;

3、实现 Button 组件

import React, {FC, memo} from 'react';
import {View, ViewStyle, StyleSheet, Text, TextStyle} from 'react-native';
import TouchableOpacity from '../TouchableOpacity';
import {useThemeFactory} from '../Theme';
import Loading from '../Loading';
import createStyle from './style';
import type {ButtonProps} from './type';

const Button: FC<ButtonProps> = memo(props => {
  const {
    type = 'default',
    size = 'normal',
    loading,
    loadingText,
    loadingType,
    loadingSize,
    icon,
    iconPosition = 'left',
    color,
    plain,
    square,
    round,
    disabled,
    textStyle,
    children,
    // 对象的解构操作,在末尾使用...会将剩余的属性都收集到 rest 对象中。
    ...rest
  } = props;
  // useThemeFactory 调用 createStyle 函数根据入参动态生成一个 StyleSheet.create<Styles> 对象
  const {styles} = useThemeFactory(createStyle, {type, size, plain});
  const text = loading ? loadingText : children;
  // 将属性合并到一个新的样式对象中,并返回这个新的样式对象。
  const textFlattenStyle = StyleSheet.flatten<TextStyle>([
    styles.text,
    !!color && {color: plain ? color : 'white'},
    textStyle,
  ]);

  // 渲染图标
  const renderIcon = () => {
    const defaultIconSize = textFlattenStyle.fontSize;
    const iconColor = color ?? (textFlattenStyle.color as string);
    let marginStyles: ViewStyle;

    if (!text) {
      marginStyles = {};
    } else if (iconPosition === 'left') {
      marginStyles = {marginRight: 4};
    } else {
      marginStyles = {marginLeft: 4};
    }

    return (
      <>
        {icon && loading !== true && (
          <View style={marginStyles}>
            {/* React 提供的一个顶层 API,用于检查某个值是否为 React 元素 */}
            {React.isValidElement(icon)
              ? React.cloneElement(icon as React.ReactElement<any, string>, {
                  size: defaultIconSize,
                  color: iconColor,
                })
              : icon}
          </View>
        )}
        {loading && (
          <Loading
            // ?? 可选链操作符,如果 loadingSize 为 null 或 undefined ,就使用 defaultIconSize 作为默认值
            size={loadingSize ?? defaultIconSize}
            type={loadingType}
            color={iconColor}
            style={marginStyles}
          />
        )}
      </>
    );
  };
  // 渲染文本
  const renderText = () => {
    if (!text) {
      return null;
    }

    return (
      <Text selectable={false} numberOfLines={1} style={textFlattenStyle}>
        {text}
      </Text>
    );
  };

  return (
    <TouchableOpacity
      {...rest}
      disabled={disabled}
      activeOpacity={0.6}
      style={[
        styles.button,
        props.style,
        plain && styles.plain,
        round && styles.round,
        square && styles.square,
        disabled && styles.disabled,
        // !!是一种类型转换的方法,它可以将一个值转换为布尔类型的true或false
        !!color && {borderColor: color},
        !!color && !plain && {backgroundColor: color},
      ]}>
      {iconPosition === 'left' && renderIcon()}
      {renderText()}
      {iconPosition === 'right' && renderIcon()}
    </TouchableOpacity>
  );
});

export default Button;

4、对外导出 Botton 组件及其类型文件

import Button from './Button';

export default Button;
export {Button};
export type {ButtonProps, ButtonSize, ButtonType} from './type';

5、主题样式

动态生成样式对象调用函数

import {useMemo} from 'react';
import {createTheming} from '@callstack/react-theme-provider';
import type {StyleSheet} from 'react-native';
import {defaultTheme} from '../styles';
// 创建主题对象:调用 createTheming 函数并传入一个默认主题作为参数
export const {ThemeProvider, withTheme, useTheme} = createTheming<DiceUI.Theme>(
  defaultTheme as DiceUI.Theme,
);

type ThemeFactoryCallBack<T extends StyleSheet.NamedStyles<T>> = {
  styles: T;
  theme: DiceUI.Theme;
};

export function useThemeFactory<T extends StyleSheet.NamedStyles<T>, P>(
  fun: (theme: DiceUI.Theme, ...extra: P[]) => T,
  ...params: P[]
): ThemeFactoryCallBack<T> {
  // 钩子,用于在函数组件中获取当前的主题
  const theme = useTheme();
  const styles = useMemo(() => fun(theme, ...params), [fun, theme, params]);

  return {styles, theme};
}

export default {
  ThemeProvider,
  withTheme,
  useTheme,
  useThemeFactory,
};

6、Demo 演示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1851307.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【活动】TSRC反爬虫专项正式启动!

活动时间 即日起 ~ 2024年7月5日 18:00 测试范围&#xff1a;微信公众号、腾讯新闻等 测试域名&#xff1a;mp.weixin.qq.com 微信公众号相关接口 1. 微信公众号文章列表 2. 历史文章 3. 文章详细内容 注&#xff1a;详情报名后公布。反爬虫专项将不定期上线新业务&#xf…

Java比较运算符

关系运算符和比较运算符适用于条件判断类型。 相当于布尔值&#xff0c;只有True和False两个 符号 说明ab,判断a的值是否等于b的值&#xff0c;条件成立为true,不成立为false ! a!b,判断a和b的值是否不相等&#xff0c;条件成立为true,不成立为false > …

【字符串 状态机动态规划】1320. 二指输入的的最小距离

本文涉及知识点 动态规划汇总 字符串 状态机动态规划 LeetCode1320. 二指输入的的最小距离 二指输入法定制键盘在 X-Y 平面上的布局如上图所示&#xff0c;其中每个大写英文字母都位于某个坐标处。 例如字母 A 位于坐标 (0,0)&#xff0c;字母 B 位于坐标 (0,1)&#xff0…

java之url任意跳转漏洞

1 漏洞介绍 URLRedirect url重定向漏洞也称url任意跳转漏洞&#xff0c;网站信任了用户的输入导致恶意攻击&#xff0c;url重定向主要用来钓鱼&#xff0c;比如url跳转中最常见的跳转在登陆口&#xff0c;支付口&#xff0c;也就是一旦登陆将会跳转任意自己构造的网站&#xf…

操作系统实验一:实验环境搭建与系统调用(在VMWare中安装xv6)

目录 一、实验目的 二、具体任务安排 1.实验环境搭建 2.系统调用 近来有空闲&#xff0c;把前几个学期做的实验上传上来。如有错误的地方欢迎大佬批评指正&#xff0c;有更好的方法也期待您的分享~ 一、实验目的 在Windows中安装VMWare虚拟机、在虚拟机中编译安装Qemu、最终…

【软件工程】【22.04】p1

关键字&#xff1a; 软件需求规约基本性质、数据字典构成、内聚程度最高功能内聚、公有属性、RUP实体类、评审、测试序列、软件确认过程、CMMI能力等级 软件需求分类、DFD数据流图组成&#xff08;实体&#xff09;、经典详细设计、数据耦合、关联多重性、状态图、黑盒测试、…

常见的Wi-Fi蓝牙模组

在嵌入式领域&#xff0c;常见的Wi-Fi蓝牙模组确实包括多个知名品牌&#xff0c;如乐鑫、安信可和移远等&#xff0c;以前可能你听的最多的是ESP8266&#xff0c;不过今天讨论的是Wi-Fi蓝牙模组&#xff0c;而8266本身并不内置蓝牙功能&#xff0c;不在介绍范围。而拿到模块之后…

4、MFC:菜单栏、工具栏与状态栏

菜单栏、工具栏与状态栏 1、菜单栏1.1 简介1.2 创建属性设置菜单消息成员函数 1.3 实例 2、工具栏2.1 简介工具栏属性2.2 创建消息CToolBar类的主要成员函数 2.3 实例 3、状态栏3.1 简介3.2 创建CStatusBar类状态栏创建 3.3 实例 1、菜单栏 1.1 简介 菜单在界面设计中是经常使…

高斯算法的原理及其与常规求和方法的区别

高斯算法的原理 高斯算法的原理源于数学家卡尔弗里德里希高斯在他少年时期发现的一种求和方法。当时老师让学生们计算1到100的和&#xff0c;高斯发现了一种快速计算的方法。 高斯注意到&#xff0c;如果将序列的首尾两数相加&#xff0c;结果总是相同的。例如&#xff1a; …

GPT-4o一夜被赶超,Claude 3.5一夜封王|快手可灵大模型推出图生视频功能|“纯血”鸿蒙大战苹果AI|智谱AI“钱途”黯淡|月之暗面被曝进军美国

快手可灵大模型推出图生视频功能“纯血”鸿蒙大战苹果AI&#xff0c;华为成败在此一举大模型低价火拼间&#xff0c;智谱AI“钱途”黯淡手握新“王者”&#xff0c;腾讯又跟渠道干上了“美食荒漠”杭州&#xff0c;走出一个餐饮IPOGPT-4o一夜被赶超&#xff0c;Anthropic推出Cl…

关于Windows系统下redis的闪退问题。

一、问题分析 首先&#xff0c;有这个问题的一般是如下操作&#xff1a; 1、在运行项目时发现无法连接到redis服务器&#xff0c; 2、进入Redis安装目录(如图)——>鼠标双击打开redis-server.exe&#xff0c;然后闪退&#xff0c; 3、运行redis-cli时提示&#xff1a;“由…

【招聘贴】JAVA后端·唯品会·BASE新加坡

作者|老夏&#xff08;题图&#xff1a;公司业务介绍页&#xff09; “ 请注意&#xff0c;这两个岗是BASE新加坡的&#xff0c;欢迎推荐给身边需要的朋友&#xff08;特别是在新加坡的&#xff09;。” VIP海外业务-产品技术团队&#xff0c;这两个岗位属于后端工程组的岗&…

STM32之二:时钟树

目录 1. 时钟 2. STM3时钟源&#xff08;哪些可以作为时钟信号&#xff09; 2.1 HSE时钟 2.1.1 高速外部时钟信号&#xff08;HSE&#xff09;来源 2.1.2 HSE外部晶体电路配置 2.2 HSI时钟 2.3 PLL时钟 2.4 LSE时钟 2.5 LSI时钟 3. STM32时钟&#xff08;哪些系统使用时…

机器学习课程复习——逻辑回归

1. 激活函数 Q:激活函数有哪些? SigmoidS型函数Tanh 双曲正切函数

【技巧】Leetcode 201. 数字范围按位与【中等】

数字范围按位与 给你两个整数 left 和 right &#xff0c;表示区间 [left, right] &#xff0c;返回此区间内所有数字 按位与 的结果&#xff08;包含 left 、right 端点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;left 5, right 7 输出&#xff1a;4 解题思路 …

外部存储器

外部存储器是主存的后援设备&#xff0c;也叫做辅助存储器&#xff0c;简称外存或辅存。 它的特点是容量大、速度慢、价格低&#xff0c;可以脱机保存信息&#xff0c;属于非易失性存储器。 外存主要有&#xff1a;光盘、磁带、磁盘&#xff1b;磁盘和磁带都属于磁表面存储器…

three.js 第八节 - gltf加载器、解码器

// ts-nocheck // 引入three.js import * as THREE from three // 导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls // 导入hdr加载器&#xff08;专门加载hdr的&#xff09; import { RGBELoader } from three/examples/jsm/loaders…

工业web4.0UI风格令人惊艳

工业web4.0UI风格令人惊艳

6月27日云技术研讨会 | 中央集中架构新车型功能和网络测试解决方案

会议摘要 “软件定义汽车”新时代下&#xff0c;整车电气电气架构向中央-区域集中式发展已成为行业共识&#xff0c;车型架构的变革带来更复杂的整车功能定义、更多的新技术的应用&#xff08;如SOA服务化、TSN等&#xff09;和更短的车型研发周期&#xff0c;对整车和新产品研…

【数据结构与算法】哈夫曼树,哈夫曼编码 详解

哈夫曼树的数据结构。 struct TreeNode {ElemType data;TreeNode *left, *right; }; using HuffmanTree TreeNode *;结构体包含三个成员&#xff1a; data 是一个 ElemType 类型的变量&#xff0c;用于存储哈夫曼树节点的数据。left 是一个指向 TreeNode 类型的指针&#xf…