用JavaScript搭建高性能App—React Native实战教程
ReactJS
魔王
某人
40人收藏 30003次学习

用JavaScript搭建高性能App—React Native实战教程

原作:React Native Tutorial: Building Apps with JavaScript , Colin Eberhardt 2015-3-26

翻译整理 1ke.co 魔王 文中有改动,以原作为准,不作说明

 

几个月之前,Facebook发布了React Native,用这个框架,我们就可以用Javascript开发原生iOS程序了(安卓版大概几个月后发布)!3月底,Facebook已经放出了官方repository 。

 

我们都在用js和HTML5写App,然后用PhoneGap包好发布到iOS(和安卓)了。 React Native真的好用么?

 

React Native还真就是那么好用。程序猿都为此兴奋无比,主要有以下两个原因:

 

  1. 用了React Native, 你的程序尽管用JavaScript写,UI却是本地的,因此无需承受HTML5的UI带来的用户体验损失。
  2. React 有特别的构建用户界面的技巧,使得界面成为一个叙述软件工作状态的交流窗口。

 

关键是,React Native是要把React的编程方式带到移动端App开发中。和Java的“开发一次,到处使用”的谎言不同,React主张“学习一次,到处开发”,至少比较现实。本教程暂时只讨论iOS(因为安卓版还没出),但是你看完以后,一定也知道在安卓上怎么用了。

 

如果你只用Objective-C或者Swift开发程序,可能你不会对用JavaScript开发程序有想法。但是作为Swift的开发者,多一门手艺并不是坏事。

 

用过Swift的你,一定掌握了更为有效和规范的算法,更好的控制程序的稳定性和可移植性。但实际上,构建UI的方法和Objective-C并没有多大区别:还是在用UIKit而且别无选择。

 

而我们的React引入了虚拟DOM的概念,将功能性(甚至面向对象)直接带到了UI开发中来。

 

本教程中,咱们来做一个搜索英国房产并生成列表的软件。

 

 

如果你没用过JavaScript也没关系,本会会详解代码。React 用CSS属性来管理样式,很简单,当然如果你有需要的话可以专门去学习一下。

 

好,我们开始吧。

起步

React Native框架就在GitHub上,你可以克隆一个项目,也可以下载一个zip文件。如果并不想看源代码,也可以用命令行来创建一个React Native项目。本教程中,咱们就这么做。

 

React使用Node.js,来构建JavaScript代码。如果没有的话需要装上。

 

先安装Homebrew(本机使用的是MAC,应该不用解释了),然后命令行安装Node.js

brew install node

 

再安装 watchman,Facebook的一款文件监控程序。

brew install watchman

 

有了这个React Native就可以监察文件的变化并重建。就好比每次保存都用Xcode来build一次。

 

接下来就可以用npm安装 React Native CLI(命令行工具)。

npm install -g react-native-cli

 

这就用Node的包管理工具在全局安装了CLI。这方面npm有点像CocoaPods 或者Carthage。

 

创建一个项目目录,然后进入这个目录:

react-native init PropertyFinder

 

这就创建了一个叫做PropertyFinder(找房产)的初始项目,里面包含了构建和运行 React Native 程序所需的所有内容。

 

进入这个目录,你会找到一个node_modules文件夹,这里面放着React Native框架。

 

同时还会找到一个 index.ios.js文件,这就是CLI创建的程序文件的骨架。

 

最后,还会看到一个Xcode项目文件和iOS目录,包含了启动程序必须的相关文件。

 

下面我们build和运行一下这个程序:

 

同时我们会注意到一个窗口跳出来:

===============================================================

 |  Running packager on port 8081.       

 |  Keep this packager running while developing on any JS         

 |  projects. Feel free to close this tab and run your own      

 |  packager instance if you prefer.                              

 |                                                              

 |     https://github.com/facebook/react-native                 

 |                                                              

 ===============================================================


Looking for JS files in

   /Users/colineberhardt/Temp/TestProject


React packager ready.

 

这是React Native的包管理程序,在Node下面运行,咱们就知道这是干什么的了。

 

不要关掉这个窗口,让它在后台自己运行。如果不小心关掉了,重新运行Xcode项目就行。

 

注意:我们等一下要写大量的JavaScript代码,最好选一个轻量级的编辑器,推荐Sublime Text,当然你自己可以在网上找找。

