Airbnb放弃React-native博客译文(五)

原文链接: https://medium.com/airbnb-engineering/whats-next-for-mobile-at-airbnb-5e71618576ab


What’s Next for Mobile at Airbnb

Airbnb在移动领域的下一步

This is the fifth in a series of blog posts in which we outline our experience with React Native and what is next for mobile at Airbnb.

这是一系列博客文章中的第五篇,其中我们概述了我们在React Native方面的经验以及Airbnb的移动应用程序。

Exciting Times Ahead

Even while experimenting with React Native, we continued to accelerate our efforts on native as well. Today, we have a number of exciting projects in production or in the pipeline. Some of these projects were inspired by the best parts and learnings from our experience with React Native.

激动人心的时刻

即使在尝试使用React Native的同时,我们也继续加快了在原生的工作。 今天,我们有许多令人兴奋的项目正在生产或正在筹备中。 其中一些项目的灵感来自于我们使用React Native的最佳部分和经验。

Server-Driven Rendering

Even though we’re not using React Native, we still see the value in writing product code once. We still heavily rely on our universal design language system (DLS) and many screens look nearly identical on Android and iOS.
Several teams have experimented with and started to unify around powerful server-driven rendering frameworks. With these frameworks, the server sends data to the device describing the components to render, the screen configuration, and the actions that can occur. Each mobile platform then interprets this data and renders native screens or even entire flows using DLS components.
Server-driven rendering at scale comes with its own set of challenges. Here is a handful we’re solving:

  • Safely updating our component definitions while maintaining backward compatibility.
  • Sharing type definitions for our components across platforms.
  • Responding to events at runtime like button taps or user input.
  • Transitioning between multiple JSON-driven screens while preserving internal state.
  • Rendering entirely custom components that don’t have existing implementations at build-time. We’re experimenting with the Lona format for this.
    Server-driven rendering frameworks have already provided huge value by allowing us to experiment with and update functionality instantly over-the-air.

服务器驱动的渲染

尽管我们没有使用React Native,但我们仍然可以在编写产品代码时看到其价值。我们仍然非常依赖我们的通用设计语言系统(DLS),并且在Android和iOS上许多屏幕看起来几乎完全相同。

几个团队已经尝试并开始统一强大的服务器驱动的渲染框架。使用这些框架,服务器将数据发送到描述要呈现的组件的设备,屏幕配置以及可能发生的操作。然后每个移动平台解释这些数据并使用DLS组件呈现本地屏幕或甚至整个流程。

大规模服务器驱动的渲染带来了自己的一系列挑战。这里有一小部分我们正在解决:

在保持向后兼容性的同时安全地更新组件定义。
跨平台共享我们组件的类型定义。
响应按钮点击或用户输入等运行时事件。
在保留内部状态的同时在多个JSON驱动的屏幕之间转换。
在构建时完全渲染没有现有实现的自定义组件。我们正在为此尝试Lona格式。
服务器驱动的渲染框架已经通过允许我们立即实验和更新功能而提供了巨大的价值。

Epoxy Components

In 2016, we open sourced Epoxy for Android. Epoxy is a framework that enables easy heterogeneous RecyclerViews, UICollectionViews, and UITableViews. Today, most new screens use Epoxy. Doing so allows us to break up each screen into isolated components and achieve lazy-rendering. Today, we have Epoxy on Android and iOS.
This is what it looks like on iOS:

BasicRow.epoxyModel(
  content: BasicRow.Content(
    titleText: "Settings",
    subtitleText: "Optional subtitle"),
  style: .standard,
  dataID: "settings",
  selectionHandler: { [weak self] _, _, _ in
    self?.navigate(to: .settings)
  })

On Android, we have leveraged the ability to write DSLs in Kotlin to make implementing components easy to write and type-safe:

basicRow {
 id("settings")
 title(R.string.settings)
 subtitleText(R.string.settings_subtitle)
 onClickListener { navigateTo(SETTINGS) }
}

一个框架组件,就不翻译了

2016年,我们为Android开源Epoxy。 Epoxy是一个框架,可以轻松实现异构RecyclerViews,UICollectionViews和UITableViews。 今天,大多数新屏幕使用Expoxy。 这样做可以让我们将每个屏幕分解为独立的组件并实现懒惰渲染。 今天,我们在Android和iOS上安装了Epoxy。

代码什么的,看原文吧

Epoxy Diffing

In React, you return a list of components from render. The key to React’s performance is that those components are just a data model representation of the actual views/HTML you want to render. The component tree is then diffed and only the changes are dispatched. We built a similar concept for Epoxy. In Epoxy, you declare the models for your entire screen in buildModels. That, paired with the elegant Kotlin DSL makes it conceptually very similar to React and looks like this:

override fun EpoxyController.buildModels() {
  header {
    id("marquee")
    title(R.string.edit_profile)
  }
  inputRow {
    id("first name")
    title(R.string.first_name)
    text(firstName)
    onChange { 
      firstName = it 
      requestModelBuild()
    }
  }
  // Put the rest of your models here...
}

