Airbnb 的 React Native 经验:放弃 React Native (译)

原文使用 Sunsetting React Native, 语气感觉没有‘放弃’那么重的负面语义,大致就是逐步停止使用的意思。

由于技术和组织方面的一系列原因,我们准备放弃使用 React Native ,并致力于让原生体验更优异。

这是 Airbnb 使用 React Native 的经验以及下一步在移动端做些什么的系列博文第四篇。今天,我们身处何方?

尽管有很多团队依赖于 React Native ,并计划在可预见的未来使用它,但我们最终没有达到当初的目标。另外,还遇见了很多技术上组织上的无法克服的挑战,使技术投入 React Native 变得十分困难。

因此, 要奋勇向前,我们决定在 Airbnb 停止使用 React Native ,重新把我们所有的精力投入原生开发。

未能达到我们的目标

迭代更迅速

在 React Native 能够如预期工作时,工程师们能维持一个相当快的迭代速度。然而,本系列里提到的许多技术上组织上的问题,增加了很多项目里的挫败和意外的耽搁时间。

保持质量标准

近期,随着 React Native 的逐渐成熟,我们也累积了不少的专业知识,我么已经能够完成不少当初不确定能够做到的事情。我们构建了共享元素转换,(滚动)视差,并戏剧性的提高了一些过去经常丢帧的屏幕的性能。但是,例如初始化和异步首次渲染这样的技术挑战,想要达到既定目标的话是非常具有挑战性的。内部与外部资源的缺乏导致这更加困难。

只编写一次代码而不是两次

即便 React Native 的代码特性几乎是可以跨平台的,我们仍只是在非常少一部分的 app 上使用 React Native。此外,为了让工程师能够有效地工作,还需要大量的桥接基础架构。因此,我们最终是在三个平台上支持代码,而非两个平台。我们看到了在移动端和 web 端共享代码的潜力,也能够共享一些 npm 包,但除此之外,再没有以有意义的方式实现了。

提升开发者体验

开发者们使用 React Native 的开发体验各有不同。从某些角度来讲,比如构建时间上,体验就要好得多。然而在其他方面,例如调试,情况就非常糟糕了。这些细节在本系列的第二部分中有讲到。

停止使用的计划

由于我们无法达到我们的特定目标,我们做了个决定,就是 React Native 不在适合我们了。目前,我们在和团队一起制定一个合适的过渡计划。我们已经停止了 React Native 新特性的使用,并且计划年底前将大多数访问量高的页面换成原生编写。这得益于一些已经敲定好了的重新设计计划。我们的原生框架团队将在2018年继续支持 React Native。在2019年,将开始逐步减少支持并降低 React Native 的开销,例如启动时的初始化运行时。

在 Airbnb,我们是开源的坚定支持者。我们积极地使用和贡献很多世界各地的开源项目,并开源一些我们的 React Native 相关的工作。鉴于我们开始不再使用 React Native ,我们没办法像社区一样去维护 React Native。为了让社区变得更好,我们准备将一些 React Native 相关的开源工作转移到 react-native-community,我们已经开始使用 react-native-maps,且将会使用 native-navigationlottie-react-native

这并非全是坏事

尽管我们无法使用 React Native 达到目标,但使用 React Native 的工程十分还是有一些不错的体验。这些工程师中:

  • 60%的人认为他们的体验是绝佳的
  • 有20%认为还不错
  • 15%认为不太好
  • 5%的人认为非常糟糕

有63%的工程师表示有机会的话会再次选择 React Native,74%的人会考虑使用 React Native 开发新项目。不过值得注意的是,这个结果是存在固有的选择性偏差的,因为接受调查的人都是选择过 React Native 的人。

React Native 在走向成熟

这一系列的文章真实地反映了我们现阶段使用 React Native 的经验。然而,Facebook 和更开放的 React Native 社区致力于让 React Native 在混合应用上成规模使用。React Native 在以前所未有的规模发展。去年有超过 2500 个 commit,Facebook 刚刚宣布他们正在解决我们正面临的一些技术挑战。即便我们不在继续使用 React Native,我们仍然很高兴可以继续关注这些进展,因为 React Native 的技术优势转化成了为真实世界的我们的用户提供的便利。

小贴士

我们将 React Native 集成在一些现有的大型项目中,并以非常快的速度迭代。我们遇到的很多困难都是由于我们使用了混合模型方法。然而,我们的规模容许我们去解决一些小公司没有时间解决的难题。想让 React Native 跟原生一起无缝合作是可以做到的,但是非常具有挑战性。每个使用 React Native 的公司由于他们的团队组成、现有应用、产品需求和 React Native 成熟度不用而有不同的独特的感受。

当这一切结合在一起时,就对许多特性都起了作用,迭代速度,质量,开发体验能够匹配甚至超越了我们的目标和期望。这时候,我们真的感觉像要是接近于改变移动开发的游戏规则了。尽管这些体验很振奋人心,但当我们权衡这些优点和痛点,以及当前的需求和工程组织的资源的时候,我们还是决定它不再适合我们了。

决定使用一个新平台与否,是一个主观的决定,它取决于你团队独有的一些因素。我们的经验和放弃的理由不一定适合你的团队。事实上, _很多_ _公司_现在还在继续成功地使用 React Native,对其他的一些公司来说,React Native 仍然可能是最好的选择。

尽管我们从未放弃过使用原生,但停止使用 React Native 后可以释放出更多的资源来把原生优化的更好。跟我们一起进入本系列的下一部分,来学习一下我们在原生方面要做的新的事情。


这是本系列博文的第四篇,主要讲述了我们在 React Native 方面的经验以及下一步在移动端要做的事情。

  • 第一弹:在 Airbnb 使用 React Native (译文)
  • 第二弹:技术 (译文)
  • 第三弹:组建一个跨平台移动端团队 (译文)
  • 第四弹:在 React Native 方面做个决定 (翻译中…)
  • 第五弹:在移动端接下来要做的事情 (翻译中…)

Airbnb 的 React Native 经验:技术 (译)

作者:Gabriel Peal
翻译:Rebecca Han

技术方面的细节

15298974917220

这是 Airbnb 对外输出使用 React Native 的经验以及下一步在移动端做些什么的系列博文第二篇。

React Native 在 Android,iOS,web 及跨平台框架中,本身算是比较新且发展迅速的平台。在其发展的两年后,我们可以肯定的说,React Native 在很多方面是革命性的。它算得上是移动领域一个范例式的转变,我们能从它所达到的许多目标中获益。然而React Native 所带来的好处还伴随着一系列的痛点。

优点

跨平台