Hello React Native

在做房产搜索App之前,咱们先做一个Hello World程序。我们在这里会接触到很多重要的组件。

 

在编辑器中打开index.ios.js,删掉全部内容,然后写下这么一行:

'use strict';

 

启用Strict模式可以增强JavaScript的除错能力并关掉一些不好的功能。

 

然后加上下面这行:

var React = require('react-native');

 

这就把React Native组件调用起来,并赋予React变量。其中require功能和Node.js是一样的。

 

接下来,加上下面这些:

var styles = React.StyleSheet.create({
  text: {
    color: 'black',
    backgroundColor: 'white',
    fontSize: 30,
    margin: 80
  }
});

 

这段代码添加了文字的样式。如果你干过web开发,你一定知道这就是CSS。

 

好了,下面真的是app程序了。

class PropertyFinderApp extends React.Component {
  render() {
    return React.createElement(React.Text, {style: styles.text}, "Hello World!");
  }
}

 

看到了吗?一个JavaScript类(Class)!

 

类从ECMAScript 6 (ES6)被引入JavaScript。由于JavaScript不断在改进,web开发人员经常因为浏览器兼容性(万恶的IE)而不得不放弃一些功能。React Native有一个JavaScriptCore,因此无需为浏览器兼容性操心。

 

注意:如果你是web开发者,我建议先从新的JavaScript标准出发,然后使用Babel之类的工具兼容旧版浏览器。

 

PropertyFinderApp 继承 React.Component,React UI的基础模块。Component(组件)包含了不可变的property(属性变量),可变的state(状态变量),并提供一个render(渲染)方法。

 

React Native 组件不是UIKit类,而是轻量级的。框架会将React组件树转换为本地UI。

 

最后我们加上这行:

React.AppRegistry.registerComponent('PropertyFinder', function() { return PropertyFinderApp });

 

AppRegistry 定义了程序的入口变量,并提供了根组件。

 

保存好index.ios.js,返回Xcode,确认把PropertyFinder 加进iPhone模拟器,我们重建并运行一下项目。

 

 

这可是本地UI,没有任何浏览器,代码全是JavaScript写的哦!

 

还是不信?:)打开Xcode的Debug\View Debugging\Capture View Hierarchy,是不是哪都找不到UIWebView ,只有一个真正的View!牛啊。

 

 

你一定很好奇是怎么回事。在Xcode中找到application:didFinishLaunchingWithOptions:,这个方法创建了一个RCTRootView,来载入JavaScript并渲染了View。

 

程序一运行,RCTRootView 立刻读取:

http://localhost:8081/index.ios.bundle

来加载程序。

 

现在知道前面弹出来那个窗口是干什么的了吧,就是处理这个东西的。

 

程序启动时,代码会载入JavaScriptCore 框架并执行。在这个程序中,PropertyFinderApp 这个component会被加载并创建一个本地的UIKit视图。后面会详细讲到。

Hello JSX

我们现在的程序使用 React.createElement创建,然后React转换为本地文件。尽管目前我们的js文件很清楚,但随着代码更复杂,各种元素的嵌套会导致代码一团乱麻。

 

在app继续运行的情况下,我们回到index.ios.js,把return改一改:

return <React.Text style={styles.text}>Hello World (again)</React.Text>;

 

这是JSX代码,全称叫做JavaScript syntax extension,使用类似HTML的标签。如果是web开发者,一看就懂。我们全程会大量使用JSX代码。

 

保存文件返回模拟器,按下Cmd+R,我们可以看到“Hello World (again)”,看到没,重新加载React Native程序就跟刷新一下网页一样简单。

 

由于我们的程序使用的是同一套JavaScript文件,以后只需要修改index.ios.js并保存,就可以实现内容的调整。

 

好了,玩够了Hello World,我们来做真正的程序吧。

添加导航控制

我们的寻找房产程序使用和UIKit类似的栈形导航。下面来做这个导航。

 

 在index.ios.js中,我们把PropertyFinderApp 重命名为HelloWorld。