Any time your data changes, you call requestModelBuild() and it will re-render your screen with the optimal RecyclerView calls dispatched.

On iOS, it would look like this:

override func itemModel(forDataID dataID: DemoDataID) -> EpoxyableModel? {
  switch dataID {
  case .header:
    return DocumentMarquee.epoxyModel(
      content: DocumentMarquee.Content(titleText: "Edit Profile"),
      style: .standard,
      dataID: DemoDataID.header)
  case .inputRow:
    return InputRow.epoxyModel(
      content: InputRow.Content(
        titleText: "First name",
        inputText: firstName)
      style: .standard,
      dataID: DemoDataID.inputRow,
      behaviorSetter: { [weak self] view, content, dataID in
        view.textDidChangeBlock = { _, inputText in
          self?.firstName = inputText
          self?.rebuildItemModel(forDataID: .inputRow)
        }
      })
  }
}

A New Android Product Framework (MvRx)

One of the most exciting recent developments is a new Framework we’re developing that we internally call MvRx. MvRx combines the best of Epoxy, Jetpack, RxJava, and Kotlin with many principles from React to make building new screens easier and more seamless than ever before. It is an opinionated yet flexible framework that was developed by taking common development patterns that we observed as well as the best parts of React. It is also thread-safe and nearly everything runs off of the main thread which makes scrolling and animations feel fluid and smooth.

So far, it has worked on a variety of screens and nearly eliminated the need to deal with lifecycles. We are currently trialing it across a range of Android products and are planning on open sourcing it if it continues to be successful. This is the complete code required to create a functional screen that makes a network request:

data class SimpleDemoState(val listing: Async<Listing> = Uninitialized)

class SimpleDemoViewModel(override val initialState: SimpleDemoState) : MvRxViewModel<SimpleDemoState>() {
    init {
        fetchListing()
    }

    private fun fetchListing() {
        // This automatically fires off a request and maps its response to Async<Listing>
        // which is a sealed class and can be: Unitialized, Loading, Success, and Fail.
        // No need for separate success and failure handlers!
        // This request is also lifecycle-aware. It will survive configuration changes and
        // will never be delivered after onStop.
        ListingRequest.forListingId(12345L).execute { copy(listing = it) }
    }
}

class SimpleDemoFragment : MvRxFragment() {
    // This will automatically subscribe to the ViewModel state and rebuild the epoxy models
    // any time anything changes. Similar to how React's render method runs for every change of
    // props or state.
    private val viewModel by fragmentViewModel(SimpleDemoViewModel::class)

    override fun EpoxyController.buildModels() {
        val (state) = withState(viewModel)
        if (state.listing is Loading) {
            loader()
            return
        }
        // These Epoxy models are not the views themself so calling buildModels is cheap. RecyclerView
        // diffing will be automaticaly done and only the models that changed will re-render.
        documentMarquee {
            title(state.listing().name)
        }
        // Put the rest of your Epoxy models here...
    }

    override fun EpoxyController.buildFooter() = fixedActionFooter {
        val (state) = withState(viewModel)
        buttonLoading(state is Loading)
        buttonText(state.listing().price)
        buttonOnClickListener { _ -> }
    }
}

MvRx has simple constructs for handling Fragment args, savedInstanceState persistence across process restarts, TTI tracking, and a number of other features.

We’re also working on a similar framework for iOS that is in early testing.

Expect to hear more about this soon but we’re excited about the progress we’ve made so far.

Iteration Speed

One thing that was immediately obvious when switching from React Native back to native was the iteration speed. Going from a world where you can reliably test your changes in a second or two to one where may have to wait up to 15 minutes was unacceptable. Luckily, we were able to provide some much-needed relief there as well.

We built infrastructure on Android and iOS to enable you to compile only part of the app that includes a launcher and can depend on specific feature modules.

On Android, this uses gradle product flavors. Our gradle modules look like this:

1_KVrbsdwESyfbtKFeh2acXg.png

This new level of indirection enables engineers to build and develop on a thin slice of the app. That paired with IntelliJ module unloading dramatically improves build and IDE performance on a MacBook Pro.

We have built scripts to create a new testing flavor and in the span of just a few months, we have already created over 20. Development builds that use these new flavors are 2.5x faster on average and the percentage of builds that take longer than five minutes is down 15x.

For reference, this is the gradle snippet used to dynamically generate product flavors that have a root dependency module.

Similarly, on iOS, our modules look like this:

1_AVB7em_JCmj-JmjTCkLdQw.png

The same system results in builds that are 3–8x faster.

Conclusion

It is exciting to be at a company that isn’t afraid to try new technologies yet strives to maintain an incredibly high bar for quality, speed, and developer experience. At the end of the day, React Native was an essential tool in shipping features and giving us new ways of thinking about mobile development. If this sounds like a journey you would like to be a part of, let us know!


This is part five in a series of blog posts highlighting our experiences with React Native and what’s next for mobile at Airbnb.


如有侵权,请立刻告知。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容