返回博客主页
← 所有文章

面向 Appcelerator Titanium 开发者的 NativeScript

2017 年 6 月 20 日 — 作者:Josh Jensen

Progress 的 NativeScript 和 Appcelerator 的 Titanium 都是使用 Javascript 构建 **原生移动应用程序** 的强大平台。精通多种平台不仅明智,而且审慎。作为一名开发人员,能够在多个平台和工具集中建立联系,使你作为一个整体变得更有价值。

在这篇文章中,我们将探讨 NativeScript 和 Titanium Alloy 之间的异同,以及如果你习惯于编写 Titanium Alloy 应用程序,如何编写 NativeScript 应用程序。

尝试新事物

NativeScript 和 Titanium Alloy 在结构和设计理念上非常相似。本文面向那些一直使用 Titanium Alloy 并希望首次尝试 NativeScript 或将这些概念应用于更大项目的开发者。

搬迁

如果你从未读过《谁动了我的奶酪》这本书,作者谈论的是如何应对生活和工作中的变化。我将为你节省阅读这本书的时间,其核心思想是变化是好的,最终会导致更好的结果。通常,当我们尝试一些新事物,但又与我们习惯的事物类似时,它可能比尝试一些完全新事物更难。我们的先入为主的观念可能会让我们看不到新的机会和做事方式。

在本文中,让我们从 *“这是 (插入宠物平台/方法) 的做法,而且这是唯一的方式”* 的思维方式转变为好奇和开放的思维方式。

入门

Nativescript 优于许多其他平台,因为它 的核心库是用 Javascript/Typescript 编写的。这使得它极其灵活,并且可以轻松地在现有库的基础上编写自己的库。

在本文中,我们将使用 NativeScript Core 方法。构建 NativeScript 应用程序时,主要有三种思路:NativeScript Core、带有 Typescript 的 NativeScript Core 和带有 Angular 的 NativeScript。

对一些人来说,这似乎支离破碎,但实际上很有道理。我们都去 Five Guys 点汉堡,但有些人喜欢在汉堡上加墨西哥胡椒,有些人则不喜欢。所以,与其将它们视为不同的食物,不如将它们视为相同的汉堡,只是配料不同。

如果我至少没有提到 Angular 及其为应用程序提供的结构,那就失职了。在使用 NativeScript 时,如果你有 Titanium Alloy 的背景,使用 Core 更有意义,但如果你愿意走出舒适区,尝试一下 Angular,你不会失望的。

这就带我们回到了入门。这不是一个入门指南,所以请前往入门指南,当你准备好深入学习时再回来。

如果你还没有阅读过 NativeScript 入门指南,请现在就开始阅读。

假设条件

  1. 我们假设你已经阅读过 NativeScript 的入门指南,或者 {N},正如我们从现在开始将要称呼它那样。
  2. 你对 Titanium Alloy 中使用的概念和方法有基本的了解,或者 Alloy,正如我们将在本文的剩余部分中称呼它那样。
  3. 本文以及下面的评论不是比较,也不是贬低任何一个社区或平台的地方。两者都有优点和缺点。

让我们开始吧。

让 Alloy 开发者体验 “Hello World”

这是一个有点狡猾的标题,因为如果你是一名 Alloy 开发者,并且阅读过 NativeScript 入门指南,那么你已经成功地在 NativeScript 中创建了一个 Hello World 应用程序,而且还做了更多的事情。

文件夹结构

我正在比较一个我编写的名为 MobileJS 的开源项目的文件夹结构。该项目是 NativeScript、React Native 和 Titanium Alloy 之间的代码比较。

Alloy

- assets
- controllers
- lib
- styles
- views
alloy.js
config.json
README
widgets

{N}

- App_Resources
- fonts
- todo
- utils
app.css
app.js
config.js
LICENSE
package.json

你会立即看到一些相似之处和不同之处。当你使用 {N} (NativeScript) 时,与 Alloy 中的 assets 文件夹对应的文件夹是 "App_Resources"。此文件夹将包含你针对平台特定的资源,例如启动图像、图标等。

在 Alloy 中,你的应用程序有两个主要的地方可以从那里启动应用程序。1. alloy.js 和/或 config.json 2. controllers/index.js

我个人不喜欢 Alloy 中的 alloy.js 文件,因为它提供了过于诱人的选项,可能导致全局范围污染和内存泄漏。我知道它为什么放在那里,但就像斯坦·李说的,“能力越大,责任越大”。

需要说明的是,alloy.js 只是一个 commonJS 模块,但是,你可以在一个地方设置某些东西,并且它几乎可以在应用程序中的任何地方自动访问,这太诱人了。