class HelloWorld extends React.Component {

 

Hello World还是会显示,不过已经不是咱们的根组件了。

 

我们在下面加一点真货:

class PropertyFinderApp extends React.Component {
  render() {
    return (
      <React.NavigatorIOS
        style={styles.container}
        initialRoute={{
          title: 'Property Finder',
          component: HelloWorld,
        }}/>
    );
  }
}

 

这样就添加了一个导航控制器,使用了原本HellowWorld组件的路径和样式。在web开发时,routing(路由或路径)被用来设计程序的导航结构,根据URL来分配页面。

 

然后我们要调整样式:

var styles = React.StyleSheet.create({
  text: {
    color: 'black',
    backgroundColor: 'white',
    fontSize: 30,
    margin: 80
  },
  container: {
    flex: 1
  }
});

 

回到Xcode再刷新一次。

 

 

好了现在我们有了导航框架,可以开始在里面做UI了。

构建搜索页面

我们创建一个新文件SearchPage.js,和index.ios.js放在一起。添加如下代码:

'use strict';

var React = require('react-native');
var {
  StyleSheet,
  Text,
  TextInput,
  View,
  TouchableHighlight,
  ActivityIndicatorIOS,
  Image,
  Component
} = React;

 

前面就不说了,下面那个变量声明你可能没有见过。

 

这叫做destructuring assignment(解构化赋值),可以把对象的多个属性赋给相应变量。于是,之后你就不必使用React这个前缀,比如你可以直接调用StyleSheet 而不是React.StyleSheet。它还可以用来操作数组,非常好用。

 

我们还要添加样式:

var styles = StyleSheet.create({
  description: {
    marginBottom: 20,
    fontSize: 18,
    textAlign: 'center',
    color: '#656565'
  },
  container: {
    padding: 30,
    marginTop: 65,
    alignItems: 'center'
  }
});

 

虽然用css没有Interface Builder那么可视化,但比用viewDidLoad()一个一个设置视图属性可方便多了。:)

 

下面来写组件:

class SearchPage extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.description}>
          Search for houses to buy!
        </Text>
        <Text style={styles.description}>
          Search by place-name, postcode or search near your location.
        </Text>
      </View>
    );
  }
}

 

render 可以完美应用JSX的结构。用css我们很容易就实现了这个图形界面:一个容器和两条label。

最后加上:

module.exports = SearchPage;

 

这条命令制定了输出SearchPage 以为他用。

 

下一步就是路由(Routing)的制作。

 

打开index.ios.js,并且在require下面加上这行:

var SearchPage = require('./SearchPage');

 

PropertyFinderApp 的render里面替换initialRoute :

component: SearchPage

 

现在可以干掉Hello World这种新手用的东西了,现在我们是高手了。

 

回模拟器我们刷新一下Cmd + R:

 

 

这就是我们的新组件,搜索页SearchPage

用Flexbox来布局

现在,我们用css调整了margin、padding和颜色,不过你可能对flexbox并不熟悉,这是最近出现的新规范,用来做app的UI布局再好不过了。

 

React Native用的是css-layout库,是flexbox标准的一种javascript实现,可以转化为C(iOS)和Java(安卓)。

 

在App中,默认的排列是依据column(列),所以,子元素默认是垂直排列的。

 

 

这个被叫做主轴(main axis),可以竖着也可以横着。

 

每个子元素的垂直位置由margin、height和padding决定。容器同时将alignItems 设置为center,所以文档是居中的。

 

好了,现在要加上input和button了。打开SearchPage.js并将下面这段插入到第二段text元素后面:

<View style={styles.flowRight}>
  <TextInput
    style={styles.searchInput}
    placeholder='Search via name or postcode'/>
  <TouchableHighlight style={styles.button}
      underlayColor='#99d9f4'>
    <Text style={styles.buttonText}>Go</Text>
  </TouchableHighlight>
</View>
<TouchableHighlight style={styles.button}
    underlayColor='#99d9f4'>
  <Text style={styles.buttonText}>Location</Text>
</TouchableHighlight>

 

现在我们有两个顶级视图了,一个有一个输入框和按钮,一个只有按钮。

 

下面我们把样式加进style里面:

