可以。面试里如果让你封装一个 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、倒计时、图片组件。