RN基础知识

268 阅读11分钟

前言

后续的RN相关的知识,是完全搬运自渡一教育的文档,以便于自己后续的复习使用

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 个:

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

使用滚动视图

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

之前在 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
  • 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,
  },
});

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 个属性,分别是

  • 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

网络连接

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

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 请求。