flowRight: {
  flexDirection: 'row',
  alignItems: 'center',
  alignSelf: 'stretch'
},
buttonText: {
  fontSize: 18,
  color: 'white',
  alignSelf: 'center'
},
button: {
  height: 36,
  flex: 1,
  flexDirection: 'row',
  backgroundColor: '#48BBEC',
  borderColor: '#48BBEC',
  borderWidth: 1,
  borderRadius: 8,
  marginBottom: 10,
  alignSelf: 'stretch',
  justifyContent: 'center'
},
searchInput: {
  height: 36,
  padding: 4,
  marginRight: 5,
  flex: 4,
  fontSize: 18,
  borderWidth: 1,
  borderColor: '#48BBEC',
  borderRadius: 8,
  color: '#48BBEC'
}

 

每项属性中用“,”隔开。这些就是输入框和按钮的样式。

 

我们会模拟器刷新一下 Cmd + R。

 

 

输入框和Go按钮在同一行,因为把它们放在了flexDirection: 'row'的容器中。我们并没有明确声明它们的宽度,而是给了一个flex值。输入框flex: 4,而按钮则是 flex: 1,所以它们的宽度比为4:1.

 

你可能已经注意到,这个按钮……并不是按钮。在UIKit中,按钮只不过是可以点击的Label,所以React Native团队决定直接用JavaScript构建按钮。程序中的按钮使用TouchableHighlight这个组件,使得按钮按下时显示透明和不同的颜色。

 

最后我们添上一些图片。可以在这里下载。在Xcode中,打开 Images.xcassets,点击加号添加图片。接下来鼠标拖动图片到相应的格子。

 

 

这次需要停掉程序并重启,来让图片加载出现。

 

将下面这行添加到location按钮TouchableHighlight 组件的下面:

<Image source={require('image!house')} style={styles.image}/>

 

下面添加图片的样式:

image: {
  width: 217,
  height: 138
}

 

上面的require('image!house') 是为了获取图片的位置。在Xcode中,如果打开Images.xcassets的话,我们会找到房屋图标。

 

返回模拟器刷新一下页面,Cmd + R。

 

注意:如果看不到图片而显示“image!house”的话,重启包管理器(或者命令行npm start)

添加组件state(状态参数)

每一个React组件都有其state状态参数,用来储存关键变量。组件初始化之前要先初始化state。

SearchPage.js中,在render()上面添加如下代码:

constructor(props) {
  super(props);
  this.state = {
    searchString: 'london'
  };
}

 

我们的组件现在有了一个state变量,名为searchString值为’london’。

 

然后就可以使用组件的状态了。在Render中,改变TextInput如下代码:

<TextInput
  style={styles.searchInput}
  value={this.state.searchString}
  placeholder='Search via name or postcode'/>

 

我们把输入框的值——也就是用户看到的那个——和我们的state值searchString 绑定起来了。

好了,值是初始化好了,但是,当用户输入时,会发生什么呢?(自己去试试)

 

我们要绑定一个事件,在SearchPage中添加如下方法:

onSearchTextChanged(event) {
  console.log('onSearchTextChanged');
  this.setState({ searchString: event.nativeEvent.text });
  console.log(this.state.searchString);
}

 

这样,系统就会取出输入框的值并且替换state的值。

 

下面要把这个方法串进输入框中,要将函数绑定到onChange属性上:

<TextInput
  style={styles.searchInput}
  value={this.state.searchString}
  onChange={this.onSearchTextChanged.bind(this)}
  placeholder='Search via name or postcode'/>

 

这样每次用户在输入框中输入,都会触动onChange,并执行onSearchTextChanged

 

注意:如果不明白bind(this)方法是干什么的,JavaScript处理this关键字与其他语言不太一样,用Swift翻译就是self。用bind是为了确保this指向组件实例。可以自己去试试就明白了。

 

最后,把下面这些记录函数添加到render中,return上面:

console.log('SearchPage.render');

 

这样我们就会明白一些非常神奇的东西。

 

现在刷新一下程序。我们应该可以看到界面中的”london”,并且在命令行中,我们看到:

 

 

嗯……顺序似乎有点问题。

  1. 首次启动render
  2. 当输入框有变化,启动onSearchTextChanged()
  3. 更新state,并引发另一次render
  4. onSearchTextChanged()并装入新的搜索内容

 

每次任何state发生变化,React组件会将整个UI全部重新render一遍,这是好的,这就解耦了state与UI状态。

 

在其他的UI框架中,要么你手动更改UI的内容,要么进行双向绑定,比如某些MVVM模式的框架。

 

