引言
React Native
提供了许多内置的 核心组件
, 相当于 Web
开发中的基础的 HTML
标签组件。 和 Web
开发类似后续开发中我们都是基于语言所提供的核心组件来绘制页面或者封装复杂的可复用的组件。
本文将按照 官方文档 的分类, 对 React Native
所提供的一些基础组件进行展开学习。
一、基本组件
用于构建 UI
的最基本的组件....
1.1 容器组件: View
可以对标为 HTML
中的 div
标签, View
是构建 UI
的最基本组件, 它就是一个容器, 支持带有 flexbox
、样式、一些触摸处理和辅助功能控件的布局。视图直接映射到 React Native
运行的任何平台上的原生视图。
View
和 div
一样, 可以有 0
到多个任何类型的子项
js
import { View } from 'react-native';
export default function HomeScreen() {
return (
<View>
<View/>
<View/>
</View>
);
}
View
如 div
一样, 默认情况下该组件独占一行(块级元素)
js
import { View } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ padding: 100 }}>
<View style={{ width: 50, height: 50, backgroundColor: 'red' }} />
<View style={{ width: 50, height: 50, backgroundColor: 'blue' }} />
</View>
);
}

View
有且只支持 flex
布局, 直接设置相关属性即可, 无需手动设置为 flex
布局(display: flex;
)
js
import { View } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ height: 100, flexDirection: 'row', margin: 10, marginTop: 100 }}>
<View style={{ backgroundColor: 'blue', flex: 0.2 }} />
<View style={{ backgroundColor: 'red', flex: 0.4 }} />
</View>
);
}

View
所支持的 props
熟悉还是挺多的, 具体的可以查看 官方文档, 常用的就是绑定事件、以及设置属性
js
import { View } from 'react-native';
export default function HomeScreen() {
return (
<View
onTouchMove={() => { console.log(111) }}
style={{ height: 100, width: 100, backgroundColor: 'blue' }}
/>
);
}
1.2 文本组件: Text
不同于 Web
开发, 在 React Native
中所有文本都必须使用 Text
进行包裹, 否则是展示不出来的!
js
import { View, Text } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ padding: 100 }}>
<Text style={{ fontSize: 20 }}>可以正常展示</Text>
不能展示
</View>
);
}

Text
组件同样支持设置样式、嵌套和触摸处理。
js
import { View, Text } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ padding: 100 }} >
<Text style={{ fontSize: 20 }} onPress={() => {console.log(1)}}>
可以正常展示
{'\n'}
<Text>支持嵌套</Text>
</Text>
</View>
);
}

对于嵌套的 Text
组件, 组件间的样式也是相互继承的
js
import { View, Text } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ padding: 100 }} >
<Text style={{ fontSize: 20, color: 'red' }}>
可以正常展示
{'\n'}
<Text>支持嵌套</Text>
</Text>
</View>
);
}

Text
组件不能使用 Flexbox
布局, Text
中的所有内容, 都是使用文本布局。这意味着 Text
内的元素不再是矩形, 而是在看到行尾时换行(内联)
js
import { View, Text } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ margin: 100 }}>
<Text style={{ flexDirection: 'column', fontSize: 20 }}>
<Text>第一段文本</Text>
<Text>第二段文本</Text>
</Text>
</View>
);
}

样式继承问题: 在 Web
上, 为整个文档设置字体系列和大小的常用方法是利用继承的 CSS
属性, 为最外层元素我们可以设置通用的一些样式。如下代码, 为根元素设置了字体样式, 之后文档中的所有元素都将继承此样式, 除非它们或其父元素之一指定了新规则。
js
html {
font-family: 'lucida grande', tahoma, verdana, arial, sans-serif;
font-size: 11px;
color: #141823;
}
然而在 React Native
中, 我们要求更加严格: 你必须将所有文本节点包装在 Text
组件中。不能在 View
下直接拥有文本节点, 而且也有且只能为 Text
组件设置字体相关的样式。

所以这里我们是无法在 View
上为整个子树设置默认字体, 与此同时 fontFamily
也只接受单个字体名称, 这与 CSS
中的 font-family
不同。
所以在开发应用程序中要想为文字设置默认的、一致的字体样式, 推荐方法是封装一个通用的组件 MyAppText
, 并在您的应用程序中使用此组件。当然我们还可以使用此组件创建更具体的组件, 例如用于其他类型的文本组件 MyAppHeaderText
js
const MyAppText = ({ children, fontSize = 20 }) => (
<Text style={{ fontSize, fontFamily: 'tahoma' }}>
{children}
</Text>
)
const MyAppHeaderText = ({ children }) => (
<MyAppText fontSize={30}>
{children}
</MyAppText>
)
export default function HomeScreen() {
return (
<View style={{ margin: 50, marginTop: 100 }}>
<Text >
<MyAppHeaderText>MyAppHeaderText</MyAppHeaderText>
{'\n'}
<MyAppText>MyAppText</MyAppText>
</Text>
</View>
);
}

当然 React Native
仍然具有样式继承的, 但仅限于文本子树。如下代码, 第二部分将同时为粗体、大号字体和红色。
js
import { View, Text } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ margin: 50, marginTop: 100 }}>
<Text style={{ fontWeight: 'bold', fontSize: 40 }}>
I am bold
<Text style={{ color: 'red' }}>and red</Text>
</Text>
</View>
);
}

