OS X - 目前ReactNative只支持Mac系統javascript
Homebrewjava
安裝NodeJs 4.0或以上版本,推薦使用NVM版本管理器安裝:node
```nvm install node && nvm alias default node```
4.brew install watchman
react
5.brew install flow
android
$ npm install -g react-native-cli $ react-native init FirstProject $ cd FirstProject/
建立新的應用以後,能夠在iOS的文件夾內看到以下的文件部署:webpack
若是是在Xcode中選擇運行該項目,或者手動容許編譯命令:npm start
,既能夠看到以下的界面:ios
一旦編譯打包完成,iOS的模擬器中會啓動默認的應用程序,呈現出以下的界面:git
常見錯誤github
TypeError: Cannot read property 'root' of nullweb
能夠嘗試使用
brew update & brew update watchman
或者
npm install --registry=http://registry.npm.taobao.org
在React Native 0.5.0版本以後,React Native已經遷移到了Babel編譯器,能夠直接查看Babel的官方文檔來獲取其編譯支持的狀況。
ES5
Reserved Words: promise.catch(function() { });
ES6
Arrow functions: this.setState({pressed: true})}
Call spread: Math.max(...array);
Classes: class C extends React.Component { render() { return ; } }
Destructuring: var {isActive, style} = this.props;
Modules: import React, { Component } from 'react-native';
Object Consise Method: var obj = { method() { return 10; } };
Object Short Notation: var name = 'vjeux'; var obj = { name };
Rest Params: function(type, ...args) { }
Template Literals: var who = 'world'; var str =
Hello ${who};
ES7
Object Spread: var extended = { ...obj, a: 10 };
Function Trailing Comma: function f(a, b, c,) { }
筆者已經習慣使用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
在模擬器中使用CMD+D命令能夠彈出開發者菜單,從該菜單中能夠選擇打開Chrome或者Safari調試器。而在真實的設備中,能夠經過搖晃設備來打開開發者菜單。在Chrome或者Safari中能夠查看console.log
的記錄從而方便調試,正如調試React Web同樣的效果。不過須要注意的是,若是在頻繁的刷新下這種調試會致使應用程序很明顯的運行變慢。
在安裝React Native開發環境時官方就推薦了Flow做爲開發輔助工具,Flow是一個用於靜態類型檢查的JavaScript的開發庫。Flow依賴於類型推導來檢測代碼中可能的類型錯誤,而且容許逐步向現存的項目中添加類型聲明。若是須要使用Flow,只須要用以下的命令:
flow check
通常狀況下默認的應用中都會包含一個.flowconfig文件,用於配置Flow的行爲。若是不但願flow檢查所有的文件,能夠在.flowconfig文件中添加配置進行忽略:
[ignore] .*/node_modules/.*
最終檢查的時候就能夠直接運行:
$ flow check $ Found 0 errors.
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.
當使用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是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
組件。
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中使用的是所謂的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, "!"), React.createElement(Text, {style: styles.instructions}, "To get started, edit index.ios.js" ), React.createElement(Text, {style: styles.instructions}, "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;});