天天看點

跨端的另一種思路 | D2分享+文章

作者 | 當軒

點選檢視視訊

大家好,我是來自阿裡巴巴集團 ICBU 互動端技術團隊的當軒,很榮幸能在第十五屆 D2 大會上和大家做一次分享 ,我此次分享的主題是《跨端的另一種思路》。

我在開發端應用的時候,常常懷念 Web 的天然跨平台能力和開發效率。然而在開發 Web 應用時又會苦惱沒有端應用那麼好的性能和體驗。是以就常常在想,有沒有一種技術方案,像 Web 一樣可以便捷的開發、在所有平台都能運作,同時又有很好的性能和體驗。

當時最有名的一句宣傳語我相信大家都有印象 --

Write Once, Run EveryWhere

,一次編寫,到處運作。

跨端的另一種思路 | D2分享+文章

然而理想很飽滿,現實很骨感,不同的跨端方案層出不窮,但是都難以達到我們最終理想中的效果。

跨端的另一種思路 | D2分享+文章

舉例來說:

  • Web 作為天然的跨端方案,在研發效率上非常不錯,然後性能和體驗的優化難度都非常高
  • React-Native 類的方案,性能上要比 Web 好了不少,然後多端一緻性和研發效率則較低
  • Flutter 在 App 上的性能和一緻性不錯,但是在差異比較大的 Web 端性能就非常糟糕了,也無法滿足 Web SEO 等訴求

之是以理想和現實存在這種差距,是因為跨端的方案本身在帶來收益的同時也意味着在某些方面會增加一定的成本。

跨端的另一種思路 | D2分享+文章

例如我們期望用同一套代碼覆寫所有平台的同時,又希望研發的自由度和表達能力不要受到限制,然而這種方案隻能使用不同平台表達力的子集。最典型的場景就是小程式的跨端方案,靜态轉譯的小程式跨端方案往往通過限制開發者的表達能力來實作一套代碼跨多端。

既然現實中的方案在帶來收益的同時同樣帶來成本,我們就不能一味的追求

Write Once, Run EveryWhere

我們的實際場景做出取舍。于是我們就提出另外一種成本更低的思路:邏輯跨端。

跨端的另一種思路 | D2分享+文章

今天以我們 Alibaba.com 的交易場景為例,分為 Android/iOS/M 站/PC 網頁幾個端。

跨端的另一種思路 | D2分享+文章

幾端的 UI 互動并不完全一緻,但具有相同的業務邏輯,同時 APP 端對于性能和體驗上有更高的要求。我們在效率上最直覺的問題在于,一個哪怕非常簡單的業務需求的上線,都需要經過多端開發同學的資源協調和互相依賴,消耗在溝通上的成本非常高。

由于 Android/iOS 端對于性能體驗的要求高,加上這兩端的 UI 和互動基本一緻,我們考慮用 Flutter 來作為主要的技術選型。然而在 Native 和 Web 之間,Flutter 并沒有成熟的跨端方案,前面我們提到過 Flutter for Web 的性能基本是不可用狀态。

通過 JavaScript 容器 + Yoga 做類似于 React-Native 的方案我們也考慮過,然而其帶來的建設成本和抽象代價在這種場景下是否合适是存疑的,因為 PC 和 App 的互動完全不同,通過引入 Yoga 類的方案反而會讓兩端的開發都束手束腳。除此之外,我們也擔心 JavaScript 容器的引入在端上帶來更高的優化成本。

于是我們就提出了另外一種思路:有沒有可能僅針對業務邏輯做跨端共享,這樣我們不需要對容器側進行太大的改造,也不用擔心引入新的優化成本,同時也能做到跨端共享代碼。進而讓前端同學去單獨 hold 業務側的需求成為一個可能的方案。

跨端的另一種思路 | D2分享+文章

那麼對于這樣一種方案我們會有幾個問題:

跨端的另一種思路 | D2分享+文章
  • 采取什麼語言
  • 如何分離邏輯和 UI
  • 差異邏輯怎麼寫

第一個問題是采取什麼語言,其實 Dart 就是一個現成的選項,因為 Dart 從一開始就是為 Web 設計的語言,甚至在 Flutter 出現前曾經一度在 Chrome 中有預設 VM 實作。雖然後來放棄了在浏覽器中的發展,但是 Dart => JavaScript 仍然是一個成熟的技術。

跨端的另一種思路 | D2分享+文章

同時因為我們的前端同學其實對于 Dart 仍然不是那麼的熟悉,另外 Dart 的類型系統在很多時候并不能滿足動态類型的訴求,以至于到處都是 dynamic。例如說基礎的聯合類型:

string | number

這樣的類型都無法直接支援,是以我們也在探索從 TypeScript 轉譯到 Dart 的方案。

跨端的另一種思路 | D2分享+文章

通過 TypeScript 提供的能力,我們可以直接把一份 TS 的代碼從源碼解析到 AST,而後通過周遊 AST 生成對應的 Dart 代碼。同時其中通過

getTypeChecker.getTypeAtLocation

等 API 擷取到 AST 對應的 TS 類型。然後通過把 TS 類型轉換成對應的 Dart 類型。對于不支援的類型降級到

dynamic

,把原有的完整類型資訊輸出到對應的注釋裡。