1.3 图片组件: Image
同 img
标签, 在 React Native
中如果我们需要展示图片资源, 则需要使用 Image
组件。
使用方法如下:
- 使用
source
来指定图片资源 - 注意: 对于网络和数据图像, 我们需要手动指定图像的尺寸, 否则不能展示
js
import { View, Image } from 'react-native';
import logo from '@/assets/images/react-logo.png';
export default function HomeScreen() {
return (
<View style={{ margin: 50, marginTop: 100 }}>
<Image source={logo} />
<Image
style={{ width: 50, height: 50 }}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/>
<Image
style={{ width: 50, height: 50 }}
source={{ uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==' }}
/>
</View>
);
}

1.4 文本输入组件: TextInput
同 input
在 React Native
中可使用 TextInput
来处理用户的输入。
如下代码是一个最基本使用例子, 如果你使用过 React
相信应该是很容易理解的! 代码中使用了 TextInput
组件来获取用户的输入, 并订阅 onChangeText
事件以读取用户输入。
js
import { useState } from 'react';
import { View, TextInput } from 'react-native';
export default function HomeScreen() {
const [text, setText] = useState();
return (
<View style={{ margin: 50, marginTop: 100 }}>
<TextInput
style={{ borderWidth: 1, height: 40, padding: 10 }}
onChangeText={(text) => {
console.log(text)
setText(text)
}}
value={text}
/>
</View>
);
}

对于多行文本, 在 Web
中我们可以使用 textarea
, React Native
则沿用 TextInput
通过 multiline
属性来区分, 同时在 Android
中 numberOfLines
可限制文本区域输入框要展示的行数, 当然在 IOS
中则只能通过样式来实现, 比如: height
、minHeight
、maxHeight
js
<TextInput
multiline
numberOfLines={4} // 针对 Android
style={{ borderWidth: 1, padding: 10, height: 80 }}
/>

同样的, 在 React Native
也是可以使用 Ref
获取到原生组件实例, 在实例上同样是有 focus
、blur
等方法

补充: 如果在 IOS
模拟器中无法输入内容, 记得开启下如下配置:

1.5 滚动视图组件: ScrollView
不同于 Web
在 React Native
中, 常规的容器 View
是不支持滚动的
js
import { View, Text, ScrollView } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ marginTop: 100, margin: 50, padding: 20, height: 200, backgroundColor: 'pink' }}>
<Text style={{ fontSize: 30 }}>
Keep in mind that ScrollViews must have a bounded height in order to work, since they contain unbounded-height children into a bounded container (via a scroll interaction). In order to bound the height of a ScrollView, either set the height of the view directly (discouraged) or make sure all parent views have bounded height. Forgetting to transfer down the view stack can lead to errors here, which the element inspector makes quick to debug.
</Text>
</View>
);
}

在 React Native
中, 如果需要一个支持滚动的容器, 需要使用专门的组件 ScrollView
diff
import { View, Text, ScrollView } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ marginTop: 100, margin: 50, padding: 20, height: 200, backgroundColor: 'pink' }}>
+ <ScrollView>
<Text style={{ fontSize: 30 }}>
Keep in mind that ScrollViews must have a bounded height in order to work, since they contain unbounded-height children into a bounded container (via a scroll interaction). In order to bound the height of a ScrollView, either set the height of the view directly (discouraged) or make sure all parent views have bounded height. Forgetting to transfer down the view stack can lead to errors here, which the element inspector makes quick to debug.
</Text>
+ </ScrollView>
</View>
);
}