以下是我将你的 alloy.js/config.json 逻辑迁移到 {N} 中新位置的建议。显然,没有 “Alloy” 命名空间,所以这不是一对一的转换。但是,在实践中,我会这样设置你的共享逻辑。

  1. 创建一个名为:lib 的新文件夹
  2. 在文件夹中创建一个名为:shared.jsglobals.jsconfig.js 的新文件
  3. 现在,将你的 “全局”、配置对象或对象放置在 config.js 文件中,例如:module.exports = { colors: { white: '#fff', black: '#000', companyDefaultColor: '#333' } }
  4. 然后,在你的应用程序中稍后只需引入 config 模块即可访问所有配置选项。var config = require('config.js'); var colorINeed = config.colors.companyDefaultColor;

有什么区别?*“难道我不能在每个控制器的顶部包含我的全局文件,然后出现同样的问题吗?”* 你问道。没错,但是,为你的数据流和应用程序逻辑创建一个定义良好的结构可以使你的应用程序更具弹性,更容易维护,并有助于管理内存泄漏。

控制器

与 Alloy 类似,{N} 使用控制器结构,或者许多 {N} 开发人员称为 “代码隐藏” 文件。“代码隐藏” 是一个微软术语,大多数 Alloy 开发人员或 Javascript 开发人员都不熟悉。作为交叉术语,我们将使用 “控制器” 来指代用于与视图交互的代码。

启动引擎

当你首次启动 Alloy 应用程序时,Alloy 会自动查找名为 app/views/index.xml 的文件和名为 app/controllers/index.js 的文件。

这两个文件就是你应用程序的启动点。你可以在这两个文件中编写整个应用程序,但不要这样做,除非你的项目非常简单,只需要几个页面。如我所说,不要这样做。

在我的 Alloy 项目中,我几乎总是将 index.xml 保持在默认状态,即 <Alloy></Alloy>。除非项目非常简单,只需要几个页面。

然后,我使用 app/controllers/index.js 作为应用程序逻辑的启动点。例如

// ALLOY
var application = require('application');
application.start();

在 Alloy 中,这需要在 lib 文件夹中创建一个名为 application.js 的文件,并调用函数 “start”。

{N} 使用与之非常相似的逻辑。该应用程序会查找 app 文件夹中的 app.js

// {N}
var application = require('application');

application.mainModule = 'todo/list';
application.cssFile = './app.css';

application.start();

在 {N} 中,application 是一个核心模块,与我前面示例中的 Alloy 示例不同。在这个 示例 中,我们将 “mainModule” 设置为 todo/list,将应用程序范围的 css 文件设置为 ./app.css

查看文件夹结构,你会看到 todo 文件夹以及 app.css 文件。

app.css 与 Alloy 项目 styles 文件夹中的 app.tss 具有相同的功能。app.css 对整个应用程序应用全局样式。与 Alloy 不同,你可以随意命名 app.css。如果你想将其命名为 legoMovie/wildStyle.css,只需确保在调用 application.start(); 之前正确引用它。

视图

在 Alloy/Titanium 中,视图至关重要,如果你来自 Web 背景,有些人认为它与 html div 几乎是一对一的关系。

你的 Alloy XML 文件可能看起来像这样。

<Alloy>
    <Window id="listWindow">
        <View id="headerWrapper">
            <Label id="backButton" class="fa"></Label>
            <Label id="header">Todos</Label>
        </View>
        <View id="wrapper">
            <View id="formWrapper">
                <Label id="selectAllIcon" class="fa"></Label>
                <TextField id="textInput" />
            </View>
      <View class="hborder" />
      <TableView id="todoTable" />
        </View>
    </Window>
</Alloy>

一个包含许多 ViewWindow。然后,这些视图中将包含 UI 元素。有时,视图仅用于 UI。例如,一个 1 像素的彩色分隔线等等。

在 {N} 中,这是一个与 Alloy 开发者习惯的做法有很大不同的重大转变。

以下是用 {N} 编写的页面的示例。

<Page xmlns="http://www.nativescript.org/tns.xsd" navigatedTo="navigatedTo">
  <DockLayout stretchLastChild="true">
    <StackLayout dock="top" text="fill" id="wrapper">
      <StackLayout>
        <Label text="Todo" id="header" horizontalAlignment="stretch" />
        <Label text="" id="backButton" cssClass="fa" horizontalAlignment="left" visibility="{{ showBack ? 'visible' : 'collapsed' }}" tap="onBackTap" />
      </StackLayout>
      <StackLayout orientation="horizontal" id="formWrapper" cssClass="fa">
        <Label text="" id="selectAllIcon" tap="markAllAsDoneOnTap" />
        <TextField text="{{ textInput }}" id="textInput" hint="What needs to be done?" />
      </StackLayout>
    </StackLayout>
    <ListView items="{{ todoItems }}" id="listView" itemTap="rowOnPress" separatorColor="#fff">
        <ListView.itemTemplate>
            <StackLayout orientation="horizontal" cssClass="todo-row">
                <Label text="{{ isChecked ? '' : '' }}" color="{{ iconColor }}" tap="onPressCheckbox" cssClass="fa checkbox"  />
                <Label text="{{ text }}" />
            </StackLayout>
        </ListView.itemTemplate>
    </ListView>
  </DockLayout>
