RN快速入门,拥抱移动端!

2,068 阅读11分钟

前言

最近,和往常一样刷着手机时,看着手机上的那些优质APP萌生出想了解一下移动端的想法,出于好奇,经过调研结合自己的技术栈得知RN(react native)对于前端工程狮的学习成本较低,于是便开启了这段奇妙探险。

image.png

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预览可以看到:

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

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

Screenshot_2025-01-01-21-59-18-354_host.exp.expon.jpg

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中预览效果如下:

Screenshot_2025-01-01-22-00-57-522_host.exp.expon.jpg

在组件样式中,使用 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,也是无法显示的。

expo预览效果如下:

Screenshot_2025-01-01-22-03-22-978_host.exp.expon.jpg

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

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>
  );
}

expo预览效果如下:

Screenshot_2025-01-01-22-05-03-201_host.exp.expon.jpg

图片

目前,我们的 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预览效果如下,当我们从键盘中输入内容时页面同时响应式的发生变化:

Screenshot_2025-01-01-22-08-40-062_host.exp.expon.jpg

按钮

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

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

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

image.png 下面是 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.png 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预览效果如下:

Screenshot_2025-01-01-22-13-31-891_host.exp.expon.jpg

使用长列表

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

  • FlatList
  • SectionList

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,
  },
});

expo中预览效果如下: Screenshot_2025-01-01-22-15-07-305_host.exp.expon.jpg

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中预览效果如下:

Screenshot_2025-01-01-22-17-30-477_host.exp.expon.jpg

在上面的示例中,我们使用到了 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

总结

好了,本篇文章到此就结束了,希望对大家了解移动端基础代码编写有所帮助,如果对你有所帮助的话,还请点赞、关注、收藏支持一波。

image.png