默认情况下 ScrollView
的高度是自适应的, 会根据父容器进行自适应! 如上代码所示, 我只是给父容器 View
设置了高度, ScrollView
是自动撑满父容器的。当然除此之外, 我们也是可以直接为 ScrollView
通过 Style
来限制高度的, 比如 height
、maxHeight
、minHeight
js
<ScrollView style={{ height: 300 }}>
{/* 内容 */}
</ScrollView>
<ScrollView style={{ minHeight: 300, maxHeight: 500 }}>
{/* 内容 */}
</ScrollView>
ScrollView
同 Web
中的 div
一样, 会一次渲染容器内部的所有子组件, 这样的话就会有一个性能缺点。想象一下, 假设我们有一个长列表, 全部展示的话可能有几百屏幕的内容。如果一次为所有内容创建组件和视图(可是其中大部分甚至可能未显示在用户界面中) 如此将导致渲染速度变慢和内存使用量增加。其实就是我们 Web
中常说的虚拟滚动场景, 对于 Web
段我们需要自己去实现虚拟滚动去弥补这个缺陷, 但是在 React Native
中是有提供基础的组件 FlatList
, 可以帮我们实现虚拟滚动的效果的。当然这里我们不对 FlatList
进行展开, 我们后续会讲到。
1.6 样式表: StyleSheet
下面是几种常见样式写法:
- 内联, 直接在组件内部进行编码
js
<View style={{ height: 100, width: 100, backgroundColor: 'red' }}>
</View>
- 组件外部引用, 将对象抽离到组件外部
js
const style = {
box: {
height: 100,
width: 100,
backgroundColor: 'red',
}
}
export default function HomeScreen() {
return (
<View style={style.box}></View>
);
}
- 使用官方提供的
StyleSheet
js
const style = StyleSheet.create({
box: {
height: 100,
width: 100,
backgroundColor: 'red',
}
})
export default function HomeScreen() {
return (
<View style={style.box}></View>
);
}
那么上面几种方式又有什么区别呢?
- 首先说下内联写法, 这种写法其实在
Web
开发中也是经常被用到的, 其最主要的缺点就是, 内联写法导致每次组件重新渲染时, 都会创建新的样式对象, 可能导致不必要的性能开销; 当然好处就是可以根据state
或者props
进行动态计算样式 - 第二种方式只是做了简单的抽离, 只是相对内联的写法稍微好点, 缺点就是无法根据
state
或者props
进行动态计算样式, 同时在编写样式时TS
的作用就没了, 没法进行智能提示 - 第三种方式则是推荐的写法, 该写法
React Native
做了很多优化:StyleSheet.create
会在JS
端进行预编译样式, 并分配一个ID
, 而React Native
在原生端
其实是引用这些ID
, 而不是解析去JS
对象(因为已经在声明时预编译好了), 同时这样的话也方便进行一些样式的拆分、复用。可以减少JS
线程的计算量, 提高渲染效率; 如下代码所示styles.box
只会在应用初始化时创建一次, 然后React Native
在内部存储这个样式, 并分配一个ID
(例如1
) 来引用它, 而不是每次都解析整个对象。
js
const style = StyleSheet.create({
box: {
height: 100,
width: 100,
backgroundColor: 'red',
}
})
StyleSheet.create
有效优点:
- 通过将
样式
从JSX
中分离
, 可以使代码更易于理解、同时也更方便样式复用 - 在大多数
IDE
中, 使用StyleSheet.create()
将提供静态类型检查和建议, 以帮助您编写有效的样式
StyleSheet.compose(style1, style2)
: 样式复用(合并)样式, 可将 style2
和 style1
中的样式进行合并, 如果有相同的样式规则, 则 style2
中的样式将会覆盖 style1
中的样式
js
const page = StyleSheet.create({
container: {
flex: 1,
padding: 24,
},
text: { },
});
const lists = StyleSheet.create({
listContainer: {
flex: 1,
backgroundColor: '#61dafb',
},
listItem: {},
});
const style = StyleSheet.compose(page.container, lists.listContainer);
为了做到更好的性能同时避免样式被修改, 保证样式的一致性, 提高安全性, 默认情况下 StyleSheet.create
和 StyleSheet.compose
生成的样式规则对象, 是一个冻结对象(immutable
), 也就是说我们是无法直接操作其中的样式规则的。
js
const page = StyleSheet.create({
container: {
flex: 1,
padding: 24,
},
text: { },
});
const lists = StyleSheet.create({
listContainer: {
flex: 1,
backgroundColor: '#61dafb',
},
listItem: {},
});
const style = StyleSheet.compose(page.container, lists.listContainer);
console.log(Object.isFrozen(style?.[0])) // true
console.log(Object.isFrozen(page.container)) // true
而你如果想要拿到一个普通的样式规则, 然后基于此进行操作的话。可以使用 StyleSheet.flatten
来实现
js
const page = StyleSheet.create({
container: {
flex: 1,
padding: 24,
},
text: { },
});
const lists = StyleSheet.create({
listContainer: {
flex: 1,
backgroundColor: '#61dafb',
},
listItem: {},
});
const style = StyleSheet.flatten([page.container, lists.listContainer]);
console.log(style); // { flex: 1, padding: 24, backgroundColor: '#61dafb' }
console.log(Object.isFrozen(style)); // false
二、用户界面
一些可供用户交互的组件...
2.1 按钮组件: Button
不同于 Web
在 React Native
中是无法直接为 View
容器组件绑定按压(点击)事件的

React Native
提供了一个最基本的按钮组件, 支持最低程度的自定义。如下代码所示, 则是最基本的一个实现, 其中 title
和 onPress
是最要属性:
title
: 按钮要显示的文本内容onPress
: 按压事件回调函数
js
import { View, Button } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ margin: 100 }}>
<Button
title="Learn More"
onPress={() => console.log('按下了')}
/>
</View>
);
}
官方提供的按钮组件, 只能实现一个最基本的按钮样式。如果需要定制更复杂的样式, 或者需要一整个容器都允许接收按压事件的话。则需要使用 Pressable
来进行构建自己的按钮。
js
// 最基本
<Pressable onPress={() => {}}>
<Text>I'm pressable!</Text>
</Pressable>
// 允许包裹任何内容
<Pressable onPress={() => console.log('按下了')}>
<View style={{ width: 100, height: 100, backgroundColor: 'pink', marginBottom: 50 }} >
</View>
<Text>我也是可以按压的</Text>
</Pressable>
丰富的事件, 可实现更友好的交互效果:
- 当用户按压时会先触发
onPressIn
事件 - 当用户按压行为停止时会触发
onPressOut
事件 - 在上面逻辑不变的情况下, 如果用户按压时间超过
500
毫秒, 还会触发onLongPress
事件

来看一个复杂的交互: 按压时, 调整容器的样式, 按压结束则恢复
js
import { useState } from 'react';
import { View, Text, Pressable } from 'react-native';
export default function HomeScreen() {
const [isPressing, setIsPressing] = useState(false)
return (
<View style={{ margin: 100 }}>
<Pressable
onPressIn={setIsPressing.bind(null, true)}
onPressOut={setIsPressing.bind(null, false)}>
<View
style={{
width: 100,
height: 100,
backgroundColor: isPressing ? 'pink' : 'red',
}}
/>
</Pressable>
</View>
);
}
2.2 开关组件: Switch
官方提供的一个受控的 Switch
组件, 需要注意的是该组件需要 onValueChange
和 value
属性配合使用, 如果 value
属性未及时更新, 组件将继续呈现所提供的 value
属性(默认 false
)
js
import { useState } from 'react';
import { View, Switch } from 'react-native';
export default function HomeScreen() {
const [value, setValue] = useState(false)
const handleValueChange = () => setValue(pre => !pre)
return (
<View style={{ margin: 100 }}>
<Switch
value={value}
onValueChange={handleValueChange}
// trackColor={{false: '#767577', true: '#81b0ff'}}
// thumbColor={isEnabled ? '#f5dd4b' : '#f4f3f4'}
// ios_backgroundColor="#3e3e3e"
/>
</View>
);
}