在React中,你无需担心哪块UI绑到什么地方,整个UI都是state的一个体现。

 

看到这里,你可能已经发现这个模式的缺点了——性能问题!

 

我们总不能每次state有点改动,就把整个UI全部扔掉,然后重新加载吧?好了React在这个问题上相当智能化。每次UI渲染时,都会把render方法返回的视图树,拿去与现存的UIKit视图树作对比。于是对比的结果,就是需要更改的视图树。所以只有改变了的部分需要重新渲染。

 

看到这里我是非常佩服React.js的虚拟DOM给iOS带来的如此特别的合作方式。

 

我们把上面那些日志方法全部删掉,下面我们开始完善程序。

启动搜索功能

要实现搜索功能,我们要处理Go按钮的按下事件,并发出API请求,同时视觉上告诉用户请求正在进行。

 

在 SearchPage.js中,更改初始化state:

this.state = {
  searchString: 'london',
  isLoading: false
};

 

在render中增加如下内容:

var spinner = this.state.isLoading ?
  ( <ActivityIndicatorIOS
      hidden='true'
      size='large'/> ) :
  ( <View/>);

 

根据isLoading这个state是否为真,显示活动视图或空视图。

 

在搜索UI的return中,在Image下面,增加:

{spinner}

 

把下面这个属性加到Go按钮的渲染方法中的TouchableHighlight 标签里:

onPress={this.onSearchPressed.bind(this)}

 

 

然后在SearchPage 类中加入以下方法:

_executeQuery(query) {
  console.log(query);
  this.setState({ isLoading: true });
}

onSearchPressed() {
  var query = urlForQueryAndPage('place_name', this.state.searchString, 1);
  this._executeQuery(query);
}

 

现在的_executeQuery只是写一条日志,将来会完善的。

 

注意: JavaScript的类没有私有前缀,所以前面加个_来表示私有。

 

当Go按钮按下时,程序会执行onSearchPressed(),启动查询。

 

最后,在searchPage类的声明上加上这个工具函数:

function urlForQueryAndPage(key, value, pageNumber) {
  var data = {
      country: 'uk',
      pretty: '1',
      encoding: 'json',
      listing_type: 'buy',
      action: 'search_listings',
      page: pageNumber
  };
  data[key] = value;

  var querystring = Object.keys(data)
    .map(key => key + '=' + encodeURIComponent(data[key]))
    .join('&');

  return 'http://api.nestoria.co.uk/api?' + querystring;
};

 

这个函数和searchPage没有依赖,所以作为一个独立函数,而不作为类的方法。这个函数先用一个data来储存参数,然后构建查询请求。其中,=>这是个新语法,(参数)=>{函数体}。

 

回到模拟器,按下Cmd + R刷新,点击Go,就会看到滚动图标,然后我们看看后台。

 

 

我们会看到url,复制下来贴进浏览器,我们来看看结果,会看到一大段JSON内容。现在还不用管这些。

 

注意:本教程使用了 Nestoria API 来获取房产信息。相关细节可以参考网站文档。

 

下一步,就是在App内发起请求了。

发起API请求

还是在SearchPage.js,添加一个message变量。

 

this.state = {
  searchString: 'london',
  isLoading: false,
  message: ''
};

 

在render中,在底部添加一行:

<Text style={styles.description}>{this.state.message}</Text>

 

这个可以用来显示信息给用户看。

 

SearchPage 中,添加以下代码到_executeQuery()中:

fetch(query)
  .then(response => response.json())
  .then(json => this._handleResponse(json.response))
  .catch(error => 
     this.setState({
      isLoading: false,
      message: 'Something bad happened ' + error
   }));

 

这里用到了fetch方法,来自Web API,提供了一个比XMLHttpRequest更好的API接口。异步回应返回一个promise,带来目标路径以及JSON文档。

 

最后我们来处理返回的文档,把这段加在SearchPage里面:

_handleResponse(response) {
  this.setState({ isLoading: false , message: '' });
  if (response.application_response_code.substr(0, 1) === '1') {
    console.log('Properties found: ' + response.listings.length);
  } else {
    this.setState({ message: 'Location not recognized; please try again.'});
  }
}

 

拿掉了isLoading并且做了日志记录。

 

