核心组件与样式
这一课你将能做到
- 用 View、Text、Image、TextInput、ScrollView、Pressable 搭建任意静态界面
- 写出能响应按压状态的可点击组件,并说明 Pressable 和 Button 的本质区别
- 用 StyleSheet.create 管理样式,理解 flex: 1 在 RN 布局中的核心含义
为什么要学
RN 里没有 HTML 标签,所有界面都靠十几个核心组件像乐高一样拼出来。这几个组件就像积木的基础块——块不多,但组合无限。掌握它们,你就掌握了 RN 界面的 80%。
人话:View 是万能容器(相当于 div),Text 是文字容器(所有文字必须用它包),Image 是图片,TextInput 是输入框,ScrollView 是滚动容器。Pressable 是「可按压的任何东西」——RN 里真正的按钮全部是自己用 Pressable 包出来的,Button 组件仅用于极快速的原型验证。
先建立图像
import { View, Text, Image, StyleSheet } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<View style={styles.card}>
<Image source={{ uri: 'https://picsum.photos/100' }} style={styles.avatar} />
<View style={styles.info}>
<Text style={styles.name}>张小明</Text>
<Text style={styles.bio}>前端工程师 · 北京</Text>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f5' },
card: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', padding: 16, borderRadius: 12, width: '90%' },
avatar: { width: 56, height: 56, borderRadius: 28 },
info: { marginLeft: 12 },
name: { fontSize: 18, fontWeight: 'bold' },
bio: { fontSize: 14, color: '#888', marginTop: 4 },
});
渲染结果:灰白屏幕中央一张白色卡片,左边圆形头像,右边姓名和简介。整个页面一个 div 没有,全是 RN 原生组件。
跟着做
步骤 1:View 与 Text 的分工与嵌套
View 只能装东西,不能直接显示文字;文字必须放 Text 里。这是 RN 的铁律,和 Web 最大的区别之一。
import { View, Text } from 'react-native';
export default function App() {
return (
<View style={{ padding: 24 }}>
{/* 错误示范:文字直接写在 View 里会红屏 */}
{/* <View>你好</View> */}
{/* 正确 */}
<Text style={{ fontSize: 20, fontWeight: 'bold' }}>标题</Text>
<Text style={{ fontSize: 14, color: '#666', marginTop: 8 }}>
普通文字。总价:<Text style={{ color: 'red', fontWeight: 'bold' }}>¥99</Text>
</Text>
</View>
);
}
Text 可以嵌套 Text,子 Text 继承父 Text 的样式,但可以被覆盖——这是 RN 里唯一有样式继承关系的地方。View 嵌 View 则不存在继承。
步骤 2:Image 显示网络图和本地图
import { Image, View } from 'react-native';
export default function App() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{/* 网络图片:必须显式给宽高,否则不显示 */}
<Image
source={{ uri: 'https://picsum.photos/200' }}
style={{ width: 120, height: 120, borderRadius: 60 }}
resizeMode="cover"
/>
{/* 本地图片:require 静态引入,不需要写宽高(能自动读取尺寸) */}
<Image
source={require('./assets/icon.png')}
style={{ width: 48, height: 48, marginTop: 16 }}
/>
</View>
);
}
网络图片必须给 style 里的 width 和 height,因为 RN 不会像浏览器那样下载后自适应布局。resizeMode 控制剪裁策略:cover(等比填充,可能裁切)、contain(等比完整显示)、stretch(拉伸填满,可能变形)。
步骤 3:TextInput 受控输入与常见属性
import { useState } from 'react';
import { View, TextInput, Text, StyleSheet } from 'react-native';
export default function App() {
const [name, setName] = useState('');
const [password, setPassword] = useState('');
return (
<View style={styles.container}>
<TextInput
value={name}
onChangeText={setName}
placeholder="请输入用户名"
autoCapitalize="none"
style={styles.input}
/>
<TextInput
value={password}
onChangeText={setPassword}
placeholder="请输入密码"
secureTextEntry
style={styles.input}
/>
<Text>你输入的用户名:{name}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24, justifyContent: 'center' },
input: {
borderWidth: 1,
borderColor: '#ddd',
padding: 12,
borderRadius: 8,
marginBottom: 12,
fontSize: 16,
},
});
注意 onChangeText 直接接收字符串,不是 Web 的 onChange 事件对象。secureTextEntry 是密码框;autoCapitalize="none" 防止 iOS 首字母自动大写;keyboardType="email-address" 唤起带 @ 符号的键盘。
步骤 4:Pressable 做自定义按钮
import { Pressable, Text, View } from 'react-native';
export default function App() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Pressable
onPress={() => alert('提交成功')}
onPressIn={() => console.log('按下')}
onPressOut={() => console.log('抬起')}
hitSlop={16} // 热区扩展 16dp,手指偏一点也能触发
style={({ pressed }) => [
{
backgroundColor: pressed ? '#0056b3' : '#007bff',
paddingVertical: 14,
paddingHorizontal: 32,
borderRadius: 8,
},
]}
>
<Text style={{ color: '#fff', fontSize: 16, textAlign: 'center' }}>立即提交</Text>
</Pressable>
</View>
);
}
style 可以传函数,参数里的 pressed 告诉你当前是否被按住,是实现按下变色效果的标准做法。hitSlop 扩展点击热区,对触控体验提升很大。还有 onLongPress、disabled、delayLongPress 等属性,满足绝大多数交互需求。
步骤 5:ScrollView 滚动与内容溢出
import { ScrollView, Text, View } from 'react-native';
export default function App() {
return (
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{ padding: 16, paddingBottom: 40 }}
showsVerticalScrollIndicator={false}
>
{Array.from({ length: 30 }, (_, i) => (
<Text key={i} style={{ paddingVertical: 12, borderBottomWidth: 1, borderColor: '#eee' }}>
第 {i + 1} 行内容
</Text>
))}
</ScrollView>
);
}
注意:style 控制 ScrollView 容器本身的大小,contentContainerStyle 控制内部内容的布局。如果内容被截断(比如最后一行没显示完),检查是否给 contentContainerStyle 加了底部 padding。
重要提醒:长列表不要用 ScrollView。ScrollView 会一次性把所有子元素全部渲染出来,100 条数据建 100 个原生视图,几百条就卡死。正式的长列表用下一课的 FlatList。
步骤 6:SafeAreaView 避开刘海和底部横条
import { SafeAreaView } from 'react-native-safe-area-context';
import { Text } from 'react-native';
export default function App() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Text>内容不会被刘海、底部手势条遮挡</Text>
</SafeAreaView>
);
}
安装:npx expo install react-native-safe-area-context。注意要用社区这个库的版本,RN 自带的 SafeAreaView 只对 iOS 有效,Android 全面屏会出问题。
步骤 7:KeyboardAvoidingView 处理键盘遮挡
import { KeyboardAvoidingView, TextInput, Button, Platform } from 'react-native';
export default function App() {
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<TextInput
placeholder="点我输入,键盘不会挡住"
style={{ borderWidth: 1, padding: 12, margin: 24 }}
/>
<Button title="提交" />
</KeyboardAvoidingView>
);
}
iOS 键盘弹起时内容自动上推,Android 用 height 行为。配合 Platform.OS 做平台区分,第 4 课会细讲。
讲清楚要点
- flex: 1 是布局的地基:RN 默认 Flexbox 布局,且主轴是纵向(column,和 Web 的默认 row 正好相反)。
flex: 1 表示「占满父容器剩余空间」,根组件几乎都要写,否则内容被截断。
- 网络图片必须给尺寸:RN 不会像浏览器那样下载完成后自适应布局,不写宽高就是一片空白。本地图片用
require() 会自动读取尺寸。
- Button 组件只是个壳:
<Button> 的样式死板且 iOS / Android 长得完全不同(iOS 是蓝色文字,Android 是绿色方块),实际项目全部用 Pressable 或 TouchableOpacity 自己封装,保证跨端一致性。
- StyleSheet.create 的价值:不仅集中管理样式,还允许 RN 做性能优化——样式对象只序列化一次传给原生侧。内联样式功能等价,但每次渲染都要重新传递。
- 样式没有继承:文字颜色、字号不会从父 View 传给子 Text,每个 Text 都得自己写 style。只有 Text 嵌套 Text 才有继承关系。
- 组件大小写敏感:
View 是大写的,来自 react-native;写 view 小写会报错。这和 Web 的 HTML 标签大小写不敏感完全不同。
你可能会懵的地方
-
为什么我的 justifyContent 方向「反了」?
RN 默认 flexDirection: 'column'(纵向),所以 justifyContent: 'center' 是垂直居中,alignItems: 'center' 是水平居中。和 Web 默认的 row 正好相反,这是移动端绝大多数布局都是上下排列决定的。
-
样式能写百分比吗?
宽高可以(width: '50%'),但 padding、margin 的百分比支持有限且行为不一致,推荐用 flex 布局代替百分比。
-
px 单位去哪了?
RN 样式数字是无单位的「密度无关像素(dp / dip)」,系统自动适配不同分辨率屏幕。例如 fontSize: 16 在 320dp 宽的低分屏和 414dp 的 iPhone 上都是 16dp,但物理像素数不同,所以看起来大小一致。你不用操心,RN 已经做了适配。
-
TextInput 不输中文没事,一输中文光标乱跳?
这是受控模式下某些安卓机型的旧 bug。保持 value + onChangeText 的受控写法,RN 0.72+ 已基本修复。如果还遇到,可尝试在 onChangeText 里不直接同步回 value 而是用 useRef 暂存,失去焦点再同步。
-
Pressable 和 TouchableOpacity 到底用哪个?
Pressable 是 RN 0.63 起官方推荐的新一代按压组件,API 更灵活(支持 pressed 状态、hitSlop、onPressIn/Out 细粒度回调);TouchableOpacity 是老 API,只提供透明度反馈,已处于维护模式。新项目一律用 Pressable。
错法对照
| 错法 | 问题 | 正解 |
|---|
<View>你好</View> | 红屏:Text strings must be within a Text component | <View><Text>你好</Text></View> |
| 网络 Image 不写宽高 | 图片空白不显示 | 显式 style={{ width: 120, height: 120 }} |
用 onChange 接 TextInput | 拿到的是事件对象不是字符串 | 用 onChangeText={(text) => ...} |
长列表用 map + ScrollView | 全量渲染,几百条就卡死崩溃 | 用 FlatList(下一课) |
| 以为样式会从父 View 继承给子 Text | 子 Text 颜色字号全是默认 | 每个 Text 单独写 style |
| ScrollView 内容被底部截断 | 忘了它默认不自动撑开底部 | 用 contentContainerStyle 加内边距 |
| 用 RN 自带的 SafeAreaView | Android 全面屏无效 | react-native-safe-area-context |
按钮用 <Button title="确定" /> | 样式不可控,两端长得不一样 | 用 Pressable 封装统一样式 |
卡点急救
- 图片不显示:检查是否给了宽高;http(非 https)图片在 iOS 默认被拦截,换 https 或在
app.json 里配 infoPlist 例外。
- 键盘挡住输入框:用
KeyboardAvoidingView 包住页面,iOS 设 behavior="padding",Android 设 height 或 padding。
- Pressable 点了没反应:检查是不是包在 ScrollView 里被手势拦截了;或外层有绝对定位元素(zIndex 更高)遮挡。
- 组件报错
Invariant Violation: View config getter...:引入了一个不存在的组件名,检查拼写和 import 路径。
- 样式不生效:RN 样式不支持
float、display: block/inline、position: sticky 等 Web 概念。布局全部靠 Flexbox 和 position: 'absolute'。
面试常问
-
TouchableOpacity 和 Pressable 的区别?
Pressable 是新一代官方推荐按压组件,API 更灵活(pressed 状态、onPressIn/onPressOut、hitSlop);TouchableOpacity 是旧 API,只提供透明度变化反馈,已不推荐使用。新项目用 Pressable。
-
StyleSheet.create 和内联样式的区别?
功能等价,但 StyleSheet.create 会做属性校验、去重,且样式对象只跨桥传递一次,性能更好,可读性和可维护性也更好。开发期建议全部用 StyleSheet.create。
-
RN 布局为什么只用 Flexbox?
Flexbox 一套模型足以描述移动端绝大多数布局,Meta 开源的 Yoga 引擎可以高效把它翻译成原生布局计算,跨端一致。RN 不支持 display: grid(虽然 Yoga 在实验性支持),实战中 Flexbox 已经够用。
-
TextInput 的受控和非受控写法?
受控:value + onChangeText 同步 state,是 RN 的标准做法;非控:只给 defaultValue,之后不再管理。RN 里非受控有已知 bug(如中文输入),强烈建议用受控。
自测三问
- RN 中文字必须放在哪个组件里?View 里直接写文字会怎样?
- 网络图片和本地图片的引入方式分别是什么?网络图片有什么必须做的事?
- 为什么实际项目用 Pressable 而不是 Button?Pressable 的
style 如何响应按压状态?
小结一句话
核心组件就那几个,但记住三条铁律——文字进 Text、图片给宽高、容器用 flex: 1——你就能搭出任何静态界面。
延伸