三、列表视图
与更通用的 ScrollView
不同, 官方还提供了两个更为强大的列表组件, 主要适用于长列表。实现了类似虚拟滚动的效果, 在渲染大列表数据时只会渲染当前屏幕上的元素, 从而提高应用性能。
3.1 扁平列表组件: FlatList
专门用于渲染扁平列表的高性能组件, 如上文所述, 该组件自带 「虚拟滚动」
效果, 对于长列表只会渲染当前屏幕用户可见内容。从而极大的提升性能。
如下代码是一个基本的 Demo
, 不难想象, 组件 FlatList
中 data
和 renderItem
一个提供列表数据, 一个则是负责渲染列表项。
js
import { View, Text, FlatList } from 'react-native';
const DATA = Array.from({ length: 100 }, (_, index) => (
{
id: `bd7acbea-c1b1-46c2-aed5-3ad53abb28ba-${index}`,
title: `Item_${index}`,
}
))
const Item = ({ title }) => (
<View style={{ margin: 5, padding: 5, backgroundColor: 'pink' }}>
<Text style={{ fontSize: 20 }}>{title}</Text>
</View>
)
export default function HomeScreen() {
return (
<View style={{ margin: 100, height: 200 }}>
<FlatList
data={DATA}
renderItem={({ item }) => <Item title={item.title} />}
/>
</View>
);
}

如下代码 keyExtractor
用于设置每个列表项的 key
值。默认列表项先尝试使用 item.key
, 如果不存在则尝试使用 item.id
, 实在都没有则回退到使用索引
js
<FlatList
data={DATA}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Item title={item.title} />}
/>
当然 FlatList
功能还是很强大的, 这里就不一一展开了, 具体可实现的功能如下:
- 完全跨平台, 无需考虑兼容问题
- 支持水平模式
- 可配置的可见性回调: 其实就是可监听列表项, 进入或移出视口事件
- 支持设置标题、页脚、分隔符
- 支持下拉刷新
- 支持滚动加载
- 支持
ScrollToIndex
: 即允许滚动到指定某条数据 - 支持多列展示
补充: 其实
FlatList
是基于虚拟列表组件VirtualizedList
进行封装出来的, 大部分场景我们直接使用FlatList
即可, 当然你的场景比较特殊, 那么可以尝试使用更为灵活、底层的VirtualizedList
组件
3.2 分区列表组件: SectionList
SectionList
组件可用于实现如下分组列表, 同 FlatList
一样该组件底层也是使用 VirtualizedList
组件所以也是支持虚拟滚动效果的, 对于长列表也只会渲染用户可见内容。

用法基本和 FlatList
类似, sections
设置数据源, renderItem
渲染列表项, renderSectionHeader
则设置分组头部
js
import { View, Text, SectionList } from 'react-native';
const DATA = Array.from({ length: 100 }, (_, index) => (
{
title: `Group_${index}`,
id: `bd7acbea-c1b1-46c2-aed5-3ad53abb28ba-${index}`,
data: ['French Fries', 'Onion Rings', 'Fried Shrimps'],
}
))
export default function HomeScreen() {
return (
<View style={{ margin: 100, height: 200 }}>
<SectionList
sections={DATA}
renderItem={({item}) => (
<View style={{ backgroundColor: '#FED6D6', margin: 5, padding: 5 }}>
<Text style={{ fontSize: 20 }}>{item}</Text>
</View>
)}
renderSectionHeader={({ section: { title } }) => (
<View style={{ backgroundColor: '#E0E0E0', margin: 5, padding: 5 }} >
<Text style={{ fontSize: 30 }}>{title}</Text>
</View>
)}
/>
</View>
);
}

默认情况下 SectionList
组件是自带吸顶效果的, 如下图所示:

可通过 stickySectionHeadersEnabled
属性进行关闭
diff
<SectionList
+ stickySectionHeadersEnabled={false}
....
>
</SectionList>
当然 SectionList
功能还是很强大的, 这里就不一一展开了, 具体可实现的功能如下:
- 完全跨平台, 无需考虑兼容问题
- 可配置的可见性回调: 其实就是可监听列表项, 进入或移出视口事件
- 支持设置标题、页脚、分组标题、可设置分组间分隔符、列表项之间的分隔符
- 支持下拉刷新
- 支持滚动加载
- 支持异构数据和多样化的项渲染
补充: 其实
SectionList
是基于虚拟列表组件VirtualizedList
进行封装出来的, 大部分场景我们直接使用SectionList
即可, 当然你的场景比较特殊, 那么可以尝试使用更为灵活、底层的VirtualizedList
组件
3.3 虚拟列表组件: VirtualizedList
VirtualizedList
是 React Native
提供的一个高性能长列表组件, 它是 FlatList
和 SectionList
的底层实现。相比 ScrollView
它能够高效渲染超大数据量的列表, 避免性能问题。如果项目远离用户可见区域, 则以低优先级 (在任何正在运行的交互之后) 逐步渲染项目, 否则以高优先级渲染项目, 以最大限度地减少看到空白区域的可能性。
大部分情况我们更推荐使用, 更方便的 FlatList
和 SectionList
组件, 这些组件也有更好的文档。一般来说, 只有当您需要比 FlatList
提供的更大的灵活性时才应该使用它。
如下代码是 VirtualizedList
的一个基本用例:
js
import { View, Text, VirtualizedList } from 'react-native';
const getItem = (data, index) => ({
id: Math.random().toString(12).substring(0),
title: `Item ${index + 1}`,
});
const getItemCount = (data) => 50;
const Item = ({ title }) => (
<View style={{ backgroundColor: 'pink', margin: 5, padding: 5 }}>
<Text style={{ fontSize: 20 }}>{title}</Text>
</View>
);
export default function HomeScreen() {
return (
<View style={{ margin: 100, height: 200 }}>
<VirtualizedList
getItem={getItem}
initialNumToRender={4}
getItemCount={getItemCount}
renderItem={({item}) => <Item title={item.title} />}
/>
</View>
);
}

