前端译趣

我们如何构建RN应用

2018-05-18  本文已影响28人  linc2046

引言

housing.com/apps

去年我们发布了PWA网站, 用于在慢网速和高延迟的网络环境提升用户体验。

这是我们追求高效产品的第一步。我们收到很多来自社区和用户的积极反馈,打算提升我们移动产品的体验。

理想的移动产品应该是移动web的扩展,而不是替代。

挑战

目标

为了克服这些挑战,我们决定尝试使用新兴的现代JS技术栈构建跨平台的原生应用。我们根据以下目标实现了应用。

技术栈

由于是纯JS实现方案,和基于redux的状态管理方案搭配良好。但是,我们也在调研其他的原生和混合导航方案。

JS生态系统仍在探索异步状态管理的最佳方案,但最后肯定不只是解决各自的问题。我们使用redux-observable, 因为它可以很好隔离副作用,

并且使用强大的Rxjs操作符处理异步。这个方案也允许我们通过独立方式测试副作用处理代码

由于reducers中频繁变化,导致我们面临很多恶心的问题,并且在之前的平台上查找BUG很困难。

为了最终缓解这个问题,我们决定在整个应用中使用不可变数据。

我们通过自定义reducer 工厂在不可变数据和纯JS数据结构之间进行转换。

我们一致采取函数式和声名式的编程范式,尽可能通纯函数处理大部分的业务逻辑。

基于这中考虑,Ramda成为我们不可替代的选项。

和web应用不同,原生应用自带离线模式和持久状态支持。

这个库和redux-persist-migrate配合,在引入后端异步存储层后完美解决web应用的问题。

工具链

使用工具链

包括一般的工具, yarn, prettier, eslinthusky, 还有跨平台统一的样式指南

样式指南

我们也使用了下面的工具:

为开发独立原生组件提供良好支持。我们可以一对一根据设计指南直接编写UI组件。

我们正考虑内部部署,让我们的设计师能够直接访问查看UI组件。

这是RN应用亮点之一。 我们使用codepush来默默向用户发布线上更新,它能够完全控制版本和更新进度。

用来管理不同的环境(预发、开发、生产),fastlane可以轻而易举的实现自动化构建。

我们使用内部Jenkins上暴露了参数化构建面板,上面管理一切任务,如:应用秘钥、代码签名、Test Flight

Crashlytics Beta上传,注册内部测试构建设备、通过codepush发布在线更新等等。

搭建组合提供了很棒的测试平台。由于需要为原生模块编写mock脚本,Jest在设置RN方面显得有点笨重,但这些都挺值得。

后面sentry.io的人实现对RN应用的头等支持。

新的SDK丰富了不同设备特定数据的错误报告,提供涵盖原生和js堆栈的完整错误报告。

在不降低性能和质量的情况下,超过90%的应用源码都用JS编写。

学习知识点

RN是一个相对年轻的平台, 社区一直致力于最佳实践和正确应用方式。

官方文档仍然是我们遇到的最好学习资源。下面是我们一直在学的知识:

当需要处理性能问题时,IM将会有最佳伙伴。由于JS的单线程特性,社区已经实现最大努力將资源消耗部分转移到原生线程执行。

如果你需要在JS中处理资源消耗处理,而不影响动画、过渡和用户交互性能。

IM提供一项调度API能够在动画、过渡和用户交互完成之后延迟执行耗时处理。

这个是参考web实现相同功能的API。 一个特定场景是安卓设备的涟漪效果。一般的实现是使用TouchableNativeFeedback作为onPress回调,但并不总是有用。

有时你可能看不到涟漪效果。如果你把onPress处理函数放到requestAnimationFrame回调内,你会看到动画完美呈现。

RN通过桥实现JS和原生平台通信。由于桥的持续通信,如果处理不当,会相反影响应用性能。

消息队列的spy方法,正如名称所起的,可以让你监听桥的通信细节,帮助理解通信内容和提升性能。

MessageQueue.spy(true)

从官方文档的解释,setNativeProps等同于直接向DOM设置属性。有时你想处理底层原生视图简化react渲染循环。

由于不是很成熟,我们也只在特定地方使用。建议你不要用或小心使用。

一开始,我们的代码仓库采用相对简单组织方式。我们从状态视图中分离处理纯UI组件。

