Skip to content

RN 基础知识

简单的复习了 React 相关知识后,接下来我们就正式进入到 RN 的学习。

首先我们根据 RN 的官网,来学习最基础的操作,主要包含:

样式与布局#

RN 中,所有组件都接受名为 style 的属性,属性值为一个对象,用来书写 CSS 样式。

书写样式时需要注意的是要按照 JavaScript 语法来使用驼峰命名法,例如将 background-color 改为 backgroundColor

还有就是在 RN 中无法使用缩写样式,例如 border<1px> solid 这样的样式是无法使用的,只能分成两条样式来写 borderWidth<1>,borderStyle:‘solid’

RN 中提供了一个 StyleSheet.create 方法来集中定义组件的样式,如下:

import { StyleSheet, Text, View } from 'react-native'
export default function App() {
return (
<View style={styles.container}>
<Text style={{ color: 'red', textDecorationLine: 'underline' }}>
内嵌样式
</Text>
<Text style={styles.red}>just red</Text>
<Text style={styles.bigBlue}>just bigBlue</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
bigBlue: {
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
},
red: {
color: 'red',
},
})

如果要复用 StyleSheet.create 中所定义的样式,可以传入一个数组,但是要注意在数组中位置居后的样式对象比居前的优先级更高,这样你可以间接实现样式的继承。

<Text style={[styles.bigBlue, styles.red]}>bigBlue,then red</Text>
<Text style={[styles.red, styles.bigBlue]}>red,then bigBlue</Text>

RN 中设置样式时,如果涉及到尺寸,默认都是不给单位的,表示的是与设备像素密度无关的逻辑像素点。

import { StyleSheet, View } from 'react-native'
export default function App() {
return (
// 在样式中指定固定的 width 和 height
// 尺寸都是无单位的,表示的是与设备像素密度无关的逻辑像素点
<View style={styles.container}>
<View style={{ width: 50, height: 50, backgroundColor: 'powderblue' }} />
<View style={{ width: 100, height: 100, backgroundColor: 'skyblue' }} />
<View style={{ width: 150, height: 150, backgroundColor: 'steelblue' }} />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
})

在组件样式中,使用 flex 可以使其在可利用的空间中动态地扩张或收缩。一般而言我们会使用 flex:1 来指定某个组件扩张以撑满所有剩余的空间。

如果有多个并列的子组件使用了 flex:1,则这些子组件会平分父容器中剩余的空间。如果这些并列的子组件的 flex 值不一样,则谁的值更大,谁占据剩余空间的比例就更大。

即占据剩余空间的比等于并列组件间 flex 值的比。

import { View } from 'react-native'
export default function App() {
return (
// 最外层使用 flex:1 来指定某个组件扩张以撑满所有剩余的空间
<View style={{ flex: 1 }}>
{/* 如果有多个并列的子组件使用了 flex:1,则这些子组件会平分父容器中剩余的空间 */}
{/* 如果这些并列的子组件的 flex 值不一样,则谁的值更大,谁占据剩余空间的比例就更大。 */}
<View style={{ flex: 1, backgroundColor: 'powderblue' }} />
<View style={{ flex: 2, backgroundColor: 'skyblue' }} />
<View style={{ flex: 3, backgroundColor: 'steelblue' }} />
</View>
)
}

注:组件能够撑满剩余空间的前提是其父容器的尺寸不为零。如果父容器既没有固定的 widthheight,也没有设定 flex,则父容器的尺寸为零。其子组件如果使用了 flex,也是无法显示的。

在进行宽高设置时,还可以很方便的使用百分比来进行设置。例如:

import React from 'react'
import { View } from 'react-native'
const PercentageDimensionsBasics = () => {
// Try removing the `height: '100%'` on the parent View.
// The parent will not have dimensions, so the children can't expand.
return (
<View style={{ height: '100%' }}>
<View
style={{
height: '15%',
backgroundColor: 'powderblue',
}}
/>
<View
style={{
width: '66%',
height: '35%',
backgroundColor: 'skyblue',
}}
/>
<View
style={{
width: '33%',
height: '50%',
backgroundColor: 'steelblue',
}}
/>
</View>
)
}
export default PercentageDimensionsBasics

在进行移动端开发时,最推荐的布局方案就是使用 flexbox 弹性盒布局。flexbox 可以在不同屏幕尺寸上提供一致的布局结构。

RN 中的 flexbox 的工作原理和 Web 上的 CSS 基本一致,当然也存在少许差异。首先是默认值不同:flexDirection 的默认值是 column 而不是 row,而 flex 也只能指定一个数字值。

下面我们来看一个 RNflexbox 的示例:

import { View } from 'react-native'
export default function App() {
return (
// 尝试把 flexDirection 改为 column 看看
<View
style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-around' }}
>
<View style={{ width: 50, height: 50, backgroundColor: 'powderblue' }} />
<View style={{ width: 50, height: 50, backgroundColor: 'skyblue' }} />
<View style={{ width: 50, height: 50, backgroundColor: 'steelblue' }} />
</View>
)
}