四、Android 定制化组件 & API
简单介绍下 React Native
中专门针对 Android
设计的几个组件或 API
4.1 应用退出 API: BackHandler
BackHandler
是 React Native
提供的 API
, 专门用于监听 Android
设备的物理返回键(IOS
因为没有物理返回键, 所以不适用)
那么该 API
适用于哪些场景呢?
- 监听
Android
物理返回键 (比如在App
里按返回键) - 自定义返回逻辑 (比如二次确认退出)
- 拦截默认行为 (比如在某些页面阻止返回桌面)
如下代码, 是一个简单的 Demo
:
js
useEffect(() => {
// 事件处理函数
const handleBackHandler = () => {
Alert.alert('Hold on!', 'Are you sure you want to go back?', [
{
text: 'Cancel',
onPress: () => null,
style: 'cancel',
},
{ text: 'YES', onPress: () => BackHandler.exitApp() }, // 手动退出应用
]);
return true;
};
// 注册事件
const backHandler = BackHandler.addEventListener('hardwareBackPress', handleBackHandler );
// 移除事件
return () => backHandler.remove();
}, []);
注意事项:
- 事件订阅按相反的顺序调用。即, 后注册的订阅优先被调用
- 同时如果当前执行的事件返回
true
那么后续事件则不再执行, 其实就表示退出应用了。自然后面的事件也就没必要执行了。- 如果订阅的事件都没有返回
true
或者未注册任何订阅,它将以编程方式调用默认的后退按钮功能来退出应用程序。
4.2 抽屉组件: DrawerLayoutAndroid
缘由: 在原生 Android
上, 是提供了一个原生的组件 DrawerLayout
用于实现抽屉效果, 因为是系统级的 UI
组件, 在性能以及用户体验上肯定是最优的。然而在 IOS
上并没有相对应的一个组件。 所以在 React Native
中则专门针对 Android
提供了 DrawerLayoutAndroid
组件, 最终打包出来的产物其实调用的也是原生的 DrawerLayout
组件。然而除非只需要开发 Android
不考虑 IOS
否则我们还是建议使用 react-navigation
的 DrawerNavigator
来实现抽屉导航。尽量减少差异化处理。减少代码的复杂度、维护成本。
如下代码是一个简单 Demo
:
js
const drawerRef = useRef(null); // 引用 DrawerLayoutAndroid 组件
<DrawerLayoutAndroid
ref={drawerRef}
drawerWidth={250} // 侧边栏宽度
drawerPosition="left" // 侧边栏位置(left / right)
renderNavigationView={() => (
<View>
<Text>这里是侧边栏内容</Text>
<Button title="关闭抽屉" onPress={drawerRef.current.closeDrawer} />
</View>
)}>
<View>
<Text>📌 主界面</Text>
<Button title="打开抽屉" onPress={drawerRef.current.openDrawer} />
</View>
</DrawerLayoutAndroid>
4.3 权限处理 API: PermissionsAndroid
安卓中, 对于常规的权限需求我们只需要在 AndroidManifest.xml
配置好即可, 用户在安装应用程序时会默认授予。但是对于一些重要(危险)的权限, 需要给出弹层提示用户。这里则需要特定的 API
进行处理。在 React Native
中则提供了 PermissionsAndroid API
供我们使用。
如下代码是一个简单 Demo
:
js
import { Button, PermissionsAndroid } from 'react-native';
const requestCameraPermission = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CAMERA, // 指定权限
{
title: 'Cool Photo App Camera Permission',
message: 'Cool Photo App needs access to your camera so you can take awesome pictures.' ,
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log('You can use the camera');
} else {
console.log('Camera permission denied');
}
} catch (err) {
console.warn(err);
}
};
<Button title="request permissions" onPress={requestCameraPermission} />
4.4 消息提示组件: ToastAndroid
同 DrawerLayoutAndroid
, 原生 Android
提供了一个系统级的 UI
组件 Toast
组件, 它可以在屏幕底部弹出一个短暂的消息, 但是又不会阻塞用户操作。然而在 IOS
中并没有类似的组件。 所以 React Native
则专门针对 Android
提供了 ToastAndroid
组件。最终编译后在 Android
中则映射为 Toast
组件。同样的, 如果你的应用不止 Android
还需要考虑 IOS
, 这里还是建议使用 Alert
或 react-native-toast-message
。
如下代码是一个简单 Demo
:
js
const showToastWithGravity = () => {
ToastAndroid.showWithGravity(
'All Your Base Are Belong To Us',
ToastAndroid.SHORT,
ToastAndroid.CENTER,
);
};
<Button title="Toggle Toast With Gravity" onPress={showToastWithGravity}/>
五、IOS 定制化组件 & API
简单介绍下 React Native
中专门针对 IOS
设计的几个组件或 API
5.1 操作弹层 API: ActionSheetIOS
一个专属于 IOS
的操作弹出框组件, 在原生 IOS
中, ActionSheet
是系统自带的 UI
组件, 通常用于 弹出操作菜单
。React Native
直接封装了这个原生组件, 因此提供了 ActionSheetIOS
供开发者使用。如果你的应用需要考虑 Android
则推荐使用 Alert
、Modal
或 react-native-actions-sheet
来实现类似效果。
如下代码是一个简单 Demo
:
js
import { ActionSheetIOS, Button } from 'react-native';
const onPress = () => ActionSheetIOS.showActionSheetWithOptions(
{
options: ['Cancel', 'Generate number', 'Reset'],
destructiveButtonIndex: 2,
cancelButtonIndex: 0,
userInterfaceStyle: 'dark',
},
buttonIndex => {
if (buttonIndex === 0) {
// cancel action
} else if (buttonIndex === 1) {
setResult(String(Math.floor(Math.random() * 100) + 1));
} else if (buttonIndex === 2) {
setResult('🔮');
}
},
);
<Button onPress={onPress} title="Show Action Sheet" />
六、其他
6.1 活动指示器组件: ActivityIndicator
其实就是 Web
开发中常见的 Loading
组件
如下代码所示, 使用起来还是比较简单的:
js
import { ActivityIndicator } from 'react-native';
<ActivityIndicator />
<ActivityIndicator size="large" />
<ActivityIndicator size="small" color="#0000ff" />
<ActivityIndicator size="large" color="#00ff00" />

