乐趣区

React-Native快速入门

准备

学习 React Native 之前,需要了解一下其他知识,帮助你更快的理解 RN
React:React 中文文档
ES6:ES6 入门教程

环境搭建

本人搭建的是 mac+Android 环境,具体过程参考:React Native 中文网 – 搭建环境。
搭建结束后,运行项目

cd AwesomeProject
react-native run-android

当在手机或模拟器上出现如下页面,则说明配置成功。

我在搭建结束后,出现红屏,报错如下:

Unable to load script.Make sure you're either running a metro server(run 'react-native start')or that
your bundle 'index.android.bundle' is packaged correctly for release.

解决方法我记录在这里:点这里。如果遇到同样的问题可以看下。

语法简介

Hello World

先看下大概长什么样

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

export default class HelloWorldApp extends Component {state={ text : 'this is state.name'}
  render() {
   let pic = {uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'};
    const stateName = this.state.name;
    return (<View style={{ flex: 1, justifyContent: "center", alignItems: "center"}}>
          <Text>Hello, world!</Text>
          <Image source={pic} style={{width: 193, height: 110}} />
          <Text>{stateName}</Text>
        </View>
    );
  }
}

基本的用法 Props,State 等 与 React 是一样的,只不过基础组件不同。React 里使用常规的 html 标签,div span 等,使用了一些 RN 自己的原生组件 Image,Text 等。

样式

RN 的样式与 React 写法页基本相同,样式名遵循了 web 的 css 命名,只是按照 JS 语法要求,改成了驼峰命名。
style 属性可以是简单的对象,也可以传入一个数组——在数组中位置居后的样式对象比居前的优先级更高,这样你可以间接实现样式的继承。
当样式复杂时,建议使用 StyleSheet.create 来集中定义组件的样式。看下例子:

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

export default class LotsOfStyles extends Component {render() {
    return (
      <View>
          <Text style={{color:'yellow',fontSize:'20'}}>just yellow</Text>
        <Text style={styles.red}>just red</Text>
        <Text style={styles.bigBlue}>just bigBlue</Text>
        <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
        <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  bigBlue: {
    color: 'blue',
    fontWeight: 'bold',
    fontSize: 30,
  },
  red: {color: 'red',},
});

StyleSheet.create 可以弥补编写复杂样式时,不能使用 css 的不便。

宽高与布局

RN 中宽高可以直接通过 style 指定,与 web 不同的是,RN 中尺寸是无单位的,表示与设备像素无关的逻辑像素点。逻辑像素不清楚的,可以看这里:手机屏幕分辨率术语:逻辑分辨率和物理分辨率。

import React, {Component} from 'react';
import {AppRegistry, View} from 'react-native';

export default class FixedDimensionsBasics extends Component {render() {
    return (
      <View>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
      </View>
    );
  }
}
弹性高宽

在组件样式中使用 flex 可以使其在可利用的空间中动态地扩张或收缩。一般而言我们会使用 flex:1 来指定某个组件扩张以撑满所有剩余的空间。如果有多个并列的子组件使用了 flex:1,则这些子组件会平分父容器中剩余的空间。如果这些并列的子组件的 flex 值不一样,则谁的值更大,谁占据剩余空间的比例就更大(即占据剩余空间的比等于并列组件间 flex 值的比)。

import React, {Component} from 'react';
import {AppRegistry, View} from 'react-native';

export default class FlexDimensionsBasics extends Component {render() {
    return (
      // 试试去掉父 View 中的 `flex: 1`。// 则父 View 不再具有尺寸,因此子组件也无法再撑开。// 然后再用 `height: 300` 来代替父 View 的 `flex: 1` 试试看?<View style={{flex: 1}}>
        <View style={{flex: 1, backgroundColor: 'powderblue'}} />
        <View style={{flex: 2, backgroundColor: 'skyblue'}} />
        <View style={{flex: 3, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
}

flex box 布局

RN 中 flex box 的使用与 web 上 css 的 flex 布局用法基本一直,不同的是 flex-direction 默认值是 column, 而且 RN 中的 flex 只能是数字,而不是 flex-grow, flex-shrink 和 flex-basis 的简写。
一般来说,使用 flexDirection、alignItems 和 justifyContent 三个样式属性就已经能满足大多数布局需求。
例如:

export default class JustifyContentBasics extends Component {render() {
    return (
      // 尝试把 `justifyContent` 改为 `center` 看看
      // 尝试把 `flexDirection` 改为 `row` 看看
      <View style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'space-between',
      }}>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};


注意:RN 中 flexbox 不需要一定指定 flex:1,只要外层元素高度存在(可以设置 height),设置 flexDirection、alignItems 或 justifyContent,内部元素都会根据样式规则,遵循 Flexbox 布局。

事件响应

RN 事件响应的写法,与 ReactJS 类似。不同的是,移动端手势的响应要比 web 端复杂的多,RN 通过响应者的声明周期,来响应不同触摸事件。

响应者的生命周期

我们通过两个方法去“询问”一个 View 是否愿意成为响应者:

  • View.props.onStartShouldSetResponder: (evt) => true, – 在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者?
  • View.props.onMoveShouldSetResponder: (evt) => true, – 如果 View 不是响应者,那么在每一个触摸点开始移动(没有停下也没有离开屏幕)时再询问一次:是否愿意响应触摸交互呢?

如果 View 返回 true,并开始尝试成为响应者,那么会触发下列事件之一:

  • View.props.onResponderGrant: (evt) => {} – View 现在要开始响应触摸事件了。这也是需要做高亮的时候,使用户知道他到底点到了哪里。
  • View.props.onResponderReject: (evt) => {} – 响应者现在“另有其人”而且暂时不会“放权”,请另作安排。

如果 View 已经开始响应触摸事件了,那么下列这些处理函数会被一一调用:

  • View.props.onResponderMove: (evt) => {} – 用户正在屏幕上移动手指时(没有停下也没有离开屏幕)。
  • View.props.onResponderRelease: (evt) => {} – 触摸操作结束时触发,比如 ”touchUp”(手指抬起离开屏幕)。
  • View.props.onResponderTerminationRequest: (evt) => true – 有其他组件请求接替响应者,当前的 View 是否“放权”?返回 true 的话则释放响应者权力。
  • View.props.onResponderTerminate: (evt) => {} – 响应者权力已经交出。这可能是由于其他 View 通过 onResponderTerminationRequest 请求的,也可能是由操作系统强制夺权(比如 iOS 上的控制中心或是通知中心)。
class App extends Component<Props> {onStartShouldSetResponder = () => {console.log("onStartShouldSetResponder");
        return false;
    };
    onMoveShouldSetResponder = () => {console.log("onMoveShouldSetResponder");
        return false;
    };
    onResponderMove = eve => {console.log("onResponderMove", eve);
    };
    onResponderGrant = eve => {console.log("onResponderGrant", eve);
    };
    onResponderReject = eve => {console.log("onResponderReject", eve);
    };

    pressIt = eve => {console.log("onPress");
    };
    render() {
        return (<View style={styles.container}>
                <View
                    onResponderMove={this.onResponderMove}
                    onStartShouldSetResponder={this.onStartShouldSetResponder}
                    onResponderReject={this.onResponderReject}
                    onResponderGrant={this.onResponderGrant}
                    onMoveShouldSetResponder={this.onMoveShouldSetResponder}
                    style={{
                        height: 50,
                        width: 50,
                        backgroundColor: "#ccc",
                        cursor: "pointer"
                    }}
                >
                        <Text> 点我!</Text>
                </View>
              )
              }

生命周期的执行顺序,可以实际触发试试。

Touchable 系列组件
  • 一般来说,你可以使用 TouchableHighlight 来制作按钮或者链接。注意此组件的背景会在用户手指按下时变暗。
  • 在 Android 上还可以使用 TouchableNativeFeedback,它会在用户手指按下时形成类似墨水涟漪的视觉效果。
  • TouchableOpacity 会在用户手指按下时降低按钮的透明度,而不会改变背景的颜色。
  • 如果你想在处理点击事件的同时不显示任何视觉反馈,则需要使用 TouchableWithoutFeedback。

滚动视图与长列表

滚动视图

ScrollView 是一个通用的可滚动的容器,你可以在其中放入多个组件和视图,而且这些组件并不需要是同类型的。ScrollView 不仅可以垂直滚动,还能水平滚动(通过 horizontal 属性来设置)。

export default class IScrolledDownAndWhatHappenedNextShockedMe extends Component {render() {
      return (
        <ScrollView>
          <Text style={{fontSize:96}}>Scroll me plz</Text>
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Text style={{fontSize:96}}>If you like</Text>
          )
          }

ScrollView 内部所有组件都会被渲染,即使这些组件在屏幕的外部。如果要显示较长滚动内容,建议使用性能更好的长列表组件:FlatList。

长列表

长列表常用的组件有 FlatList 和 SectionList。

FlatList 用法
  • 用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同
  • 更适于长列表数据,且元素个数可以增删。FlatList 并不立即渲染所有元素,而是优先渲染屏幕上可见的元素。
  • FlatList 组件必须的两个属性是 data 和 renderItem。data 是列表的数据源,而 renderItem 则从数据源中逐个解析数据,然后返回一个设定好格式的组件来渲染。
export default class FlatListBasics extends Component {render() {
    return (<View style={styles.container}>
        <FlatList
          data={[{key: 'Devin'},
            {key: 'Jackson'},
            {key: 'James'},
            {key: 'Joel'},
            {key: 'John'},
            {key: 'Jillian'},
            {key: 'Jimmy'},
            {key: 'Julie'},
          ]}
          renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
   flex: 1,
   paddingTop: 22
  },
  item: {
    padding: 10,
    fontSize: 18,
    height: 44,
  },
})
SectionList 的用法

SectionList 与 FlatList 相似,只不过多了一个分组功能。

export default class SectionListBasics extends Component {render() {
    return (<View style={styles.container}>
        <SectionList
          sections={[{title: 'D', data: ['Devin']},
            {title: 'J', data: ['Jackson', 'James', 'Jillian', 'Jimmy', 'Joel', 'John', 'Julie']},
          ]}
          renderItem={({item}) => <Text style={styles.item}>{item}</Text>}
          renderSectionHeader={({section}) => <Text style={styles.sectionHeader}>{section.title}</Text>}
          keyExtractor={(item, index) => index}
        />
      </View>
    );
  }
}

网络请求

React Native 提供了和 web 标准一致的 Fetch API,用于满足开发者访问网络的需求。
Fetch 的第一个参数用来指定请求路径,可选的第二个参数,可以指定 http 一些请求参数。

fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    firstParam: 'yourValue',
    secondParam: 'yourOtherValue',
  }),
});

Fetch 返回一个 Promise 对象,RN 中可以使用 promise 这样获取数据:

fetch('https://facebook.github.io/react-native/movies.json')
    .then((response) => response.json())
    .then((responseJson) => {return responseJson.movies;})
    .catch((error) => {console.error(error);
    });

也可以使用 async/await

