我们想为用户设计一款移动端的应用,却不知从何下手,我们只知道每一个人都希望做一款又酷又好玩儿体验又十分顺滑的应用,然而团队里没人有移动端的经验。
于是,我们最终只好选择React Native作为我们的开发工具。结果证明,这是一个非常明智的选择,我们从开始到现在的所思所感,全都总结如下:
为什么要用React Native
我们是一群Web开发者,而不是iOS开发者。我曾参加过几个在纽约举办的有关Swift和Objective-C的线下聚会,但最后我依然认为编写Ruby和JavaScript代码最令我愉悦。我们团队在2015年初开始使用Facebook的React框架并取得了初步成效,React Native面世后,我们一度怀疑像这样的交叉设备平台会有很多潜在的问题。但是随着我们的研究深入,我们越发喜欢它了:
一次学习,随处可用交叉设备平台通常品相不佳,而且只支持不同设备间相同的功能。而React Native虽然基于React.js框架,但为Android和iOS两个平台编写的项目代码却不尽相同,它们之间只共享一部分代码。因此这些代码在每个平台上都能调用所有原生组件,发挥其最大的效用。
声明式的视图当初我们在Web平台用React编写代码的时候就已经爱上了这种声明式的视图,用React Native在iOS平台开发延续了这一传统,如此一来,我们的代码就变得可预测且bug更少。
为移动平台设计的Flexbox(弹性盒模型)我们不了解iOS布局约束求解器的工作原理,但幸运的是,React Native引入了大部分浏览器都支持的Flexbox以使布局的过程变得更直观。
iOS约束器(Constraints)
Flexbox
用JavaScript编写代码构建移动应用完全是我们未知的领域,显而易见,如果我们用已掌握的语言来编写代码会节省非常多的时间。况且,我们曾经痴迷于CoffeeScript优雅的语法,而现在React Native已经默认支持了ES6,这让我们激动不已,因为我们可以很轻松地过渡到新的语法体系中。
不用React Native的几个保留意见
我们深知节省下来的时间再多也抵不过捉襟见肘的可定制性和有限可用的React Native组件。我们也曾犹豫过是否一定要是用React Native,毕竟以下几条理由确实值得仔细琢磨:
-
React Native生态系统的限制性2015年9月,我们刚开始研究React Native,彼时非常多的iOS原生组件已经被实现出来,而且还有更多的功能正紧锣密鼓地开发当中。然而,我们还是会担心React Native最终不支持某些我们非常需要的组件,如此一来我们就只能在React Native桥接器的基础上构建其它相关的SDK了(例如提供对AWS和Mixpanel的支持)。
-
太前沿React Native更新非常快,支持老版本的代码有可能(极少数情况下)在新版本中就被废除。我们在过往的开发周期中遇到过几次这类问题,最后我们不得不作出这样的决定:当且只当亟需某些经过严格测试的新特性时才会主动升级。
-
Google搜索到的资料少之又少为一个新系统编写代码意味着很有可能遇到前人没经历过的错误,这些无法通过搜索得到解决方案的问题远比我们编写Ruby on Rails代码的时候要多。想要找到这些问题的解决方案需要足够多的韧劲儿:在文档中定位问题,在源代码中挖掘可能的漏洞。不管怎样,对于React Native团队和它的生态系统而言,只有更多地解决此类问题,多发布一些常规修订才能给他们带来更好的信誉。
React Native的包管理
事实证明,支持React Native的组件简直多到爆炸。无论是ActivityIndicator(加载中指示器)、Alert(警告框)还是Slide(轮播),这些组件与JavaScript中的同类组件并无二异。我无法用语言描述在不了解Objective C和Swift的前提下开发原生iOS应用有多么省心。此外,npm上每天都会出现许多新的代码包,以下这些我们都很喜欢(排名不分先后):
- react-native-simple-store我们刚开始用的是AsyncStorage,但却不得不一遍又一遍地重复构建相同的save和get函数。Simple Store是一个基于AsyncStorage的解决方案,它十分出色,可以分分钟连接到设备的存储区。
Store.get('user').then((user)=> { // 一些代码 }).catch((error) => { console.warn(error) }).done
<Icon name='trophy' /> <EvilIcon name='check' />
var Person = t.struct({ name: t.String, // 必填,字符串格式 surname: t.maybe(t.String), // 可选,字符串格式 age: t.Number, // 必填,数字格式 rememberMe: t.Boolean // 布尔值 });
Actions.dashboard
不可思议之处
作为Web开发人员,我们遇到了一些令人感到十分头疼的问题:
- 样式在React Native中可以使用样式表,但只能使用非级联的行内样式,用这种方式定义的样式非常难理解。我们可以通过行内越权的方式定义多级样式来解决这一问题。
Styles.js
module.exports = StyleSheet.create({ title: { fontSize: 23, textAlign: 'center', color: blueText, fontFamily: 'Avenir', fontWeight: '700', }, header: { padding: 20, paddingTop: 30, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: '#ccc', }, })
Component.js
... <Text style={[Styles.header, {color: 'white'}]}> 一些文字 </Text> ...
标准样式如下所示:
style={Styles.header}
自定义行内样式如下所示:
style={{color: 'white'}}
我们将二者进行了整合:
style={[Styles.header, {color: 'white'}]}
这种hack的方式让人很不爽……可能有更好的方法可以解决此类问题,但是当我们想到这个方案的时候压根儿不了解。
你很可能在使用NavigatorIOS的时候遇到此类问题,这是很糟糕的选择(除递归外的大多数循环都很糟糕)。Web路由是单一过程的,你可以获取到当前位置及历史位置的所有信息。而在iOS中,你需要将视图置入栈或从中取出相应视图,如果置入一个已存在于栈中的组件将会触发错误。如果调用组件时将当前组件置入到栈中可能会引发调用循环的问题,从而导致程序中断执行。react-native-router-flux可以帮我们解决这个问题,它能用一种可伸缩的方式推导出我们使用的历史路径。
最后的话
React Native是一个优秀的框架,用它来构建移动应用是非常好的选择。由于我们不了解Swift和Objective C,很难将纯原生与React Native两种实现进行比较,单就使用体验而言,它已经可以满足绝大多数的使用场景。我们乐于见到React Native带给我们的首个移动应用,它也持续发展力图变得更加完善。最后我始终认为,对于我们这样一支敏捷灵活的开发团队而言,这项技术帮我们快速地从Web环境切换到移动应用开发领域,非常感谢!
在后续的几篇文章中,我将继续深入剖析我们解决某些特定问题的思路。
- 登录和认证
- API和回调
- 整合iOS SDKs与React Native桥接器
作者简介
Tom Tang,目前任职于HireArt,负责移动开发业务
您好,朋友!
您需要注册一个InfoQ账号或者登录才能进行评论。在您完成注册后还需要进行一些设置。获得来自InfoQ的更多体验。
告诉我们您的想法
社区评论
by
发布于