6.2 对话框组件: Alert
用于实现弹出对话框(弹窗/警告框), 可选择提供一组按钮列表, 点击任何按钮都会触发相应的 onPress
回调函数并关闭弹层。默认情况下, 唯一的按钮将是「确定」按钮。

上图 👆🏻 效果, 核心代码如下:
js
import { Button, Alert } from 'react-native';
const createTwoButtonAlert = () => Alert.alert('Alert Title', 'My Alert Msg', [
{
text: 'Cancel',
style: 'cancel',
onPress: () => console.log('Cancel Pressed'),
},
{ text: 'OK', onPress: () => console.log('OK Pressed') },
]);
const createThreeButtonAlert = () => Alert.alert('Alert Title', 'My Alert Msg', [
{
text: 'Ask me later',
onPress: () => console.log('Ask me later pressed'),
},
{
text: 'Cancel',
style: 'cancel',
onPress: () => console.log('Cancel Pressed'),
},
{ text: 'OK', onPress: () => console.log('OK Pressed') },
]);
<Button title="双按钮" onPress={createTwoButtonAlert} />
<Button title="三按钮" onPress={createThreeButtonAlert} />
这是一个适用于 Android
和 IOS
的 API
, 但是该组件在这两个平台还是存在差异的:
- 是否允许输入: 在
IOS
上支持在弹层上, 展示输入框, 并允许用户进行输入, 但是Android
则不行 - 按钮数量: 在
IOS
上, 您可以指定任意数量的按钮。但是在Android
上最多可以指定三个按钮 - 样式、交互: 在
UI
以及交互上也有所不同
如何选择?
- 仅仅需要简单的确认对话框, 又比较住在原生体验(
IOS/Android
各自风格) 则选择Alert
- 如需要自定义
UI
、注重跨平台一致的弹窗样式、需要输入框(Android
也支持) 则推荐使用react-native-dialog
进行定制
6.3 动画组件以及相关 API: Animated
不同于 Web
在 React Native
中是无法使用 CSS
来实现动画效果的, 只能通过 JS
或者官方提供的 Animated
组件以及相关 API
来实现高性能的动画。
Animated
提供了一些封装好的组件: Animated.View
、Animated.Text
、Animated.Image
、Animated.ScrollView
、Animated.FlatList
....
创建动画的核心工作流程是创建一个 Animated.Value
, 将其连接到动画组件的一个或多个样式属性, 然后使用 Animated.timing()
通过动画驱动更新。
如下, 是一个简单的 Demo
:
- 使用官方提供的
useAnimatedValue
来创建一个Animated.Value
值 - 通过事件调用
Animated.timing
来驱动Animated.Value
值过渡更新 - 使用
Animated.timing
来动态设置视图(样式)
js
import { View, Button, Text, Animated, useAnimatedValue } from 'react-native';
export default function HomeScreen() {
const fadeAnim = useAnimatedValue(0);
const fadeIn = () => {
// Will change fadeAnim value to 1 in 5 seconds
Animated.timing(fadeAnim, {
toValue: 1,
duration: 5000,
useNativeDriver: true,
}).start();
};
const fadeOut = () => {
// Will change fadeAnim value to 0 in 3 seconds
Animated.timing(fadeAnim, {
toValue: 0,
duration: 3000,
useNativeDriver: true,
}).start();
};
return (
<View style={{ margin: 100, height: 200 }}>
<Animated.View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
opacity: fadeAnim, // 动画
}}>
<Text style={{ fontSize: 20 }}> Fading View! </Text>
</Animated.View>
<View
style={{
flexBasis: 100,
marginVertical: 16,
justifyContent: 'space-evenly',
}}>
<Button title="Fade In View" onPress={fadeIn} />
<Button title="Fade Out View" onPress={fadeOut} />
</View>
</View>
);
}
当然不管在哪里, 动画都是一个复杂的主题, 这里我们就不进行展开, 后面可以单独开一篇来说明!
6.4 屏幕尺寸管理 API: Dimensions
Dimensions
相关 API
是 React Native
提供的 屏幕尺寸管理工具
, 主要是用于获取设备的屏幕宽高。它通常用于适配不同屏幕尺寸的 UI
布局。
如下代码所示, 视图和控制台将展示 Dimensions
中 window
和 screen
的所有内容:
js
import { View, Text, Dimensions } from 'react-native';
export default function HomeScreen() {
const windowDimensions = Dimensions.get('window');
const screenDimensions = Dimensions.get('screen');
console.log('screenDimensions', screenDimensions);
console.log('windowDimensions', windowDimensions);
return (
<View style={{ marginTop: 100 }}>
<Text style={{ fontSize: 20, padding: 20, lineHeight: 30 }}>
<Text >Window Dimensions {'\n'}</Text>
{Object.entries(windowDimensions).map(([key, value]) => (
<Text>{key} - {value}{'\n'}</Text>
))}
{'\n'}
<Text >Screen Dimensions {'\n'}</Text>
{Object.entries(screenDimensions).map(([key, value]) => (
<Text>{key} - {value}{'\n'}</Text>
))}
</Text>
</View>
);
}

