RN 组件就是原生 UI
React Native 的 UI 组件与浏览器中的 HTML 元素有本质区别。当你在浏览器里写 <div>,浏览器使用自己的渲染引擎绘制一个盒子。但 React Native 里写 <View>,框架会调用平台原生 API 创建一个真正的原生视图——iOS 上是 UIView,Android 上是 android.view.View。
这就是为什么 React Native 的用户体验比 WebView 方案好得多:动画、滚动、手势等都是原生实现,系统的无障碍(Accessibility)、键盘管理、安全区域等也自然支持。
核心组件速查
View
最基础的容器组件,相当于 HTML 的 div。支持 Flexbox 布局、样式、触摸事件。几乎所有其他组件都是基于 View 构建的。
Text
唯一用于显示文字的组件。与 Web 不同,RN 里所有文本必须包裹在 <Text> 中,不能直接在 <View> 里写纯文字。支持嵌套实现富文本样式。
Image
显示图片,支持本地资源(require)和网络图片({uri: '...'})。网络图片需要指定 width 和 height,否则不会显示。
ScrollView
可滚动的容器,会一次性渲染所有子元素。适合内容量少(少于 20 个)的场景。内容过多时会导致内存爆炸和卡顿。
FlatList
虚拟化列表,只渲染当前可见区域附近的元素,超出视口的元素会被回收复用。大列表的唯一正确选择,支持下拉刷新、上拉加载等。
SectionList
带分组标题的虚拟化列表,适合联系人列表、分类展示等场景。数据格式为 [{title, data: [...]}]。
TextInput
文本输入框,受控组件模式(value + onChangeText)。注意 iOS 和 Android 的默认样式差异,通常需要自定义样式统一外观。
TouchableOpacity
点击时降低不透明度给予视觉反馈的可触摸容器。是实现按钮最常用的方案之一。activeOpacity 属性控制按下时的透明度(默认 0.2)。
Pressable
新一代可触摸组件,API 更灵活,支持 pressed 状态样式、hitSlop 扩大点击区域、Android ripple 水波纹效果。推荐新项目使用。
SafeAreaView
自动处理 iPhone 刘海/灵动岛、Android 状态栏的安全区域,确保内容不被遮挡。推荐用 react-native-safe-area-context 的版本,兼容性更好。
Flexbox 布局——RN 的默认值与 Web 不同
React Native 使用 Flexbox 进行布局,与 CSS Flexbox 概念相同,但有几个重要的默认值差异,初学者经常在这里踩坑:
CSS Flexbox 默认值
- flexDirection: row(横向排列)
- alignContent: stretch
- flexShrink: 1
- position: static
RN Flexbox 默认值
- flexDirection: column(纵向排列)✦
- alignContent: flex-start ✦
- flexShrink: 0 ✦
- position: relative
最重要的一点:RN 中 flexDirection 默认是 column(垂直方向),而 Web CSS 默认是 row(水平方向)。理解这一点,就能避免大量布局困惑。
此外,RN 中的 flex: 1 非常常用,它让组件占满父容器的剩余空间。要让根容器撑满整个屏幕,只需给容器设置 flex: 1。
FlatList 虚拟化原理
FlatList 是 React Native 处理长列表的核心组件。理解它的虚拟化机制,能帮助你写出高性能的列表界面。
FlatList 虚拟化列表渲染原理
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
屏幕可视区域(viewport)
┌────────────────────────┐
│ Item 3 [已渲染] │ ← initialNumToRender 范围
│ Item 4 [已渲染] │
│ Item 5 [已渲染] │ ← 当前可见区域
│ Item 6 [已渲染] │
│ Item 7 [已渲染] │ ← windowSize 预渲染缓冲
└────────────────────────┘
↕ 用户滚动
Item 1 [已卸载/回收] ← 超出 windowSize 范围被回收
Item 2 [已卸载/回收]
...
Item 8 [已渲染/预载] ← 即将进入视口,提前渲染
Item 9 [未渲染]
...
Item 1000 [未渲染] ← 远离视口,不占内存
关键参数:
• initialNumToRender = 10 首屏渲染数量
• windowSize = 21 以屏幕高度为单位的渲染窗口(前后各10屏)
• maxToRenderPerBatch = 10 每批次最多渲染数量
• updateCellsBatchingPeriod = 50ms 批次更新间隔
警告
永远不要在大列表(超过 20 条数据)里用
ScrollView。ScrollView 会一次性渲染所有子元素并保持在内存中,1000 条数据就意味着 1000 个原生视图同时存在,导致严重卡顿和内存溢出崩溃。
完整 FlatList 示例
import {
FlatList, View, Text, Image,
RefreshControl, StyleSheet,
ListRenderItem
} from 'react-native';
import { useState, useCallback } from 'react';
interface Post {
id: string;
author: string;
avatar: string;
content: string;
likes: number;
}
const POSTS: Post[] = Array.from({ length: 100 }, (_, i) => ({
id: String(i),
author: `用户 ${i + 1}`,
avatar: `https://i.pravatar.cc/40?img=${i % 70}`,
content: `这是第 ${i + 1} 条帖子的内容,展示 FlatList 的用法。`,
likes: Math.floor(Math.random() * 1000),
}));
// 抽离 renderItem 为独立组件,配合 React.memo 避免不必要的重渲染
const PostItem = React.memo(({ item }: { item: Post }) => (
<View style={styles.item}>
<Image source={{ uri: item.avatar }} style={styles.avatar} />
<View style={styles.itemBody}>
<Text style={styles.author}>{item.author}</Text>
<Text style={styles.content}>{item.content}</Text>
<Text style={styles.likes}>♥ {item.likes}</Text>
</View>
</View>
));
export default function FeedScreen() {
const [refreshing, setRefreshing] = useState(false);
const handleRefresh = useCallback(async () => {
setRefreshing(true);
await new Promise(r => setTimeout(r, 1500)); // 模拟网络请求
setRefreshing(false);
}, []);
return (
<FlatList
data={POSTS}
renderItem={({ item }) => <PostItem item={item} />}
keyExtractor={item => item.id} // 必须提供,用于 diff 优化
initialNumToRender={10} // 首屏渲染数量
windowSize={5} // 渲染窗口(5倍屏高)
maxToRenderPerBatch={5} // 每批次渲染数量
removeClippedSubviews={true} // 裁剪屏幕外的视图(Android)
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor="#38bdf8"
/>
}
ListEmptyComponent={
<Text style={styles.empty}>暂无数据</Text>
}
/>
);
}
StyleSheet 最佳实践
StyleSheet.create() 不只是组织代码的方式,它在运行时会将样式对象序列化为 ID 引用,减少跨线程传输的数据量。此外,它还提供完整的 TypeScript 类型推断,能在编写时发现样式属性拼写错误。
最佳实践
避免在 JSX 中写内联样式对象(
style={{ color: 'red' }}),每次渲染都会创建新对象,对垃圾回收造成压力。应始终用 StyleSheet.create 在组件外部定义样式,或使用样式数组 style={[styles.base, isActive && styles.active]}。
处理键盘遮挡(KeyboardAvoidingView)
import {
KeyboardAvoidingView, Platform,
ScrollView, TextInput
} from 'react-native';
export function CommentForm() {
return (
// iOS 用 padding,Android 用 height
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<ScrollView>
{/* 表单内容 */}
</ScrollView>
<TextInput
placeholder="写下你的评论..."
style={styles.input}
multiline
/>
</KeyboardAvoidingView>
);
}