返回博客首页
← 所有文章

头脑风暴:Web/移动端代码共享的替代方案

2020年6月22日 — 作者:Mahmoud Abduljawad

前言

代码共享一直是 NativeScript 的强项,毕竟它只是 JavaScript。这也是一个永远令人着迷和兴奋的话题,因为有许多有效的策略可以与 NativeScript 共享代码。

以下文章由才华横溢的开发人员 Mahmoud Abduljawad 撰写,对这个有趣的话题提供了另一种视角。你可以从以下平台了解到更多关于 Mahmoud 的信息:

文章内容也可以在 dev.to 找到。

Code Sharing

使用 Angular 和 NativeScript 在 Web、Android 和 iOS 上运行单个代码库。

代码共享:挑战

最近使用 Flutter 的经历让我对 Web 和移动端代码共享充满热情。Flutter 团队对此做出了雄心勃勃的承诺,但从我自己的角度来看,这似乎还需要一年的时间。我最终回到了 NativeScript,我已经使用它三年来开发移动应用程序,并开始思考如何在 Web 和移动端实现终极代码共享状态。

共享单个代码库的挑战在于每个平台都拥有不同的机制。在 NativeScript、React Native 或 Xamarin 等混合框架中,很容易发现此类问题,你可能最终需要根据平台添加条件代码。将所有不同的底层结构(从移动 API 到 Web API)统一起来,也是一项艰巨的任务。

头脑风暴:一种替代方案

当我查看一个按照 NativeScript 自身指南 创建的新项目时,它已经是 Web 和移动端代码共享的绝佳路径,但我仍然对分离的模板文件感到困扰。对于那些不熟悉的人来说,NativeScript 使用 MVVM 结构,其中模板与代码隔离。我更喜欢这种方法,而不是 React(或 Flutter),在 React 中,一切都汇聚在一个意大利面条式的代码中(仅代表个人意见)。当我查看移动端的模板文件(一种 XAML 的变体)和 Web 端的模板文件(一个基本的 HTML Angular 模板)时,我产生了一个想法:如果我能使用单一的模板语言来同时处理 Web 和移动端!幸运的是,Angular 一直在结构上帮助我构建概念验证。我开始为以下元素构建组件:

  1. Label:表示文本的 XAML 元素。
  2. Button:表示按钮的 XAML 元素。
  3. Image:表示图像的 XAML 元素。

这项工作非常基础,我只是在以下元素中添加了一些 InputOutput 属性来与 XAML 组件进行交互:

@Component({
    selector: 'Label',
    template: `{{ text }}`,
})
export class XAMLLabel {
    
  @Input('text') text: string = '';
}

@Component({
    selector: 'Button',
    template: `{{ text }}`,
})
export class XAMLButton {
    
  @Input('text') text: string = '';
    
  @Output('tap') tap: EventEmitter<any> = new EventEmitter<any>();

  @HostListener('click', ['$event']) onTap($event) {
      if (this.tap.observers.length > 0) {
          this.tap.next($event);
      }
  }
}

@Component({
    selector: 'Image',
    template: `
    <img [src]="_src" [attr.async]="(loadMode == 'async') ? 'on' : 'off'">
    `,
})
export class XAMLImage {

    _src: string = '';
    
    @Input('src')
    set src(v: string) {
        try {
            if (v[0] == '~') {
                this._src = v.slice(2);
            } else {
                this._src = v;
            }
        } catch (error) {

        }
    }

    
    @Input('loadMode') loadMode: 'async' | 'sync' = 'async';
}

这在 Angular 中非常基础,我只需要添加一些 InputOutput 属性来与 XAML 组件交互。我创建了一个 XAMLModule,它只在 Web 应用程序中导入,而不是在移动端导入,而且令人高兴的是,我有一个展示 Web 和移动端代码共享的概念验证。

旅程继续

取得了小小的成功后,我开始实现更多组件。我添加了 StackLayoutGridLayout,顾名思义,它们是 XAML 中的布局元素。由于我是 Bootstrap 的粉丝,经常使用它,所以我决定在 Bootstrap 框架的基础上构建布局元素,并且确实有效。我不得不引入一些重大更改和替代用法,以便元素在 Web 上的行为与移动端一致。也就是说,这并不是一条特别棘手的道路,但我最终用纯 CSS 网格方法构建了大部分工作。此时,我的概念验证已经开始成形--我的 Pokédex 应用程序正逐渐成为一个在 Web 和移动端都能运行的应用程序。我一直用 Pokédex 应用程序作为概念验证,因为它可以让我摆脱提出想法的痛苦,同时还可以享受再次看到我童年的宠物的乐趣。

最终结果

在页面(或屏幕)级别,我总共编写了 321 行代码,跨越模板和代码。其中 17 行(或 5.2%)是 Web 特定的,12 行(或 3.7%)是移动端特定的!这意味着应用程序页面之间高达 96.3% 的代码共享。这对我来说是一个非常惊人的结果!

除了这篇文章的封面图片,这里还有两个屏幕截图:

Screenshot of macOS screen with Firefox window, Android emulator, and, iOS simulator running NativeScript Pokédex proof of concept app

Screenshot of macOS screen with Firefox window, Android emulator, and, iOS simulator running NativeScript Pokédex proof of concept app

除了跨 Web 和移动端共享代码库之外,我还实现了一些事件处理流程,以及 CSS 网格值的动态生成,这使我能够使用相同的 XAML 模板为 Web 创建响应式布局。

对该流程的思考

我惊讶于自己在两周内就能取得这样的成果。原因是,如果可行,为什么不呢!用 Flutter 为 Web 构建的应用程序的 SEO 评级是 O(1) 的。当我开始使用 Flutter 时,这确实让我担心。这个项目在 Web 上的结果远远好于我在 Flutter 中能达到的任何结果,这让我有了最终的想法,那就是将这个项目作为基线,开始更广泛地尝试构建一个完整的方案,让开发人员能够更轻松地在 Web 和移动端共享代码。

目前,你可以自己查看该项目的成果:

https://mahmoudajawad.github.io/nativescript-pokedex/dist/nativescript-pokedex/

你也可以查看完整的源代码:

github mahmoudajawad/nativescript-pokedex