JSX语法
RN使用JSX语法来构建页面。JSX并不是一门新的开发语言,而是Facebook技术团队提出的一种语法方案,即一种可以在JS代码中使用HTML标签来编写JS对象的语法糖,所以JSX本质上还是JS。
在React和RN应用开发中,不一定非要使用JSX,也可以使用JS进行开发。不过,因为JSX在定义上类似HTML这种树型结构,所以使用JSX可以极大地提高阅读和开发效率,减少代码维护的成本。
在React开发中,React的核心机制之一就是可以在内存中创建虚拟DOM元素,进而减少对实际DOM的操作从而提升性能,而使用JSX语法可以很方便地创建虚拟DOM。
例如,初始化ReactNative项目时的默认示例如下:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
class HelloWorldApp extends Component {
render() {
return (
<View style={{
flex: 1,
justifyContent: "center",
alignItems: "center"
}}>
<Text>Hello, world!</Text>
</View>
);
}
}
export default HelloWorldApp;
在上述代码中,组件的render()方法主要用于页面的渲染操作,它返回的是一个视图(View)对象,之所以没有看到创建对象和设置属性的代码,是因为JSX提供的JSXTransformer可以帮助我们把代码中的XML-Like语法编译转换成JS代码。借助JSX语法,开发者不仅可以用它来创建视图对象、样式和布局,还可以用它构建视图的树形结构。并且,JSX语法的可读性也非常好,非常适合前端页面的开发。
//例如
<Text>"hello world"</Text>
//经过React转换之后会变成如下代码
React.createElement("Text",null,"hello world")
可以说React.createElement很少使用,用户可能永远不需要使用此语法创建RN元素,但是在对创建组件的方式需要更多的控制或在阅读他人的代码时,它可能会派上用场。
React.createElement(type,props,children){}
//这三个参数分别为:
type —— 要渲染的元素
props —— 希望组件拥有的各种属性
children —— 子组件或文本
语法基础
RN目前支持ES5及以上版本,不过实际开发中使用得最多得还是ES6。
let和const命令
ES6中新增了let命令,主要用来声明变量。它得用法类似于var,但是let声明的变量只在let命令所在的代码块内有效,如下所示:
{
let a = 10; //代码块内有效
var b = 1;
}
a //ReferenceError
b //1
另外,使用let声明变量时不允许在相同的作用域内重复声明,如下所示。
//报错
function func(){
let a = 10;
var a = 1;
}
//报错
function func(){
let a = 10;
let a = 1;
}
const用于声明一个只读的常量,一旦声明,常量的值就不能改变,如下所示。
const PI = 3.1415;
PI //3.1415
PI = 3;//报错
在上面的示例代码中,由于PI是常量,所以值是不能改变的。并且,const声明的常量,一旦声明就必须立即初始化,不能留到后面再赋值。事实上,与let一样,const声明的常量也不可重复声明。
类
作为一门基于原型的面向对象语言,js一直没有类的概念,而是使用对象来模拟类。现在,ES6添加了对类的支持,引入了class关键字,新的class写法让对象的创建和继承更加直观,也让父类方法的调用、实例化、静态方法和构造函数等概念更加具象。
class App extends Component{
render(){
return <View></View>
}
}
同时,新的ES6语法可以直接使用函数名字来定义方法,方法结尾也不需要使用逗号。
class App extends Component{
componentWillMount(){}
}
在ES5语法中,属性类型和默认属性通过propTypes和getDefaultProps()来实现。而在ES6语法中,属性类型和默认属性则统一使用static修饰。
class App extends Component{
static defaultProps = {
autoPlay:false
}
static propTypes = {
autoPlay:React.PropTypes.bool.isRequired
}
}
箭头函数
ES6中新增了箭头操作符(=>),可以用它来简化函数的书写,如下所示:
var f = v => v;
//等价于
var f = function(v){return v}
如果箭头函数带有多个参数,可以使用一个圆括号代表参数部分,如下所示:
var sum = (a,b) => a+b;
//等价于
var sum = function(a,b){
return a+b;
}
如果函数体涉及多条语句,就需要使用大括号。
var add = (a,b) => {
if(typeof a == 'number' && typeof b == 'number'){
return a+b;
}else{
return 0;
}
}
使用箭头函数时,需要注意以下几点。
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 箭头函数不支持new命令,否则会抛出错误。
- 不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以使用rest参数代替。
- 不可以使用yield命令,因此箭头函数不能用作generator函数。
模块
历史上,js一直没有模块体系,无法将一个大程序拆分为互相依赖的小文件,也无法将简单的小文件拼装成一个模块。
在ES6之前,js社区制定了一些模块化开发方案,比较著名的有AMD和CommonJS两种,前者适用于浏览器,后者适用于服务器。不过随着ES6的出现,js终于迎来了模块开发体系,并逐渐成为浏览器和服务器通用的模块解决方案。
ES6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系以及输入和输出的变量。ES6模块有两个最重要的命令,即export和import。其中,export用于对外输出模块,import用于导入模块。
在ES6语法中,一个模块就是一个独立的文件,文件内部的所有变量都无法被外部获取,只有通过export命令导出后才能被另外的模块使用。例如,有一个名为a.js的文件,代码如下:
let sex = 'boy';
let echo = function (value){
console.log(value);
}
export {sex,echo}
要在另一个文件中使用这个a.js文件的内容,就需要使用import命令导入模块(文件)。
import {sex,echo} from "./a.js"
console.log(sex)
echo(sex)
当然,多个模块之间也是可以互相继承的。例如,有一个circleplus模块,该模块继承自circle模块,代码如下:
export * from 'circle'
export var e = 2.12
export default function(x){
return Math.exp(x)
}
在上面的代码中,使用export*导出circle模块的所有属性和方法,然后又导出自定义的e变量和默认方法。
Promise对象
Promise是异步编程的一种解决方案,比传统的回调函数更合理、更强大。Promise最早由js社区提出和实现,并最终在ES6版本写进编程语言标准。
简单来说,Promise就是一个容器,里面保存着某个未来才会结束的事件结果。从语法上说,Promise是一个对象,它可以通过异步方式获取操作的结果。使用Promise修饰的对象,对象的状态不受外界影响,一旦状态改变就不会再变,任何时候都可以得到这个结果。
在ES6语法规则中,Promise对象是一个构造函数,用来生成Promise实例。
const promise = new Promise((resolve,reject)=>{
if(success){
resolve(value)
}else{
reject(error)
}
})
在上面的示例中,Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject。其中,resolve函数的作用是将Promise对象的状态从pending变为resolved;而reject函数的作用则是将Promise对象的状态从pending变为rejected。
Promise示例生成后,就可以使用then()方法给resolved状态和rejected状态指定回调函数(变成这两个状态会回调该方法),格式如下:
promise.then((value)=>{
//成功
},(error)=>{
//失败
});
then方法可以接收两个回调函数作为参数:第一个回调函数表示Promise对象的状态为resolved时被调用;第二个回调函数表示Promise对象的状态变为rejected时被调用。这两个函数都接收Promise对象传出的值作为参数,且第二个函数是可选的。
async函数
async函数是一个异步操作函数,不过从本质上来说,它仍然是一个普通函数,只不过是将普通函数的*替换成async,将yield替换成await而已。作为一种新的函数语法糖,async函数可以多种形式存在,如下所示:
async function foo() {}//函数声明
var bar = async function() {}//表达式声明
var obj = { async bar(){}} //对象声明
var fot = async()=>{} //箭头函数声明
async函数会返回一个Promise对象,可以使用then方法和catch方法来处理回调的结果。如果函数内部出现异常,会导致返回的Promise对象状态变为reject,抛出的错误会被catch方法接收;如果正常则返回的Promise对象状态变为resolve。
async function getStockPriceByName(name){
let symbol = await getStockSymbol(name);
let price = await getPriceByName(symbol);
return price;
}
getStockPriceByName('goog').then((result)=>{
console.log(result);
}).catch(error =>{
console.log(err)
})
在上面的代码中,当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成后,在执行函数体内后面的语句。
同时,async函数返回的Promise对象,必须等到内部所有await命令后面的Promise对象执行完成之后,状态才会发生变化,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的所有异步操作执行完,才会执行then方法指定的回调函数。(await命令必须出现在async函数的内部)
正常情况下,await命令后面是一个Promise对象,如果不是Promise对象,则直接返回对应的值。
async function f(){
return await 123;
}
f().then(v => console.log(v))//返回v的数值
上面的代码中,await命令的参数的值是123,不是Promise对象,所以直接返回参数的数值。
当然,await命令后面还可能是一个thenable对象,即定义then()方法的对象,那么await会将其等同于Promise对象。
class Sleep{
constuctor(props){
this.props = props;
}
then(res,rej){
...
}
}
(async() =>{
const time = await new Sleep(1000);
console.log(time)
})();
在async函数中,await命令会串行执行,任何一个await语句后面的Promise对象变为reject状态,那么整个async函数都会中断执行。如果希望异步操作失败后不中断后面的异步操作,可以将异常的部分放在try…catch语句结构里面。
async function myFunction(){
try{
await d();
}catch(err){
console.log(err)
}
}
当然,处理上面的问题还有另一种方法,即在await后面的Promise对象再跟一个catch()方法,用于处理前面可能出现的错误。
async function fun(){
await f().catch((err)=>{
console.log(err)
})
}
如果存在多个await命令修改的异步操作,且不存在继承关系,最后让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
上面的代码中,getFoo和getBar是两个独立的异步操作函数,不存在任何依赖关系。但是上面的代码被写成继承关系,因而比较耗时。因为只有getFoo执行完成后才会执行getBar,其实完全可以让它们同时触发。如果确实希望多个请求并发执行,可以使用Promise.all()方法。
let [foo,bar] = await Promise.all([getFoo(),getBar()])
在Promise机制中,Promise作为一个代理对象,被代理的值在Promise对象创建时可能是未知的。同时,它需要开发者在异步操作成功或失败后绑定相应的处理方法,并且异步方法并不会立即返回最终的执行结果,而是返回一个能代表未来结果的Promise对象。
let promise = new Promise((res.rej)=>{
setTimeout(()=>{
resolve('foo')
},1000)
})
promise.then((value)=>{
console.log(value)//1秒后输出foo
})
console.log(promise)//[object Promise
执行上面代码先输出[object Promise],然后延迟1秒在输出foo
FlexBox布局
Flexbox布局简介
无论是web前端开发还是移动开发,布局技术都是必不可少的。在传统的HTML文件中,每个元素都被描绘成一个矩形盒子,这些矩形盒子通过一个模型来描述其占用的空间,此模型即被称为盒模型。盒模型包含margin、border、padding和content4个边界对象。
其中,margin用于描述边框外的距离,border用于描述围绕在内边距和内容外的边框,padding用于表示内容与边框之间的填充距离,content用于表示需要填充的空间。
由于CSS盒模型需要依赖于position属性、float属性以及display属性来进行布局,所以对于一些特殊但常用的布局实现起来就比较麻烦。为此,W3C组织提出了一种新的布局方案,Flexbox布局。
Flexbox又称弹性盒子布局,旨在提供一个更加高效的方式制定、调整和排布一个容器里的项目布局,即使它们的大小是未知或者动态的。Flexbox布局的主要思想是,让容器有能力使其子项目改变其宽度、高度(甚至顺序),并以最佳方式填充可用空间。
RN实现了Flexbox的大部分功能,因此在实际应用开发中可以直接使用Flexbox布局来进行布局开发。
在flexbox布局中,按照作用对象的不同,可以将flexbox布局属性分为决定子组件的属性和决定自身的属性两种。其中,决定子组件的属性有flexWrap、alignItems、flexDirection和justifyContent,决定组件自身的属性有alignSelf和flex等。上面列举的这6个属性是RN开发中最常用的属性。
属性 | 作用 |
flexDirection | 表示布局中子组件的排列方向,取值包括column、row、column-reverse、row-reverse ,默认值是column(即控件从上往下排列) |
flexWrap | 用于控制子组件是单行还是多行显示,取值包括wrap、nowrap和wrap-reverse,默认值wrap,即默认当一行显示不下时会换行显示 |
justifyContent | 用于表明容器中子组件横向排列的位置,取值包括flex-start、flex-end、center、space-between和space-around |
alignItems | 控制容器中子组件的纵向排列的位置,取值包括flex-start、flex-end、center、baseline和stretch |
flex | 表示子控件占用父控件的比例,即组件可以动态计算和配置自己所占用的空间大小,取值是数值,默认是0,即不占用任何父容器的空间. |
alignSelf | 用于表明组件自己的排列情况 |
注意:flex属性是flexbox布局的重要内容之一,也是实现自适应设备和屏幕尺寸的核心。合理使用flex属性,可以提高页面开发的效率和质量。