图片#

目前,我们的 RN 应用已经有了显示文本的能力,你能够通过弹性盒布局将文本显示到合适的位置。

但是一个应用中不单单只有文字,还会存在图片。在 RN 中,提供了一个名为 Image 的组件来显示图片。例如:

<Image source={require('./my-icon.png')} />

来看一个具体的示例,我在项目中的 assets 目录下添加一张名为 ok.png 的图片,然后通过 Image 组件显示这张图片,如下:

import { StyleSheet, Image, View } from 'react-native'
export default function App() {
return (
<View style={styles.container}>
<Image source={require('./assets/ok.png')} style={styles.tinyLogo} />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
tinyLogo: {
width: 200,
height: 200,
borderWidth: 1,
borderColor: '#000',
},
})

require 中的图片名字必须是一个静态字符串,不能使用变量!因为 require 是在编译时期执行,而非运行时期执行!。

// 正确
;<Image source={require('./my-icon.png')} />
// 错误
const icon = this.props.active ? 'my-icon-active' : 'my-icon-inactive'
;<Image source={require('./' + icon + '.png')} />
// 正确
const icon = this.props.active
? require('./my-icon-active.png')
: require('./my-icon-inactive.png')
;<Image source={icon} />

本地图片在引入时会包含图片的尺寸(宽度,高度)信息,但是如果是网络图片,则必须手动指定图片的尺寸。

// 正确
<Image source={{uri: 'https://facebook.github.io/react/logo-og.png'}}
style={{width: 400, height: 400}} />
// 错误
<Image source={{uri: 'https://facebook.github.io/react/logo-og.png'}} />

文本输入与按钮#

目前,我们的应用已经能够显示图片和文字,最基本的信息展示已经没问题了。但是一个应用往往还会涉及到用户的文本输入以及最基本的交互——按钮。

文本输入#

我们先来看文本输入。

RN 中提供了一个 TextInput 组件,该组件是一个允许用户输入文本的基础组件。它有一个名为 onChangeText 的属性,此属性接受一个函数,而此函数会在文本变化时被调用。另外还有一个名为 onSubmitEditing 的属性,会在文本被提交后(用户按下软键盘上的提交键)调用。

例如:

import React, { useState } from 'react'
import { Text, TextInput, View, StyleSheet } from 'react-native'
export default function App() {
const [text, setText] = useState('')
return (
<View style={styles.container}>
<TextInput
style={{ width: 300, height: 40, borderWidth: 1, borderColor: '#000' }}
placeholder="Type here to translate!"
onChangeText={(text) => setText(text)}
defaultValue={text}
/>
<Text style={{ padding: 10, fontSize: 18 }}>你输入的内容为:</Text>
<Text>{text}</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
})

按钮#

按钮也是一个应用中最基本的需求,在 RN 中提供了 Button 组件来渲染按钮,这是一个简单的跨平台的按钮组件,会调用原生环境中对应的按钮组件。

Android 设备中,Button 组件显示为一个按钮,而在 IOS 设备中,则显示为一行文本。

image-20220603094042200

该组件需要传递两个必须的属性,一个是 onPress,对应点击后的事件,另一个是 title,用来指定按钮内的文本信息。

image-20220603094107945

下面是 Button 组件的一个简单示例:

import React from 'react'
import { View, StyleSheet, Button } from 'react-native'
export default function App() {
function onPressLearnMore() {
alert('this is working')
}
return (
<View style={styles.container}>
<Button title="这是一个测试按钮" onPress={onPressLearnMore}></Button>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
})

由于 Button 组件是调用原生代码,因此不同的平台显示的外观是不同的,如果想要各个平台显示的外观都相同,则可以使用 Touchable 系列组件。

image-20220603094130542

Touchable 系列组件一共有 4 个,其中跨平台的有 3 个:

另外在 Android 平台上支持一个叫 TouchableNativeFeedback 的组件:

示例如下:

import React from 'react'
import { Text, TouchableHighlight, View, StyleSheet } from 'react-native'
export default function App() {
return (
<View style={styles.container}>
<TouchableHighlight
onPress={() => {
console.log('触摸效果')
}}
onLongPress={() => {
console.log('长按效果')
}}
disabled={false} // 默认是 false,如果是 true 表示关闭该组件的触摸功能
onPressIn={() => {
console.log('触摸开始')
}}
onPressOut={() => {
console.log('触摸结束')
}}
>
<View
style={{
width: 260,
height: 50,
alignItems: 'center',
backgroundColor: '#2196F3',
}}
>
<Text
style={{
lineHeight: 50,
color: 'white',
}}
>
Touch Here
</Text>
</View>
</TouchableHighlight>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
})

使用滚动视图#

到目前为止,我们的应用能够显示文字、图片,也能够和用户进行简单的互动。但是还有一个很重要的需求,那就是滑屏操作。