那么问题来了, window
和 screen
有啥区别呢?
API |
作用 |
---|---|
Dimensions.get('window') |
应用窗口的宽高(不包含状态栏 & 导航栏) |
Dimensions.get('screen') |
整个屏幕的宽高(包含状态栏 & 导航栏) |
虽然屏幕的尺寸在组件加载前就可以立即获取, 但它们可能会发生变化(例如由于设备旋转、可折叠设备等), 因此任何依赖于这些常量的渲染逻辑或样式都应尝试在每次渲染时都调用相关函数去实时获取, 而不是缓存该值(例如, 使用内联样式而不是在 StyleSheet
中设置值)。甚至我们可能需要监听屏幕尺寸变化, 来动态渲染视图, 这里同样需要通过 Dimensions
来实现:
js
const [dimensions, setDimensions] = useState({
window: Dimensions.get('window'),
screen: Dimensions.get('screen'),
})
useEffect(() => {
// 监听屏幕尺寸的变更
const subscription = Dimensions.addEventListener(
'change',
({ window, screen }) => {
setDimensions({ window, screen });
},
);
return () => subscription?.remove();
});
当然我们实际上可能并不需要这么麻烦, 官方其实给我们提供了一个特别好用的 hook
即 useWindowDimensions
该 hook
帮我们作了封装, 它会在窗口大小变化时自动更新结果。
js
const window = useWindowDimensions()
console.log(window); // { fontScale: 1, height: 874, scale: 3, width: 402 }
当然需要注意的是官方并没有提供对应的 screen
相关的 hook
, 为什么没有 useScreenDimensions
下面是 GPT
的一个解释