我们发现很多分散的副作用生成代码让代码性能和测试方面形成瓶颈。

我们使用的redux-observable在消除这些障碍上有点用户。可以看下面的例子:

export default function localitySelect(action$, store, { ajax }) {
  return action$
    .ofType('LOCALITY_AUTOCOMPLETE')
    .debounceTime(150)
    .distinctUntilChanged()
    .switchMap(({ payload: { text, cursor } }) => {
      return ajax
        .getJSON(
          `${api.searchSuggest}&cursor=${cursor}&string=${text}`
        )
        .retry(3)
        .map(({ response }) => ({
          type: 'LOCALITY_SUGGEST',
          payload: { data: response }
        }))
        .catch(error =>
          Observable.of({
            type: 'LOCALITY_SUGGEST',
            payload: { error },
            error: true
          })
        )
    })
}

我们把所有副作用代码包装在一个函数中,而不是放在丑陋的生命周期钩子函数。

另外我们在函数内部注入ajax,这里可以在测试环境中被网络请求模拟代码替代。

由于整个应用状态包括导航都由redux维持, redux中间件成为响应action执行代码不可或缺的部分。

我们在应用中把统计(屏幕检测)、日志、错误上报、修改设备状态和内存管理都代理到中间件里。

中间件可以独立单独界面运行,保持精简界面逻辑。

下面是iOS上根据当前屏幕切换黑白状态条的例子:

const statusBarMiddleware = ({ getState }) => next => (action) => {
  if (!Object.values(NavigationActions).includes(action.type)) {
    return next(action)
  }
  const currentScreen = getCurrentRouteName(getState().rootNavigation)
  const result = next(action)
  const nextScreen = getCurrentRouteName(getState().rootNavigation)
  if (nextScreen !== currentScreen && Platform.OS === 'ios') {
    setStyleForRoute(nextScreen)
  }
  return result
}

构建流程

构建流程

官方文档提供API和平台的完整介绍。最终,你需要部署你的应用,还有维护多个测试和预发环境,统一集成不同的证书,生成发布说明和通知相关人员(产品经理、测试和设计

师)。我们尝试了很多方法后,选择Fastlane来自动化整个流程。

下面是用来iOS平台删减的beta发布流程:

desc "Submit a new Beta Build to Crashlytics"
  lane :beta do |options|
    automatic_code_signing(
      path: "housing.xcodeproj",
      use_automatic_signing: true
    )
    register_devices(devices_file: "./devices.txt")
    match(
      type: "development",
      force_for_new_devices: true
    )
    humanable_build_number(update: true)
    gym(
      scheme: "housing",
      clean: true
    )
    crashlytics(
      api_token: "XXXXXXXX",
      build_secret: "XXXXXXXX",
      crashlytics_path: "./Pods/Crashlytics",
      emails: user_email,
      groups: "coders,qa",
      notes: options && options[:notes] ? options[:notes]
        : "Branch #{git_branch} built by #{user_email}\n#{changelog_from_git_commits(
          commits_count: sh("git cherry beta | wc -l").to_i,
          date_format: "short",
          merge_commit_filtering: "exclude_merges"
        )}"
    )
    release(
      bundle_identifier: "XXXXXXX",
      sentry_organisation: "housing",
      sentry_app_name: "housing-app-staging",
      deployment_name: "Staging",
      target_version: "1.0"
    )
    slack(
      slack_url: "https://hooks.slack.com/services/XXXXXXXXXX",
      payload: {
        "Build Number" => humanable_build_number,
        "Built By" => user_email
      }
    )
    add_git_tag(
      grouping: "ios",
      prefix: "v",
      build_number: humanable_build_number
    )
  end

上面的代码处理代码签名、注册测试设备、递增构建版本号、应用构建、上传至Crashlytics Beta、生成发布说明、

根据codepush发布版本并上传sourcemap到sentry中、周知slack频道,最后在github中添加发布标签。

理论上你可以实现任何构建。上面是整个应用代码中的一部分。

由于CI会在构建之前拉取仓库最新代码,无需暂停CI就可以十分容易地修改构建流程。

专业建议

里面还有React Inspector 和 Redux DevTools

译者注

上一篇下一篇

猜你喜欢

热点阅读