React Native 带来的最主要的好处就是你写的代码可以以原生的形式跑在 Android 和 iOS平台上。大多数 React Native 特性能够实现95%-100%的共享代码,而0.2%的文件是有平台特殊性的(.android.js/**.ios.js)。

统一的设计语言系统(DLS)

我们开发了一种叫做 DLS 的跨平台设计语言。每一个组件都有 Android,iOS,React Native 和 web 的版本。用一套容易的设计语言便于开发跨平台特性,因为这意味着设计,组件名称和界面在平台间是一致的。然而我们仍然需要在适当的情况下做出适合平台的决策。例如,我们在 Android 上使用原生的 Toolbar ,在 iOS 上使用 UINavigationBar ,且选择在 Android 上隐藏 disclosure indicators ,因为他们没有遵守 Android 平台的设计准则。

我们选择重写组件,来代替包装原生的组件,因为为每个平台单独创建平台适用的 API 更加可靠,且能够减少不知道如何在 React Native 上测试变动的 Android 和 iOS 工程师的维护开销。然而,这确实导致了同一组件在原生和 React Native 不同版本间不同步,而产生的平台间的碎片化。

React

React 成为最受欢迎的 web 框架是有原因的。它很简单同时功能又很强大,适用于大型代码库。其中我们比较喜欢的有:

  • 组件:React 的组件强制将关注点与定义良好的属性和状态分离开来。这为 React 的扩展性做了主要贡献。
  • 简明的生命周期:众所周知,Android(程度相对轻微)和 iOS 生命周期很复杂。功能灵活 React Native 组件从根本上解决了这个问题,使学习 React Native 比学习 Android 或 iOS 更加简单。
  • 声明式:React 的生命特性有利于 UI 层与底层状态保持同步。

迭代速度

在使用 React Native 开发的过程中,我们能够可靠的使用热重载来一两秒内在 Android 和 iOS 上测试变动。尽管构建性能是原生应用的首要任务,但它从来没有接近于我们用 React Native 所达到的迭代速度。最好的情况下,原生编译要15秒,对于完整的构建,最长可达20分钟。

投资基础设施

我们在原生基础设施上做了大范围的整合。所有的核心部分,例如网络,国际化,实验,共享的元素转换,设备信息,账户信息,以及很多其他的都被包进了一个 React Native API 中。这些桥梁中有些事十分复杂的,因为我们想要将现有的 Android 和 iOS 的 APIs 包装成对于 React 来说具有一致性和权威性的东西。虽然说通过快速迭代和新的基础设施开发来保持桥梁的更新,是一个持续追赶的过程,但是基础设施团队的投入能够使产品工作变得更加容易。

如果不对基础设施做大量的投入,React Native 会出现表现欠佳的开发者和不尽如人意的用户体验。因此,我们不应该认为 React Native 可以无需大量持续的投资,就被简单的附加进现有的 app 中。

性能

人们对 React Native 最大的担忧之一来自于它的性能。然而,在实践中,这并不算是问题。我们绝大的多数的 React Native 项目的界面表现像我们的原生应用一样流畅。性能通常被认为是一个单一维度。经常会看到移动端工程师看着 JS,心里想到的是“比 Java 慢”。然而在很多情况下,把业务逻辑和布局从主线程移出去,实际上提升了渲染的性能。

当出现性能问题时,通常时由于过度渲染引起的,可以通过,使用shouldComponentUpdateremoveClippedSubviews,以及更好的使用 Redux 来有效的缓解。

然而,初始化和首次渲染的时间(如下所述)使得 React Native 在启动界面,深层链接方面表现差强人意,以及会增加界面跳转时的传输时间间隔。此外,丢帧的界面很难调试,因为 Yoga 在 React Native 组件和原生视图间转化。

Redux

我们使用 Redux 来做状态管理,能够有效的阻止 UI 和状态不同步的情况,且能方便的实现不用页面间的数据共享。然而 Redux 因为它的模板而臭名昭著,还有着陡峭的学习曲线。虽然我们为一些通用模板提供了生成器,但在使用 React Native 的过程中,模板仍然是最具挑战之一和混淆的源头。值得指出的是,这些挑战并非是 React Native 特有的。

原生支持

由于 React Native 中的所有内容都可以由原生代码来进行桥接,所以我们最终能够构建一开始不确定的东西,比如:

  1. 共享元素的转换 : 我们建立了 组件,这个组件由 Android 和 iOS 的原生共享元素代码来支持。甚至能够在原生和 React Native 之间工作。
  2. Lottie : 通过包装 Android 和 iOS 上现有的库,我们可以让 Lottie 在 React Native 上工作。
  3. 原生网络栈 : React Native 在两个平台上均使用已有的原生网络栈和缓存。
  4. 其他的核心设施 :像网络模块一样,我们包装了其余的现有原生基础设施,例如国际化,实验等,以便能够在 React Native 上无缝运行。

静态分析

在 web 开发中,我们有着悠久的使用 eslint 的历史,本可以利用这一点,但我们又是 Airbnb 内部第一个推行 prettier 的平台。使用 prettier 可以有效的减少处理 PR 时的 nits( = nit-pick = 吹毛求疵的的小变动 = Small change that may not be very important, but is technically correct )和自行车棚效应。我们的 web 基础框架团队正在积极的调研 prettier。

我们还使用 analytics(分析)来计算渲染时间和性能,用来指出性能问题中,对哪些界面进行优先排查。

由于 React Native 比我们的 web 框架更加新,且体量更小,十分适合用来做新想法的试验台。很多我们使用 React Native 创立起来的工具和想法,现在都在用 web 来发展壮大。

动效

多亏了 React Native 的 Animated 库,我们得以实现无抖动的动效,甚至能够实现例如滚动视差(scrolling parallax)这样的,交互驱动的动效。

弹性盒子(flexbox)

React Native 使用 Yoga 处理布局,Yoga 是一个跨平台的 C 语言库,用来处理经由 flexbox API 生成的布局计算。前段时间,我们触到了 Yoga 的盲区,比如缺少宽高比,不过已经在后去的迭代中更新了。另外,例如像 flexbox froggy 这样有趣的教程,让入门更加享受。

跟 Web 的协作

在 React Native 后期的探索中,我们开始一次性构建 web,iOS 和 Android 三个平台的应用。鉴于 web 端也使用了 Redux,我们发现大量的代码是可以同时在 web 和原生平台上使用的,不需要更改。

缺点

React Native 不够成熟

React Native 跟 Android 和 iOS 平台相比,依旧不够成熟。React Native 更加年轻,雄心勃勃,且迭代更新很迅速。
虽然说 React Native 在大多数情况下表现的都很好,依旧有部分情况下,React Native 的不成熟就体现出来了,使得本来在原生上实现起来微不足道的东西变得很困难。不幸的是,这类的情况很难预测,要花费上几小时到几天不等的时间来解决。

维护 React Native 的分支

由于 React Native 的不成熟,有很多次我们需要给 React Native 的源代码打补丁。除了参与 React Native 的贡献外,我们还必须维护一个分支,用来快速的合并变动和突破我们的版本。在两年中,我们在 React Native 上提交了大概50个 commits。这使更新 React Native 的过程太痛苦了。

JavaScript 工具

JavaScript 是弱类型语言。类型安全性的缺乏不仅让扩展变得困难,同时也成为了有兴趣学习 React Native 但习惯了强类型语言的移动工程师讨论的焦点。我们调研了流程,但令人费解的错误提示导致了很糟糕的开发体验。我们还调研了 TypeScript,但是发现把它集成进我们现有的框架中非常困难,例如 babelmetro bundler。不过,我们持续的在 web 端积极调研 TypeScript。

重构

弱类型语言 JavaScript 带来的一个副作用是重构很困难还容易出错。重命名属性,尤其是有着通用名字的属性,比如 onClick 这种,或者是通过多个组件传递的属性,重构起来就是噩梦。更糟糕的是,重构是在在生产环境中断,而不是在编译时,这就很难去增加属性的静态分析。

JavaScript 内核的不一致

React Native 比较微妙和棘手的方面在于它是在 JavaScript 内核的环境中执行的。以下是我们遇到的情况的总结结果:

  • iOS 环境中运行会使用平台自己的 JavaScript 内核。这意味着 iOS 一如既往的不会给我们添麻烦。
  • Android 并不会使用它自己的 JavaScript 内核,所以 React Native 捆绑了自己的。然而默认情况下你拿到的是古老的版本,因此,我们需要自己动手捆绑一个新的
  • 调试的时候,React Native 附带了一个 Chrome 的开发者工具。这点很棒,Chrome 开发者工具是一个非常强大的调试工具。但是一旦接入了这个调试工具,所有的 JavaScript 代码就跑在了 Chrome 的 V8 引擎上了。这在 99.99%的情况下是 OK 的,但在我们遇到了这么一种情况,toLocalString 在 iOS 环境下很正常,但是在 Android 上只有调试状态下才工作。这说明 Android 的 JavaScript 内核不包含 toLocalString,它就默默的失败了,除非你在调试,但这种情况下它使用的又是 V8 引擎。如果不知道这样的技术细节,带给工程师们的会是几天痛苦的调试过程。

React Native 开源库

学习一个平台是困难且耗时的。大多数的人只熟悉1-2个平台。React Native 库包含对原生的桥接,例如地图,视频等,这需要三个平台等量的知识才能自如的运用。我们发现大多数的 React Native 开源项目是由只有一个或两个平台经验的人编写的。这会导致在 Android 和 iOS 平台上出现表现不一致和意外的 bug。

在 Android 平台,很多 React Native 库依旧要求你对 node_modules 使用相对路径,而不是发布与社区期待不一致的 maven artifacts。

并行的基础架构和产品功能性工作

我们积攒了很多年的 Android 和 iOS 平台的原生基础设施。然而在 React Native 方面,是从零开始为已有的基础设施编写或者创造桥接。这意味着发生过很多次产品工程师需要一些还不存在的功能的情况。基于此,工程师们要么需要在他们不熟悉的平台上干活,在他们的项目之外进行构建,要么被卡住等到有人创建这个功能。

崩溃监控

在 Android 和 iOS 平台上,我们使用 Bugsnag来上报崩溃。虽然能让 Bugsnag 在两个平台上都正常工作,但它可靠性较低,而且跟在我们其他的平台上相比,工作量也要更大。因为 React Native 在行业内是较新的框架且相对比较少见,我们不得不建立大量的例如 ‘在内部上传 source maps’ 这样的基础设施,而且还要与 Bugsnag 合作来做到一些类似 ’过滤出仅在 React Native 中发生的崩溃‘ 这样的事情。

鉴于围绕 React Native 的定制基础设施的数量,我们偶尔遇到一些严重的问题,但没有上报崩溃或者 source map 没有上传。

最后,如果问题跨域 React Native 和原生两部分的代码,调试 React Native 崩溃就非常有挑战性了,因为堆栈跟踪不会在 React Native 和原生之间跳转。

原生桥接

React Native 自带桥接的 API,以便在原生和 React Native 之间通信。虽说它能够正常的工作,但是编写起来还是极其麻烦的。首先,它需要三种开发环境才能正确的建立好。我们还遇到了些来自于 JavaScript,出乎意料的类型问题。举个栗子,整数常常被字符串包裹,这个问题直到它要通过桥接时才被发现。更加糟糕的是,有时 iOS 会默默地失败,但 Android 会崩溃。我们开始研究用 TypeScript 的定义来自动生成桥接代码直到2017年底,但是量太少也太晚了。

初始化时间

在 React Native 能开始初次渲染之前,必须初始化它的进行时。不幸的是,对我们的 app 的体积来说,需要几秒钟,这还是在高端机上。这让使用 React Native 来启动页面几乎不太可能。我们在 app 启动时就初始化React Native,来尽可能压缩它的首次渲染时间。

初始渲染时间

与原生界面不同的是,在有足够的信息首次渲染界面前,渲染 React Native 至少需要一个完整的主线程 -> js -> yoga 布局线程 -> 主线程的闭环。我们可以看到在 iOS 上平均初始 90% 渲染要 280 毫秒,Android 上要 440 毫秒。在安卓上,我们使用通常用于共享元素转换的 postponeEnterTransition API 来实现延迟显示界面直到渲染完成。在 iOS 上我们遇到了从 React Native 快速设置导航栏配置不够快速的问题。因此,我们对所有的 React Native 界面过渡加上了50毫秒的仿真延迟,以防止配置加载后导航栏出现闪烁。

App 的大小

React Native 对 app 大小也有这不可忽视的影响。在 Android 上,React Native 的总大小(Java + JS + 原生库,比如Yoga + Javascript 运行时)达到了 8MB 每个 ABI。在一个 APK 中使用 x86 和 arm(仅 32 位),体积达到了接近 12MB。

64位

因为这个问题,我们仍然不能在 Android 上安装一个 64 位的 APK。

手势

我们避免在涉及到有复杂手势操作的界面上使用 React Native,因为 Android 和 iOS 的触摸子系统完全不一样,整理出一套统一的 API 对于整个 React Native 社区都是一个挑战。但这项工作还在持续进行,react-native-gesture-handler 刚刚发布了1.0版本。

过长的 list

React Native 在这方面取得了一些进展,比如 FlatList 这样的库。但成熟度和灵活性还是远不及 Android 上的 RecyclerView 和 iOS 上的 UICollectionView。由于线程问题,很多限制很难突破。适配数据无法同步访问,会导致视图闪烁,因为在快速滚动的时候进行的是异步渲染。文本也无法做到同步测算,所以 iOS 无法使用预先计算的 cell 高度来做某些优化。

升级 React Native

虽然大多数的 React Native 升级都很微不足道,但还是有一些让人非常痛苦。尤其是 React Native 0.43(2017年4月)至 React Native 0.49(2017年10月)版本几乎无法使用,因为其中使用了 React 16 alpha 和 beta。这是个很严重的问题,因为大多数专为 web 设计的 React 库不支持以前的 React 版本。争论此次升级适当依赖关系的过程,对2017年中其他的 React Native 基础架构工作造成了重大损害。

辅助功能

2017年,我们进行了一次辅助功能的彻底检修,在这其中投入了大量的精力,以保证残障人士可以使用 Airbnb 预定到满足他们使用需要的房源。但是 React Native 的辅助功能 API 有着许多漏洞。为了满足最小可接受程度的辅助功能条,我们必须维护一个自己的用于合并修复的 React Native 分支。对于这些情况,Android 或 iOS 上的一个一行的修复,需要数天时间才能确定如何将其添加到 React Native,然后 cherry pick 它,在 React Native Core 上提交 issue,然后接下来的几周跟踪这个问题。

麻烦的崩溃

我们还不得不处理一些很难解决的奇葩的崩溃。举个栗子,我们最近在这个注释下遇到了这个崩溃,且无法在任何设备上复现,即便是那些跟一直崩溃的设备具有相同硬件和软件的也是如此。

Android 上的 SavedInstanceState 跨进程

Android 会经常去清理后台进程,但给了他们一个把状态同步保存进 bundle 的机会 。但在 React Native 上,所有的状态都在 js 线程中访问,无法同步进行。即便状况不是这样,存储状态的 redux 也与这种方式不兼容,因为它包含的是可序列化数据和不可序列化数据的混合,而且还包括了超出适用于 savedInstanceState 中的数据,这会导致在生产环境中的崩溃。


这是系列博客文章的第二部分,重点讲述了我们使用 React Native 的经验,以及 Airbnb 在移动端接下来要做的事情。

  • 第一弹:在 Airbnb 使用 React Native (译文)
  • 第二弹:技术
  • 第三弹:组建一个跨平台移动端团队 (译文)
  • 第四弹:在 React Native 方面做个决定 (译文)
  • 第五弹:在移动端接下来要做的事情 (翻译中…)

翻译:Rebecca Han
译自 Gabriel Peal(Airbnb 工程师) 发表在 Medium 上的文章。
原文链接: [https://medium.com/airbnb-engineering/react-native-at-airbnb-the-technology-dafd0b43838)

组建一个跨平台移动端团队(译)

作者:Gabriel Peal
翻译:Rebecca Han

用 React Native 适配全世界的移动端

这是 Airbnb 对外输出使用 React Native 的经验以及下一步在移动端做些什么的系列博文第三篇。

React Native 除了技术上数不清的优缺点外,我们还学到了 React Native 之于工程师团队来说有多么重要意义。采用 React Native 比向现有的平台增加新的库或者模式要复杂得多。这样做带来了一些组织上的挑战。与技术上的挑战通常能够有效的解决不同,组织上的挑战很难被发现,修正和恢复。庆幸的是,我们的移动端文化很健康,但考虑到 React Native 时,还是有不少问题需要注意。

React Native 是两极分化的

在我们的经验里,工程师们对于 React Native 的看法大相径庭,从称赞它是将 Android,iOS 和 web 联合在一起的银子弹,到完全反对在他们的团队中使用 React Native 褒贬不一。即便使用了 React Native 后也会发生同样的情况。一些团队有好得难以置信的体验,而另一些则觉得后悔并重新拥抱原生。

根本原因

在使用 React Native 时,有一些难以避免的 bugs, polish(这个翻译无力了。。。)和性能上的问题。然而,还有很多变动的部分:

  1. React Native 本身变动很频繁

  2. 我们同时在做基础设施和功能特性的开发

  3. 工程师们是同事学习 React Native,且它相对于每个人来说都是全新的。

  4. 我们开发中的文档和 debug 指引,与产品在同一时间是不一致的,这会令人很困惑。

所以,经常很难找出问题的根本原因。又是也很难判断这个问题应该归结于哪个团队,或者是否是 React Native 本身固有的问题。

React Native 仍然是原生的

一个通常的错误观念是 React Native 可以让你完全的避免写原生代码。然而这并不是目前的现状。React Native 的原生根基有时还是非常有存在感的。例如,文本的渲染在每个平台上都略有不同,键盘的处理也不一样,在Android 上默认情况下,手机转动后 Activites 会重新创建。高质量的 React Native 开发体验依赖于两个原生领域知识量相对的平衡。这一点,再加上需要在三个平台上有相当知识水平是比较难的,使得始终有用高质量的体验是十分不容易的。

跨平台调试

大多的工程师会相对熟悉一两个平台,一个人在 Android,iOS,React 三个方面都有很高技术水平的情况非常少见的。尽管一个成熟的 React Native 生态中大量的工作已经用 JavaScript 和 React 做好了,有时仍然要深入的原生层面构建或者调试一些东西。这些情况使得工程师在超出其专长外,他们从来没有用过的平台上去做调试的时候陷入困境。当工程师没有办法确定根本问题出在哪里的时候,情况会更糟糕。

招聘

虽说我们大量投入在 React Native 上,我们在移动端的野心和团队组建仍然在同步加速。即便如此,通过社区的传播,许多人开始把 Airbnb 和 React Native 联系起来,甚至不少人相信我们的 app 是百分之百用 React Native 开发的。尽管这远不是事实,但结果却是许多 Android 和 iOS 工程师在应聘 Airbnb 时犹豫。如果你恰好是其中一员,我们依旧在招聘哦

Hybrid Apps 很难

100%原生或者100% React Native 的路径是相对简单直观的。当你在代码库里有混合的使用,就会冒出很多新的问题。如何拆分团队?团队间怎么合作?跨 app 如何共享状态?怎么保证所有的都做了测试?工程师怎么有效的跨平台调试?怎样决定新的功能在哪个平台上使用?在组织间如何招聘和分配资源?这些都是些如果你走这条路就无法规避,但又没什么切实解决办法的问题。

三种开发环境

为了成为高效的 React Native 工程师,拥有用一个稳定且更新到最新的 React Native ,Android,iOS 环境是十分重要的。对于 Airbnb 这样庞大的团队来说,每个平台都需要大量且必要的时间来装载,学习及保持更新。

在原生和 React Native 之间平衡

在很多情况下,问题的最佳解决方案是介于原生和 React Native 之间的。例如,我们导航的实现使用了 Activities 和 ViewControllers,大部分的代码用的还是每个平台原生代码。经常发生的是,你并不能很清楚的知道这段代码是应该用原生写,还是用 React Native 写。自然地,工程师会经常选择他们觉得舒服的平台,这将会导致代码不够理想。

跨平台测试

我们发现由于便捷性和舒适性的考量,工程师会在一个平台上做主要的开发。经常性的,他们会假定,在测试过的平台跑的通的话,那在其他平台也可行。大多数情况下,这是 React Native 很强大的一个证明。然而,事实并不常常是这样的,就会导致在 QA 后期或是生产环境中出现问题。

团队拆分

使用原生工作的团队和使用 React Native 工作的团队都会面临技术和沟通上的挑战。一旦代码库被分成了原生和 React Native 的,代码就碎片化了。共享业务逻辑,模型,状态等,会变得更困难,工程师在整个流中的工作不再具有专业优势。我么一开始认为这种情况只会在一开始出现,但其实这可能需要加强 web 的联合来解决。部分团队已经在共享跨 web 和 移动端的资源和代码,但大多数并没有意识到这么做的好处。

可感知的迭代速度

我们对于 React Native 在质量方面的期望其中之一是提升开发速度。React Native 的特点是项目可以由一个工程师来写,而不是每一个平台都需要一个工程师。从 React Native 工程师的角度看,如果他们写一个功能的时间超过写 Android 或 iOS 的时间50%的话,即便总的用时更短,依然会觉得耗费了更多的时间。

公共资源和文档

Android 和 iOS 有着十年的历史和上百万的工程师贡献学习资源,开源项目和在线帮助。我们利用 CodePath等平台来帮助人们学习 Android 和 iOS。这说明了一个事实,就是我们必须在内部做大部分的基础设施建设,意味着,我们有限的 React Native 资源相对于原生来讲过度的投资在了教育和培训上。


这是 Airbnb 对外输出使用 React Native 的经验以及下一步在移动端做些什么的系列博文第三篇。

  • 第一弹:在 Airbnb 使用 React Native (译文)
  • 第二弹:技术 (译文
  • 第三弹:组建一个跨平台移动端团队 (译文)
  • 第四弹:在 React Native 方面做个决定 (译文)
  • 第五弹:在移动端接下来要做的事情 (翻译中…)

翻译:Rebecca Han
译自 Gabriel Peal(Airbnb 工程师) 发表在 Medium 上的文章。
原文链接: https://medium.com/airbnb-engineering/building-a-cross-platform-mobile-team-3e1837b40a88

Airbnb 的 React Native 经验(译)

作者:Gabriel Peal


2016年,我们在 React Native 上投入非常多。两年后,我们已经准备好跟世界分享经验并聊聊接下来要做的事情。

15295502446088

这是首次在连载的博客中披露我们使用 React Native 的经验和 Airbnb 在移动端方面接下来的动作。

在十年前 Airbnb 诞生时,智能手机还处在发展初期。自那时起,智能手机就变成了不可或缺的工具,引领着我们每天的生活,尤其是越来越多的人开始在世界各地旅行。作为一个可以让数百万人拥有新的出行模式的社区,拥有一个世界级的 APP 显得尤为重要。当离家很远时,移动设备就成为了主要的,或者说是唯一的交流形式。

从2008年最初的三名旅客落脚在 Rausch Street 到现在,移动端的使用从0增长到每年上百万的预定。我们的 apps 能让房主在外出的时候也能够管理登记列表,同时也能为旅客提供灵感,让他们在指尖上就能发现新的地方和体验。

为了跟上移动使用加速的步伐,我们的团队发展到了超过100名移动工程师,来支持新的体验以及提升现有的体验。

将赌注压在 React Native 上

我们在持续评估新的技术,用来为房主和旅客提升使用 Airbnb 的体验,迅速行动,以及保持良好的开发者体验。2016年,React Native 便是其中一个。当时,我们已经意识到移动端对业务发展来说有多重要,但又没有足够的移动端工程师来实现业务目标。因此,我们便开始探索可替代的方案。Web 端站点是基于 React 构建的,且 React 在 Airbnb 内部一直是一个高效又普遍受欢迎的 web 框架。正因如此,我们认为 React Native 能够成为一个机会,使得移动开发可以面向更多工程师开放,以及可以利用其跨平台特性快速的发布代码。

我们开始在 React Native 上边投入时,就知道是存在风险的。这意味着在代码库中添加一个全新的,快速发展的,未经过验证的平台,这个平台会将项目分解,而不是统一起来。同时我们也清楚,如果要在 React Native 上投入,那就希望把它做好。我们对 React Native 的期望是:

  1. 能够让我们作为组织加快步伐
  2. 能保证原生应用那样的质量标准
  3. 移动端只写一套业务代码,而不是两套
  4. 提升开发体验

我们的经验

在过去的两年间,我们已经在 apps 中构建了非常强大的集成,以支持复杂的原生特性,如共享元素过渡、视差和地理围栏,以及连接到我们现有的本地基础设施,如网络、实验和国际化。

Airbnb 已经推出了一系列使用 React Native 开发的产品。使用 React Native,我们推出了 “Experiences” 这款产品,这是 Airbnb 的新业务,还有从评论到礼品卡的一系列功能。这里边很多的功能都是在没有足够的 native 工程师来实现的情况下构建的。

不同的团队对于 React Native 都有着广泛的经验。React Native 被认为是不可思议的工具,同时又会给人带来技术和组织上的挑战。在这个系列的文章中,我们会详细介绍在使用过程中的经验,和接下来要做的事情。

在第二部分中,列举了 React Native 技术中哪些是有用的,哪些是无用的。(译文

在第三部分中, 列举了跟组建跨平台移动团队相关的组织方面的挑战。(译文

第四部分中,强调了现在我们对于 React Native 的立场,以及它未来在 Airbnb 中的角色。(译文)

第五部分,介绍了我们用从 React Native 中学到的知识,去使原生变得更好。


翻译:Rebecca Han
译自 Gabriel Peal(Airbnb 工程师) 发表在 Medium 上的文章。
原文链接: https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c

Alfred 平台的知乎搜索 workflow

听说 Mac 平台上流行用 Alfred 代替 spotlight, 我平常的使用是结合 Dash 居多, 再就是各种 workflow 了.

网络上基于 JavaScript 开发的 workflow 不多, 作为前端, js 是主力, 所以写了这个知乎搜索的 workflow.

项目地址: github.com/RebeccaHanjw/zhihu_search_alfred_workflow

下载地址

Basics

开发基于 JavaScript, node, cheerio, 知乎搜索
使用平台: Alfred

使用: 下载 .alfredworkflow 文件, 双击运行
由于使用 js 开发, 需要 node 环境

response 的解析依赖于开源的 cheerio
知乎没有开放的 api, 所以 workflow 的结果来源于未登录也能使用的 /search, 其结果是未经过解析的页面文件, 使用开源项目 cheerio 进行解析.

功能

使用 Alfred 基于关键词快捷搜索知乎平台上的问题及回答

触发 keyword: zh

列表 title 为每一条结果的 title

列表 subtitle 为每一条结果的 author&summary 拼接

预览

zhihu_search1
zhihu_search2

并列 inline-block 元素间隙问题

想起来记录下这个问题是因为我这两天刚好写了个简单的翻页, 于是乎我写了这样一段代码:

1
2
3
4
5
6
7
8
9
10
11
<div class="pages">
<span>prev</span>
<span>1</span>
<span>2</span>
<span>3</span>
<span>next</span>
</div>

.pages span{
display: inline-block;
}

control+s过后就是下面这个:
inlineblock1
似乎并没有什么不对…
然后我给页码加了边框, 非常普通的实线黑边框
下面, control~~~s!
inlineblock2
奇怪的间隙出现了, 从开发者工具下可以看出, 这个间隙不属于内外边距等样式.
如果你的项目有设计师扔过来的设计图, 或者你自身是个要求高的人, 那是万万不能容忍这个奇怪的间隙的.
Let’s make a small change.

1
2
3
4
5
6
7
<div class="pages">
<span>prev</span><span>1</span><span>2</span><span>3</span><span>next</span>
</div>

.pages span{
display: inline-block;
}

inlineblock3
间隙没有了, 唯一的改变就是所有 inline-block 的元素中的换行去掉了, 也就是连起来写.
经过测试, 不论是 inline 元素还是 block 元素, 设置 inline-block 之后都会出现这个问题. ( 这里不包括 IE6,IE7等几个小婊砸🙄, 他们的问题很复杂, 被转换元素本来的display 值对间隙有影响 )

那么间隙是来自于换行了, 其实这锅应该是空白字符压缩(white space collapse)来背, 历史遗留问题, 暂且不表.

诚然上面这种”连起来”的写法能够解决间隙问题, 但是对于有代码洁癖的程序员们来说, 是不可接受的. 那么就要寻求些其他解决方法.

####干掉闭合标签
意思就是下面这样

1
2
3
4
5
6
7
<div class="pages">
<span>prev
<span>1
<span>2
<span>3
<span>next</span>
</div>

我觉得这也是个挑战代码洁癖的方式, 且在我自己的试验中并没有成功, i really don’t know why (无辜脸…

font-size = 0

这是给父元素的, 本仙女用这个开心的解决了间隙问题.
原理其实就是将那个换行的那个空白字符的字号设为了0

letter-spacing 或 word-spacing

1
2
3
4
5
6
.page{
letter-spacing: -6px;
}
.pages span{
letter-spacing: 0px;
}
1
2
3
4
5
6
.page{
word-spacing: -6px;
}
.pages span{
word-spacing: 0px;
}

此方法亲测有效, 但是要注意兼容问题 ( 比如 IE7 那个小婊砸 😷, 她会有残留)

负 margin

1
2
3
.pages span{
margin-right: -6px;
}

这个方法显而易见, 缺点是在不同的字体字号等等换将下, 这个负 margin 的值并不固定.


最后贴一个网上翻来的大招

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.page{
font-size:0;
[;font-size:12px;];
*font-size:0;
font-family:arial;
[;letter-spacing:-3px;];
*letter-spacing:normal;
*word-spacing:-1px;
}
.page span{
display:inline-block;
*display:inline;
*zoom:1;
font-size:14px;
letter-spacing:normal;
word-spacing:normal;
}

考虑到了 IE, 考虑到了 chrome 及 safari 的较低版本 ( 没错, 我说的就是那个 12px 最小字号的问题 )等不同情况. 我自己没有测这么全过, 先行膜拜🙇‍♀️

总之, 本仙女手里的间隙问题解决了~

微信小程序Demo--仿知乎

wechat

从微信小程序开始内测到现在, 已经一个月过去了,终于把我自己的微信小程序 demo 墨迹完成了. 真的是墨迹完的, 连我自己都佩服自己的拖延症了(懒癌少女已弃疗ヾ(´A`)ノ゚), 总之算是基本完成了(明明有很多组件啊 API 啊根本都没用好嘛 →_→) 以及从来不写 blog 的我也出来码字啦 (ノ◕ヮ◕)ノ:・゚✧

-

之前有很长一段时间我算是知乎重度依赖, 所以这次 demo 的模仿对象选择的是知乎(但是写到一半发现我这个决定坑了, 这是后话).
demo 的界面设计以及交互设计均来自于知乎 Android 版本

已经全面对非邀请内测用户开放, 且在持续更新
(我码代码的过程中就更新了两版, 所以开发时 IDE 版本不唯一)

不过其实忍受了半个小时微信的开发者工具之后, 我就改在 webstorm 中编辑了, 微信工具成了运行预览的工具, 不过听说IDE 中预览的效果, 也不能保证与真机一样哦~

1. 基础文件

app.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
"pages":[
"pages/index/index",
"pages/discovery/discovery",
"pages/notify/notify",
"pages/chat/chat",
"pages/more/more",
"pages/answer/answer",
"pages/question/question"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#0068C4",
"navigationBarTitleText": "知乎",
"navigationBarTextStyle":"white",
"enablePullDownRefresh":true
},
"tabBar": {
"color": "#626567",
"selectedColor": "#2A8CE5",
"backgroundColor": "#FBFBFB",
"borderStyle": "white",
"list": [{
"pagePath": "pages/index/index",
"text": "",
"iconPath": "images/index.png",
"selectedIconPath": "images/index_focus.png"
}, {
"pagePath": "pages/discovery/discovery",
"text": "",
"iconPath": "images/discovery.png",
"selectedIconPath": "images/discovery_focus.png"
}, {
"pagePath": "pages/notify/notify",
"text": "",
"iconPath": "images/ring.png",
"selectedIconPath": "images/ring_focus.png"
}, {
"pagePath": "pages/chat/chat",
"text": "",
"iconPath": "images/chat.png",
"selectedIconPath": "images/chat_focus.png"
}, {
"pagePath": "pages/more/more",
"text": "",
"iconPath": "images/burger.png",
"selectedIconPath": "images/burger_focus.png"
}]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": true
}

app.json文件中是对整个小程序的全局配置, 主要用到的字段有pages, window, tabBar, networkTimeout.

  • pages 字段: 所有小程序的页面都要在该字段中注册, 该字段数组中的第一个page 默认为小程序首页(设置tab 除外), 没有在 pages 字段注册过的页面貌似不能够进行有效的编译(之前版本的编辑器可以,只是会影响配置文件等的生效, 编辑器更新后会报未注册的错误).

  • window 字段: 大多是关于小程序顶部 navigationbar 的一些设置

  • tabBar字段: 如果你需要首页面底部带tabbar的样式, 那么就在 tabBar 字段中设置每个 tab 对应的页面, 按顺序对应左至右, 包括路径, tab 文字, tab图标和选中状态图标.

  • netwoTimeout: 设置网络超时时间.

  • debug: 开启 debug 模式.


app.wxss 文件中为全局样式, 也就是说这个文件中的样式在所有的 page 中均可使用, 若其他页面文件的 wxss 中定义了与该样式文件中相同的属性, 则该文件中的样式被覆盖, 规则与 css 优先规则大致相通.


app.js: 调用 login接口, 回调, 周期函数, 本地存储等等逻辑代码.

2. 页面文件:

struct

页面文件由四部分组成
例如我们有一个首页叫做 index, 则需要在 pages 文件夹下创建文件名相通的三个必要文件:

  • index.wxml
  • index.wxss
  • index.js

另外 index.json文件为可选, 功能与 app.json 相同, 为该页面的配置文件, 但定义功能有限.

3. UI

跟平时开发一样, 最开始当然是码 UI

除了需要依照微信的一些新的标签和样式规则, 其他与平时码 UI 并没有太大的不同

需要强调的是, flex 布局在微信小程序中 hin~~~~好用

不过, 同时作为女生和程序员, 不挑刺可就不是我了, 所以下面列举了一些我遇上的坑, 其中有些也许不正确(多多包涵啦(//∇//)), 有些也许已在 IDE 更新中修正

坑们:
  1. 有一些 css 样式在微信 IDE 中不支持, 例如 font-weight, letter-spacing(及类似调整字间距的样式)等
  2. 不支持嵌套, 两层嵌套的结构下, 内层中的内容会连续显示两次
    (在 IDE 后续更新中已修正)
  3. 在结构上并列的话, 显示上会重合, 感受上类似级别高于, 防止内容相叠, 必须使用相并列. 所以并不能像某些地方说的, 把当做
    去使用!
  4. 元素之前有垂直相邻 margin 的时候(符合 margin 折叠规则), 在微信小程序中会double 显示, 即两个元素的 margin 均摊开, 不遵循 margin 折叠规则.
  5. 标签 hidden 属性无效 ( v0.10.101400 中已修正 )
  6. 部分情况下, 平级标签 A 与 B 并列, 当 B 通过某些调整向 A 元素位置相叠的时候, 微信 IDE 解析出的效果是 A 的内容和背景色会覆盖 B 元素与之重叠的部分. ( 普通浏览器解析应该是 B 覆盖 A ).
  7. 如果用模板+列表渲染的方式来渲染数据的话, 在模板中使用列表渲染的{ {item}}是无效的, 无法被正确识别, 所以列表渲染的时候要把复用的部分写在列表渲染的代码块内 ( 属于数据渲染部分, 后面会提到 )

( 待续… … )

后面将对于一些我 demo 中写到用到的部分进行说明

列表式的数据渲染

首页

index

类似于首页以及发现页这种标准列表式的数据展现方式, 微信提供了很好的方案—列表渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<block wx:for="{ {feed}}" wx:for-index="idx" wx:for-item="item" data-idx="{ {idx}}">
<view class="feed-item">
<view class="feed-source">
<a class="">
<view class="avatar">
<image src="{ {item.feed_source_img}}"></image>
</view>
<text>{ {item.feed_source_name}}{ {item.feed_source_txt}}</text>
</a>
<image class="item-more" mode="aspectFit" src="../../images/more.png"></image>
</view>
<view class="feed-content">
<view class="question" qid="{ {question_id}}" bindtap="bindQueTap">
<a class="question-link">
<text>{ {item.question}}</text>
</a>
</view>
<view class="answer-body">
<view bindtap="bindItemTap">
<text class="answer-txt" aid="{ {answer_id}}">{ {item.answer_ctnt}}</text>
</view>
<view class="answer-actions" bindtap="bindItemTap">
<view class="like dot">
<a>{ {item.good_num}} 赞同 </a>
</view>
<view class="comments dot">
<a>{ {item.comment_num}} 评论 </a>
</view>
<view class="follow-it">
<a>关注问题</a>
</view>
</view>
</view>
</view>
</view>
</block>

可以直观的看出, 就是 for 循环来用重复的结构渲染一组数据

  • for=”{ {}}”中的内容是想要循环的一组数据, 最外层为数组结构
  • for-item 指定数组中当前元素的变量名
  • for-index 指定数组中当前元素下标变量名
同样也使用了 for 渲染的还有顶部的发现页和通知页等顶部的自定义 tabbar

顶部 tabbar 实现

discovery1

discovery2

notify

微信只提供了底部 tabbar, 所以顶部的要自己写喽~
顶部 tabbar 的实现在于 for 列表渲染以及 js 配合
wxml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<view class="top-tab flex-wrp flex-tab " >
<view class="toptab flex-item { {currentNavtab==idx ? 'active' : ''}}" wx:for="{ {navTab}}" wx:for-index="idx" wx:for-item="itemName" data-idx="{ {idx}}" bindtap="switchTab">
{ {itemName}}
</view>
</view>
<scroll-view scroll-y="true" class="container discovery withtab" bindscrolltoupper="upper" bindscrolltolower="lower" scroll-into-view="{ {toView}}" scroll-top="{ {scrollTop}}">
<view class="ctnt0" hidden="{ {currentNavtab==0 ? '' : true}}">
</view>
<view class="ctnt1 placehold" hidden="{ {currentNavtab==1 ? '' : true}}">
<text>圆桌</text>
</view>
<view class="ctnt2 placehold" hidden="{ {currentNavtab==2 ? '' : true}}">
<text>热门</text>
</view>
<view class="ctnt3 placehold" hidden="{ {currentNavtab==3 ? '' : true}}">
<text>收藏</text>
</view>
</scroll-view>

js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//discovery.js
Page({
data: {
navTab: ["推荐", "圆桌", "热门", "收藏"],
currentNavtab: "0"
},
onLoad: function () {
console.log('onLoad')
},
switchTab: function(e){
this.setData({
currentNavtab: e.currentTarget.dataset.idx
});
}
});

由于微信不支持任何 dom 和 window 对象, 所以 tabbar的实现依赖于微信提供的视图层的展示逻辑, 以及视图与数据之间的绑定机制.
绑定点击事件, 通过改变一个 data- 属性的值, 来控制元素的类的改变( 从而改变样式等 )

轮播图

1
2
3
4
5
6
7
8
<swiper class="activity" indicator-dots="{ {indicatorDots}}"
autoplay="{ {autoplay}}" interval="{ {interval}}" duration="{ {duration}}">
<block wx:for="{ {imgUrls}}">
<swiper-item>
<image src="{ {item}}" class="slide-image" width="355" height="155"/>
</swiper-item>
</block>
</swiper>
1
2
3
4
5
6
7
8
9
10
11
imgUrls: [
'../../images/24213.jpg',
'../../images/24280.jpg',
'../../images/1444983318907-_DSC1826.jpg'
],
indicatorDots: false,
autoplay: true,
interval: 5000,
duration: 1000,
feed: [],
feed_length: 0

轮播图的实现使用的是微信提供的 swiper 组件, 该组件贴心的提供了各种属性选择, 常用的包括autoplay, interval 时间, duration等

中包含的是所有轮播的图片, 为了方便修改图片数据, 同样采用 for 渲染绑定 data 的方式

下拉刷新, 上拉加载, 以及数据请求

刷新及继续加载的动作, 依靠的是标签, 及配套的upper 和 lower 事件

标签的属性提供了 bindscrolltoupper 和 bindscrolltolower来绑定滚动到顶部及底部所触发的事件, 同时upper-threshold 和 lower-threshold 能够调整触发时距边界的距离

除上述之外, 还提供横向滚动, 滚动触发事件, 及设置滚动条位置等…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<scroll-view scroll-y="true" class="container" bindscrolltoupper="upper" upper-threshold="10" lower-threshold="5" bindscrolltolower="lower"  scroll-into-view="{ {toView}}" scroll-top="{ {scrollTop}}">
<block wx:for="{ {feed}}" wx:for-index="idx" wx:for-item="item" data-idx="{ {idx}}">
<view class="feed-item">
<view class="feed-source">
<a class="">
<view class="avatar">
<image src="{ {item.feed_source_img}}"></image>
</view>
<text>{ {item.feed_source_name}}{ {item.feed_source_txt}}</text>
</a>
<image class="item-more" mode="aspectFit" src="../../images/more.png"></image>
</view>
<view class="feed-content">
<view class="question" qid="{ {question_id}}" bindtap="bindQueTap">
<a class="question-link">
<text>{ {item.question}}</text>
</a>
</view>
<view class="answer-body">
<view bindtap="bindItemTap">
<text class="answer-txt" aid="{ {answer_id}}">{ {item.answer_ctnt}}</text>
</view>
<view class="answer-actions" bindtap="bindItemTap">
<view class="like dot">
<a>{ {item.good_num}} 赞同 </a>
</view>
<view class="comments dot">
<a>{ {item.comment_num}} 评论 </a>
</view>
<view class="follow-it">
<a>关注问题</a>
</view>
</view>
</view>
</view>
</view>
</block>
</scroll-view>

滚动至顶或至底时, 触发的加载数据的事件, 本应该调用微信提供的网络请求 API 来获取数据. 但是比较坑的是, 我在选择写仿知乎 demo 的时候没有注意到知乎不提供开放 API, 而微信的 API 不支持直接对.json 文件进行本地请求, 无奈之下, 选择在 js 文件中伪造一段数据, module.exports抛出, 来 fake 数据请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
upper: function () {
wx.showNavigationBarLoading()
this.refresh();
console.log("upper");
setTimeout(function(){wx.hideNavigationBarLoading();wx.stopPullDownRefresh();}, 2000);
},
lower: function (e) {
wx.showNavigationBarLoading();
var that = this;
setTimeout(function(){wx.hideNavigationBarLoading();that.nextLoad();}, 1000);
console.log("lower")
},
//scroll: function (e) {
// console.log("scroll")
//},

//网络请求数据, 实现刷新
refresh0: function(){
var index_api = '';
util.getData(index_api)
.then(function(data){
//this.setData({
//
//});
console.log(data);
});
},
//使用本地 fake 数据实现刷新效果
refresh: function(){
var feed = util.getDiscovery();
console.log("loaddata");
var feed_data = feed.data;
this.setData({
feed:feed_data,
feed_length: feed_data.length
});
},
//使用本地 fake 数据实现继续加载效果
nextLoad: function(){
var next = util.discoveryNext();
console.log("continueload");
var next_data = next.data;
this.setData({
feed: this.data.feed.concat(next_data),
feed_length: this.data.feed_length + next_data.length
});
}

由于是 fake 的数据, 所以这个 demo 并没有做真实的带参跳转, 查询等功能

加载数据的同时, 使用微信提供的加载动画wx.showNavigationBarLoading();

loading

其他

  • 绑定点击事件, 进行页面的跳转wx.navigateTo
  • 部分模块化
  • input, image 组件等

后续

其实还有大量的组件和 API 还没有用过, 这个 demo 也许后续还会有更新呦, 这取决于懒癌少女的病情严重程度了

项目地址

传送门: https://github.com/RebeccaHanjw/weapp-wechat-zhihu

也许算是些感受?

其实作为一个小前端, 由于工作中的原因, 使用 MVVM 其实非常少的, 不过写了这个微信小程序 demo 之后, 更加把这方便的思维理顺了. 当然, 写完之后回头看, 还是有超多的不足, 明明好些地方能再换一种写法的. 不过毕竟是我第一次尝试用新鲜热乎的东西写小 demo, 也是第一次尝试写教程…或者算是记录? whatever~~

还有, 非项目的配图均来自网络哦~~

Anyway~ 希望除了写代码之外, 还能在码文字的道路上也多走走吧, 毕竟我是要做代码小仙女的人呀(≧∀≦)ゞ

322675