1. React Native 基础与语法

3,306 阅读9分钟

开发环境安装及相关代码库配置见官方文档

Windows系统搭建React Native开发环境

创建RN项目

react-native init ProjectName --version 0.61.3 // 根据指定版本号创建RN项目

添加指定版本依赖库

yarn add libName@version_numberi    // 添加指定版本号依赖库,需安装yarn环境

或者

npm install --save libName@version_number

开启Developer Menu

模拟器: Command + D

真机:摇一摇

远端(浏览器)代码调试

  1. Developer Menu选择Debug
  2. Command + Option + I打开浏览器开发者工具

真机调试

iOS

打开RCTWebSocketExecutor.m,将localhost修改为本机电脑IP,再开启远端调试


ECMAScript6基础

1.支持letconst

let: 在代码块内定义局部作用域的变量,支持与全局变量重名,需要在使用前先定义
const: 定义常量,需要在使用之前先定义并同时赋值

2. 类(class)

可以使用class关键字定义类,类似于其他面向对象语言。

let methodName = 'getPoint';

class Point {
  // constructor 为构造方法
  constructor(x, y) {
    // this 指代类实例对象
    this.x = x;
    this.y = y;
  }
  
  // 使用 static 声明类属性,只可以通过类名访问,不可以通过实例访问
  static greeting = 'hello'
  
  // 在方法前加 static 关键字,表示此方法是类方法,只能通过类名调用,类似OC中的 + 
  static classMethod() {
      // 类方法中的 this 指代类本身
      return this.greeting;
  }

  // 定义类的方法时,不需要加 function 关键字
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
  
  // 使用变量当函数名或属性名,需要用 [] 将变量名包起来
  [methodName] () {
    // do sth.
  }
}

// 类的实质是函数
typeof Point // "function"
// 类实质上等价于其构造函数
Point === Point.prototype.constructor // true

// 使用 new 关键字来创建实例
let p = new Point();
// 实例的函数实际就是其原型的函数
p.constructor = Point.prototype.constructor // true

// 使用 Object.assign 来一次性注入多个函数
Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

类的继承

// 使用 extends 关键字来表明继承关系
class ColorPoint extends Point {
  // 子类的 constructor 方法
  constructor(x, y, color) {
    super(x, y); // 必须要调用父类的 constructor(x, y)。这里 super 是方法,作为方法时只能在子类的构造函数中使用
    this.color = color; // 在调用父类的 constructor 之后才可以使用 this
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()。这里 super 指代的是父类的原型对象
  }
}

// 判断一个类是否继承自另一个类
Object.getPrototypeOf(ColorPoint) === Point // true

3.模块化(module)

每一个模块都有单独的作用域,模块之间的相互调用通过export来规定模块对外暴露的接口,通过import来引用其他模块提供的接口。模块化同时提供了命名空间,防止函数的命名冲突。

export
export var name = 'Rainbow';

export function someFunc(argu) {
	// Do sth.
}
import
import {module1, module2,...} from 'fileRelativePath';	// module.js
import {name} from 'Module';

4.箭头函数

普通匿名函数定义的缩写方式,形如:

// 如果仅有一个入参,可以省略 argus 的圆括号
// 执行语句如果只有一句,可以省略花括号,该语句的返回值会被返回。
(argus) => {
  // operations
  // return values
}

箭头函数内部的this总是指向定义时所在的对象。

5.函数参数默认值

function foo(color = 'red', height = 50) {
	// Do sth.
}

6.动态字符串

Chrome貌似不支持

var firstName = 'Bill', secondName = 'Gates';
var str = `My name is ${firstName} ${secondName}.`;

7.解构赋值

[a, b, c] = [1, 2, 3, 4];	// a = 1, b = 2, c = 3
[a, , , d] = [1, 2, 3, 4];	// a = 1, d = 4
// 提供默认值
[a = 3, b = 5] = [1];	// a = 1, b = 5
[a = 3, b = 5] = [b, a];	// a = 5, b = 3
[a = 3, b = a] = [1];   // a = 1, b = 1