所以如果我们需要监听 screen
的变更, 那只能手动调用 Dimensions
来进行监听了...
6.5 键盘遮挡处理组件: KeyboardAvoidingView
在移动端, 当用户点击输入框, 弹出键盘时经常会出现键盘遮挡输入框的问题, 尤其是在 IOS
上。而如果不加以处理的话, 用户很容易就看不到输入框输入的内容。
而针对上诉问题, React Native
官方则专门提供了 KeyboardAvoidingView
组件来处理该问题。
下面代码是一个简单的 Demo
:
js
import React from 'react';
import { KeyboardAvoidingView, TextInput, Platform } from 'react-native';
const KeyboardAvoidingComponent = () => {
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ margin: 50 }}>
<TextInput placeholder="Username" />
</KeyboardAvoidingView>
);
};
export default KeyboardAvoidingComponent;
上文 behavior
属性则用于规定如何对键盘的存在做出合理的布局调整。
behavior 值 |
作用 |
---|---|
height |
减少 View 高度(适用于 Android ) |
padding |
整体向上移动(适用于 IOS ) |
position |
使用绝对定位移动(较少使用) |
推荐写法:
js
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
6.6 深链接 API: Linking
首先我们需要先了解下何谓深链接(Deep Linking
), 该链接可用于在 App
之间进行跳转。类似于网页的超链接, 不同的是它是用于移动应用。
深链接 VS 普通链接
| 对比 | 普通链接(网页) | 深链接(App 内部) | | --- | --- | | 格式 | https://example.com/page
| myapp://page
| | 作用 | 打开网页 | 直接跳转 App
内部页面 | | 适用场景 | 浏览器、社交平台 | App
之间导航、广告推广 |
深链接的工作原理: 深链接本质上就是一个自定义
URL Scheme
, 当用户点击myapp://profile/123
, 系统会尝试检查是否安装了App
- 如果已安装 --> 直接打开
App
并跳转到profile/123
页面- 如果未安装 --> 可能会显示错误(可配合
Universal Links
引导用户下载App
)
内置URL
方案: 如简介中所述, 每个平台上都存在一些用于核心功能的URL
方案。以下是非详尽的列表, 但涵盖了最常用的方案。当然除了下列几个常见的方案, 我们也是可以自定义URL Scheme
| Scheme 方案 | Description 描述 | iOS | Android | | --- | --- | --- | | mailto
| 打开邮件应用程序, 例如: mailto: [email protected]
| [x] | [x] | | tel
| 打开电话应用程序, 例如: tel: +123456789
| [x] | [x] | | sms
| 打开短信应用程序, 例如: sms: +123456789
| [x] | [x] | | https / http
| 打开网页浏览器应用程序, 例如: https ://expo.io
| [x] | [x] |
下面是在 React Native
中处理深层链接的方式:
js
// 1. 如果该应用程序已打开, 则可以使用 Linking.addEventListener 监听到跳转过来的深链接
Linking.addEventListener('url', event => {
console.log(`检测到深链接: ${event.url}`);
});
// 2. 如果应用程序尚未打开, 则可以使用 Linking.getInitialURL() 获取到跳转过来的深链接
const initialUrl = await Linking.getInitialURL();
下面是在 React Native
中从一个 App
内跳转深链接的方式:
js
const handlePress = useCallback(async () => {
// 检查是否支持自定义 URL 方案的链接
const supported = await Linking.canOpenURL(url);
if (supported) {
// by some browser in the mobile
await Linking.openURL(url);
} else {
Alert.alert(`Don't know how to open this URL: ${url}`);
}
}, [url]);
当用户缺少需要的一些系统权限设置时, 也可通过 Linking
引导用户进去设置中心进行配置:
js
await Linking.openSettings();
当然在安卓中我们还可以使用 Linking.sendIntent()
调用 Intent
进行系统级的 API
, 比如:
- 发送短信(不跳转到短信
App
) - 拨打电话(不弹出拨号界面)
- 打开
WiFi
、蓝牙、位置设置等系统界面 - 与其他
App
交互(如打开特定页面)
js
const action = "android.settings.APP_NOTIFICATION_SETTINGS";
const extras = [
{
key: 'android.provider.extra.APP_PACKAGE',
value: 'com.facebook.katana',
},
]
try {
await Linking.sendIntent(action, extras);
} catch (e) {
Alert.alert(e.message);
}
6.7 模态框组件: Modal
在 React Native
中, 官方就给我们提供了现成的弹窗组件。
如下代码是一个简单的 Demo
:
transparent
: 该属性决定了模态框整体背景是否是透明的, 设置为true
则模态框背景透明(仅遮罩部分), 否则, 模态框背景不透明(默认白色背景, 覆盖整个屏幕)animationType
: 该属性控制模态框的打开和关闭时的动画效果, 该属性有三个可选值,slide
(从屏幕底部进出)、fade
(透明度渐变进出)、none
(默认, 没有动画过渡)visible
: 该属性则控制模态框的显示/隐藏
js
import { useState } from 'react';
import { Button, Modal, Text, View } from 'react-native';
const App = () => {
const [modalVisible, setModalVisible] = useState(false);
return (
<View style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Modal
transparent
animationType="slide"
visible={modalVisible}>
{/* 弹窗外层, 占满整个屏幕 */}
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<View style={{ backgroundColor: 'white' }}>
<Text>Hello World!</Text>
<Button title='Hide Modal' onPress={() => setModalVisible(!modalVisible)} />
</View>
</View>
</Modal>
<Button title='Show Modal' onPress={setModalVisible.bind(null, true)} />
</View>
);
};
export default App;
6.8 像素比 API: PixelRatio
PixelRatio
用于处理屏幕像素密度(DPR
, 设备像素比)相关的事情。主要作用其实就用来获取设备的像素密度信息 , 以及做一些 像素与 dp 单位
的换算, 解决不同屏幕上 UI
元素尺寸不一致的问题。
PixelRatio.get()
获取当前设备的像素比
js
import { PixelRatio, View } from 'react-native';
PixelRatio.get() // 3
PixelRatio.getPixelSizeForLayoutSize(layoutSize)
将布局尺寸(dp
)转换为像素尺寸(px
)
js
// 根据设备像素比, 来获取正确大小的图像
const image = getImage({
width: PixelRatio.getPixelSizeForLayoutSize(200), // 200 * 3 px
height: PixelRatio.getPixelSizeForLayoutSize(100), // 100 * 3 px
});
<Image source={image} style={{ width: 200, height: 100 }} />;
PixelRatio.roundToNearestPixel(layoutSize)
将布局尺寸(dp
)四舍五入为整数像素尺寸(px
)。例如, 在PixelRatio
为3
的设备上,PixelRatio.roundToNearestPixel(8.4)
会返回8.33
, 而8.33pd
则等于(8.33 * 3) = 25
像素。
js
PixelRatio.roundToNearestPixel(3) // 3db => 3 * 3px
PixelRatio.roundToNearestPixel(8.4) // 8.3db => 25px
PixelRatio.getPixelSizeForLayoutSize(8.3) // 25
PixelRatio.getFontScale()
获取设备字体的缩放比例, 在Android
或IOS
都是可以在设置中设置字体大小。
js
PixelRatio.getFontScale() // 1
6.9 刷新控制组件: RefreshControl
该组件通常配合 ScrollView
或 ListView
等组件一起使用, 主要是用于给列表添加下拉刷新的功能。当 ScrollView
等组件位于 scrollY: 0
时继续向下滑动 RefreshControl
组件将会触发 onRefresh
事件。
细节不展开了, 直接看代码...
js
import React from 'react';
import { ScrollView, View, RefreshControl } from 'react-native';
import {SafeAreaView, SafeAreaProvider} from 'react-native-safe-area-context';
const data = Array.from({ length: 20 }).fill(1)
const App = () => {
const [refreshing, setRefreshing] = React.useState(false);
const onRefresh = React.useCallback(() => {
setRefreshing(true);
setTimeout(() => setRefreshing(false), 2000);
}, []);
return (
<SafeAreaProvider>
<SafeAreaView >
<ScrollView
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
style={{ height: 600, backgroundColor: 'pink' }}>
{data.map(() => <View style={{ height: 50, margin: 10, backgroundColor: 'red' }} />)}
</ScrollView>
</SafeAreaView>
</SafeAreaProvider>
);
};
6.10 状态栏组件: StatusBar
StatusBar
就是用来管理手机系统顶部的那条 信号/时间/电池
状态栏区域的样式。你可以控制它:
props 属性 | 描述 | Android |
IOS |
---|---|---|---|
backgroundColor |
设置状态栏背景色 | [x] | [ ] |
backgroundColor |
设置状态栏背景色 | [x] | [ ] |
barStyle |
设置状态栏文本的颜色, 可选值有 default(默认, IOS 为深色, Android 为浅色) 、light-content (浅色)、dark-content (深色) |
[x] | [x] |
hidden |
是否将状态栏隐藏 | [x] | [x] |
animated |
状态栏属性变化之间的过渡是否应以动画形式呈现 | [x] | [x] |
translucent |
状态栏是否半透明 | [x] | [ ] |
showHideTransition |
使用 hidden 属性显示和隐藏状态栏时的过渡效果。可选值有 default 、light-content 、dark-content |
[ ] | [x] |
js
<StatusBar
animated={true}
backgroundColor="#61dafb"
barStyle={statusBarStyle}
showHideTransition={statusBarTransition}
hidden={hidden}
/>