react navite封装一个RN 通用按钮组件

可以。面试里如果让你封装一个 RN 通用 Button 组件 ,建议不要只写一个 TouchableOpacity,而是把 类型、loading、disabled、尺寸、主题、icon、样式扩展 都考虑进去。

下面给你一个比较适合面试手写/实际项目的版本。

1. 通用 Button

复制代码
import React from 'react';
import {
  ActivityIndicator,
  Pressable,
  StyleProp,
  StyleSheet,
  Text,
  TextStyle,
  View,
  ViewStyle,
} from 'react-native';

type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'text';
type ButtonSize = 'small' | 'medium' | 'large';

interface ButtonProps {
  title: string;
  onPress?: () => void;

  // 状态
  disabled?: boolean;
  loading?: boolean;

  // 样式
  variant?: ButtonVariant;
  size?: ButtonSize;
  fullWidth?: boolean;
  style?: StyleProp<ViewStyle>;
  textStyle?: StyleProp<TextStyle>;

  // 扩展
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
}

const COLORS = {
  primary: '#1677FF',
  white: '#FFFFFF',
  text: '#333333',
  border: '#1677FF',
  disabled: '#CCCCCC',
};

const Button = ({
  title,
  onPress,
  disabled = false,
  loading = false,
  variant = 'primary',
  size = 'medium',
  fullWidth = false,
  style,
  textStyle,
  leftIcon,
  rightIcon,
}: ButtonProps) => {
  const isDisabled = disabled || loading;

  const getButtonStyle = (): ViewStyle => {
    const baseStyle: ViewStyle = {
      ...styles.base,
      ...styles[size],
    };

    if (fullWidth) {
      baseStyle.width = '100%';
    }

    switch (variant) {
      case 'primary':
        return {
          ...baseStyle,
          backgroundColor: isDisabled
            ? COLORS.disabled
            : COLORS.primary,
        };

      case 'secondary':
        return {
          ...baseStyle,
          backgroundColor: '#F5F5F5',
        };

      case 'outline':
        return {
          ...baseStyle,
          backgroundColor: COLORS.white,
          borderWidth: 1,
          borderColor: isDisabled
            ? COLORS.disabled
            : COLORS.border,
        };

      case 'text':
        return {
          ...baseStyle,
          backgroundColor: 'transparent',
        };

      default:
        return baseStyle;
    }
  };

  const getTextStyle = (): TextStyle => {
    const baseTextStyle: TextStyle = {
      ...styles.text,
    };

    switch (variant) {
      case 'primary':
        return {
          ...baseTextStyle,
          color: COLORS.white,
        };

      case 'outline':
      case 'text':
        return {
          ...baseTextStyle,
          color: isDisabled
            ? COLORS.disabled
            : COLORS.primary,
        };

      case 'secondary':
        return {
          ...baseTextStyle,
          color: COLORS.text,
        };

      default:
        return baseTextStyle;
    }
  };

  return (
    <Pressable
      onPress={onPress}
      disabled={isDisabled}
      style={({ pressed }) => [
        getButtonStyle(),
        pressed && !isDisabled && styles.pressed,
        style,
      ]}
    >
      {loading ? (
        <ActivityIndicator
          size="small"
          color={
            variant === 'primary'
              ? COLORS.white
              : COLORS.primary
          }
        />
      ) : (
        <View style={styles.content}>
          {leftIcon}

          <Text style={[getTextStyle(), textStyle]}>
            {title}
          </Text>

          {rightIcon}
        </View>
      )}
    </Pressable>
  );
};

const styles = StyleSheet.create({
  base: {
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },

  small: {
    height: 36,
    paddingHorizontal: 16,
  },

  medium: {
    height: 44,
    paddingHorizontal: 20,
  },

  large: {
    height: 52,
    paddingHorizontal: 24,
  },

  content: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    gap: 8,
  },

  text: {
    fontSize: 16,
    fontWeight: '500',
  },

  pressed: {
    opacity: 0.7,
  },
});

export default Button;

2. 使用方式

复制代码
<Button
  title="登录"
  onPress={handleLogin}
/>

不同类型:

复制代码
<Button
  title="主要按钮"
  variant="primary"
/>

<Button
  title="次要按钮"
  variant="secondary"
/>

<Button
  title="边框按钮"
  variant="outline"
/>

<Button
  title="文字按钮"
  variant="text"
/>

不同尺寸:

复制代码
<Button
  title="Small"
  size="small"
/>

<Button
  title="Medium"
  size="medium"
/>

<Button
  title="Large"
  size="large"
/>

Loading:

复制代码
<Button
  title="提交"
  loading={isSubmitting}
  onPress={handleSubmit}
/>

此时按钮自动:

复制代码
loading = true
       ↓
disabled
       ↓
不允许重复点击
       ↓
显示 ActivityIndicator

3. 面试时可以重点讲这几个点

这个题真正考的不是 JSX,而是组件抽象能力

可以这样回答:

我会把 Button 分成基础能力和业务表现两层。基础层统一处理点击、disabled、loading;表现层通过 variant、size 控制不同视觉样式,同时通过 style、textStyle、icon 保留扩展能力。

另外有一个比较容易被面试官追问的问题:

为什么使用 Pressable 而不是 TouchableOpacity

可以答:

Pressable 对 pressed、disabled、hover、focus 等交互状态控制更加灵活。现在的新 RN 项目里,如果没有历史兼容需求,我更倾向使用 Pressable。

4. 如果是"高频 RN 手写题",我建议继续把它升级

实际面试可以进一步要求你实现:

复制代码
<Button
  title="立即购买"
  variant="primary"
  size="large"
  loading={loading}
  disabled={disabled}
  leftIcon={<ShoppingCart />}
  onPress={handleBuy}
/>

然后继续考:

  • forwardRef

  • ref 获取 Button

  • 防止重复点击

  • debounce

  • Theme / Design Token

  • icon

  • children

  • onPress 类型

  • Android ripple

  • hitSlop

  • accessibility

  • testID

  • Dark Mode

如果你是在准备 RN 面试手写题 ,这个 Button 很适合作为第一题,后面可以继续练 Input、Modal、Toast、Loading、FlatList、下拉刷新、分页 Hook、useRequest、倒计时、图片组件

相关推荐
晴天1614 分钟前
CSS 预处理器深度解析-Day35
前端·css
软件黑马王子15 分钟前
1.non-MonoBehaviour 单例模式泛型基类
unity·前端框架·c#
恋猫de小郭16 分钟前
AI 时代,也许你的 Flutter 需要一套 Dartastic OpenTelemetry 监控
android·前端·flutter
独立开发之道17 分钟前
【three.js教程】Three.js 矩阵变换:position/quaternion/scale 和 matrix 到底怎么配合
开发语言·javascript·矩阵
wujiuhsu19 分钟前
前端实现二维码生成器:Canvas、SVG、纠错等级、扫码校验与批量
前端
计算机魔术师24 分钟前
从卖铲人到圈地:英伟达129亿拿下Hugging Face
前端
攻城狮-申28 分钟前
git本地分支对齐远程分支
前端·git
IT_陈寒35 分钟前
Vite热更新突然失效?可能是这个配置在捣鬼
前端·人工智能·后端
一只小阿乐43 分钟前
java 快速上手开发 2
java·开发语言·前端