对象解构赋值。对象的解构必须保证变量名与对象的属性名相同。

const student = {
	name: 'XiaoMing',
	age: 8,
}
// 实际是 const { name: name, age: age } = student 的缩写
const { name, age } = student;	// name = 'XiaoMing', age = 18

8.扩展运算符

符号: ...。作用:将数组按顺序展开,或者将对象按键值对进行展开。

const group1 = [2, 3];
const group2 = [1, ...group1, 4, 5];	// [1, 2, 3, 4, 5]
const group3 = [...group1];	// [2, 3]

ECMAScript2018中延展操作符增加了对对象的支持

var obj1 = {x: 1, y: 2};
var obj2 = {x: 5, z: 6};
var obj3 = {...obj1};	// {x: 1, y: 2}
var obj4 = {...obj1, ...obj2};	// {x: 5, y: 2, z: 6}

var params = {x: 1, y: 2, z: 3};
var {z, ...other} = params;	// 相当于定义了一个other变量,且other = {x: 1, y: 2}

9.对象属性简写

ES6直接使用定义的变量作为对象的属性而无需按照键值对的方式去书写定义对象

var name = 'xiaoming', age = 10, height = 100;
var student = {name, age, height};

10.Promise

用于异步回调。

function multiply(input) {
    return new Promise(function (resolve, reject) {
        console.log('calculating ' + input + ' x ' + input + '...');
        setTimeout(resolve, 500, input * input);
    });
}

function add(input) {
    return new Promise(function (resolve, reject) {
        console.log('calculating ' + input + ' + ' + input + '...');
        setTimeout(resolve, 500, input + input);
    });
}

var p = new Promise(function (resolve, reject) {
    console.log('start new Promise...');
    resolve(123);
});

p.then(multiply)
 .then(add)
 .then(multiply)
 .then(add)
 .then(function (result) {
    console.log('Got value: ' + result);
});

ECMA新特性API

asyncawait

async: 异步执行函数,默认返回一个Promise对象。 await: 等待指定函数执行结束后再执行后续代码

Object.values()

返回object自身所有属性的值,不包括继承得到的属性

Object.entries()

for (const [key, value] of Object.entries(obj)) {
    console.log('key: '+ key + ', value: ' + value);
}

React基础

JSX

JSX是一个JavaScript的语法扩展,可以定义简洁且包含属性的树状结构语法

React组件与HTML标签对比

React可以渲染HTML标签或React组件。渲染HTML标签,需在JSX里使用小写字母开头的标签名。

var myDivElement = <div className='foo'/div>;
React.render(myDivElement, document.root);

渲染React组件,需创建大写字母开头的本地变量

var myComponent = <MyComponent someProps={true} />;
React.render(myElement, document.body);

组件的属性(props)

可以通过this.props.p的方式获取组件的任意属性,对象的属性也可以任意定义。

遍历对象的所有属性:
this.props.children:返回组件对象的所有属性。可以通过React.Children.map()React.Children.forEach()来遍历子节点。

PropTypes: 添加属性传值要求
defaultProps: 为属性添加默认值

class MyTitle extends React.Component {
	static PropTypes = {
		title: PropTypes.string.isRequired,	// 要求传入字符串,且不可为空
		// other requirements
	};
	
	static defaultProps = {
		shortName = 'MyTitle';
	};
}

组件的状态state

state是组件私有的,可以通过state = {}方式来初始化,通过this.setState()来改变。当state更新后,组件会重新渲染。

render()方法依赖于组件的propsstate。框架会确保渲染出来的UI界面和组件的属性和状态保持一致。

初始化state
// 此初始化只会在组件的生命周期中执行一次
constructor(props) {
	super(props);
	this.state = {
		name: '小明';
	};
}
更新state

通过this.setState()来更新状态。调用该方法后,组件会重新渲染相关UI。

this.setState({favourite:!this.state.favourite});

组件的生命周期

