ReactNative系列(三):必会知识整理

ReactNative.jpg

ReactNative整理:《ReactNative系列》

前言

  前面两篇文章我们已经介绍了RN的基本知识和生命周期,这篇讲讲对RN中常用常见知识的理解,想看前面基础文章的可以点下面的链接去看。
  我们可以将这些关键字两两分为一组,statepropsletconstPromise机制中的resolverejectthencatchasyncawait相互类比起来更容易理解。
ReactNative开发(一):简介及环境搭建
ReactNative开发(二):组件生命周期详解


一、状态与属性(stateprops

 官方文档的说明:
props:组件在创建时可以用参数来定制。用于定制的参数就称为props
state:props是在父组件中指定,而且一经指定,在被指定的组件生命周期中不再改变。对于需要改变的数据,我们需要用state
 通俗来讲,propsstate是控制一个组件的两种数据。属性props是组件生成时所带的参数,组件自身内部不可改变;如果想要修改,只能通过外部修改参数达到效果。状态state是组件自身私有的,是没有办法从外部传入组件内部的。React把组件看成是状态机,会监听组件中state的变化,只要state发生了变化就会引起组件的重新render,更新DOM。我们可以做个简单Demo帮助理解:
1、父组件:

import React, { Component } from 'react';
import {StyleSheet, Text, View, TouchableOpacity } from 'react-native';

// 父组件
export default class Parent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      parent: '父组件初始值'
    };
    this.handleParentPress = this.handleParentPress.bind(this);
  }

  componentWillMount() {
    console.log('-parent-WillMount-');
  }

  componentDidMount() {
    console.log('-parent-DidMount-');
  }

  componentWillReceiveProps() {
    console.log('-parent-WillReceiveProps-');
  }

  componentWillUpdate() {
    console.log('-parent-WillUpdate-');
  }

  componentDidUpdate() {
    console.log('-parent-DidUpdate-');
  }

  /** 按钮点击处理 **/
  handleParentPress() {
    this.setState({ parent: '父组件改变' });
  }

  render() {
    console.log('-parent-render-');
    return (
      <View style={styles.parentContainer}>
        <TouchableOpacity onPress={this.handleParentPress}>
          <Text style={styles.contentText}>
            {this.state.parent}
          </Text>
        </TouchableOpacity>
      </View>
    );
  }
}

在这里把组件生命周期方法也加了进来以便理解,通过log可以看到周期方法的运行顺序。在这里强调一下:如果组件render函数中存在对state的使用,必须要在构造函数中声明this.state = {},否则运行会报错;如果没有使用state,不会影响运行。
页面运行结果如下:

默认展示.jpg

按钮点击后更新state:
按钮点击.jpg

可以看到两次state不同,由开始的“父组件初始值”修改成了“父组件改变”,页面刷新后文字也发生了改变。生命周期方法少了shouldComponentUpdate,建议最好不要随意重写这个方法,除非很明确页面刷新的判断条件,否则容易导致state或者props改变之后页面也不刷新;当然,这个方法是应用性能优化时的一个优化点。如果重写,可以返回默认值return true

2、子组件:

class Child extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: props.childContent
    };
    this.handleChildPress = this.handleChildPress.bind(this);
  }

  componentWillMount() {
    console.log('-child-WillMount-');
  }

  componentDidMount() {
    console.log('-child-DidMount-');
  }

  componentWillReceiveProps(nextProps) {
    console.log('-child-WillReceiveProps-');
    console.log(this.props, '-this-');
    console.log(nextProps, '--next--');
    this.setState({ text: nextProps.childContent });
  }

  componentWillUpdate() {
    console.log('-child-WillUpdate-');
  }

  componentDidUpdate() {
    console.log('-child-DidUpdate-');
  }

  handleChildPress() {
    console.log('-child-handleChildPress-');
    this.setState({ text: '子组件变了'});
  }

  render() {
    console.log('-child-render-');
    console.log(this.props, '==child-props==');
    return (
      <View style={styles.childContainer}>
        <TouchableOpacity onPress={this.handleChildPress}>
          <Text style={styles.contentText}>
            {this.state.text}
          </Text>
        </TouchableOpacity>
      </View>
    );
  }
}