</Page>

注意布局,它们类似于视图,但有非常具体的布局目的。深入了解 NativeScript 布局 是开始理解使用布局而不是视图时范式转换的好地方。你会注意到,Page 元素代替了 Window。这个页面元素包装了所有其他元素,是基础。然后,正如我们将在稍后讨论的那样,页面元素有一些生命周期管理回调,这些回调非常有用。

一旦你进入布局,它们看起来就很熟悉了,里面包含着标签、文本字段和其他 UI 元素,并按布局排列。

类和 ID

Alloy 和 {N} 在使用类方面非常相似。它们主要,如果不是全部的话,都是为了样式目的。例如,在关联的 TSS 或 CSS 文件中引用类。

但是,Alloy 严重依赖 ID 来在 XML 文档中引用元素。没有 DOM,所以不要混淆术语。在 Alloy 中,你会有一个 Label 在你的 XML 中,它看起来像这样:<Label id="myLabel">Alloy</Label>。要在控制器中引用该标签,你可以调用 $.myLabel。然后,例如,我们可以将文本设置为其他内容 $.myLabel.setText('Alloy!');

{N} 绝对倾向于使用绑定上下文来更改内容或参数。但是,我将向你展示如何在 {N} 中使用元素的 ID 来定位元素,因为这是 Alloy 方法论中不可或缺的一部分。请注意:在 {N} 中,最好使用绑定上下文来设置元素的参数,但如果你想例如在文本输入上调用 focus(),那么使用以下方法是合适的。

让我们在这个示例中使用一个文本字段

<TextField text="{{ textInput }}" id="textInput" hint="What needs to be done?" />

在你的控制器中,你将拥有类似于以下内容的代码

exports.onLoad = function(args) {
    var page = args.object;

    page.getViewById("textInput").focus();
};

请注意,在控制器中,有一个名为 onLoad 的导出函数。该函数与页面属性中指定的函数名称相关联。

<Page xmlns="http://www.nativescript.org/tns.xsd" onLoad="onLoad">

{N} 正在寻找几个这样的函数。我使用最多的两个是 onLoadnavigatedTo。我注意到 onLoad 在页面加载时被调用。但是,navigatedTo 在页面被 (你能猜到吗?) 导航到时被调用。因此,如果页面是导航结构的一部分,这非常有用,因为你可以使用此函数将数据向下传递到被导航到的页面。从父页面传递下来的数据可以在 page.navigationContext 对象中使用。 你可以在这里看到它的用法.

数据绑定

我承认我不太喜欢 Alloy 中的数据绑定。有些人很喜欢。我只是不喜欢。在 Alloy 中,我喜欢对自己的内容和应用程序状态进行非常具体的控制。也就是说,在 Alloy 和 {N} 中提及数据绑定是不公平的,因为它们非常相似,并且功能非常强大。

要了解更多关于数据绑定的信息

1. Alloy 数据绑定
2. {N} 数据绑定

数据绑定示例不在本文讨论范围之内,但我还是想说,在使用 NativeScript 时,你可能会像在 Alloy 中一样,尝试通过 ID 来查找元素。也许你和我一样,没有使用 Alloy 中的数据绑定,因此你也不习惯在 {N} 中尝试它们。我鼓励你尝试在 {N} 中使用数据绑定。还记得之前我提到的“移动奶酪”和尝试新事物吗?这是一个尝试新事物的绝佳时刻。

你会发现,{N} 的数据绑定不仅性能出色,而且一旦你习惯了,就会非常直观。如果需求很大,或许可以写一篇关于 Alloy 开发人员使用数据绑定的后续文章。

原生库

如果你是一名 Alloy 开发人员,你就会知道如何将原生库添加到你的项目中。你可以找到已经转换为 TiModule 的库,自己动手,或者付费让其他人来做。

随着 HyperLoop 的出现,你可以使用 JavaScript 直接访问所有 iOS 和 Android API。类似地,{N} 允许你直接从 JavaScript 引用 iOS 和 Android 原生库。

只需包含该库,然后从你的应用程序逻辑中引用它公开的 API。

查看这篇博文 在你的 NativeScript 应用程序中使用原生库 获取更多信息。

或者查看这个核心概念文档 使用 JavaScript 访问原生 API 获取更多信息。

最后但并非最不重要的一点

正如我在开头所说,“NativeScript 和 Titanium Alloy 在结构和设计概念方面有非常相似的理念”。我希望本文能让你开始探索使用 JavaScript 在 NativeScript 中进行原生移动开发的新领域。

查看 mobileJS.io,并随时下载源代码,体验 {N} 和 Alloy 应用程序。如果你有任何问题,请在下方评论。