组件生命周期的三种状态:

  • Mounting:加载中,对应componentDidMount()

  • Updating:更新中,对应componentDidUpdate()

  • Unmounting:移除中,对应componentDidUnmount()

  • componentDidMount: 在组件被装配后立即调用,通常在该方法中执行一些初始化相关操作,例如:网络请求。如果在此方法中修改组件的状态,会触发重新渲染。但此时组件并未被展示,所以用户不会看到中间中台

  • shouldComponentUpdate(nextProps, nextState): 针对指定属性或状态,询问是否需要更新

  • componentDidUpdate: 组件更新后的通知回调

  • componentDidUnmount: 组件被移除后的回调,在该方法中执行清理相关操作

React中的Function Component与Class Component

  • Function Component:通过函数创建组件,在函数的结尾处返回一个组件。函数组件内部无法添加状态、不能提供组件的生命周期方法回调,属于一种无状态的组件(通过其他手段也可以实现状态功能)。可以通过函数参数传入创建组件所需的参数,直接在函数内部使用。
  • Class Component:通过定义class创建组件,通过render()方法渲染组件内容。此方式相当于定义新类型,在内部可以提供属性、状态等相关参数,同时也可以实现组件生命周期相关方法。实际使用组件时的代码书写格式与函数调用完全一致,但是相关的参数需要在render()方法中额外进行解构获取。

关于二者更多的差别,参考:
精读《Function Component 入门》
精读《Function VS Class 组件》

React中的this绑定

JavaScript中的this指向并不清晰。在类组件内部定义的函数中使用this,实际调用该函数时,可能会出现this丢失而并不指向该组件类对象,而是指向全局window对象的问题。

因此,需要将函数中使用到的this绑定到该类对象,具体实现方法有如下几种:

1. 使用Function.prototype.bind

在使用到该类函数的地方,通过.bind{this}来将该函数内部用到的this绑定到该组件对象

<Button type="primary" onClick={this.onSearch.bind(this)}>搜索</Button>
2. ES7函数绑定语法

就是上述方式的语法糖,通过在函数前添加::来代替在函数后面使用.bind(this)

<Button type="primary" onClick={::this.onSearch}>搜索</Button>
3. 在构造函数中bind(this)

可以在构造函数中将组件内部的方法绑定到该组件的对象

constructor(props) {
	super(props);
	this.onSearch = this.onSearch.bind(this)	// 将onSearch方法中的this绑定到这个初始化的对象
}
4. 使用箭头函数

在为组件中的子组件赋值函数时直接通过定义箭头函数来赋值,该箭头函数函数体中的this会默认绑定到该组件对象。

<Button type="primary" onClick={() => { /* Do sth with this */ }>搜索</Button>
5. core-decorators.js

在组件内部使用@autobind修饰符,则该组件内部使用到的this默认都会绑定到该组件对象

class Search extends Component {
    @autobind
    onSearch() {
        console.log('表单值:', this.field.getValues());
        this.props.onSearch(this.field.getValues());
    }
    render(){
        const {init} = this.field;
        return <div>
            <Form direction="hoz" labelAlign="left">
                    <FormItem label="loginID:">
                        <Input placeholder="请输入loginID" {...init('loginID')}/>
                    </FormItem>
                    <Button type="primary" onClick={this.onSearch}>搜索</Button>
            </Form>
        </div>
    }
}

使用VSCode断点调试RN项目

  1. 搜索安装 React Native Tools 插件
  2. 点击IDE左侧面板的Debug图标,打开调试界面
  3. 点击左边顶部齿轮按钮,添加启动配置文件,在弹出的环境选择列表中选择React Native
  4. 选择环境后,根据需要,选择Debug iOS或对应的平台
  5. 点击确定后,会自动添加配置信息,在左侧顶部点击绿色运行按钮即可。
可能遇到的问题
  1. 添加启动配置文件,选择环境时可能列表中没有React Native选项。此时可以先到已安装的插件中选择查看该插件,再回到Debug界面添加配置文件,即可出现React Native选项
  2. 项目可以成功运行,但调试工具启动失败。引发原因可能是RN项目中的库与调试工具的依赖库不匹配。进入RN项目,执行npm install --reset-cache重置缓存重新安装一遍npm依赖库可解决此问题。