在父组件中添加一条state数据,用来控制子组件属性值,并在点击父组件时,修改这条state数据:

  // 父组件中构造函数
  this.state = {
      parent: '父组件初始值',
      childContent: '子组件初始值'
    };
  
  // 父组件点击事件回调方法
  handleParentPress() {
    console.log('-parent-handleParentPress-');
    this.setState({
      parent: '父组件改变',
      childContent: '子组件改变'
    });
  }

另外需要调整父组件的render函数,在其中加入子组件,并传入属性值childContent

  // 父组件render函数
  render() {
    console.log('-parent-render-');
    console.log(this.state, '-parent--state-');
    return (
      <View style={styles.parentContainer}>
        <TouchableOpacity onPress={this.handleParentPress}>
          <Text style={styles.contentText}>
            {this.state.parent}
          </Text>
        </TouchableOpacity>
        <Child childContent={this.state.childContent}/>
      </View>
    );
  }

注意:组件的构造方法中存在props,此时的props就是该组件的属性值。所以,我们可以通过构造函数中的props,将子组件的属性值childContent,当做其state的初始值this.state = { text: props.childContent }用作页面展示。

运行效果.jpg

上面是运行效果图:
 注意两个箭头所指的日志,第一个是点击父组件按钮,第二个是点击子组件按钮;
(1)未点击父组件前:可以看到调用生命周期方法时,先调用父组件的componentWillMount再调用子组件的;而componentDidMount则是先调用子的,再调用父的。因为渲染入口是父组件,子组件是挂到父组件的render函数中的,所以会先调用父组件的componentWillMount和render,但是componentDidMount是渲染完成时的回调,只有当父组件中包含的所有子组件渲染完毕,父组件才算是渲染完毕,因此子组件调用componentDidMount方法在父组件之前。
(2)点击父组件按钮后:更新了state值,传入子组件的属性值发生了改变,所以在子组件中调用了componentWillReceiveProps方法,this.propsnextProps分别代表初始属性值和修改过后的属性值;在该方法中修改子组件的state值为修改后的属性值,可以看到子组件的显示值变化,而且没有重复调用子组件的render函数。
(3)点击子组件按钮后:更新了子组件的state值,只调用了子组件自身的周期函数,并没有影响到父组件,不会引起父组件的渲染。可以利用拆分子组件的方式减少全局渲染频率,提高性能。
 总之,stateprops的深入理解和灵活使用会使页面体验效果更好,数据处理更便捷;同时也是性能优化点,尽量减少页面状态的刷新能降低页面渲染资源开销,提升应用性能。


