美文网首页RN
ReactNative系列(三):必会知识整理

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

作者: 猿海一粟 | 来源:发表于2019-04-09 17:01 被阅读138次
    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的同学看到。仅供参考,如果看到有问题或错处,欢迎指出,互相交流!!

    相关文章

      网友评论

        本文标题:ReactNative系列(三):必会知识整理

        本文链接:https://www.haomeiwen.com/subject/ifthuqtx.html