作为一个 JavaScript 开发者,我之前从来没想过用 JavaScript 很容易地写原生移动应用。当然,我们已经有了如 PhoneGap 等工具,但在原生应用中封装一个基于浏览器的应用还有许多需要改进的地方。
现在这一切都改变了—— Facebook 的 React 团队发布了 React Native。它不仅可以让我们使用 React 框架来使用原生移动组件创建应用程序,但它使一切成为了现实——这意味着我们在开发应用时不需要重新编译——这使得它非常容易地创建移动应用!我有幸预览了 React Native 的 beta 版本,从那开始它大规模成长了起来。
请注意,目前已经支持 iOS。因此你需要运行 OS X 上的 Xcode 来跟随本教程。
如果你还没有过机会学习 React, 看看我的教程 来开始用用它吧.
要重点注意学习这个并不意味着我们可以写一次代码就能将这段代码用到每一个地方。尝试那样做会因为疯狂的抽象级别而陷入一场灾难。React Native 则让我们可以学习一次,到处编写。
如果你关注社交网络领域的话你会记得 FaceMash,正式这个应用开创了 Facebook。对于不关注这个领域的人,其实是11年前(哇塞) Mark Zuckerberg 创建了 FaceMash,它是一个你可以用来查看两个人之中谁更加热门的应用程序。每一个人都有一个能反映他们有多“热门”的分数值 (尽管不知道原来使用的是什么算法,不过那部电影(社交网络)显示 Elo 排名算法 曾被使用过) .
它全部的荣耀都在于此 -
让我们整个来过一遍吧 - 我们准备用 React Native 来重新创建 FaceMash。如果你觉得凭外貌来评价姑娘们不道德,你可以把图片变成你觉得能吸引人的其它事务(狗狗,代码块,等等,我不做评价),随便。
如你所愿,你可以从 这儿 clone 到初始的代码库。这不是必须的,不过为了不让你错过不同阶段代码的不同分支,你可以 clone 一份下来!
如果你没有 clone 代码库,就需要设置基础项目. React Native 可以让我们使用 react-native-cli npm 包 CLI 快速开始一个项目。如果你还没有安装这个,可以快速运行命令:
npm install -g react-native-cli
然后我们就可以开始了.
在终端里导航到一个文件夹并运行命令:
react-native init FaceMash
这样做能为我们准备好基础到应用程序,供我们挖掘和加入更多东西.
打开 XCode 并浏览到你创建了应用程序的目录里面. 你需要从这里打开 facemash.xcodeproj。
React Native 支持我们在 iOS 模拟器和实际的 iOS 设备上工作.
我将会在 iOS 模拟器上面进行开发,因为它运行更多快速的应用程序开发 - 当我们修改了JavaScript 时,可以按下 Command + R 组合建来刷新应用,或者我们也可以通过 developer 菜单(通过 Command + Control + Z 就能访问到)启用动态重新载入来变成超级懒人。我们设置可以在Chrome的开发者工具中调试我们的代码。
如果你希望使用你的 iOS 设备来开发你的应用程序,就需要让设备痛你的计算机处于同一个网络中。React 默认会在 localhost 找到 JavaScript,所以就需要你将它指向你的计算机.
我们可以通过编辑 AppDelegate.m 文件,将 localhost 改成我们的本地 IP 来达成这个目的. 你可以通过按下 Alt 的同时点击 wireless 菜单 在 OS X 来找到这个东西.
现在就可以运行我们的应用程序了。应用程序会在你在 XCode 中选择的目标中打开. 当我们点击运行,同时会产生一个在我们应用程序目录中运行着 npm start 的终端线程. 如果你不希望通过 XCode 运行应用,确保你运行了 npm start。这将会创建一个在端口8081上的本地 web 服务器,它指向我们编译好的 JavaScript 代码,并且也会监视到我们保存代码的动作以进行重新编译。
我把应用程序运行在一个模拟的 iPhone6 之上, 屏幕是真实设备的50%那么大.
这就是了,我们有了一个空的 canvas,有好多空间活动啊!
让我来看看拿来渲染我们可以在上面的截屏中所看到的东西的代码. 打开 index.ios.js.
/** * Sample React Native App * https://github.com/facebook/react-native */ 'use strict'; var React = require('react-native'); var { AppRegistry, StyleSheet, Text, View, } = React; var facemash = React.createClass({ render: function() { return ( <View style={styles.container}> <Text style={styles.welcome}> Welcome to React Native! </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> ); } }); var styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, }, }); AppRegistry.registerComponent('facemash', () => facemash);
你可以合上你惊讶的嘴了 - 是的,这就是我们拿来渲染我们的应用程序的全部东西。看起来熟悉,对不对?
不是 React Native 的所有东西都能满足你在浏览器中使用React的用途. 不过,两者之间的区别是如此的微不足道,所以完全没有必要担心它们.
不使用诸如 div 活着 section 之类的块元素, 我们在React中使用的是View组件. 它会映射到原生的 iOS 组件 UIView.
所有的文本都必须被封装到 Text 组件里面。
没有样式表 - 你的所有的样式都是被写成 JavaScript 对象的。
我们没有必要担心浏览器的兼容性问题 - ES6 harmony 是在盒子之外受到支持的,flexbox也是如此。
我们准备从清理 React 组件的样式表盒渲染函数开始。为了对 React Native 有一个理想的基本了解,我们将尝试使用尽可能多的不同组件。
让我们先从 TabBarIOS 组件开始. 你也许能认出 TabBar 组件来,它被用在诸如时钟和照片这样一些核心的iOS应用中。
var React = require('react-native'); var { AppRegistry, StyleSheet, Text, View, TabBarIOS } = React; var facemash = React.createClass({ getInitialState() { return { selectedTab: 'faceMash' } }, render: function() { return ( <TabBarIOS> <TabBarIOS.Item title="FaceMash" icon={ require('image!facemash') } selected={ this.state.selectedTab === 'faceMash' }> <View style={ styles.pageView }> <Text>Face Mash</Text> </View> </TabBarIOS.Item> <TabBarIOS.Item title="Messages" icon={ require('image!messages') } selected={ this.state.selectedTab === 'messages' }> <View style={ styles.pageView }> <Text>Messages</Text> </View> </TabBarIOS.Item> <TabBarIOS.Item title="Settings" icon={ require('image!settings') } selected={ this.state.selectedTab === 'settings' }> <View style={ styles.pageView }> <Text>Settings</Text> </View> </TabBarIOS.Item> </TabBarIOS> ); } }); var styles = StyleSheet.create({ pageView: { backgroundColor: '#fff', flex: 1 } }); // omitted code
看看这个!你会注意到当前的文本覆在了状态条上面,我们稍后会修复这个问题。
TabBarIOS 组件对它的每一个子项都使用了 TabBarIOS.Item。我们将会有三个页面——分别是你给人们评级的页面,一个消息列表以及一个设置的页面。
TabBarIOS.Item 必须有一个子项。他将会是已经被选取的页面的内容(你可以发现我们会根据组件的状态来选择设置成true还是false)。
很明显,一个 TAB 条没有图标不会好看。有几个系统图标是你可以拿来用的,不过如果你用了他们的话,TAB 的文字也会发生变化,以与系统的图标配对. 所以我们会使用自己的图标。为了在 React Native 中引入本地的图片资源,你可以使用 require 后面带上图片的资源名称!
我使用的图标是可以免费拿来用的,来自于 flaticon 的 CC 3.0 许可.
为了向 React Native 添加静态图片,请打开 XCode。在 Project Navigator (左手边的第一个图标)中, 打开 Images.xcassets 。你所有的图片都在那儿。
这可以让我们将所有的资源保持在同一个名称下,这样可以针对每一个分辨率、甚至是设备的特定图片提供不同的图像资源。
图像必须遵循一个严格的命名约定。使用的资源名称(比如 messages 或者是 settings) 并在后面给它带上它应该适用来显示的分辨率。例如,我要为 iPhone6 构建一个应用程序,我会为此使用 @2x 分辨率。
一旦为你的图片进行了正确的命名,就可以将它拖入左手边的 Images.xcassets 中了。
然后你就可以在 React Native 中使用 require('image!assetname') 了!
下一个逻辑步骤就是设置我们的主组件使得 Tab 之间的切换可用。我们可以通过设置用户点击它时的状态来做到。TabBarIOS.Item 让我们可以给它一个 onPress 属性,可以拿来检测用户何时按下了一个tab。
// omitted code var facemash = React.createClass({ getInitialState() { return { selectedTab: 'faceMash' } }, changeTab(tabName) { this.setState({ selectedTab: tabName }); }, render: function() { return ( <TabBarIOS> <TabBarIOS.Item title="FaceMash" icon={ require('image!facemash') } onPress={ () => this.changeTab('faceMash') } selected={ this.state.selectedTab === 'faceMash' }> <View style={ styles.pageView }> <Text>Face Mash</Text> </View> </TabBarIOS.Item> <TabBarIOS.Item title="Messages" icon={ require('image!messages') } onPress={ () => this.changeTab('messages') } selected={ this.state.selectedTab === 'messages' }> <View style={ styles.pageView }> <Text>Messages</Text> </View> </TabBarIOS.Item> <TabBarIOS.Item title="Settings" icon={ require('image!settings') } onPress={ () => this.changeTab('settings') } selected={ this.state.selectedTab === 'settings' }> <View style={ styles.pageView }> <Text>Settings</Text> </View> </TabBarIOS.Item> </TabBarIOS> ); } }); // omitted code
可以了!它是多么的简单. 通过在 iOS 模拟器中按下 Command+R 来刷新应用(或者如果你是在真实设备上开发,可以通过 XCode 来对它进行重新编译) 你就会看到现在我们可以进行按下 tab 的操作了,并且主屏幕的显示也发生了变化!
尽管我们还没有写太多的代码,但是已经见第一个步骤分支的代码 checkout 出来了,里面也包含了我们在这个 tab 上用上了的图标。
让我们来实现 FaceMash 的 tab 界面吧。我们将会从一个端点那里使用获取来加载到数据。在步骤一的分支中,我已经在 rest/ 目录中包含进来了一个 config.yaml 文件,那是我们将会用来使用 stubby 对端点进行模拟的。所有 endpoint/pictures 中的用户都会被从 randomuser.me 处随机的生成。
打开你的终端并且运行命令
stubby -d rest/config.yaml
接着我们就开始吧!
在名为 tabs/ 的目录中创建一个新文件,命名为 FaceMash.js,在里面放一个基础的 React 组件 -
'use strict'; var React = require('react-native'); var { StyleSheet, Text, View } = React; var facemashTab = React.createClass({ render: function() { return ( <View style={ styles.container }> <Text> FaceMash tab! </Text> </View> ); } }); var styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff' } }); module.exports = facemashTab;
目前我们能从这个Tab上得到全部就是一个里面有一些文字的基础的 View 组件。我们还可以为这个 View 弄一些基础的样式,这样可以确保它具有合适的高和宽。
我们会添加一个头部,纯粹是用于展示的目的.
// omitted code var facemashTab = React.createClass({ render: function() { return ( <View style={ styles.container }> <View style={ styles.header }> </View> <View style={ styles.content }> <Text> FaceMash tab! </Text> </View> </View> ); } }); var styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff' }, header: { height: 40, background: '#ff0000' } }); module.exports = facemashTab;
现在我们会抱怨状态条的黑色很糟糕,不过不要担心,因为我们可以使用 StatusBarIOS 的 API 来对其进行修改。当 changeTabfunction 被调用时,我们可以检查看看当前的 tab 是不是 FaceMash 的 tab。如果是的话,我们将会把状态调的状态设置为1(白色),如果不是就设置为0(黑色).
// omitted code var { AppRegistry, StyleSheet, Text, View, TabBarIOS, StatusBarIOS } = React; var facemash = React.createClass({ ..., changeTab(tabName) { StatusBarIOS.setStyle(tabName === 'faceMash' ? 1 : 0); this.setState({ selectedTab: tabName }); }, ... }); // omitted code
刷新你就会看到一个白色的状态条 - 解决了!
我们现在可以访问端点来向我们的用户进行展示了。我们将会使用 fetch,它在 React Native 中默认是被包含了进来的。
// omitted code var facemashTab = React.createClass({ getInitialState: function() { return { list: [], currentIndex: 0 }; }, componentWillMount: function() { fetch('http://localhost:8882/rest/mash') .then(res => res.json()) .then(res => this.setState({ list: res })); }, render: function() { return ( ... ); } }); // omitted code
请求会用返回的数据对我们的状态进行填充。因为初始的数据时一个空的数组,所以我们可以在 render 函数中进行检查,在他们等待的时候显示一个加载页面。
var { StyleSheet, Text, View, ActivityIndicatorIOS } = React; var facemashTab = React.createClass({ ..., render: function() { var contents; if (!this.state.list.length) { contents = ( <View style={ styles.loading }> <Text style={ styles.loadingText }>Loading</Text> <ActivityIndicatorIOS /> </View> ) } else { contents = ( <View style={ styles.content }> <Text>Loaded</Text> </View> ) } return ( <View style={ styles.container }> <View style={ styles.header }> <Text style={ styles.headerText }>FaceMash</Text> </View> { contents } </View> ); } }); var styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff' }, loading: { flex: 1, backgroundColor: '#fff', justifyContent: 'center', alignItems: 'center' }, loadingText: { fontSize: 14, marginBottom: 20 }, header: { height: 50, backgroundColor: '#760004', paddingTop: 20, alignItems: 'center' }, headerText: { color: '#fff', fontSize: 20, fontWeight: 'bold' } });
现在我们将对位于 this.state.list 的数据进行访问。我们也会在端点返回一个对象的数组时,得到位于状态中的数组的当前索引 - 每个对象都是用户可以进行评比的两个人.
因为要从两个人中选一个,两者都有同自身相关联的相同数据,我们将创建一个 React 组件来展示他们的数据。
// omitted code var Person = React.createClass({ render: function() { var person = this.props.person; return ( <View style={ styles.person }> <Text>Person!</Text> </View> ) } }); var facemashTab = React.createClass({ getInitialState: function() { return { list: [], currentIndex: 0 }; }, componentWillMount: function() { fetch('http://localhost:8882/rest/mash') .then(res => res.json()) .then(res => this.setState({ list: res })); }, render: function() { var contents; if (!this.state.list.length) { contents = ( <View style={ styles.loading }> <Text style={ styles.loadingText }>Loading</Text> <ActivityIndicatorIOS /> </View> ) } else { var { list, currentIndex } = this.state; var record = list[currentIndex]; var people = record.users.map(person => <Person person={ person } />); contents = ( <View style={ styles.content }> { people } </View> ) } return ( <View style={ styles.container }> <View style={ styles.header }> <Text style={ styles.headerText }>FaceMash</Text> </View> { contents } </View> ); } }); var styles = StyleSheet.create({ ..., person: { flex: 1, margin: 10, borderRadius: 3, overflow: 'hidden' } });
我们现在就有了一个进行两次数据装入(每个人一次)的组件,合适的配置会向它进行传递。现在就可以将个人资料图片和相关的用户信息展示出来了。
评论删除后,数据将无法恢复
评论(29)
引用来自“kut”的评论
这货目测现在还是一玩具,上不了生产,哪个团队用都要承担项目失败的巨大风险。引用来自“1棵拼搏的寂静草”的评论
天猫,淘宝用的引用来自“javadeveloper”的评论
用在什么产品上了,介绍介绍。引用来自“kut”的评论
这货目测现在还是一玩具,上不了生产,哪个团队用都要承担项目失败的巨大风险。引用来自“1棵拼搏的寂静草”的评论
天猫,淘宝用的引用来自“javadeveloper”的评论
用在什么产品上了,介绍介绍。引用来自“kut”的评论
这货目测现在还是一玩具,上不了生产,哪个团队用都要承担项目失败的巨大风险。引用来自“1棵拼搏的寂静草”的评论
天猫,淘宝用的引用来自“工头叫我去搬砖”的评论
怪不得我觉得淘宝app那么难用呢,与他的公司地位完全不符啊引用来自“1棵拼搏的寂静草”的评论
最近刚发布的版本才是react,之前的不是引用来自“kut”的评论
这货目测现在还是一玩具,上不了生产,哪个团队用都要承担项目失败的巨大风险。引用来自“1棵拼搏的寂静草”的评论
天猫,淘宝用的引用来自“工头叫我去搬砖”的评论
怪不得我觉得淘宝app那么难用呢,与他的公司地位完全不符啊引用来自“kut”的评论
这货目测现在还是一玩具,上不了生产,哪个团队用都要承担项目失败的巨大风险。引用来自“1棵拼搏的寂静草”的评论
天猫,淘宝用的引用来自“kut”的评论
这货目测现在还是一玩具,上不了生产,哪个团队用都要承担项目失败的巨大风险。引用来自“1棵拼搏的寂静草”的评论
天猫,淘宝用的引用来自“javadeveloper”的评论
用在什么产品上了,介绍介绍。