二、变量声明(letconst

 简述:letconst是ES6中新加入的命令,在ES5中用的是var;它们的区别主要在于:var定义的变量可以变量提升,没有块的概念;letconst定义的变量只能在块作用域内访问。let定义的是变量,const定义的是常量,需要初始化赋值。const定义基本类型时不可修改,比如数字、字符串;定义复合类型时可修改其中的值,比如数组。

Q:什么是变量提升?
A:是JS语法中的概念,ES6之前是没有块作用域,只有全局作用域及函数作用域,变量提升是将变量声明提升至所在作用域的最开始部分。

下面用代码帮助理解:

  • let用法
function test() {
  console.log(name, '-name-');
  // 输出 => undefined "-name-"
  let name;
  name = '姓名初始值';
  console.log(name, '==name11==');
  // 输出 =>  姓名初始值==name11==
  name = '姓名赋值成功';
  console.log(name, '==name22==');
  // 输出 =>  姓名赋值成功 ==name22==
}

let声明变量,可以不用赋初始值;在变量的作用域内,可以进行修改;不能在同一作用域内重复声明;在变量声明前是不可用的。

  • const用法
function test() {
  console.log(name, '-name-');
  // 输出 => undefined "-name-"
  const name = 'zhang';
  const array = [];
  // name = 'qwert';
  // array = [0, 1, 2];
  // 提示Attempt to assign to const or readonly variable
  console.log(array, '==array11==');
  // 输出 => [] "==array11=="
  array[0] = 0;
  array[1] = 1;
  array[2] = 3;
  console.log(array, '==array22==');
  // 输出 => (3) [0, 1, 3] "==array22=="
}

const声明常量,必须赋初始值,并且基本类型赋值后不可改变。与let类似,不能在同一作用域重复声明,在声明前不可用。
注意:在声明数组、map这类复合变量时,是可以其修改内容的。上面例中我们可以看到,定义数组array赋空值,如果用array = [1, 2, 3]的方式是不可以修改数组的,也不合语法;而用array[i] = 值的方式却能修改其内容。这是因为第一种赋值方式是相当于将数组的地址赋给变量array,定义的数组地址不可修改,所以不成功;而第二种赋值方式是在不修改变量地址的前提下为数组修改内容,所以可以生效。

  • var用法
{
  var name = 'qwert';
  var name = '1111';
}
// 输出 => 1111 ==name==
console.log(name, '==name==');

(function abcd() {
  var age = 15;
  console.log(age, '==age==');
})();
// console.log(age, '==age00==');    报错

可以看到var声明的变量可以跨块访问,但是不能跨函数访问;而且在同一块内可以重复声明。正因为可以跨块访问,同时可以重复声明,所以有可能会引起不同块区域中变量的相互影响。
 对比letconstvar可以发现:前两者类似Java中的变量声明方式,都是先声明后使用,不可重复声明;但是var可以跨块访问,有可能引起变量值的覆盖或混乱,所以使用时需要特别注意。


三、Promise机制

  • Promise简介
    Promise是ES6中新增的编程方式,是异步编程的一种解决方案;它可以在异步操作中灵活的处理错误;支持链式调用;从字面含义来看表示承诺,承诺过一段时间会返回一个结果,同时它也是个对象,从中可以获取到异步操作的信息。
  • 三种状态
    Promise有三种状态,分别是:
     1. pending -- 等待状态;
     2. fullfield(或resolved) -- 成功状态;
     3. rejected -- 失败状态。
    三种状态的转换途径只有两种,初始状态是pending
     1. 任务完成:pending --> resolved (等待 --> 完成);
     2. 任务失败:pending --> rejected (等待 --> 失败);
  • Promise的方法
    Promise中方法可以用console.dir(Promise)打印出来观察:包含allracerejectresolve方法,同时prototype中还有我们常见的thencatchfinally方法。
    Promise方法.jpg
  1. Promise创建
 let promise = new Promise((resolve, reject) => {});

JS中提供了构造函数去创建Promise对象,需要传的参数是用户自定义的方法,用来处理异步任务;其中resolvereject是JS提供的回调函数,不需要自己定义实现。异步操作成功,就调用resolve,将结果数据传入resolve,Promise从pending状态变成resolve;异步操作失败,则调用reject,需要将错误对象当参数传进去,此时,Promise从pending状态变成reject

  1. then方法
    但凡异步操作,一般都是需要回调函数来处理接下来的业务的,而then方法就是将原来的回调函数抽离出来,在异步操作结束后,通过链式调用的方式执行回调;then方法强大的地方在于它可以在内部继续写Promise对象,并返回;也可以直接return数据而不是Promise对象。
    then方法可以有两个参数,一个是成功resolve的回调,第二个是失败reject的回调方法。
   let promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('-start-');
        // 取随机数 * 10
        let random = Math.random() * 10;
        // 向下取整
        let count = Math.floor(random);
        console.log(count, '-count-');
        if (count < 5) { // 整数小于5判定为成功
          resolve('--任务成功--');
        } else { // 整数大于等于5判定为失败
          reject('==任务失败==');
        }
      }, 1000);
    });
    promise.then(success => {
      console.log(success, '--success--');
      return '成功之后返回';
    }, failed => {
      console.log(failed, '--failed--')
    }).then(data => {
      console.log(data, '=data=')
    });

结果输出日志:
成功日志:

