ReactNative入门基础(2)

props和state

  • 我们使用两种数据来控制一个组件:props和state。
  • props是在父组件中指定,而且一经指定,在被指定的组件的生命周期中则不再改变。对于需要改变的数据,我们需要使用state。

Props(属性)

  • 大多数组件在创建时就可以使用各种参数来进行定制,用于定制的这些参数就称为props(属性)
  • 以常见的基础组件Image为例:在创建一个图片时,可以传入一个名为source的 prop 来指定要显示的图片的地址,以及使用名为style的 prop 来控制其样式。

例子(自定义组件)

自定义的组件也可以使用props:
通过在不同的场景使用不同的属性定制,可以尽量提高自定义组件的复用范畴。只需在render函数中引用this.props,然后按需处理即可。
【可以联想一下react中的父子组件传值方式

下面例子中,我们在Greeting组件中将name作为一个属性来定制,这样可以复用这一组件来制作各种不同的“问候语”:
例子
效果
从react的角度理解, 父组件LotsOfGreetings1 在调用 子组件Greeting 时将name作为属性传值给子组件,在子组件中通过this.props.name获取到父组件传递过来的属性值,最终显示在父组件中】


State(状态)

  • props是在父组件中指定,而且一经指定,在被指定的组件的生命周期中则不再改变。对于需要改变的数据,我们需要使用state。
  • 一般来说,你需要在class的 constructor(构造函数) 中初始化中一个state对象,然后在需要修改时调用setState方法。
  • 在React中,状态(state)一旦发生变化,就会触发界面的重新渲染。一切界面变化都是状态state变化。
  • state的修改必须通过setState()方法,this.state.num = 100; 这样的直接赋值修改无效!
  • setState() 是异步操作,修改不会马上生效
  • State 的工作原理和 React.js 完全一致,所以对于处理 state 的一些更深入的细节,你可以参考react的笔记。

例子

假如我们需要制作一段不停闪烁的文字。
文字内容本身在组件创建时就已经指定好了,所以文字内容应该是一个prop。
而文字的显示或隐藏的状态(快速的显隐切换就产生了闪烁的效果)则是随着时间变化的,因此这一状态应该写到state中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import React, { Component } from 'react';
import { Text, View } from 'react-native';

class Blink extends Component {
// 声明state对象
state = { isShowingText: true };

componentDidMount() {
// 每1000毫秒对showText状态做一次取反操作
setInterval(() => {
this.setState({
isShowingText: !this.state.isShowingText
});
}, 1000);
}

render() {
// 根据当前showText的值决定是否显示text内容
if (!this.state.isShowingText) {
return null;
}

return (
<Text>{this.props.text}</Text>
);
}
}

export default class BlinkApp extends Component {
render() {
return (
<View>
<Blink text='I love to blink' />
<Blink text='Yes blinking is so great' />
<Blink text='Why did they ever take this out of HTML' />
<Blink text='Look at me look at me look at me' />
</View>
);
}
}


ref 获取某个子组件

  • React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。
  • 例子

关于ref的理解:
ref : 指向某个子组件
this : 指向当前export的组件本身
this.refs : 指向当前export的组件的所有子组件
this.refs.ref_phoneInput : 指向当前import的自定义封装组件
this.refs.ref_phoneInput.refs : 指向当前import的自定义封装组件的所有子组件
this.refs.ref_phoneInput.refs.PhoneNumberInput : 指向当前import的自定义封装组件的其中一个子组件

例子:
例子
注意:这里的this.refs.myInput就是组件实例,可以获取到实例的属性和方法。


基础组件

