要点
JSX 表达组件树,View 组织布局,Text 显示文字。原生布局中的尺寸、主轴和交叉轴需要结合父容器理解。
核心组件速览
| 组件 | 类比 Web | 用途 |
|---|---|---|
View | div | 布局容器,支持 Flexbox |
Text | p/span | 唯一能渲染文本的组件 |
Image | img | 网络图/本地资源/URI 图片 |
ScrollView | overflow 滚动区 | 全量渲染内容的滚动容器 |
FlatList | 无直接对应 | 虚拟化长列表,按需渲染 |
核心规则: RN 中文本必须包在 <Text> 里,<View>hello</View> 会直接报错。
样式与 Flexbox
RN 样式是 CSS 的子集,没有单位(数字 = 密度无关像素 dp),不支持伪类与选择器,属性名一律驼峰。
import { StyleSheet, Text, View } from 'react-native';
function LayoutDemo() {
return (
// 默认 flexDirection 是 'column'(Web 默认 row),这是新手第一大坑
<View style={styles.page}>
<View style={styles.header}>
<Text style={styles.headerText}>顶部栏</Text>
</View>
<View style={styles.body}>
<View style={styles.card}>
<Text>卡片一:flex:1 平分剩余空间</Text>
</View>
<View style={styles.card}>
<Text>卡片二</Text>
</View>
</View>
<View style={styles.footer}>
<Text>底部固定栏</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
page: { flex: 1 },
header: { height: 56, backgroundColor: '#4A90D9', justifyContent: 'center', alignItems: 'center' },
headerText: { color: '#fff', fontSize: 18 },
body: { flex: 1, flexDirection: 'row', padding: 12, gap: 12 },
card: { flex: 1, backgroundColor: '#f0f2f5', borderRadius: 8, padding: 16 },
footer: { height: 64, backgroundColor: '#eee', justifyContent: 'center', alignItems: 'center' },
});
Web ↔ RN 差异对照:
flexDirection默认column(Web 默认row)- 主轴为 column 时,
alignItems/justifyContent的作用方向与 Web 直觉相反 - 无
position: static;默认relative,absolute相对父容器定位 - 支持简写
flex: 1(等价flexGrow: 1, flexShrink: 1, flexBasis: 0%) - 尺寸无
px/rem,纯数字按 dp/pt 处理;可用PixelRatio换算
Image 的三种来源
import { Image } from 'react-native';
// 1. 本地静态资源(构建期打包,必须用 require 且路径为字面量)
const imageExamples = <>
<Image source={require('./assets/logo.png')} style={{ width: 40, height: 40 }} />
// 2. 网络图片(必须显式声明宽高)
<Image
source={{ uri: 'https://example.com/avatar.png' }}
style={{ width: 48, height: 48, borderRadius: 24 }}
/>
// 3. base64 / data URI
<Image source={{ uri: 'data:image/png;base64,iVBORw0KG...' }} style={{ width: 24, height: 24 }} />
</>;
ScrollView vs FlatList
ScrollView: 一次性渲染全部子元素。适合表单、设置页等确定数量的内容。
FlatList: 只渲染可视区域附近的条目。数据量未知、可能持续增长,或行组件成本较高时优先考虑它;不存在适用于所有设备的“超过 20 条就必须换”的阈值。
import { FlatList, Text, View } from 'react-native';
interface Todo {
id: string;
title: string;
done: boolean;
}
const TODOS: Todo[] = Array.from({ length: 200 }, (_, i) => ({
id: `${i}`,
title: `任务 ${i + 1}`,
done: i % 3 === 0,
}));
const ROW_HEIGHT = 57; // 本示例:56 高的行 + 1 高的分隔线
function TodoList() {
return (
<FlatList
data={TODOS}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={{ height: ROW_HEIGHT - 1, justifyContent: 'center', paddingHorizontal: 16 }}>
<Text style={{ textDecorationLine: item.done ? 'line-through' : 'none' }}>
{item.title}
</Text>
</View>
)}
// 性能相关 props 的完整清单见 reference 组件 Props 全表
initialNumToRender={10}
getItemLayout={(_data, index) => ({
length: ROW_HEIGHT, // 只在“行高与分隔线总高度真的固定”时使用
offset: ROW_HEIGHT * index,
index,
})}
ListEmptyComponent={<Text>暂无待办</Text>}
ItemSeparatorComponent={() => <View style={{ height: 1, backgroundColor: '#eee' }} />}
/>
);
}
FlatList 性能 props(windowSize、removeClippedSubviews 等)的完整参数表见 组件 Props 全表。
最佳实践
列表中的 key 用来识别项目身份,插入或排序后仍应对应同一条数据;用数组下标可能让输入状态跟错行。大量内容用虚拟列表减少同时挂载的项目,并通过滚动检查空白、复用和触控行为。
StyleSheet 有助于组织与检查样式,动态样式对象也合法;是否值得稳定引用要看实际渲染成本。网络图需要可确定的布局尺寸,测量布局用 onLayout,不靠猜测延时。长列表的页头可交给列表自身,避免外层同向滚动容器削弱虚拟化。
常见问题
Q1: 为什么我的元素不显示?
A: 三查——父容器是否有 flex: 1 撑开、元素是否有显式宽高或 flex、Text 是否存在。RN 没有 Web 的”内容自动撑高”兜底那么宽松。
Q2: 阴影样式在 Android 上不生效?
A: 先检查所用 RN 版本及是否采用新架构:较旧 Android 实现常用 elevation,而 shadowColor、shadowOffset、shadowOpacity、shadowRadius 的跨平台支持随版本变化。为目标版本分别在 Android/iOS 设备确认,不要只在一端看到阴影就假定另一端相同。
Q3: FlatList 滑动掉帧怎么办?
A: getItemLayout 跳过测量、renderItem 里的组件用 React.memo、图片用固定宽高,进阶优化见 07-advanced-features。
练习与实践
练习一:个人资料卡
任务要求: 1. 用 View/Image/Text 实现一张资料卡:头像(网络图)、姓名、简介、三个横排统计数字 2. 头像圆形,卡片圆角带 elevation 阴影
评估标准: 在已选目标平台确认头像不变形、长简介会换行且统计数字不溢出;第二平台可作为后续差异检查,记录实际差异而非预设“必然一致”。
练习二:长列表性能对比
任务要求: 1. 分别用 ScrollView 和 FlatList 渲染 500 条数据,给每一行加入稳定 id。 2. 在同一台目标设备上快速滚动、跳转到中部,再删掉一项;观察初次打开时间、滚动是否空白和删除后内容是否错行。
提示: ScrollView 是否卡顿取决于设备和行组件复杂度,不能把某个数量当作必然 OOM 的证明。验收重点是:长列表需要按需渲染时,FlatList 的稳定 key、行高度假设和滚动行为都可解释。
相关文档
- 📄 状态与 Hooks: 下一课,让组件动起来
- 📄 组件 Props 全表: 本文组件的完整属性字典
- 📄 React Navigation API 速查: 搭配导航组件使用
- 📄 TS 类型模式: 给组件 Props 加类型
💡 学习建议: 每学一个组件就在练习 App 里造一个真实场景的小界面,比通读文档十遍都有效。
可复跑:JSX 子节点的结构契约
React Native 的 <Text>、<View> 等最终是 React 元素。JSX 中嵌套数组与条件子节点会先经过 React 的子节点规则;列表项必须提供稳定 key,不能把“数组能渲染”误解成“下标 key 在插入后仍安全”。下面运行真实 React 的 Children.toArray,只验证 JSX/React 的元素结构,不声称验证原生 Text 渲染或 Flexbox。
import React from 'react'
const rows = [{ id: 'a', title: '第一项' }, { id: 'b', title: '第二项' }]
const children = rows.map((row) => React.createElement('Text', { key: row.id }, row.title))
const flattened = React.Children.toArray([null, children, false])
if (flattened.length !== 2) throw new Error('空条件节点不应成为可渲染条目')
if (flattened.map((child) => child.props.children).join(',') !== '第一项,第二项') {
throw new Error('JSX 子节点顺序错误')
}
if (!flattened.every((child) => child.key !== null)) throw new Error('列表项缺少稳定 key')
console.log('React Native JSX children contracts passed')
要验证原生规则,请继续在 Android 与 iOS 模拟器或真机运行:View 下的裸文本报错、长文本换行、无障碍树和触摸区域都属于宿主平台行为,不由这段 Node 程序覆盖。