成功日志.jpg

失败日志:
失败日志.jpg

  1. catch方法
    catch方法的作用主要有两个:一是用来指定reject的回调;二是捕捉then方法中的异常,避免程序报错卡死。
    修改上面代码中的Promise调用方法:
    promise.then(data => {
      console.log(data, '=then=');
      console.log(aaa);
    }).catch(error => {
      console.log(error, '=error=')
    });

异步操作成功时,会链式执行then方法,但是其中aaa是未声明的变量,所以会报错:

then方法报错.jpg

异步操作失败时,会调用rejectcatch方法中接收到抛出的错误日志并打印:
reject操作回调.jpg

  1. all方法
    all方法为Promise提供了并行异步操作的能力。简单来说,即可以同时执行多个异步操作,all方法所需要的参数是以需要执行的异步操作为元素的数组。并且在所有异步任务执行完毕后才执行回调操作。等到都执行完后,会回调到then方法中,data就是最终的返回结果。
  let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('=执行1=');
        resolve('--任务1--');
      }, 1000);
    });
    let promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('=执行2=');
        resolve('--任务2--');
      }, 1000);
    });
    let promise3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('=执行3=');
        resolve('--任务3--');
      }, 1000);
    });
    Promise.all([promise1, promise2, promise3]).then(data => {
      console.log(data, '--执行结果--');
    }).catch(error => {
      console.log(error, '--error--')
    })

注意: 返回的结果也是数组,结果元素的顺序和传入异步参数的顺序一一对应;异步任务只要有任一个失败都会回调catch方法。

all方法日志.jpg

  1. race方法
    race字面意思是赛跑,所以强调的是以速度最快的为准执行回调。race的用法和all类似,参数都是以异步操作为元素的数组,不同的是:all是所有异步操作执行完后才会回调then方法,以最慢的为准;而race是只要有一个执行完就立刻回调then,以最快的为准。
    将上面代码中的all改为race,并且调整后两个异步操作的执行延迟时间为1500,时间相同的话表现不出来前后差异:
    Promise.all([promise1, promise2, promise3]).then(data => {
      console.log(data, '--执行结果--');
    }).catch(error => {
      console.log(error, '--error--')
    })

注意:第一个异步操作执行完后回调then后,其他未执行完的异步操作会继续执行,不会中断停止。

race方法输出.jpg

Promise常用的知识和方法这里就介绍完了,更深入的理解可以结合具体的项目来研究。


四、异步与等待(asyncawait

  • async是异步的意思。用来标识函数为异步函数,往往函数中有耗时操作时会用到async,而异步函数在执行时不会阻塞后面代码运行。
  • await是等待的意思。必须在async修饰的函数中使用,用来标识耗时操作的语句;当程序执行到await所在行时,会阻塞并等待,直到异步任务得到返回结果。
    代码验证:
  componentDidMount() {
    console.log(new Date().toTimeString(), '==start==');
    this.getImageData().then(data => {
      console.log(data, new Date().toTimeString() + '   -data-');
    });
    console.log(new Date().toTimeString(), '==end==');
  }

  async getImageData() {
    try {
      let response = await fetch('http://www.pptbz.com/pptpic/UploadFiles_6909/201309/2013093019370302.jpg');
      console.log(response, new Date().toTimeString() + '   --response--');
      let path = response.url;
      this.setState({ image: path });
      return path;
    } catch (error) {
      console.log(error, '-error-');
    }
  }

输出日志:

async:await日志.jpg

可以看到:从打印时间上来看,start日志和end日志基本没有时间差,说明用async修饰的getImageData方法没有阻塞end日志的输出;而response日志输出时间要比end日志晚5秒左右,说明用await修饰的fetch语句在做耗时操作,并且一直在等待,直到拿到返回值,输出日志;async函数返回的是Promise,因此可以在then方法中得到返回值。


结尾

 以上介绍的技术点都是ReactNative中常见常用的,是个人回顾整理的记录,分享出来希望更多想学RN的同学看到。仅供参考,如果看到有问题或错处,欢迎指出,互相交流!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容