ScrollView 滚动

  • ScrollView 是一个通用的可滚动的容器,你可以在其中放入多个组件和视图,而且这些组件并不需要是同类型的
  • ScrollView 不仅可以垂直滚动,还能水平滚动(通过horizontal属性来设置。)
  • 属性值:当horizontal属性为true的时候,所有的子视图会在水平方向上排成一行,而不是默认的在垂直方向上排成一列。默认值为false。(更多属性可查看文档
  • ScrollView 适合用来显示数量不多的滚动元素。放置在ScrollView 中的所有组件都会被渲染,哪怕有些组件因为内容太长被挤出了屏幕外。如果你需要显示较长的滚动列表,那么应该使用功能差不多但性能更好的FlatList组件

例子


列表视图组件

和一般化用途的ScrollView不同,下面的列表组件只会渲染当前屏幕可见的元素,这样有利于显示大量的数据。

FlatList 长列表组件

  • React Native 提供了几个适用于展示长列表数据的组件,一般而言我们会选用FlatList或是SectionList。(如果要渲染的是一组需要分组的数据,也许还带有分组标签的,那么SectionList将是个不错的选择)
  • FlatList组件用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同
  • 区别于ScrollView: FlatList更适于长列表数据,且元素个数可以增删。和ScrollView不同的是,FlatList并不立即渲染所有元素,而是优先渲染屏幕上可见的元素
  • 必须的两个属性:data(数组)和renderItem(事件)。data是列表的数据源,而renderItem则从数据源中逐个解析数据,然后返回一个设定好格式的组件来渲染。【事件:是指发生在该组件上的事情。】
属性
data 为了简化起见,data 属性目前只支持普通数组。如果需要使用其他特殊数据结构,例如 immutable 数组,请直接使用更底层的VirtualizedList组件
renderItem(事件) 函数,可接受3个参数。renderItem({item, index, separators});
item:data中的各个数据项
index:与数据数组中该项对应的索引
separators:参考文档
作用:从数据源中逐个解析数据,然后返回一个设定好格式的组件来渲染各个数据

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


SectionList 【分组】长列表组件

  • 如果要渲染的是一组需要分组的数据,也许还带有分组标签的,那么SectionList将是个不错的选择。
  • 列表的一个常用场景就是从服务器端取回列表数据然后显示,要实现这一过程还需要学习React Native 的网络相关用法
  • 必须的属性:sections(数组)、renderItem(事件)。(更多属性可参考文档
属性
sections 数组,用来渲染的数据,类似于FlatList中的data属性
renderItem 函数,用来渲染每一个section中的每一个列表项的默认渲染器。必须返回一个react组件。可以在section级别上进行覆盖重写。
renderSectionHeader 函数,在每个section的头部渲染。在iOS上,这些headers是默认粘接在ScrollView的顶部的
renderSectionFooter 每个组的尾部组件

例子

注意

  • 当某行滑出渲染区域之外后,其内部状态将不会保留。请确保你在行组件以外的地方保留了数据。
  • 本组件继承自PureComponent而非通常的Component,这意味着如果其props在浅比较中是相等的,则不会重新渲染。所以请先检查你的 renderItem函数 所依赖的 props数据(包括 data属性 以及可能用到的 父组件的state )。如果是一个引用类型(Object或者数组都是引用类型),则需要先修改其引用地址(比如先复制到一个新的Object或者数组中【深拷贝】),然后再修改其值,否则界面很可能不会刷新。(这一段不了解的朋友建议先学习下js中的基本类型和引用类型。)
  • 为了优化内存占用同时保持滑动的流畅,列表内容会在屏幕外异步绘制。这意味着如果用户滑动的速度超过渲染的速度,则会先看到空白的内容。这是为了优化不得不作出的妥协,而我们也在设法持续改进。
  • 默认情况下每行都需要提供一个不重复的key属性。你也可以提供一个keyExtractor函数来生成key

其他组件

WebView 加载网页内容

WebView组件可以实现直接加载网页内容在React Native框架中显示
例子

更多组件
更多组件可查询文档


网络

很多移动应用都需要从远程地址中获取数据或资源。你可能需要给某个 REST API 发起 POST 请求以提交用户数据,又或者可能仅仅需要从某个服务器上获取一些静态内容。

fetch的基本用法

  • React Native 提供了和 web 标准一致的Fetch API,用于满足开发者访问网络的需求。
  • 这篇文章只会列出 Fetch 的基本用法,并不会讲述太多细节,你可以使用你喜欢的搜索引擎去搜索fetch api关键字以了解更多信息。
  • 可以参考视频教程

发起请求

要从任意地址获取内容的话,只需简单地将网址作为参数传递给 fetch 方法即可(fetch 这个词本身也就是获取的意思):

1
fetch('https://mywebsite.com/mydata.json');//获取该网址中mydata.json的内容

fetch() 还有可选的第二个参数,可以用来定制 HTTP 请求一些参数。你可以指定 header 参数,或是指定使用 POST 方法,又或是提交数据等等:

1
2
3
4
5
6
7
8
9
10
11
12
fetch('https://mywebsite.com/endpoint/', {
method: 'POST',//GET请求数据,POST修改数据
headers: {
Accept: 'application/json',//要求获取json格式的数据
'Content-Type': 'application/json',//表示发送的实体数据为json类型
},
//使用POST请求需要把body部分以字符串或者FormData对象传过去
body: JSON.stringify({
firstParam: 'yourValue',
secondParam: 'yourOtherValue',
}),
});

提交数据的格式关键取决于 headers 中的Content-Type。Content-Type有很多种,对应 body 的格式也有区别。
到底应该采用什么样的Content-Type取决于服务器端,所以请和服务器端的开发人员沟通确定清楚。
常用的’Content-Type’除了上面的’application/json’,还有传统的网页表单形式,示例如下:

1
2
3
4
5
6
7
fetch('https://mywebsite.com/endpoint/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'key1=value1&key2=value2',
});

可以参考Fetch 请求文档来查看所有可用的参数。

注意:使用 Chrome 调试目前无法观测到 React Native 中的网络请求,可以使用第三方的react-native-debugger来进行观测


补充:json()

  • json() 方法接收一个 Response对象,读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 JSON 格式的 Promise 对象
  • MDN文档

处理服务器的响应数据

上面的例子演示了如何发起请求。很多情况下,你还需要处理服务器回复的数据。

网络请求天然是一种异步操作(异步的意思是你应该趁这个时间去做点别的事情,比如显示 loading,而不是让界面卡住傻等)。无论请求成功与否,fetch()都返回一个 promise 对象,请求成功时 resolve 对应请求的 Response对象(如果数据获取失败就会到进入catch),这种模式可以简化异步风格的代码(别忘了 catch 住fetch可能抛出的异常,否则出错时你可能看不到任何提示):

1
2
3
4
5
6
7
8
9
10
11
12
function getMoviesFromApiAsync() {
return fetch('https://facebook.github.io/react-native/movies.json')
//成功获取到的数据(Response对象)作为参数response传入then()
.then((response) => response.json())//解析为 JSON 格式的 Promise 对象
.then((responseJson) => {//responseJson为上一个then()返回的数据
return responseJson.movies;//返回响应数据中的movies
})
.catch((error) => {
//catch 住fetch可能抛出的异常,否则出错时你可能看不到任何提示
console.error(error);
});
}

fetch()返回的是promise 对象,请求成功时将获取到的数据(Response对象)传入并执行then(),response是Response对象,包含Header、status、statusText等属性。
Response对象 要
获得具体数据
需要使用.json()(解析为JSON格式)、.text()(解析为 USVString 格式)、.formData()(用于FormData对象)等方法。
response.json()返回的是一个Promise对象,所以只能先return为下一个then()中的参数responseJson,再在下一层处理。【Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值。所以我们不能直接操作Promise对象,需要等异步操作成功以后使用then()取到成功后返回的数据,在then()中对数据进行操作

注意

  • 默认情况下,iOS 会阻止所有 http 的请求,以督促开发者使用 https。如果你仍然需要使用 http 协议,那么首先需要添加一个 App Transport Security 的例外,详细可参考这篇帖子
  • 从 Android9 开始,也会默认阻止 http 请求,请参考相关配置

使用其他的网络库

  • React Native 中已经内置了XMLHttpRequest API(也就是俗称的 ajax)。
  • 一些基于 XMLHttpRequest 封装的第三方库也可以使用,例如frisbee或axios等。但注意不能使用 jQuery,因为 jQuery 中还使用了很多浏览器中才有而 RN 中没有的东西(所以也不是所有 web 中的 ajax 库都可以直接使用)。【想要使用其他的网络库可以参考这个文档
  • 但是**不推荐使用第三方库,建议React Native中使用fetch()**。
  • 需要注意的是,安全机制与网页环境有所不同:在应用中你可以访问任何网站,没有跨域的限制。

fetch和axios相比

  • 使用fetch()多了一步,axios通过get()获取到的直接是数据,而fetch()获取到的是Promise对象,还要通过.json()解析为JSON格式才能获取到数据。
  • axios可参考笔记