ReactNative 实战之QuickStart

Quick Start

Installation

Requirements

  1. OS X - 目前ReactNative只支持Mac系统

  2. Homebrew

  3. 安装NodeJs 4.0或以上版本,推荐使用NVM版本管理器安装:

    ```nvm install node && nvm alias default node```

4.brew install watchman

5.brew install flow

Create New Application(创建新的应用)

$ npm install -g react-native-cli 
$ react-native init FirstProject 
$ cd FirstProject/

创建新的应用之后,可以在iOS的文件夹内看到如下的文件部署:

Build

如果是在Xcode中选择运行该项目,或者手动允许编译命令:npm start,既可以看到如下的界面:

一旦编译打包完成,iOS的模拟器中会启动默认的应用程序,呈现出如下的界面:

常见错误

  • TypeError: Cannot read property 'root' of null

可以尝试使用brew update & brew update watchman

或者

npm install --registry=http://registry.npm.taobao.org

JavaScript Environment(JavaScript运行环境)

在React Native 0.5.0版本之后,React Native已经迁移到了Babel编译器,可以直接查看Babel的官方文档来获取其编译支持的情况。

ES5

  • Reserved Words:promise.catch(function() { });

ES6

ES7

React Native Webpack Server

笔者已经习惯使用Webpack作为模块管理与编译工具,在React Native的开发中,同样可以使用Webpack进行开发,笔者参考的是这个Repo

  • 安装

npm install --save-dev react-native-webpack-server
  • 使用

React Native命令行默认会查找index.ios.js或者index.android.js文件作为整个项目的根文件,在Webpack的配置文件中则可以按照Webpack的风格进行如下配置:

entry: {
  'index.ios': ['./src/main.js']
}

完整的webpack.config.js配置文件如下:

var fs = require('fs');
var path = require('path');
var webpack = require('webpack');