之前在 WebApp 课程里面,我们的滑屏操作是需要禁用默认事件后自己来写的,当然我们也可以选择使用第三方库,例如 Swiper.js 来实现滑屏效果。

而在 RN 中,则直接为我们提供了滚动视图的组件 ScrollView

ScrollView 是一个通用的可滚动的容器,你可以在其中放入多个组件和视图,而且这些组件并不需要是同类型的。ScrollView 不仅可以垂直滚动,还能水平滚动(通过 horizontal 属性来设置)。

示例如下:

import React from 'react'
import { Image, ScrollView, Text } from 'react-native'
const logo = {
uri: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.51yuansu.com%2Fpic2%2Fcover%2F00%2F31%2F92%2F5810d2ed3fda3_610.jpg&refer=http%3A%2F%2Fpic.51yuansu.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1654851583&t=cebc2481560183caea4c8dca86fa88b6',
width: 64,
height: 64,
}
export default function App() {
return (
<ScrollView>
<Text style={{ fontSize: 96 }}>Scroll me plz</Text>
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Text style={{ fontSize: 96 }}>If you like</Text>
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Text style={{ fontSize: 96 }}>Scrolling down</Text>
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Text style={{ fontSize: 96 }}>What's the best</Text>
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Text style={{ fontSize: 96 }}>Framework around?</Text>
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Image source={logo} />
<Text style={{ fontSize: 80 }}>React Native</Text>
</ScrollView>
)
}

使用长列表#

除了 ScrollView 滚动视图组件外,RN 中还提供了用于长列表组件。常用的长列表有两个:

FlatList#

FlatList 组件用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同。

FlatList 更适于长列表数据,且元素个数可以增删。和 ScrollView 不同的是,FlatList 并不立即渲染所有元素,而是优先渲染屏幕上可见的元素。

FlatList 组件必须的两个属性是 datarenderItemdata 是列表的数据源,而 renderItem 则从数据源中逐个解析数据,然后返回一个设定好格式的组件来渲染。

下面的例子创建了一个简单的 FlatList,并预设了一些模拟数据。首先是初始化 FlatList 所需的 data,其中的每一项(行)数据之后都在 renderItem 中被渲染成了 Text 组件,最后构成整个 FlatList

import React from 'react'
import { FlatList, StyleSheet, Text, View } from 'react-native'
export default function App() {
return (
<View style={styles.container}>
<FlatList
data={[
{ key: 'Devin' },
{ key: 'Dan' },
{ key: 'Dominic' },
{ key: 'Jackson' },
{ key: 'James' },
{ key: 'Joel' },
{ key: 'John' },
{ key: 'Jillian' },
{ key: 'Jimmy' },
{ key: 'Julie' },
]}
renderItem={({ item }) => <Text style={styles.item}>{item.key}</Text>}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22,
},
item: {
padding: 10,
fontSize: 18,
height: 44,
borderWidth: 1,
borderColor: '#ccc',
marginBottom: 10,
},
})

SectionList#

如果要渲染的是一组需要分组的数据,也许还带有分组标签的,那么 SectionList 将是个不错的选择。

import React from 'react'
import { SectionList, StyleSheet, Text, View } from 'react-native'
export default function App() {
return (
<View style={styles.container}>
<SectionList
sections={[
{ title: 'D', data: ['Devin', 'Dan', 'Dominic'] },
{
title: 'J',
data: [
'Jackson',
'James',
'Jillian',
'Jimmy',
'Joel',
'John',
'Julie',
],
},
]}
renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
renderSectionHeader={({ section }) => (
<Text style={styles.sectionHeader}>{section.title}</Text>
)}
keyExtractor={(item, index) => index}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22,
},
sectionHeader: {
paddingTop: 2,
paddingLeft: 10,
paddingRight: 10,
paddingBottom: 2,
fontSize: 14,
fontWeight: 'bold',
backgroundColor: 'rgba(247,247,247,1.0)',
},
item: {
padding: 10,
fontSize: 18,
height: 44,
borderWidth: 1,
borderColor: '#ccc',
marginBottom: 10,
},
})

在上面的示例中,我们使用到了 SectionList 组件的 4 个属性,分别是

网络连接#

开发应用时,我们往往还需要从服务器上面获取数据。

RN 中,支持 fetchAPI 以及传统的 Ajax 的形式来发送网络请求,但是这里推荐使用最新的 fetch 形式来发送请求。

下面我们来看一个在 RN 中使用 fetch 发送请求的示例:

import React from 'react'
import { StyleSheet, View, Button } from 'react-native'
export default function App() {
function getInfo() {
return fetch('https://cnodejs.org/api/v1/topics')
.then((res) => res.json())
.then((res) => {
console.log(res, 'res')
})
.catch((error) => {
console.error(error)
})
}
return (
<View style={styles.container}>
<Button onPress={getInfo} title="点击按钮获取数据" color="skyblue" />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
})

注:默认情况下 iOS 会阻止所有 http 的请求,以督促开发者使用 https。从 Android9 开始,也会默认阻止 http 请求。