我们想为用户设计一款移动端的应用,却不知从何下手,我们只知道每一个人都希望做一款又酷又好玩儿体验又十分顺滑的应用,然而团队里没人有移动端的经验。
于是,我们最终只好选择 React Native 作为我们的开发工具。结果证明,这是一个非常明智的选择,我们从开始到现在的所思所感,全都总结如下:
为什么要用 React Native
我们是一群 Web 开发者,而不是 iOS 开发者。我曾参加过几个在纽约举办的有关 Swift 和 Objective-C 的线下聚会,但最后我依然认为编写 Ruby 和 JavaScript 代码最令我愉悦。我们团队在 2015 年初开始使用 Facebook 的 React 框架并取得了初步成效,React Native 面世后,我们一度怀疑像这样的交叉设备平台会有很多潜在的问题。但是随着我们的研究深入,我们越发喜欢它了:
- 一次学习,随处可用 交叉设备平台通常品相不佳,而且只支持不同设备间相同的功能。而 React Native 虽然基于 React.js 框架,但为 Android 和 iOS 两个平台编写的项目代码却不尽相同,它们之间只共享一部分代码。因此这些代码在每个平台上都能调用所有原生组件,发挥其最大的效用。
2. 声明式的视图 当初我们在 Web 平台用 React 编写代码的时候就已经爱上了这种声明式的视图,用 React Native 在 iOS 平台开发延续了这一传统,如此一来,我们的代码就变得可预测且 bug 更少。
请阅读:响应式编程
3. 为移动平台设计的 Flexbox(弹性盒模型) 我们不了解 iOS 布局约束求解器的工作原理,但幸运的是,React Native 引入了大部分浏览器都支持的 Flexbox 以使布局的过程变得更直观。
请阅读: Flexbox 指南
iOS 约束器(Constraints)
Flexbox
4. 用 JavaScript 编写代码 构建移动应用完全是我们未知的领域,显而易见,如果我们用已掌握的语言来编写代码会节省非常多的时间。况且,我们曾经痴迷于 CoffeeScript 优雅的语法,而现在 React Native 已经默认支持了 ES6,这让我们激动不已,因为我们可以很轻松地过渡到新的语法体系中。
请阅读:用 ES6 代替 CoffeeScript
不用 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()
- react-native-vector-icons 这是我们已知的最好的向量图标包,我们同时使用 FontAwesome 和 EvilIcons,它们可以完美地结合。MaterialIcons、IonIcons 和其它图标库均可直接通过这个包获取到。
<Icon name='trophy' /> <EvilIcon name='check' />
- tcomb-form-native 通过这个包可以轻松地创建表单。你可以既可以使用其默认的 Input 组件,也可以自定义 Input 类型,键盘和自动纠正等功能。
var Person = t.struct({ name: t.String, // 必填,字符串格式 surname: t.maybe(t.String), // 可选,字符串格式 age: t.Number, // 必填,数字格式 rememberMe: t.Boolean // 布尔值 });
- react-native-router-flux 合理的路由设计可以简化业务流程,通过一行简单的代码你就可以将定义好的路由置入视图层。
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', }, })
<span>Component.js</span>
... <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,负责移动开发业务
查看英文原文: http://code.hireart.com/2016/02/24/react-native-ios-app/
评论