var config = {

  debug: true,devtool: 'source-map',entry: {
    'index.ios': ['./src/main.js'],},output: {
    path: path.resolve(__dirname,'build'),filename: '[name].js',module: {
    loaders: [{
      test: /\.js$/,exclude: /node_modules/,loader: 'babel',query: {
        stage: 0,plugins: []
      }
    }]
  },plugins: [],};

// Hot loader
if (process.env.HOT) {
  config.devtool = 'eval'; // Speed up incremental builds
  config.entry['index.ios'].unshift('react-native-webpack-server/hot/entry');
  config.entry['index.ios'].unshift('webpack/hot/only-dev-server');
  config.entry['index.ios'].unshift('webpack-dev-server/client?http://localhost:8082');
  config.output.publicPath = 'http://localhost:8082/';
  config.plugins.unshift(new webpack.HotModuleReplacementPlugin());
  config.module.loaders[0].query.plugins.push('react-transform');
  config.module.loaders[0].query.extra = {
    'react-transform': {
      transforms: [{
        transform: 'react-transform-hmr',imports: ['react-native'],locals: ['module']
      }]
    }
  };
}

// Production config
if (process.env.NODE_ENV === 'production') {
  config.plugins.push(new webpack.optimize.OccurrenceOrderPlugin());
  config.plugins.push(new webpack.optimize.UglifyJsPlugin());
}

module.exports = config;

在项目的package.json可以添加如下控制脚本:

"scripts": {
  "bundle": "rnws bundle","start": "rnws start"
}

使用npm start命令即可开启开发服务器,在代码中设置路径如下即可:

jsCodeLocation = [NSURL URLWithString:@"http://localhost:8080/index.ios.bundle"];

如果需要构建一个发布版本,则可以使用如下命令:

rnws bundle
# OR,using the above package.json script:
npm run bundle

Debug & Test

Chrome Debugger

在模拟器中使用CMD+D命令可以弹出开发者菜单,从该菜单中可以选择打开Chrome或者Safari调试器。而在真实的设备中,可以通过摇晃设备来打开开发者菜单。在Chrome或者Safari中可以查看console.log的记录从而方便调试,正如调试React Web一样的效果。不过需要注意的是,如果在频繁的刷新下这种调试会导致应用程序很明显的运行变慢。

Type-Checking with Flow

在安装React Native开发环境时官方就推荐了Flow作为开发辅助工具,Flow是一个用于静态类型检查的JavaScript的开发库。Flow依赖于类型推导来检测代码中可能的类型错误,并且允许逐步向现存的项目中添加类型声明。如果需要使用Flow,只需要用如下的命令:

flow check

一般情况下默认的应用中都会包含一个.flowconfig文件,用于配置Flow的行为。如果不希望flow检查全部的文件,可以在.flowconfig文件中添加配置进行忽略:

[ignore]
.*/node_modules/.*

最终检查的时候就可以直接运行:

$ flow check
$ Found 0 errors.

Testing with Jest

React Native支持使用Jest进行React组件的测试,Jest是一个基于Jasmine的单元测试框架,它提供了自动的依赖Mock,并且与React的测试工具协作顺利。

npm install jest-cli --save-dev

可以将test脚本加入到package.son文件中:

{
  ...
  "scripts": {
    "test": "jest"
   }
   ...
}

直接使用npm test命令直接运行jest命令,下面可以创建tests文件夹,Jest会递归搜索tests目录中的文件,这些测试文件中的代码如下:

'use strict';

describe('a silly test',function() {
 it('expects true to be true',function() {
   expect(true).toBe(true);
 });
});

而对于一些复杂的应用可以查看React Native的官方文档,以其中一个getImageSource为例:

**
 * Taken from https://github.com/facebook/react-native/blob/master/Examples/Movies/__tests__/getImageSource-test.js
 */

'use strict';

jest.dontMock('../getImageSource');
var getImageSource = require('../getImageSource');

describe('getImageSource',() => {
  it('returns null for invalid input',() => {
    expect(getImageSource().uri).toBe(null);
  });
  ...
});

因为Jest是默认自动Mock的,所以需要对待测试的方法设置dontMock.

Application Exploration(应用探究)

Architecture(应用架构)

当使用react-native命令创建新的项目时,调用的即https://github.com/facebook/react-native/blob/master/react-native-cli/index.js这个脚本。当使用react-native init HelloWorld创建一个新的应用目录时,它会创建一个新的HelloWorld的文件夹,包含如下列表:

HelloWorld.xcodeproj/

Podfile

iOS/

Android/

index.ios.js

index.android.js

node_modules/

package.json

React Native最大的卖点在于(1)可以使用JavaScript编写iOS或者Android原生程序。(2)应用可以运行在原生环境下并且提供流畅的UI与用户体验。众所周知,iOS或者Android并不能直接运行JavaScript代码,而是依靠类似于UIWebView这样的原生组件去运行JavaScript代码,也就是传统的混合式应用。整个应用运行开始还是自原生开始,不过类似于Objective-C/Java这样的原生代码只是负责启动一个WebView容器,即没有浏览器界面的浏览器引擎。

而对于React Native而言,并不需要一个WebView容器去执行Web方面的代码,而是将所有的JavaScript代码运行在一个内嵌的JavaScriptCore容器实例中,并最终渲染为高级别的平台相关的组件。这里以iOS为例,打开HelloWorld/AppDelegate.m文件,可以看到如下的代码:

.....................
RCTRootView *rootView = [[RCTRootView alloc]     
    initWithBundleURL:jsCodeLocation
    moduleName:@"HelloWorld"
    launchOptions:launchOptions];
.....................

AppDelegate.m文件本身是iOS程序的入口,相信每一个有iOS开发经验的同学都不会陌生,这也是本地的Objective-C代码与React Native的JavaScript代码胶合的地方。而这种胶合的关键就是RCTRootView这个组件,可以从React声明的组件中加载到Native的组件。RCTRootView组件是一个由React Native提供的原生的Objective-C类,可以读取React的JavaScript代码并且执行,除此之外,也允许我们从JavaScript代码中调用iOS UI的组件。

到这里我们可以看出,React Native并没有将JavaScript代码编译转化为原生的Objective-C或者Swift代码,但是这些在React中创建的组件渲染的方式也非常类似于传统的Objective-C或者Swift创建的基于UIKit的组件,并不是类似于WebView中网页渲染的结果。

这种架构也就很好地解释了为什么可以动态加载我们的应用,当我们仅仅改变了JS代码而没有原生的代码改变的时候,不需要去重新编译。RCTRootView组件会监听Command+R组合键然后重新执行JavaScript代码。

Virtual Dom的扩展

Virtual Dom是React的核心机制之一,对于Virtual Dom的详细说明可以参考笔者React系列文章。在React组件被用于原生渲染之前,Clipboard已经将React用于渲染到HTML的Canvas中,可以查看render React to the HTML element这篇文章。对于React Web而言,就是将React组件渲染为DOM节点,而对于React Natively而言,就是利用原生的接口把React组件渲染为原生的接口,其大概示意图可以如下:

虽然React最初是以Web的形式呈现,但是React声明的组件可以通过bridge,即不同的桥接器转化器会将同样声明的组件转化为不同的具体的实现。React在组件的render函数中返回具体的平台中应该如何去渲染这些组件。对于React Native而言,<View/>这个组件会被转化为iOS中特定的UIView组件。

载入JavaScript代码

React Native提供了非常方便的动态调试机制,具体的表现而言即是允许以一种类似于中间件服务器的方式动态的加载JS代码,即

jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"];

另一种发布环境下,可以将JavaScript代码打包编译,即npm build

jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

如果在Xcode中直接运行程序会自动调用npm start命令来启动一个动态编译的服务器,如果没有自动启动可以手动的使用npm start命令,就如定义在package.json文件中的,它会启动node_modules/react-native/packager/packager.sh这个脚本。

React Native中的现代JavaScript代码

从上文中可以看出,React Native中使用的是所谓的JSX以及大量的ES6的语法,在打包器打包之前需要将JavaScript代码进行一些转换。这是因为iOS与Android中的JavaScript解释器目前主要还是支持到了ES5版本,并不能完全识别React Native中提供的语法或者关键字。当然,并不是说我们不能使用ES5的语法去编写React Native程序,只是最新的一些语法细则规范可以辅助我们快速构建高可维护的应用程序。

譬如我们以JSX的语法编写了如下渲染函数:

render: function() {
  return (
    <View style={styles.container}>
      <TextInput
      style={styles.nameInput}
      onChange={this.onNameChanged}
      placeholder='Who should be greeted?'/>
      <Text style={styles.welcome}>
      Hello,{this.state.name}!</Text>
      <Text style={styles.instructions}>
      To get started,edit index.ios.js
      </Text>
      <Text style={styles.instructions}>
      Press Cmd+R to reload,{'\n'}
      Cmd+Control+Z for dev menu
      </Text>
    </View>
  );
}

在JS代码载入之前,React打包器需要首先将JSX语法转化为ES5的表达式:

render: function() {
  return (
      React.createElement(View,{style: styles.container},React.createElement(TextInput,{
    style: styles.nameInput,onChange: this.onNameChanged,placeholder: "Who should be greeted?"}),React.createElement(Text,{style: styles.welcome},"Hello,",this.state.name,"!"),{style: styles.instructions},"To get started,edit index.ios.js"
    ),"Press Cmd+R to reload,'\n',"Cmd+Control+Z for dev menu"
    )
  )
);
}

另一些比较常用的语法转换,一个是模块导入时候的结构器,即我们常常见到模块导入:

var React = require('react-native');
var {
AppRegistry,StyleSheet,Text,TextInput,View,} = React;

上文中的用法即是所谓的解构赋值,一个简单的例子如下:

var fruits = {banana: "A banana",orange: "An orange",apple: "An apple"};
var { banana,orange,apple } = fruits;

那么我们在某个组件中进行导出的时候,就可以用如下语法:

module.exports.displayName = "Name";
module.exports.Component = Component;

而导入时,即是:

var {Component} = require("component.js");

另一个常用的ES6的语法即是所谓的Arrow Function,这有点类似于Lambda表达式:

AppRegistry.registerComponent('HelloWorld',() => HelloWorld);

会被转化为:

AppRegistry.registerComponent('HelloWorld',function() {return HelloWorld;});

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom