快速反应本机:立即开始使用JavaScript学习本机iOS开发!
本书是关于React Native进行移动iOS开发入门的指南。 您可以在https://github.com/azat-co/react-native-quickly中找到源代码和手稿。 您可以在此处或在reactnativequickly.com上在线阅读本书,或者, 如果您喜欢视频 ,可以在Node.University上观看项目视频:http://node.university/courses/react-native-quickly。
在本书中,我将向您介绍React Native,以进行本机移动iOS和Android开发……并迅速进行。 我们将讨论诸如
- 为什么React Native很棒
- 为iOS设置React Native Development
- Hello World和React Native CLI
- 样式和Flexbox
- React Native UI的主要组件
- 将模块导入Xcode项目
- 项目:计时器
- 项目:天气应用
这本书是关于快速开始使用React的,而不是关于React Native的,从技术上讲,它是一个单独的库(有些甚至可以称为框架)。 但是我认为在与React一起进行Web开发的八章工作之后,通过利用这个很棒的库将我们的知识应用于移动开发会很有趣。 您会惊讶于您已经从React中学到了多少React Native技能。
使示例过于复杂或过于简单,从而使它们不现实且无用,总是存在一个平衡。 在本书中,准备构建两个移动应用程序:Timer和Weather应用程序。 Weather应用程序具有3个屏幕录像,您可以在Node.Unversity上观看。 他们将引导您完成“天气”应用程序。
[边注]
阅读博客文章固然不错,但观看视频课程则更好,因为它们更具吸引力。
许多开发人员抱怨Node上缺乏负担得起的高质量视频材料。 观看YouTube视频让人分心,疯狂地花500美元购买Node视频课程!
去看看Node University,它在Node:node.university上有免费的视频课程。
[旁注结尾]
项目的源代码(以及提交问题/错误的手稿)在https://github.com/azat-co/react-native-quickly存储库中。 请享用!
为什么React Native很棒
React Native应用程序不同于混合应用程序或所谓的HTML5应用程序。 如果您不熟悉混合方法,那就是将网站包装在无头浏览器中的时候。 无头浏览器是没有URL栏或导航按钮的浏览器视图。 基本上,开发人员使用常规的网络技术(例如JavaScript,HTML和CSS)以及可能的框架(例如jQuery Mobile,Ionic,Ember或Backbone)来构建响应式网站。 然后,他们将其与该无头浏览器一起打包为本地应用程序。 最后,您可以跨平台重用相同的代码库,但是通常缺乏使用混合应用程序的经验。 与本机应用程序相比,它们通常不那么敏捷,或者缺少某些功能。 混合应用程序最受欢迎的框架包括Sencha Touch,Apache Cordova,PhoneGap和Ionic。
另一方面,React Native应用程序不是包装在无头浏览器中的网站。 它是与React的JavaScript通信的本机Objective C或Java代码。 与本地开发相比,这具有以下优点:
旁注:如果您喜欢这篇文章,并且对公司现场JavaScript,Node.js和React.js培训感兴趣,以提高您的团队的生产力,请联系NodeProgram.com。
- 热/实时重载。 开发人员可以在不重新编译应用程序的情况下重新加载其应用程序,从而加快了开发速度,并且无需使用复杂的“所见即所得”(WYSIWYG)编辑器和IDE。
- Flexbox布局系统。 这是一个类似于CSS的综合布局系统,可以进行跨平台开发。
- Chrome调试。 开发人员可以使用已经熟悉的DevTools。
- 编写一次即可使其跨平台工作。
- 从Web React轻松移植,例如使用像ComponentKit这样的框架。
- 利用大量开放源代码工具,实用程序,库,知识,最佳实践,ES6 / 7 +和有关JavaScript(世界上最受欢迎的编程语言)的书籍。
- 使用本机元素,它们比Web技术更好且更强大(HTML5 /包装器方法)。
- 反应 没有特定的数据绑定,事件管理或视图的微观管理,所有这些都会增加复杂性。 React使用声明性方法和易于扩展的单向数据流。
由于这些原因,大型和小型公司都跳上React Native火车并放弃混合和本机方法也就不足为奇了。 我每天都读博客文章,说某家公司或某家iOS开发人员已改用React Native,以及他们对此举感到满意。 您准备好开始进行下一代移动开发了吗?
设置React Native开发
本章仅涉及针对iOS的React Native开发。 我将仅使用通用的跨平台组件(例如Navigator而不是NavigatorIOS),因此提供的代码也应适用于Android。 但是,我不会详细介绍如何编译Android项目。
如果您不能在Mac OS X的Apple硬件上工作,则可以按照本指南在Linux或Windows OS上安装运行Mac OS X的虚拟机。 展望未来,我假设我们都在Mac OS X上(无论是否虚拟)进行工作,以构建iOS应用。
要安装所有内容,您可以手动进行或使用程序包管理器。 由于我们在Mac OS X环境中工作,因此我建议使用Homebrew(又名brew)安装一些必需的工具。 如果您还没有Homebrew,则可以访问其网站http://brew.sh,或运行以下Ruby命令(Mac OS X随Ruby一起提供):
$ ruby -e“ $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)”
我们将需要以下工具和库:
- Node.js v5.1和npm v3.3.12 —如果您阅读第1-8章,则应该已经拥有它们。 如果您跳到这里,请按照附录B中的说明进行操作。
- Watchman v4.1.0-此工具将根据源代码文件的更改进行监视和更新。 使用$ brew install watchman@4.1.0进行安装。
- Google Chrome-浏览器将允许您在开发过程中调试React Native应用。 这是下载链接。
- React Native CLI v0.1.7-此工具将允许您为React Native应用程序创建样板。 使用$ npm install -g react-native-cli@0.1.7安装它。
- Xcode v7.2-适用于iOS,OS X,tvOS和watchOS的IDE,编译器和SDK。 要安装它,请单击https://developer.apple.com/xcode/download上的链接以打开Mac App Store。
- Flow — JavaScript的静态类型检查器。 要使用Homebrew进行安装,请运行$ brew install flow@0.19.1。
我建议使用NVM v0.29.0,n或类似的Node版本管理器。 此步骤是可选步骤,但建议您这样做,因为它意味着即使您的主版本是最新版本,也可以切换到Node.js v5.1。 要使用Homebrew,请执行$ brew install nvm并按照说明进行操作。
您的系统应已准备好开发iOS应用程序。 让我们从典型的编程示例Hello World开始。
Hello World和React Native CLI
首先,导航到您要放置项目的文件夹。 我的是/ Users / azat / Documents / Code / react / ch9 /。 然后运行$ react-native init terminal命令,通过创建iOS和Android项目,package.json以及其他文件和文件夹来启动项目:
$ react-native初始化你好
等待。 可能需要一些时间。 此刻正在发生一些事情。 显然,文件夹hello已创建。 然后,该工具创建package.json。 (我喜欢Node和npm如今无处不在。2012年不是这样!)在package.json中,react-native CLI(是全局的)放置了一个本地依赖关系,即react-native。 这类似于运行$ npm i react-native-保存。
在该步骤之后,全局react-native CLI运行来自hello / node_modules / react-native / local-cli / cli.js文件的本地代码,并依次运行辅助bash脚本hello / node_modules / react-native / init.sh。 该bash脚本使用index.ios.js和index.android.js文件以及ios和android文件夹中的iOS和Android项目中的React Native代码创建支架。
在ios文件夹中,该工具使用Objective C代码创建Xcode项目文件。 这就是我们目前的重点。 这是该工具创建的样板文件夹结构:
/安卓
/ app
/ gradle
-build.gradle
-gradle.properties
-gradlew
-gradlew.bat
-settings.gradle
/ ios
/你好
/hello.xcodeproj
/ helloTests
/ node_modules
-...
-index.android.js
-index.ios.js
-package.json
-.watchman.config
-.flowconfig
生成所有内容后,您将返回到命令提示符。 我计算机上的输出是这样,它甚至告诉我如何启动应用程序:
要在iOS上运行您的应用,请执行以下操作:在Xcode中打开/Users/azat/Documents/Code/react/ch9/hello/ios/hello.xcodeproj点击运行按钮要在Android上运行您的应用,请执行以下操作:运行Android模拟器(最快的方法是入门),或连接了cd / Users / azat / Documents / Code / react / ch9 / hello react-native run-android的设备
您有两个选择。 您可以手动打开Xcode,然后从“文件”菜单中选择“打开”(Command + O),打开hello.xcodeproj文件,然后单击黑色矩形进行构建和运行。 或者,您可以使用$ cd hello导航到该文件夹,运行$ open ios / hello.xcodeproj,然后单击Xcode中的“播放”以生成并运行。
如果正确地遵循了这些步骤,您将看到一个新的终端窗口,显示React Packager。 它以一条消息开头:
〜/ Documents / Code / react / ch9 / hello / node_modules / react-native / packager〜┌─────────────────────── ────────────────────┐│跑步打包程序位于端口8081上。││││在任何JS项目上开发时,请保持此打包程序运行。 ││││随意随意关闭此选项卡并运行自己的打包程序实例。 ││││https://github.com/facebook/react-native│││└───────────────────── ────────────────────┘寻找/ Users / azat / Documents / Code / react / ch9 / hello中的JS文件[12:15:42 PM] 构建依赖关系图[12:15:42 PM] 爬网文件系统[12:15: 42 PM] 加载捆绑包布局[12:15:42 PM] 加载捆绑包布局(0ms)
那么这里发生了什么? React Native将我们的React Native JavaScript文件打包并在localhost:8081上提供它们。 没错,就像您在http:// localhost:8081 / index.ios.bundle?platform = ios&dev = true上打开浏览器一样,它就像其他任何Web服务器一样。 立即在浏览器中打开它。 搜索“ hello”。 您将看到React Native代码捆绑在一个大文件中。 大多数Web开发人员应该对此感到熟悉。 😉
我从哪里获得http:// localhost:8081 / index.ios.bundle?platform = ios&dev = true URL? 它位于第34行的hello / ios / hello / AppDelegate.m文件中(您使用的是与我相同的版本,对吗?):
jsCodeLocation = [NSURL URLWithString:@“ http:// localhost:8081 / index.ios.bundle?platform = ios&dev = true”];
Objective C代码从服务器获取JavaScript。 这是默认选项一。 还有第二种选择,此刻对此进行评论。 它从静态文件(同一文件的第42行)中获取JavaScript代码。 有选择是件好事!
这些评论告诉我们如何启动服务器。 只是$ npm start命令,它运行$ react-native start,因此我们也可以使用后者。 因此,如果您要在项目之间进行切换,或者不想使用Xcode自动打开的终端进程,则始终可以启动新服务器。 就像任何服务器一样,请记住,您不能让其中两个在同一端口上侦听。 因此,在启动localhost:8081上的新服务器之前,请终止旧进程。
启动模拟器窗口需要一些时间。 我更喜欢使用iPhone 6,而不是iPhone 6 Plus。 这样,我在屏幕上就有了更多的开发空间。 现在,您应该已经打开了Simulator窗口。 闲逛。 没什么可看的,如图1所示。
继续并打开index.io.js文件。 您可以看到外观熟悉的JavaScript / Node代码。 如果您还不熟悉ES6(或ES2015,它的正式名称),请查看第10章和附录I。
在文件的开头,有一个解构语句,用于从React Native导入对象:
var React = require('react-native'); var {AppRegistry,StyleSheet,Text,View,} = React;
接下来,您可以使用render方法看到您的好朋友React.createClass():
var hello = React.createClass({render:function(){return( 欢迎使用React Native! 首先,编辑index.ios.js 按Cmd + R重新加载,{'\ n'} Cmd + D或为开发菜单摇动 );}});
天哪,像这样的好评论,我很快就会破产,这意味着我不需要写书。 they正如他们所说,在模拟器中按Command + R将重新加载它。 继续,将“欢迎使用React Native!”更改为“ Hello World!”。保存index.ios.js,然后在Simulator窗口中重新加载该应用程序。
注意:如果使用的是Dvorak或Colemak之类的非标准键盘布局(如我所做的那样),则在“模拟器”窗口中,您将必须使用标准的美国布局作为快捷方式以及键入文本。
观察更改,并注意到我们不必重建Xcode项目。 保存文件后,Watchman更新了软件包。 新代码已在localhost:8081的服务器上提供。 如果您转到http:// localhost:8081 / index.ios.bundle?platform = ios&dev = true,则可以在浏览器中看到文本“ Hello World!”。 重新加载模拟器后,新代码就在那里了!
index.ios.js中还有另外两个有趣的东西(然后我们将继续分别研究每个组件):StyleSheet和AppRegistry。 它们不在Web React中,所以让我解释一下。
样式和Flexbox
前者是在元素中创建布局,样式和格式的方法。 我们使用StyleSheet.create()创建一个对象。 例如,以下是我们的Hello World样式:
var styles = StyleSheet.create({container:{flex:1,justifyContent:'center',alignItems:'center',backgroundColor:'#F5FCFF',},welcome:{fontSize:20,textAlign:'center',margin :10,},指令:{textAlign:'center',color:'#333333',marginBottom:5,},});
我希望您能猜出某些属性的含义,例如backgroundColor和fontSize。 它们与CSS中的background-color和font-size相似,并且您已经知道React使用camelCase而不是破折号。 其他样式属性(例如flex)特定于React Native。 这是他们的意思:
- 柔性
- 证明内容
- alignItems
- flexDirection
样式属性中的数字是点,而不是像素。 区别在于,根据屏幕的不同,点可能意味着1个或更多像素,因此使用点可以使开发人员不必编写各种屏幕格式的if / else条件。 最值得注意的是,在像iPhone 3GS这样的旧iPhone上,1点是1像素(1:1)。 另一方面,在具有Retina屏幕的新iPhone(例如iPhone 6)上,1点是2×2正方形像素(1:2)。
该文件的最后一条语句类似于Web React开发中的ReactDOM.render():
AppRegistry.registerComponent('hello',()=> hello);
它在注册表中注册了我们的组件hello。 您可以将粗箭头功能(第二个参数)中的名称更改为其他名称,但不要更改第一个参数。 第10章和附录I中介绍了ES6粗箭头。现在,让我们更详细地研究React Native组件。
React主要的原生UI组件
您可能已经注意到,在render方法中,我们使用特殊的标记/元素,例如和而不是
。 这些特殊元素或React Native组件来自react-native库。 其中有很多,我相信很快还会有更多。 有特定于iOS和Android的组件,以及可在各个平台上运行的合成组件。 通常,仅iOS组件的名称末尾带有IOS(例如NavigatorIOS),而通用跨平台组件则没有这样的结尾(例如Navigator)。
描述所有React Native组件将自己拿一本书。 而且,正如我之前说过的那样,社区和Facebook开发人员本身会不懈地不断添加新组件并更新现有组件。 最好参考官方文档以获取受支持组件的最新列表。 但是,为了能够使用React Native开发最少的移动应用程序,您需要学习主要(在我看来)组件。 他们是:
- 视图-基本视图组件。 每个渲染器必须至少具有一个空的视图。
- 文本—文本组件。 与Web React中的文本不同,所有文本都必须包装在此组件中。
- TextInput —表单输入字段组件。 使用它来捕获用户输入。
- ScrollView —具有可滚动内容的视图。 当您的内容无法在一个屏幕上显示时,请使用它。
- ListView —具有结构化数据的视图。 用它来输出列表或表。
- TouchableHighlight —用户触摸组件。 使用它来捕获用户触摸事件,类似于Web开发中的定位标记。
- 开关—布尔型开/关开关。 将其用于设置和表格。
- 导航器—高度可定制的导航组件。 使用它在屏幕之间导航并实现导航栏和/或面包屑导航栏。
之所以选择所有这些组件,是因为了解它们将为您提供构建一些有用的应用程序的最低要求,正如您将在Timer和Weather App项目中看到的那样。 而且,这些组件是通用的。 也就是说,您可以(并且应该)将它们用于iOS和Android。 甚至您甚至可以为index.ios.js和index.android.js使用相同的代码库。
对于本书的这一部分,我将使用Timer和Weather App项目中的代码片段使示例比某些foo-bars更实际。 计时器的代码在计时器中。 Weather App的代码在天气中。
视图
如前所述,View是最基本的组件。 如果您不知道使用什么,请使用“视图”。 您可以将多个其他组件包装在View中,类似于将它们包装在
var Timer = React.createClass({render(){// ... return( {this.props.time} 第二秒 )}})
文本
文本组件用于呈现文本。 像大多数其他组件一样,我们可以为其提供样式。 例如,此Text元素正在使用Flex,字体大小为36,在40的顶部填充,空白为10:
var TimerWrapper = React.createClass({// ... render(){return( 计时器 ... }}})var styles = StyleSheet.create({...标题:{flex:1,fontSize:36,paddingTop:40,margin:10},...})
结果如图1所示。
方便地,我们可以使用数组在style属性中组合两个或更多样式对象。 例如,此Text元素使用navBarText和navBarButtonText中的样式:
{'<'} {previousRoute.name}
样式属性和样式组合并非Text独有。 您可以将它们应用于其他组件。
文字输入
TextInput是输入字段组件。 您通常会以表格形式使用它来捕获用户输入,例如电子邮件地址,密码,名称等。此组件具有一些熟悉的属性,例如:
- 占位符-值为空时将显示的示例文本
- value —输入字段的值
- 样式—样式属性
其他属性特定于React Native。 主要的是:
- enabledReturnKeyAutomatically —如果为false(默认值),则通过禁用返回键来防止用户提交空文本值。
- onChange —在值更改时调用的方法。 将事件对象作为参数传递。
- onChangeText —值更改时调用的方法。 传递文本值作为参数。
- onEndEditing —当用户按下虚拟键盘上的返回键时要调用的方法。
- 多行—如果为true(默认为false),则该字段可以采用多行。
- keyboardType —枚举器值之一,例如’default’,’numeric’或’email-address’。
- returnKeyType —返回键的枚举数,例如’default’,’go’,’google’,’join’,’next’,’route’,’search’,’send’,’yahoo’,’done’,或“紧急呼叫”。 仅限iOS。
iOS和Android的TextInput的最新属性的完整列表位于https://facebook.github.io/react-native/docs/textinput.html#props。
考虑以下示例,该示例使用处理程序this.search渲染城市名称输入字段。 键盘上的按钮将显示Search,将值分配给状态(受控组件!),并且占位符是San Francisco:
结果如图2所示,您可以在其中观察虚拟键盘上的Search键。
使用onChangeText属性,我们获得输入字段的值作为处理程序函数(handleCityName(event))的参数。 例如,要处理城市的名称并在受控组件中设置cityName的状态,我们需要实现handleCityName,如下所示:
... handleCityName(cityName){this.setState({cityName:cityName})},...
另一方面,如果您需要的不仅仅是文本,那么还有onChange。 当事件进入onChange处理函数时,事件参数具有称为nativeEvent的属性,而该属性又具有称为text的属性。 您可以像这样实现onChange处理程序:
... onNameChanged:function(event){this.setState({name:event.nativeEvent.text}); },... render(){return(}}})
滚动视图
这是View组件的增强版本。 它允许内容可滚动,因此您可以使用触摸手势上下滚动。 当内容不能在一个屏幕上显示时,这很有用。 例如,我可以将ScrollView用作我的render()的根目录,因为我知道timerOptions可以是一个非常大的数组,因此可以呈现很多行数据(Button组件):
var TimerWrapper = React.createClass({// ... render(){return( 计时器 按下按钮 {timerOptions.map((item,index,list)=> {return })} ... }}}}
列表显示
ListView是一个视图,可从提供的数据中呈现行列表。 在大多数情况下,您希望将ListView包装在ScrollView中。 数据必须采用某种格式。 使用dataSource = new ListView.DataSource()创建数据源对象,然后使用dataSource.cloneWithRows(list)用来自标准JavaScript数组的数据填充数据源。
这是一个例子。 首先,我们创建数据源对象:
让dataSource = new ListView.DataSource({rowHasChanged:(row1,row2)=> row1!== row2})
然后,我们使用cloneWithRows方法填充来自数组response.list的数据:
this.props.navigator.push({name:'Forecast',component:Forecast,passProps:{ForecastData:dataSource.cloneWithRows(response.list),ForecastRaw:response}})
现在忽略导航器调用。 它会在本章后面介绍。
我们有数据,因此现在让我们通过提供属性dataSource和renderRow来呈现ListView。 例如,这是预测信息的列表,每一行都是某天的预测。 ListView的父级是ScrollView:
module.exports = React.createClass({render:function(){return( {this.props.forecastRaw.city.name} }}})
如您所料,renderRow(在此示例中为ForecastRow)是另一个组件,负责从提供的数据源中渲染单个项目。 如果没有方法或状态,则可以创建一个无状态组件(有关第10章中的无状态组件的更多信息)。 在ForecastRow中,我们输出日期(dt_txt),描述(描述)和温度(温度):
const ForecastRow =(forecast)=> {return( {forecast.dt_txt}:{forecast.weather [0] .description},{forecast.main.temp} )}
您可以使用简单的Array.map()构造实现ListView的功能。 在这种情况下,不需要数据源。
可触摸的高光
TouchableHighlight捕获用户触摸事件。 开发人员在Web开发中实现类似于锚定()标签的按钮。 该动作作为onPress属性的值传递。 要实现按钮,我们还需要在其中添加一些文本。
例如,这是一个触发startTimer的按钮,其文本由time属性和单词“ minutes”或“ seconds”组成:
var Button = React.createClass({startTimer(event){// ...},render(){return( {this.props .time} {(this.props.isMinutes)?'minutes':'seconds'} }}})
TouchableHighlight本身的样式是什么? 因此,当我们实现按钮时,我们要么在TouchableHighlight(图3)内部设置文本样式,要么将图像与Image组件一起使用。
与TouchableHighlight类似的组件是:
- TouchableNativeFeedback
- 可触摸的不透明度
- 可触摸而无反馈
开关
您可能已经多次看到并使用了Switch组件或类似的本机元素。 一个可视示例如图9-X所示。 这是一个很小的切换,与复选框没有什么不同。 这是布尔型开/关输入元素,在表单和应用程序设置中非常方便。
在实现Switch时,您至少提供两个属性onValueChange和value(再次是受控组件!)。 例如,此切换使应用程序保存城市名称,或不保存城市名称:
... 还记得吗? ...。
在处理程序toggleRemember中,我将状态设置为与当前this.state.isRemember相反的值:
// ... toggleRemember(){this.setState({isRemember:!this.state.isRemember},()=> {//如果(!this.state.isRemember)this.props从存储中删除城市名称.storage.removeItem('cityName')})},// ...
航海家
Navigator是一个高度可定制的导航组件,可在应用程序的屏幕之间进行导航。 我们可以使用它来实现导航栏和/或面包屑导航栏。 导航栏是屏幕顶部的菜单,带有按钮和标题。
还有NavigatorIOS,Facebook并未使用它,因此未得到社区的正式支持和维护。 NavigatorIOS具有内置的导航栏,但仅适用于iOS开发。 另一个缺点是,当这些路径的属性更改时,NavigatorIOS不会刷新这些路径/屏幕。 相反,Navigator可以在iOS和Android上使用,并且在传递给它们的属性更改时刷新路由。 您可以根据自己的喜好自定义导航栏。
由于Navigator非常灵活,因此我找到了几种实现它的方法。 有一种方法可以让您拥有路由堆栈,然后使用路由ID和前进/后退方法进行导航。 我决定使用这种模式,该模式使用抽象和NavigatorIOS接口(passProps)。 假设App组件是您在AppRegistry中注册的组件。 然后,您想在App的render方法中渲染Navigator:
const App = React.createClass({render(){return(<Navigator initialRoute = {{name:'Search',index:0,component:Search,passProps:{storage:storage}}} ref ='navigator'navigationBar = {}} renderScene = {(route,navigator)=> {let props = route.passProps props.navigator =导航器props.name = route.name返回React.createElement(route.component,props)}} />)}})
您可以观察到Navigator的几个属性:
- initialRoute —我们渲染的第一个路由对象。
- ref —将具有Navigator对象的App元素的属性。 我们可以用它跳到新的场景。
- navigationBar —带有标题以及左右按钮的顶部菜单。
- renderScene —在每条路线的导航事件中触发的方法。 我们获取route对象,并使用route.component和route.passProps渲染组件。
要导航到新屏幕,如Forecast(预测组件),并将属性传递给它,请调用navigator.push():
// ... this.props.navigator.push({{name:'Forecast',component:Forecast,passProps:{ForecastData:dataSource.cloneWithRows(response.list),ForecastRaw:response}}))// //
在此示例中,我将在每次push()调用时传递组件和道具。 如果使用的路由栈基本上是组件列表,则只能传递ID或组件名称,而不传递整个对象,然后从堆栈中获取对象。 与往常一样,有多种方法去to鱼。
将模块导入Xcode项目
如果您想使用社区React Native组件,即不属于react-native而是作为独立npm模块提供的组件,该怎么办? 您可以将模块导入项目中!
在计时器中,我们需要在时间到时播放声音。 在撰写本文时(2016年1月),尚无用于声音的官方组件,但有多个userland模块。 其中之一是react-native-audioplayer。 首先,使用npm将其安装在项目文件夹中:
$ npm install react-native-audioplayer@0.2.0-保存
目前,我们专注于iOS,因此安装如下:
- 在Xcode中打开您的项目。
- 在Xcode中,在左侧栏中找到“项目浏览器”。
- 在项目浏览器中,右键单击“库”。
- 在上下文菜单中,单击“将文件添加到”计时器”。 (如果需要,将另一个项目名称替换为“ timer”。)
- 导航到node_modules / react-native-audioplayer。 添加文件RNAudioPlayer.xcodeproj。 结果如图5所示。
- 在项目浏览器中,选择您的项目(计时器)。
- 在“目标”列表中单击计时器的构建目标(图9-X)。
- 单击“构建阶段”选项卡将其打开。
- 通过单击展开“使用库链接二进制文件”。
- 单击加号(+),然后在Workspace下添加libRNAudioPlayer.a,或从项目导航器中拖放libRNAudioPlayer.a。 它在Libraries / RNAudioPlayer.xcodeproj / Products下。
- 运行您的项目(按Command + R或单击表示“播放”的黑色矩形)。
如果您正确执行了所有操作,则可以在index.ios.js文件中使用require()导入模块:
AudioPlayer = require('react-native-audioplayer')
并使用play()播放声音:
AudioPlayer.play('flute_c_long_01.wav')
声音文件需要包含在捆绑软件中。 为此,选择Copy Bundle Resources并添加flute_c_long_01.wav或您自己的声音文件,如图7所示。
这就是所有的准备。 现在我们可以实现Timer了!
项目:计时器
您已经看到了Timer应用程序中的点点滴滴(图8)。 我认为如果我们立即进行实施将是有益的。 主文件是index.ios.js。 它包含三个组件,与我的React Quickly浏览器/ Web React Timer(Manning,2016年)(GitHub)不同:
- TimerWrapper —一个智能组件,具有计时器的大部分逻辑
- 计时器—一种哑音组件,在时间到时播放声音并显示剩余的秒数
- Button —一个组件,它显示一个按钮并通过调用父级传递给它的处理程序(TimerWrapper)触发倒数开始
我们使用React Native,其对象和Audio Player的导入来启动index.ios.js文件:
'使用严格'var React = require('react-native-audioplayer'),AudioPlayer = require('react-native-audioplayer')var {AppRegistry,StyleSheet,Text,View,ScrollView,TouchableOpacity,Switch} = React
下一条语句声明了Timer按钮的选项数组,通过使用Switch,我们将其转换为秒数或分钟数:
const timerOptions = [5、7、10、12、15、20]
我从第5章项目增强了TimerWrapper,它具有动态生成的按钮和秒至分钟的切换功能。 开关正在使用isMinutes状态,因此让我们在一开始将其设置为false。 提醒您,此示例使用了某些ES6 + / ES2015 +语法。 如果您不熟悉它,或者不确定您是否熟悉它,请查看第10章和附录I。
var TimerWrapper = React.createClass({getInitialState(){return {time:null,int:null,isMinutes:false}},
isMinutes的初始值为false。 toggleTime是Switch的处理程序。 我们使用逻辑非(!)翻转isMinutes的值。 将时间设置为零很重要,否则每次我们按下开关时都会触发声音。 声音播放的时间为0,因此,如果将其设置为null,则不会播放。 声音逻辑在计时器组件中。 当我们更改isMinutes的状态时,React算法决定重新渲染它:
toggleTime(){let time = this.state.time if(time == 0)time = null this.setState({isMinutes:!this.state.isMinutes,time:time})},
下一个方法将启动计时器。 如果您遵循了第5章中的项目,您将知道它是如何工作的。 React Native提供了用于计时器的API,即clearInterval()和setInterval()作为全局对象。 即使我们在按钮上看到分钟并且开关已打开,时间状态下的数字也始终以秒为单位:
startTimer(time){clearInterval(this.state.int)var _this =此var int = setInterval(function(){console.log('2:在setInterval的内部')var tl = _this.state.time-1如果( tl == 0)clearInterval(int)_this.setState({time:tl})},1000)console.log('1:setInterval')返回this.setState({time:time,int:int})} ,
在render方法中,我们使用一个简单的map()迭代器来生成一列按钮。 它包装在ScrollView中,因此您可以通过添加更多元素来使timerOptions数组真正着迷,看看发生了什么:
render(){return( 计时器 按一个按钮 {timerOptions.map((item,index,list)=> {return })}
在按钮之后,我们有一个文本标签,上面写着Minutes和Switch控件组件:
分钟 )}})
我们在TimerWrapper中渲染的按钮来自此组件。 它具有三元条件(也称为Elvis运算符),可以通过将分钟乘以60(每分钟60秒)或秒来设置分钟:
var Button = React.createClass({startTimer(event){let time =(this.props.isMinutes)?this.props.time * 60:this.props.time返回this.props.startTimer(time)},
渲染时,我们使用TouchableOpacity,其功能类似于TouchableHighlight,但视觉表示有所不同(触摸时是透明的)。 根据isMinutes属性的值,有一个三元条件来输出单词“ minutes”或“ seconds”:
render(){return( {this.props.time} {(this.props.isMinutes)?'minutes':'seconds'} )}})
当此数字为0时,Timer组件将呈现剩余的秒数并播放声音:
var Timer = React.createClass({render(){if(this.props.time == 0){AudioPlayer.play('flute_c_long_01.wav')} if(this.props.time == null || this.props .time == 0)返回 return( {this.props.time} 第二秒 }}})
样式对象使用Flex。 在容器中,有flexDirection,设置为column。 它将元素垂直放置,如在列中。 另一个值是row,它将水平放置它们。
var styles = StyleSheet.create({容器:{flex:1,flexDirection:'column',alignItems:'center'},标题:{flex:1,fontSize:36,paddingTop:40,margin:10},说明: {color:'#333333',marginBottom:15,},按钮:{color:'#111',marginBottom:15,borderWidth:1,borderColor:'blue',padding:10,borderRadius:20,fontWeight:'600 '},按钮:{flex:1,alignItems:'center',justifyContent:'flex-start'}})
最后,有一个register语句:
AppRegistry.registerComponent('timer',()=> TimerWrapper)
现在,我们可以按照上一节中的步骤将Audio Player安装并导入Xcode项目。 不要忘记也包括声音文件。 完成后,导航至ch9 / timer文件夹,并使用$ react-native start启动本地服务器。 您应该看到:
反应包装器准备好了。
转到您的模拟器并刷新它。 您应该看到按钮上带有秒的按钮,并且开关处于关闭位置。 将其打开以使用分钟,按钮将改变。 按下5分钟将开始倒数,显示剩余的秒数,如图9所示。
我敢于您重新设计这个小应用程序(使其更漂亮!),将其发布到App Store,然后将链接发送给我。 也许您可以获得最高排名。 飞扬的鸟做到了。
项目:天气应用
这个项目的想法是根据用户提供的城市名称从OpenWeatherMap API获取天气预报(图10)。 在该项目中,我们将利用导航器在屏幕之间切换,并在顶部显示导航菜单,并带有一个返回按钮。
此外,还将具有“记住我”功能,以保存输入的城市名称以备将来使用。 持久性将通过AsyncStorage实现。
生成的预测数据将显示在网格中,其中日期,描述和温度以F和C表示,如图11所示。
首先,请使用React Native CLI工具提供的支架(如果您没有v0.1.7,请按照本章开头的说明进行操作):
$ react-native初始天气
该命令将输出如下内容:
这将引导您在/ Users / azat / Documents / Code / react / ch9 / weather中创建一个新的React Native项目,从npm安装react-native程序包...在/ Users / azat / Documents /中设置新的React Native应用程序Code / react / ch9 / weather要在iOS上运行您的应用:在Xcode中打开/Users/azat/Documents/Code/react/ch9/weather/ios/weather.xcodeproj点击“运行”按钮要在Android上运行您的应用: Android模拟器正在运行(最快速的入门方法),或者设备已连接cd / Users / azat / Documents / Code / react / ch9 / weather react-native run-android
使用以下命令在Xcode中打开iOS项目:
$打开ios / weather.xcodeproj
除了已经存在的index.ios.js,还创建四个文件:Forecast.ios.js,search.ios.js,weather-api.js和response.json,因此项目结构如下所示:
/ weather / android ... / ios / weather /Base.Iproj ... /Images.xcassets ...-AppDelegate.h-AppDelegate.m-Info.plist-main.m /weather.xcodeproj /project.xcworkspace。 .. / xcshareddata ... / xcuserdata ...-project.pbxproj / weatherTests-Info.plist-weatherTests.m / node_modules ...-.flowconfig-.gitignore-.watchmanconfig-Forecast.ios.js-index.android .js-index.ios.js-package.json-response.json-search.ios.js-weather-api.json
文件search.ios.js和Forecast.ios.js将是第一个屏幕的组成部分,第一个屏幕将具有城市名称的输入字段,第二个屏幕将分别显示预测。 但是在开始实施“搜索和预测”之前,让我们对App组件和导航进行编码,这将使我们能够在“搜索”和“预测”屏幕之间进行切换。
在index.ios.js文件中,添加以下列表中所示的React Native类。 到目前为止,您不应该熟悉的唯一类是AsyncStorage和PixelRatio-本章前面已介绍了所有其他内容:
'使用严格'var React = require('react-native')var {AppRegistry,StyleSheet,Text,View,Navigator,ListView,AsyncStorage,TouchableOpacity,PixelRatio} = React
导入搜索。 const是ES6的东西。 您可以使用var或了解const并让其进入ES6 / ES2016速查表。
const Search = require('./ search.ios.js')
现在让我们为存储创建一个抽象,即AsyncStorage。 您可以直接使用AsyncStorage,但最好有一个类似于此处所示的抽象。 AsyncStorage接口非常简单。 它使用getItem(),removeItem()和setItem()方法。 我敢肯定你能猜出它们是什么意思。 唯一有趣的部分是,对于getItem(),我们需要利用Promise。 其背后的想法是getItem()结果是异步的。 备忘单上还有更多关于ES6承诺的信息。
const storage = {getFromStorage(name,callback){AsyncStorage.getItem(name).then((value)=> {console.log(`$ {name}的AsyncStorage GET:“ $ {value}”`)if(value )callback(value)else callback(null)})。done()},setInStorage(name,value){console.log(`$ {name}的AsyncStorage SET:“ $ {value}”`)AsyncStorage.setItem(名称,值)},removeItem:AsyncStorage.removeItem}
删除样板组件并将其替换为App:
const App = React.createClass({render(){return(
App组件需要呈现Navigator。 我们提供搜索组件作为初始路线:
<Navigator initialRoute = {{名称:'Search',索引:0,组件:Search,passProps:{storage:storage}}}
ref属性是我们如何访问App组件本身中的Navigator实例的方法。 假设这是指App,则导航器对象将位于this.refs.navigator中:
ref ='导航器'
导航栏是屏幕顶部的菜单,我们使用Navigator.NavigationBar组件并提供routeMapper属性来渲染它(我们仍然需要实现此功能):
navigationBar = {}
虽然导航栏是一个不错的功能,但不是必需的功能,但下一个属性很重要。
基本上,它渲染每条路线。 在此示例中,我假设route参数具有我需要的所有内容,例如组件和属性。 实现Navigator的另一种方法是仅在路由中传递ID,并通过使用某些哈希表(即,路由堆栈对象)从ID中解析组件对象。
renderScene = {(route,navigator)=> {let props = route.passProps
您可以通过将导航器对象设置为要使用的任何属性来控制其在子级中的位置。 我保持一致; 导航器对象放置在this.props.navigator下:
props.navigator =导航器props.name = route.name
添加导航器和名称后,props对象即可进行渲染:
返回React.createElement(route.component,props)
然后,关闭所有括号和标签:
}} />)}})
我们已经完成了大部分繁重的工作。 如果您选择不实施导航栏,则可以跳过NavigationBarRouteMapper。 如果要使用该栏,则可以使用它来实现它。
路由映射器必须具有某些方法:LeftButton,RightButton和Title。 这种模式的灵感来自于官方的React导航栏示例。 第一种方法使用index == 0条件检查这是否是初始路由。 另外,我们可以检查场景的名称,例如name ==’Search’。
var NavigationBarRouteMapper = {LeftButton(route,导航器,索引,navState){如果(index == 0)返回null
如果我们通过第一条陈述,那么我们就在预测中。 设置上一条路线(搜索):
var previousRoute = navState.routeStack [index-1]
现在,返回按钮,它是其中包含Text的TouchableOpacity组件。 我将尖括号用上一条路线的名称作为按钮标签,如图12所示。您可以使用“下一条”或其他名称。 该导航器组件是高度可定制的。 最有可能的是,您还将具有一些设计精美的图像。
返回(<TouchableOpacity
事件处理程序使用pop()方法。 与Array.pop()类似,它从堆栈/数组中删除最后一个元素。 最后一个元素是当前屏幕,因此我们返回到先前的路线:
onPress = {()=> navigator.pop()} style = {styles.navBarLeftButton}> {'<'} {previousRoute.name} )},
在此项目中,我们不需要右键,但是如果需要,可以类似于左键来实现。 您可能要使用路由列表,以便根据当前路由的索引知道下一个路由。
RightButton(route,导航器,索引,navState){return()},
最后一种方法很简单。 我们将路线名称作为标题。 如果愿意,可以使用title属性代替name; 只是不要忘记在任何地方进行更新(即在Search中的initialRoute,renderScene和push()中)。
标题(路线,导航器,索引,导航状态){返回( {route.name} )}}
最后,样式! 它们很容易阅读。 PixelRatio是新增加的一项。 它将为我们提供像素比率,因此我们可以将值控制在较低水平:
var styles = StyleSheet.create({navBar:{backgroundColor:'white',borderBottomWidth:1 / PixelRatio.get(),borderBottomColor:'#CDCDCD'},navBarText:{fontSize:16,marginVertical:10,},navBarTitleText: {color:'blue',fontWeight:'500',marginVertical:9,},navBarLeftButton:{paddingLeft:10,},navBarRightButton:{paddingRight:10,},navBarButtonText:{color:'black'}})
在注册调用中将天气部分更改为App:
AppRegistry.registerComponent('weather',()=> App)
我们完成了一个文件,还有两个要处理。 按照应用程序流程的逻辑顺序,我们通过导入对象继续执行search.ios.js:
'使用严格'var React = require('react-native')const Forecast = require('./ forecast.ios')var {StyleSheet,Text,TextInput,View,Switch,TouchableHighlight,ListView,Alert} = React
接下来,我们要声明OpenWeatherMap API密钥,您可以在注册为开发人员后从其网站获取该密钥。 选择免费计划,除非您确定自己的应用程序在iTunes上排名第一(或者是App Store?)时会达到限制。 避免使用我的密钥,并获得自己的密钥:
const openWeatherAppId ='2de143494c0b295cca9337e1e96b00e0',//这是Azat的密钥。 拥有自己的!
如果OpenWeatherMap更改了响应格式,或者您要脱机开发(如我一样),请保留真实URL的注释并使用本地版本(weather-api.js Node.js服务器):
// openWeatherUrl ='http://api.openweathermap.org/data/2.5/forecast'//真实API openWeatherUrl ='http:// localhost:3000 /'//模拟API,以$ node weather-api开头
由于此文件是由index.ios.js导入的,因此我们需要导出所需的组件。 您可以创建另一个变量/对象,但是我只是为了雄辩而将组件分配给module.exports:
module.exports = React.createClass({getInitialState(){
当获得初始状态时,我们要检查是否保存了城市名称。 如果是,那么我们将使用该名称并将isRemember设置为true,因为在先前的使用中会记住城市名称:
this.props.storage.getFromStorage('cityName',(cityName)=> {如果(cityName)this.setState({cityName:cityName,isRemember:true})})
在等待存储API执行带有城市名称的异步回调时,我们将该值设置为none:
return({isRemember:false,cityName:''})},
接下来,我们通过设置isRemember的状态来处理开关,因为它是受控组件:
toggleRemember(){console.log('toggle:',this.state.isRemember)this.setState({isRemember:!this.state.isRemember},()=> {
如果您还记得以前的章节(我知道,很久以前!),则setState()实际上是异步的。 如果要记住,我们要删除城市名称。 切换功能已关闭,因此我们需要在setState()的回调中实现removeItem(),而不仅是在下一行中实现(我们可能有竞争条件,如果不使用回调,状态将是旧的):
如果(!this.state.isRemember)this.props.storage.removeItem('cityName')})},
城市名称TextInput每次更改时,我们都会更新状态。 这是onChangeText的处理程序,因此我们将值作为参数而不是事件:
handleCityName(cityName){this.setState({cityName:cityName})},
search()方法由“搜索”按钮和虚拟键盘的“输入”触发。首先,我们将状态定义为局部变量,以消除不必要的输入:
搜索(事件){让cityName = this.state.cityName,isRemember = this.state.isRemember
最好检查城市名称是否为空。 为此,有一个跨平台组件Alert:
如果(!cityName)返回Alert.alert('无城市名称','请输入城市名称',[{text:'OK',onPress:()=> console.log('OK Pressed!')}])
整个应用程序中最有趣的逻辑是我们进行外部调用的方式。 答案很简单。 我们将使用新的提取API,该API已经是Chrome的一部分。 我们现在不太在意Chrome。 我们需要知道的是React Native支持它。 在此示例中,我求助于ES6字符串插值(也称为字符串模板)来构造URL。 如果您使用本地服务器,则响应将是相同的(response.json),因此URL无关紧要。
fetch(`$ {openWeatherUrl} /?appid = $ {openWeatherAppId}&q = $ {cityName}&units = metric`,{method:'GET'})。then((response)=> response.json()).then ((响应)=> {
获取数据后,我们要存储城市名称。 也许您想在进行调用之前执行此操作。 由你决定。
如果(isRemember)this.props.storage.setInStorage('cityName',cityName)
ListView将呈现网格,但是它需要特殊的对象数据源。 像这样创建它:
让dataSource = new ListView.DataSource({rowHasChanged:(row1,row2)=> row1!== row2})
一切准备就绪,可以呈现预测。 通过调用push()并传递所有必要的属性来使用Navigator对象:
this.props.navigator.push({{name:'Forecast',component:Forecast,
passProps是一个任意名称。 我在这里遵循了NavigatorIOS语法。 您可以选择其他名称。 对于ListView,我们使用cloneWithRows()填充JavaScript / Node数组中的行:
passProps:{ForecastData:dataSource.cloneWithRows(response.list),ForecastRaw:response}})}).catch((error)=> {console.warn(error)})},
我们已经完成了搜索方法。 现在我们可以渲染元素:
渲染:function(){return( 欢迎使用Weather App,快速反应项目 输入您的城市名称:
下一个元素是城市名称的TextInput。 它有两个回调,分别是onChangeText(触发handleCityName)和onEndEditing(调用搜索):
最后几个元素是开关的标签,开关本身和“搜索”按钮:
还记得吗? 搜索 )}})
当然还有样式-没有它们,布局和字体将全部倾斜。 这些属性在大多数情况下都是不言自明的,因此我们将不对其进行详细介绍。
var styles = StyleSheet.create({navigatorContainer:{flex:1},容器:{flex:1,justifyContent:'center',alignItems:'center',backgroundColor:'#F5FCFF',},欢迎:{fontSize:20 ,textAlign:'center',margin:10,},指令:{textAlign:'center',颜色:'#333333',marginBottom:5,},textInput:{borderColor:'#8E8E93',borderWidth:0.5,backgroundColor :'#fff',高度:40,marginLeft:60,marginRight:60,padding:8,},按钮:{color:'#111',marginBottom:15,borderWidth:1,borderColor:'blue',padding: 10,borderRadius:20,fontWeight:“ 600”,marginTop:30}})
因此,我们在按Search时从Search组件调用push()方法。 这将在Navigator元素中触发一个事件:即renderScene,用于渲染预测。 让我们实现它。 我保证,我们快完成了!
Forecast.ios.js文件以导入开头。 现在,如果您不熟悉,我将无能为力。
'使用严格'var React = require('react-native')var {StyleSheet,Text,TextInput,View,ListView,ScrollView} = React
我编写了此函数(主要是针对美国人),以根据C计算F。 这可能不是很精确,但是现在就可以了:
const fToC =(f)=> {return Math.round((f-31.996)* 100 / 1.8)/ 100}
ForecastRow组件是无状态的(有关第10章中的无状态组件的更多信息)。 其唯一目的是呈现单个预测项目:
const ForecastRow =(forecast)=> {return(
在该行中,我们以C和F格式输出日期(dt_txt),描述(多雨或晴天)和温度(图9-X)。 后者是通过调用此文件前面定义的fToC函数来实现的:
{forecast.dt_txt}:{forecast.weather [0] .description},{forecast.main.temp} C / {fToC(forecast.main.temp)} F ) }
结果将如图9-X所示。
接下来,我们导出Forecast组件,它是一个带有Text和ListView的ScrollView:
module.exports = React.createClass({render:function(){return( {this.props.forecastRaw.city.name}
ListView使用dataSource和renderRow属性来渲染网格。 数据源必须是特殊类型。 它不能是普通的JavaScript / Node数组:
}}})
和样式。 塔达!
var styles = StyleSheet.create({listView:{marginTop:10,},row:{flex:1,flexDirection:'row',justifyContent:'center',alignItems:'center',backgroundColor:'#5AC8FA',paddingRight :10,paddingLeft:10,marginTop:1},rightContainer:{flex:1},滚动:{flex:1,padding:5},文本:{marginTop:80,fontSize:40},副标题:{fontSize:16 ,fontWeight:'normal',颜色:'#fff'}})
最后一点是您是否要脱机工作并使用本地URL。 您需要拥有两个文件:
- response.json —对伦敦的真实API调用的响应
- weather-api.js —超简约节点Web服务器,它接收response.json并将其提供给客户端
继续并从GitHub复制response.json。 然后仅使用核心模块(我喜欢Express或Swagger,但在这里使用它们是一个过大的功能)来实现此Node.js服务器:
var http = require('http'),ForecastData = require('./ response.json')http.createServer(function(request,response){response.end(JSON.stringify(forecastData))})。listen(3000 )
使用$ node weather-api启动服务器,将React Native代码与$ react-native start捆绑在一起,然后重新加载模拟器。 捆绑程序和服务器必须一起运行,因此您可能需要在终端app / iTerm中打开一个新选项卡或一个窗口。
注意:如果收到“不变违规:ID为1–5的回调”错误,请确保您没有打开Chrome调试器多次。
您应该看到一个空的城市名称字段。 没关系,因为这是您首次启动该应用程序。 我故意将日志保留在存储实现中。 当您在Chrome标签中打开DevTools调试React Native时,应该会看到以下内容(通常在进入Chrome浏览器中的Hardware-> Shake Gestures-> Debug中启用它后,它会自动打开-并不是要摇动笔记本电脑! ):
cityName的AsyncStorage GET:“空”
进行拨动,输入名称(图13),并获取天气报告。 该应用程序已完成。 繁荣! 现在,在其上放一些漂亮的UI并发货!
测验
- 如何创建新的React Native项目:手动创建文件,或运行$ npm init,$ react-native init或$ react native init?
- ListView接受哪种类型的数据:数组,对象或数据源? (数据源)
- React Native与Native开发相比的好处之一是React Native具有实时重载功能。 对或错? (真正)
- 您可以使用React Native StyleSheet对象样式的任何CSS。 对或错? (假)
- 您可以在以下哪个Objective C文件中切换React Native包的位置:bundle.cc,AppDelegate.m,AppDelegate.h,package.json或index.ios.js? (AppDelegate.m)
动作
单纯的阅读学习不如先阅读再学习。 是。 甚至像这样的好书。 因此,请立即采取行动以巩固知识。
- 在Node.Unversity观看React Native Quickly截屏视频,它将引导您完成Weather应用程序
- 从源代码在计算机上运行Weather and Timer
- 更改按钮标签或菜单名称之类的文本,请在模拟器中查看结果
- 在计时器中更改声音文件
- 将地理位置添加到“天气”(请参阅地理位置)
摘要
这是一本快速的书,但是我们不仅涵盖了一个项目,还涵盖了两个项目。 除此之外,我们还介绍了:
- React Native如何与Xcode项目中的Objective C代码粘合
- 主要组件,例如View,Text,TextInput,Touchables和ScrollView
- 使用导航器实现应用
- 如何在设备上本地保留数据
- 使用访存API与外部HTTP REST API服务器通信(您可以使用相同的方法将数据保留在外部服务器上,或者进行登录或注销)
React Native是一项了不起的技术。 一旦开始学习和使用它,我就以一种积极的方式感到非常惊讶。 有大量证据表明,React Native可能会成为开发移动应用程序的下一个实际方法。 实时重新加载功能可以使开发人员将代码推送到他们的应用程序,而无需将其重新提交到App Store-很酷,对吗?
测验答案
- $ react-native init是因为手动创建文件很麻烦并且容易出错
- 数据源
- 真正
- 假
- AppDelegate.m
–
Azat Mardan
https://www.linkedin.com/in/azatm
要联系此博客的主要作者Azat,请提交联系表。
另外,请确保在注册时事通讯时免费获得3个惊人的资源。
简单。
简单。
没有承诺。