    let response = await fetch('https://facebook.github.io/react-native/movies.json',);
    let responseJson = await response.json();
    return responseJson.movies;

除了 Fetch 之外也可以使用 frisbee 和 axios 等其他网络库。

React Native 的优缺点

RN 的语法先介绍到这里,详细的用法可以看这个 React Native 中文网。
在具体的使用之前,我们了解下 RN 的优劣势,来判断是否需要用它。
简单介绍下 RN 的优劣势,来决定是否需要用它。

优点

  1. 调试方便
    RN 支持 hotReload 以及 liveReload,在调试界面的时候非常方便,修改代码之后保存,界面就自动跟着变化。也可以使用 chrome,react-devtools 调试。
  2. 跨平台开发,可以写一套代码,Android 和 ios 并行开发。
  3. 使用热更新,便于维护快速更迭的模块,跳过漫长的 App 审核。

缺点

  1. 长列表及动画性能不佳

    长列表快速向上滚动时,列表会出现白屏的情况,RN 的动画会出现卡顿问题。这两个问题基本是因为 js 与 native 频繁通信造成的,很难从根本上解决。
  2. 版本更新快,项目中更新一次 RN,对代码改动较大,很耗时。
  3. 两个平台还没有完全统一,一些控件在两个平台变现会有差异。

Flutter,比 RN 更好的跨平台方案?

在实际工作中 RN 一般作为原生开发的补充,来开发一些快速迭代的页面,但很难取代原生开发,成为 App 开发的最优解。Flutter 作为目前炙手可热的跨平台方案,能否取代 RN,Weex,还不能确定。首先我们先简单了解下 Flutter。
React-Native、Weex 核心是通过 Javascript 开发,执行时需要 Javascript 解释器,UI 是通过原生控件渲染。Flutter 与用于构建移动应用程序的其它大多数框架不同,因为 Flutter 既不使用 WebView,也不使用操作系统的原生控件。相反,Flutter 使用自己的高性能渲染引擎来绘 制 widget。
它的开发语言是 Dart,Dart 是面向对象的,类定义的,单继承语言,语法类似 c 语言,可以转义为 js。

Flutter 特点:

  • 快速开发
    由于 Flutter 选用了 Dart 作为其开发语言,Dart 既可以是 AOT(Ahead Of Time)编译,也可以是 JIT(Just In Time)编译,其 JIT 编译的特性使 Flutter 在开发阶段可以达到亚秒级有状态热重载,从而大大提升了开发效率。但是 Flutter 不支持代码的热更新。
  • 性能优越
    使用自带的高性能渲染引擎(Skia)进行自绘,渲染速度和用户体验堪比原生。
  • UI 一致性
    Flutter 因为是自己做的渲染,因此在 iOS 和 Android 的效果基本完全一致。React Native 存在将 RN 控件转换为对应平台原生控件的过程,存在一定的差异(如 Button 在 iOS 和 Android 下面显示效果不一样)。
  • App 体积
    Flutter iOS 空项目 30M 左右,Android 空项目 7M 左右(iOS 需要额外集成 Skia)。React Native iOS 空项目 3M 左右,Android20M 左右(Android 会加入 OKHttp 导致体积增大)。

具体 flutter 介绍可以看下:
移动跨平台框架 Flutter 介绍和学习线路
如何评价 Google 的 Fuchsia、Android、iOS 跨平台应用框架 Flutter?
此外作为 Fuchsia OS 的正统 app framework,Flutter 的应用前景,与 Fuchsia 的推广程度息息相关。可以看下知乎对 Fuchsia 和 Flutter 的讨论:
如何看待 Google 的新操作系统 Fuchsia?
Flutter 在 2019 年会有怎样的表现

总结

React Native 语法和基本用法与 ReactJs 类似,之前用过 React 的,RN 上手很快。虽然 RN 在性能上和跨平台一致性等方面算不上完善,但目前在国内主流大厂,RN 仍是最常用的跨平台解决方案之一。对于日常工作中可能会用到技术,花时间学学还是很必要的。即使 Flutter 真的取代了 RN,Flutter 的 React 风格,也会让熟悉 RN 开发的同学更好上手。

参考

React Native 中文网
react-native 技术的优劣
React Native 官网

退出移动版