保存起来,然后回模拟器Cmd + R。我们搜索london,应该会看到20条记录在日志里面。然后试一下随便输一个’ narnia’,你会看到如下情况:

 

显示结果

创建一个SearchResults.js,并添加代码如下:

'use strict';

 
var React = require('react-native');
var {
  StyleSheet,
  Image, 
  View,
  TouchableHighlight,
  ListView,
  Text,
  Component
} = React;

 

这一段,看是很眼熟吧?

 

然后是组件本身:

class SearchResults extends Component {

 
  constructor(props) {
    super(props);
    var dataSource = new ListView.DataSource(
      {rowHasChanged: (r1, r2) => r1.guid !== r2.guid});
    this.state = {
      dataSource: dataSource.cloneWithRows(this.props.listings)
    };
  }

 
  renderRow(rowData, sectionID, rowID) {
    return (
      <TouchableHighlight
          underlayColor='#dddddd'>
        <View>
          <Text>{rowData.title}</Text>
        </View>
      </TouchableHighlight>
    );
  }

 
  render() {
    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderRow.bind(this)}/>
    );
  }

 
}

 

上面的代码用了一个ListView 组件,用于在可滚动的容器内添加多行内容,有点像UITableView。数据的来源是ListView.DataSource

 

构建数据源时,需要提供一个函数来对比一对数据。ListView在组建的过程中会调用这个函数,用于编制列表。这次的回传数据中,Nestoria API提供guid ,就刚好适合这个目的。

 

别忘了添加输出模块代码:

module.exports = SearchResults;

 

讲一下这段添加到SearchPage.js中,require下面:

var SearchResults = require('./SearchResults');

 

这样我们就可以在SearchPage  中调用SearchResults 了。

 

编辑_handleResponse 方法:

this.props.navigator.push({
  title: 'Results',
  component: SearchResults,
  passProps: {listings: response.listings}
});

 

上面的代码导向SearchResults 页面,并且将结果列表传进去。用push方法将搜索结果传入导航,这样就可以用回退功能返回之前页面。

 

返回模拟器并Cmd + R,你会看到列表:

 

 

当然,这是看不得的,太丑了。

妙手生花

React Native 对你来说应该比较熟了,我们就加快脚步。

 

将代码加到SearchResults.js:中的结构化定义下面:

var styles = StyleSheet.create({
  thumb: {
    width: 80,
    height: 80,
    marginRight: 10
  },
  textContainer: {
    flex: 1
  },
  separator: {
    height: 1,
    backgroundColor: '#dddddd'
  },
  price: {
    fontSize: 25,
    fontWeight: 'bold',
    color: '#48BBEC'
  },
  title: {
    fontSize: 20,
    color: '#656565'
  },
  rowContainer: {
    flexDirection: 'row',
    padding: 10
  }
});

 

然后把renderRow()替换成下面这些

renderRow(rowData, sectionID, rowID) {
  var price = rowData.price_formatted.split(' ')[0];

  return (
    <TouchableHighlight onPress={() => this.rowPressed(rowData.guid)}
        underlayColor='#dddddd'>
      <View>
        <View style={styles.rowContainer}>
          <Image style={styles.thumb} source={{ uri: rowData.img_url }} />
          <View  style={styles.textContainer}>
            <Text style={styles.price}>£{price}</Text>
            <Text style={styles.title} 
                  numberOfLines={1}>{rowData.title}</Text>
          </View>
        </View>
        <View style={styles.separator}/>
      </View>
    </TouchableHighlight>
  );
}

这次,图片使用URL,React Native会负责从主线程中解析。而且rowData.grid使用=>方法来获取。

 

最后一步是处理按下:

rowPressed(propertyGuid) {
  var property = this.props.listings.filter(prop => prop.guid === propertyGuid)[0];
}

 

刷新一下,我们可以看到:

 

看起来很不错。但是,这个房价根本没人买的起吧。

 

我们准备将最后一个视图做出来。

房产详情视图

新建一个PropertyView.js,然后添加以下代码:

'use strict';

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

 

现在,你睡着了都应该写的出来。

 

然后是样式:

var styles = StyleSheet.create({
  container: {
    marginTop: 65
  },
  heading: {
    backgroundColor: '#F8F8F8',
  },
  separator: {
    height: 1,
    backgroundColor: '#DDDDDD'
  },
  image: {
    width: 400,
    height: 300
  },
  price: {
    fontSize: 25,
    fontWeight: 'bold',
    margin: 5,
    color: '#48BBEC'
  },
  title: {
    fontSize: 20,
    margin: 5,
    color: '#656565'
  },
  description: {
    fontSize: 18,
    margin: 5,
    color: '#656565'
  }
});

 

接着是组件:

class PropertyView extends Component {

  render() {
    var property = this.props.property;
    var stats = property.bedroom_number + ' bed ' + property.property_type;
    if (property.bathroom_number) {
      stats += ', ' + property.bathroom_number + ' ' + (property.bathroom_number > 1
        ? 'bathrooms' : 'bathroom');
    }

    var price = property.price_formatted.split(' ')[0];

    return (
      <View style={styles.container}>
        <Image style={styles.image} 
            source={{uri: property.img_url}} />
        <View style={styles.heading}>
          <Text style={styles.price}>£{price}</Text>
          <Text style={styles.title}>{property.title}</Text>
          <View style={styles.separator}/>
        </View>
        <Text style={styles.description}>{stats}</Text>
        <Text style={styles.description}>{property.summary}</Text>
      </View>
    );
  }
}

 

render的第一部分对数据做了一些处理,因为很多情况下数据是不完整的。

 

最后别忘了输出:

module.exports = PropertyView;

 

回到SearchResults.js,增加一个require,你懂的:

var PropertyView = require('./PropertyView');

 

改一下rowPressed()来实现PropertyView的导航。

rowPressed(propertyGuid) {
  var property = this.props.listings.filter(prop => prop.guid === propertyGuid)[0];

  this.props.navigator.push({
    title: "Property",
    component: PropertyView,
    passProps: {property: property}
  });
}

 

刷新一下,你也懂的。

 

最后一步:让用户搜索附近的房子。

基于地理位置的搜索

在Xcode中,打开 Info.plist并添加新key,右键点击编辑期内并选择Add RowNSLocationWhenInUseUsageDescription 作为键名,以下作为键值:

PropertyFinder would like to use your location to find nearby properties

 

以下是完成的plist :

 

打开SearchPage.js,找到渲染“Location”那个按钮的TouchableHighlight,并且加上下列:.

onPress={this.onLocationPressed.bind(this)}

 

当你点击按钮,就会触发onLocationPressed 。

 

打开SearchPage ,添加:

onLocationPressed() {
  navigator.geolocation.getCurrentPosition(
    location => {
      var search = location.coords.latitude + ',' + location.coords.longitude;
      this.setState({ searchString: search });
      var query = urlForQueryAndPage('centre_point', search, 1);
      this._executeQuery(query);
    },
    error => {
      this.setState({
        message: 'There was a problem with obtaining your location: ' + error
      });
    });
}

 

获取现在位置的方法是navigator.geolocation,这是Web API,React有自己的一套实现,用的是iOS原生的位置服务。

 

如果成功获取现在的位置,系统会向Nestoria拉取数据,如果失败,就会显示错误信息。

 

由于改变了plist,这次我们就不能Cmd +R了。重新建立工程并启动吧。

 

在启动位置搜索服务前,我们必须指定一处Nestoria 数据库覆盖的地方。作为测试,我们在模拟器中选择Debug\Location\Custom Location输入北纬55.02,经度-1.42,这是北英格兰一处安静的港口。

 

今后的路

恭喜你,做出了第一个React Native程序!源代码可以在Github上找到

 

如果你是web开发的世界过来的,你会发现用javaScript和React做一个本地iOS(几个月后安卓)App是多么简单的事情。

 

也许你下一个App就用这套程序写了?还是说你会坚持用Objective-C或者Swift?不论如何,还是希望本篇文章让你有所收获,并且在下一个项目中有新的理念。

 

如果有任何疑问,欢迎到1ke.co上讨论,或者到原作者Colin Eberhardt论坛提问。本文完成翻译时(2015年6月13日),Andoid版的发布日期暂定在2015年10月。我们拭目以待。

加入1KE学习俱乐部

1KE学习俱乐部是只针对1KE学员开放的私人俱乐部
课程交流
sandheart
2016-05-26

这个写的好