跨端的另一種思路 | D2分享+文章

第二個問題是如何有效的分離邏輯和 UI,其實我們都知道如果隻是單純的把純函數作為一個獨立子產品給抽離出來并不困難。然而邏輯中必然不僅僅是單純的輸入得到輸出的純子產品,還有很多涉及到元件生命周期、渲染、狀态變化等副作用(side effect)。如果我們需要在 Flutter 和 Web 間複用邏輯,我們就需要定義一份類似的接口。

我們可以先看看 Flutter 和 Web(我們這裡采用的是 React)究竟存在多大差異,這裡是 Flutter 和 React 建構元件的一個簡單對比。其實我們知道,在代碼組織方式上,Flutter 和 React 是非常相似的,事實上他們背後的的狀态更新 => 觸發 Diff => 重新渲染的邏輯也基本一緻。是以說最理想的情況下,我們能直接用同樣的邏輯抽象方式共同來書寫邏輯,這樣可以避免在不同場景下的接口對接方式和心智負擔。

跨端的另一種思路 | D2分享+文章

那麼,有什麼合适的方案可以用于分離邏輯和 UI 呢。其實對于很多前端同學來說,可能都了解 React 16 推出的 React Hooks,我們可以在這裡看到相比 React 15 的 Class API 的一個差別。

跨端的另一種思路 | D2分享+文章

看上去好像除了代碼量變小外似乎問題不大?但其實如果我們把這個元件拆成兩個函數,就能明顯看出差别了。

跨端的另一種思路 | D2分享+文章

沒錯,React Hooks 最大的作用不在于單純的少寫代碼,而是讓我們可以以非常低的成本把邏輯從 UI 或者其他邏輯中抽離出來,并且進行再組合。也就是說其實 Hooks 就是這麼一個現成的邏輯拆分方案,大家也廣泛接受其理念和 API。那麼,有沒有可能讓 Hooks 的邏輯在 Flutter 上也能使用呢?

前面我們提到,Flutter 建構元件的方式以及運作原理都和 React 十分的相似,那麼我們其實隻要了解了 React 中 Hooks 工作的原理,就能在 Flutter 中再實作一遍。Hooks 可以簡單了解為 閉包 + 數組(實際上在 React 中是連結清單)。以

useState

為例,Hooks 和普通函數最大的差異在于其可以在多次

render

調用中保持狀态,實際上就是通過在閉包中保留狀态,同時通過每次重新

render

時重置計數,進而依賴執行順序還原出具體的狀态。

跨端的另一種思路 | D2分享+文章

那麼到了 Flutter 中,我們就可以實作一個

HooksWidget

,在觸發渲染時重置計數,同時把目前元件存儲到閉包中,進而讓

Hooks

能夠根據計數找到對應的狀态,并且知道應該去觸發那個元件的重渲染。

跨端的另一種思路 | D2分享+文章

最後一點就是對于差異化邏輯的書寫,Dart 從 x.x 版本就引入了

condtional import

的能力,讓我們可以根據不同的環境(Flutter 和 Web 引入不同的包)。于是我們可以在不同的包中書寫接口相同,但底層邏輯不同的類,進而實作差異化邏輯。

跨端的另一種思路 | D2分享+文章

同時在 Web 端,我們可以通過 Dart 官方提供的

js

包,輕松的和浏覽器中的 JavaScript 原有能力進行互動。于是我們可以通過這樣的一層膠水層,讓我們在 Flutter 端的邏輯走我們上面寫的

Hooks

能力,而在 Web 端則直接調用

React

的 Hooks。

最後我們能實作的一個效果,就是用同一份邏輯代碼來表達 Flutter 和 Web 端的業務邏輯,并且作為我們非常熟悉的 Hooks,引入到 UI 上并且直接綁定。

跨端的另一種思路 | D2分享+文章
跨端的另一種思路 | D2分享+文章
跨端的另一種思路 | D2分享+文章

同時借助 SourceMap,我們在 Devtools 裡也能進行調試。

跨端的另一種思路 | D2分享+文章

由于我們在渲染上并不強制差異很大的 Web 和 Flutter 保持統一,是以在性能上可以針對不同平台的特性做出優化,比 Flutter for Web 的性能表現更好。

這個 DEMO 僅代表這個場景下的一個性能對比,并不代表所有場景下的方案性能都如上所示,事實上針對不同的方案我們也可以再采取不同的優化措施去改進。之是以放這個 DEMO,主要是為了解釋這種方案在不經過太大投入的情況下,也能達到理想的性能。

跨端的另一種思路 | D2分享+文章

最後我們再做一個大概的總結:

  • Write Once 是一個理想目标,Write Logic Once 在部分場景下能更好的解決我們的問題。
  • 介紹的方案僅适合重邏輯的場景,對于重 UI 表現場景不适用。我們需要根據場景做出對應的選擇取舍。
  • 對于寫一份邏輯,無論是 Dart 還是 TS,仍然存在一定的抽象洩露問題,對于一些複雜問題的排查仍依賴開發者的經驗

🔥第十五屆 D2 前端技術論壇 PPT 集合已放出,馬上擷取

跨端的另一種思路 | D2分享+文章

關注「Alibaba F2E」

回複 「PPT」一鍵擷取大會完整PPT

繼續閱讀