前言
最近,和往常一样刷着手机时,看着手机上的那些优质APP萌生出想了解一下移动端的想法,出于好奇,经过调研结合自己的技术栈得知RN(react native)
对于前端工程狮的学习成本较低,于是便开启了这段奇妙探险。
RN 基础知识
样式与布局
在 RN 中,所有组件都接受名为 style 的属性,属性值为一个对象,用来书写 CSS 样式。
书写样式时需要注意的是要按照 JavaScript 语法来使用驼峰命名法,例如将 background-color 改为 backgroundColor。 (这一点与h5有所不同)
还有就是在 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",
},
});
使用expo预览可以看到:
如果要复用 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",
},
});
expo中预览效果如下:
在组件样式中,使用 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>
);
}
注:组件能够撑满剩余空间的前提是其父容器的尺寸不为零。如果父容器既没有固定的 width 和 height,也没有设定 flex,则父容器的尺寸为零。其子组件如果使用了 flex,也是无法显示的。
expo预览效果如下:
在进行宽高设置时,还可以很方便的使用百分比来进行设置。例如:
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 也只能指定一个数字值。
下面我们来看一个 RN 中 flexbox 的示例:
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>
);
}
expo预览效果如下:
图片
目前,我们的 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",
},
});
expo预览效果如下,当我们从键盘中输入内容时页面同时响应式的发生变化:
按钮
按钮也是一个应用中最基本的需求,在 RN 中提供了 Button 组件来渲染按钮,这是一个简单的跨平台的按钮组件,会调用原生环境中对应的按钮组件。
在 Android 设备中,Button 组件显示为一个按钮,而在 IOS 设备中,则显示为一行文本。
该组件需要传递两个必须的属性,一个是 onPress,对应点击后的事件,另一个是 title,用来指定按钮内的文本信息。
下面是 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 系列组件。
Touchable 系列组件一共有 4 个,其中跨平台的有 3 个:
-
TouchableHighlight
Touchable 系列组件中比较常用的一个,它是在 TouchableWithoutFeedback 的基础上添加了一些 UI 上的扩展,即当手指按下的时候,该视图的不透明度会降低,同时会看到视图变暗或者变亮,该标签可以添加 style 样式属性。
-
TouchableOpacity
完全和 TouchableHighlight 相同,只是不可以修改颜色,只能修改透明度。
-
TouchableWithoutFeedback
最基本的一个 Touchable 组件,只响应用户的点击事件,不会做任何 UI 上的改变,所以不用添加 style 样式属性,加了也没效果。
另外在 Android 平台上支持一个叫 TouchableNativeFeedback 的组件:
- TouchableNativeFeedback:为了支持 Android 5.0 的触控反馈而新增的组件。该组件在 TouchableWithoutFeedback 所支持的属性的基础上增加了触摸的水波纹效果。可以通过 background 属性来自定义原生触摸操作反馈的背景。(仅限 Android 平台,IOS 平台使用会报错)
示例如下:
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",
},
});
使用滚动视图
到目前为止,我们的应用能够显示文字、图片,也能够和用户进行简单的互动。但是还有一个很重要的需求,那就是滑屏操作。
在 RN 中,则直接为我们提供了滚动视图的组件 ScrollView。
ScrollView 是一个通用的可滚动的容器,你可以在其中放入多个组件和视图,而且这些组件并不需要是同类型的。ScrollView 不仅可以垂直滚动,还能水平滚动(通过 horizontal 属性来设置)。
示例如下:
import React from "react";
import { Image, ScrollView, Text } from "react-native";
const logo = {
uri: "https://img.soogif.com/XYCV5S6mSWgLabQy8jwdEYy2rph9n3pk.jpeg_jpg",
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>
);
}
expo预览效果如下:
使用长列表
除了 ScrollView 滚动视图组件外,RN 中还提供了用于长列表组件。常用的长列表有两个:
- FlatList
- SectionList
FlatList
FlatList 组件用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同。
FlatList 更适于长列表数据,且元素个数可以增删。和 ScrollView 不同的是,FlatList 并不立即渲染所有元素,而是优先渲染屏幕上可见的元素。
FlatList 组件必须的两个属性是 data 和 renderItem。data 是列表的数据源,而 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,
},
});
expo中预览效果如下:
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,
},
});
expo中预览效果如下:
在上面的示例中,我们使用到了 SectionList 组件的 4 个属性,分别是
-
sections(必填):用来渲染的数据,类似于 FlatList 中的 data 属性。
-
renderItem(必填):用来渲染每一个 section 中的每一个列表项的默认渲染器。必须返回一个 react 组件。
-
renderSectionHeader:在每个 section 的头部渲染。在 IOS 上,这些 headers 是默认粘接在 ScrollView 的顶部的。
-
keyExtractor:此函数用于为给定的 item 生成一个不重复的 key。
Key 的作用是使 react 能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。
若不指定此函数,则默认抽取 item.key 作为 key 值。若 item.key 也不存在,则使用数组下标。注意这只设置了每行(item)的 key,对于每个组(section)仍然需要另外设置 key。
总结
好了,本篇文章到此就结束了,希望对大家了解移动端基础代码编写有所帮助,如果对你有所帮助的话,还请点赞、关注、收藏支持一波。