В то время как вокруг Angular 2 и Ionic Framework 2 ходит большой ажиотаж, в тени мира кроссплатформенной разработки пробуждается новая сила. Хотя эта сила еще не достигла уровня зрелости других фреймворков, потенциал и шансы на успех делают ее достойным кандидатом на то, чтобы стать одной из сильнейших фреймворков на кросс-платформенном горизонте.

Новая сила, о которой мы говорим, — это NativeScript, фреймворк для создания действительно нативных приложений с помощью JavaScript, как говорится на их странице.

Не другой фреймворк, да ладно!

Это было мое первое впечатление, когда мой хороший друг Ник Рабой начал вести блог о NativeScript.

Но несколько недель назад на конференции German Developer Week у меня была возможность пообщаться с великим Sebastian Witalec из команды Telerik, создателей NativeScript. Он дал мне очень хорошее представление о том, что такое NativeScript и чем он не является, так что сегодня давайте рассмотрим, что NativeScript может сделать для вас!

Что такое нативскрипт?

По сути, NativeScript позволяет разрабатывать мобильные приложения из единой базы кода, написанного на JavaScript. В выпуске 2 теперь вы также можете использовать Angular 2 + TypeScript для своих приложений NativeScript, что также является одним из лучших способов разработки будущих веб-приложений.

Что в этом хорошего?

Хотя Ionic 2 также использует Angular 2, код приложения Ionic 2 не полностью совпадает с кодом веб-приложения Angular 2. С NativeScript ваш код практически одинаков, что дает вам возможность использовать общий код между мобильным приложением и веб-приложением. Довольно круто!

Мы еще не рассмотрели одну из самых важных частей NativeScript: собственную производительность.

Как?

NativeScript не запускается в Webview, как Ionic или общие приложения Cordova, а компилируется в собственные компоненты пользовательского интерфейса. JavaScript для логики по-прежнему упакован внутри приложения и не компилируется в нативный код, но это не проблема.

Таким образом, чтобы получить собственные компоненты пользовательского интерфейса на iOS и Android, NativeScript использует специальный синтаксис для создания представлений. Это не HTML, так как он не работает в Webview (да, Себастьян, теперь я понимаю причины!). Синтаксис представления аналогичен XML, но с поддержкой Angular 2 вы также можете использовать элементы Angular 2. На данный момент это немного сбивает с толку, и документация пока не очень хороша, но вы скоро увидите ее в действии.

Можно еще многое сказать о системе сборки и других вещах, связанных с NativeScript или платформой Telerik, но на данный момент знаний достаточно, чтобы погрузиться в некоторые практические вещи.

Я новичок в NativeScript, но хотел произвести на вас самое лучшее впечатление, поэтому, как всегда, мы разработаем небольшое приложение и посмотрим, как оно пойдет. Кому-то понравится такой способ ведения дел, кому-то нет, и это нормально.

Ни один фреймворк не должен быть нацелен на всех, потому что в конечном итоге это будет означать, что он не будет нацелен ни на кого!

Давайте погрузимся в код NativeScript и создадим небольшой Pokéindex, используя Pokémon API!

Запуск нового приложения NativeScript

Мы начинаем с пустого приложения NativeScript и передаем флаг —ng, чтобы указать, что мы хотим использовать Angular 2.

Поскольку CLI на данный момент не такой мощный, нам нужно создать папки и файлы, которые мы хотели бы иметь, вручную, поэтому не стесняйтесь копировать сенсорные команды.

Я использую Mac, поэтому я могу добавить платформу iOS и Android, если у вас нет Mac, вы можете использовать только Android!

Запуск приложения NativeScript с Angular 2

// Install NativeScript if needed
npm install -g nativescript
//Create Project
tns create devdactic-pokemon --ng
cd devdactic-pokemon
tns platform add ios
tns platform add android
mkdir app/pages
mkdir app/pages/list
mkdir app/pages/pokemon
touch app/pages/list/list.component.ts
touch app/pages/list/list.component.html
touch app/pages/pokemon/pokemon.component.ts
touch app/pages/pokemon/pokemon.component.html
touch app/pages/pokemon/pokemon.component.css

Если вы хотите запустить свое приложение на iOS, вам нужно внести небольшое изменение в app/shared_resources/info.plist, чтобы получить доступ к внешним API. Это также одна из сильных сторон NativeScript, вы действительно можете легко изменить поведение или настройки для одной платформы!

Разрешить безопасность транспорта для iOS

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true></true>
</dict>

Если вы хотите запустить эту демонстрацию, вам нужно это в той или иной форме, NSAllowsArbitraryLoads в этом примере просто разрешает все внешние подключения, что может быть не лучшей практикой для продуктивных приложений. Он был представлен в iOS9 и должен быть добавлен, потому что Apple хочет убедиться, что вы используете безопасные соединения. "Об этом подробнее здесь".

К настоящему времени ваше приложение уже должно работать, так что попробуйте и запустите приложение на любой платформе, которая вам нравится:

Запуск на Android или iOS

tns run android
tns run ios
 
// or inside the emulator
tns run android --emulator
tns run ios --emulator

Как было сказано ранее, приложение не запускается в Webview, поэтому мы не можем просто отлаживать приложение внутри Chrome, как с Ionic. В любом случае, NativeScript предлагает livereload для улучшения процесса разработки. На данный момент это немного медленно, но улучшения скорости запланированы для следующих выпусков!

Нам также нужно применить небольшой патч к нашему приложению, чтобы использовать HTTP-запросы. Это относится только к текущей версии 2.2 NativeScript и, надеюсь, скоро будет исправлено.

Итак, откройте app/main.ts и вставьте:

Добавить грязный хак в main.ts

// this import should be first in order to load some required settings (like globals and reflect-metadata)
import {nativeScriptBootstrap} from "nativescript-angular/application";
import {AppComponent, APP_ROUTER_PROVIDERS} from "./app.component";
 
// HACK - patch dom adapter
// For more info see: https://github.com/NativeScript/nativescript-angular/issues/305
import {Parse5DomAdapter} from '@angular/platform-server/src/parse5_adapter';
(<any>Parse5DomAdapter).prototype.getCookie = function (name) { return null; };
 
nativeScriptBootstrap(AppComponent, [APP_ROUTER_PROVIDERS]);

Настройка углового маршрутизатора

Наше приложение в настоящее время пусто, поэтому давайте начнем с некоторой логики для маршрутизации к нашим различным представлениям. Как было сказано ранее, NativeScript использует настоящий маршрутизатор Angular.

Поэтому откройте app/app.component.ts и вставьте:

Отправная точка для нашего приложения в app.ts

import {Component} from "@angular/core";
import {RouterConfig} from "@angular/router";
import {NS_ROUTER_DIRECTIVES, nsProvideRouter} from "nativescript-angular/router";
 
import {ListPage} from "./pages/list/list.component";
import {PokemonPage} from "./pages/pokemon/pokemon.component";
 
@Component({
    moduleId: module.id,
    selector: "my-app",
    directives: [NS_ROUTER_DIRECTIVES],
    template: `
      <StackLayout>
        <page-router-outlet></page-router-outlet>
      </StackLayout>
    `
})
export class AppComponent {
}
 
export const APP_ROUTES: RouterConfig = [
  { path: "", component: ListPage },
  { path: "pokemon/:url", component: PokemonPage  }
];
 
export const APP_ROUTER_PROVIDERS = nsProvideRouter(
  APP_ROUTES,
  { enableTracing: false }
);

Это очень похоже на Angular 2! Мы просто определяем 2 пути внутри нашего приложения:

  • /list: путь к нашей ListPage, странице по умолчанию, которую мы показываем после запуска приложения.
  • /pokemon/:url: страница сведений о конкретном покемоне, которая является PokemonPage.

Итак, это основа нашего приложения, давайте перейдем к более интересным вещам!

Загрузка 150 покемонов в список

Поскольку все знают, что настоящими покемонами являются только первые 150, мы просто загрузим это количество из PokéAPI.

Наш ListPage загружает информацию и сохраняет данные в массив, а также поддерживает 2 состояния загрузки. Эти состояния используются для отображения индикатора загрузки, а также для перехода в список с фактическими результатами.

Откройте app/pages/list/list.component.ts и вставьте:

list.component.ts для списка покемонов

import { Component } from "@angular/core";
import { Http, HTTP_PROVIDERS } from '@angular/http';
import 'rxjs/add/operator/map';
import {Router} from "@angular/router";
 
@Component({
  selector: "list",
  providers: [HTTP_PROVIDERS],
  templateUrl: "pages/list/list.component.html"
})
export class ListPage {
  pokemon: Array<any> = [];
  isLoading = false;
  listLoaded = false;
 
  constructor(private router: Router, private http: Http) {}
 
  ngOnInit() {
    this.isLoading = true;
    this.http.get("http://pokeapi.co/api/v2/pokemon?limit=150")
      .map(res => res.json())
      .subscribe((data) => {
        this.pokemon = data["results"];
      },
      (err) => {
        console.error(err);
        alert("Failed to load the data:" + JSON.stringify(err));
      },
      () => {
        this.isLoading = false;
        this.listLoaded = true;
      })
  }
 
  public showDetails(args: any) {
      let selectedPokemon = this.pokemon[args.index];
      this.router.navigate(["/pokemon", encodeURIComponent(selectedPokemon["url"]) ]);
  }
}

Как видите, мы также создали функцию showDetails, которая будет вызываться, когда мы захотим детализировать информацию о конкретном покемоне из нашего списка. Нам просто нужно получить выбранный объект, а затем вызвать маршрутизатор и передать URL-адрес для этого покемона (страница сведений обработает остальную часть загрузки новых данных).

Пока все выглядит как приложение Angular 2, но давайте посмотрим, как создается представление.

Откройте app/pages/list/list.component.html и заполните:

Список наших 150 покемонов

<ActionBar title="My Pokedex"></ActionBar>
<GridLayout>
  <Image src="http://pokeapi.co/media/sprites/pokemon/150.png" [class.hide]="!isLoading"></Image>
 
  <ListView [items]="pokemon" [class.visible]="listLoaded" class="small-spacing" (itemTap)="showDetails($event)">
    <template let-item="item" let-x="index">
      <DockLayout stretchLastChild="true">
          <Image 
            dock="left" 
            [src]="'http://pokeapi.co/media/sprites/pokemon/' + (x+1) + '.png'"
            width="45"></Image>
          <Label [text]="x+1 + '.  ' + item.name" class="medium-spacing"></Label>
      </DockLayout>
    </template>
  </ListView>
  <ActivityIndicator [busy]="isLoading" [visibility]="isLoading ? 'visible' : 'collapse'" row="1" horizontalAlignment="center"
    verticalAlignment="center"></ActivityIndicator>
</GridLayout>

Это не стандартный HTML в файле *.html! Это Дафак?

Вы только что познакомились с NativeScript!

На первый взгляд это выглядит немного странно. Всегда помните, что этот компонент представления должен отображаться как нативные компоненты, а не веб-представление.

Итак, здесь происходит то, что мы используем простой контейнер GridLayout Layout из Параметры макета NativeScript. Это позволяет нам легко добавлять различные компоненты представления, и они позиционируются как в таблице со строками и представлениями.

Кроме того, мы стилизуем наши строки с помощью DockLayout, чтобы показать небольшое изображение покемона перед нашей строкой. Большая часть остального — это просто синтаксис Angular 2!

Все в NativeScript живет в этих контейнерах макетов, и как только вы к ним привыкнете, это действительно поможет вам быстрее создавать представления. Также сейчас отличные ребята работают над отрисовкой компонентов NativeScript внутри веба. Это действительно может быть огромной помощью, поскольку большую часть времени вам нужно приложение и веб-сайт.

Подумайте о компоненте представления, что вы хотите; это немного раздражает в первый раз, когда вы исходите из ионного и стандартного HTML, но это пилюля, которую вы должны проглотить, если хотите пойти по пути NativeScript.

Каковы ваши первые впечатления? Дайте мне знать ниже!

Чтобы получить лучший внешний вид и стили для списка, мы добавим стили и анимацию в app/app.css (есть лучшие места для стилей, но это тоже работает):

Добавить стиль для списка

.small-spacing {
  margin: 5;
}
 
.medium-spacing {
  margin: 10;
}
 
ListView {
  opacity: 0;
}
 
// Animations
.visible {
  animation-name: show;
  animation-duration: 1s;
}
 
@keyframes show {
  from { opacity: 0; }
  to { opacity: 1; }
}
 
.hide {
    animation-name: animate-hide;
    animation-duration: 2s;
    animation-fill-mode: forwards;
}
 
@keyframes animate-hide {
  from {  opacity: 1}
  to {  opacity: 0 }
}

На самом деле это стандартные анимации CSS, которые будут сопоставлены с собственными изменениями пользовательского интерфейса, такими как описанные в этом посте.

По сути, мы определяем некоторые общие свойства в верхней половине файла и анимацию для появления или исчезновения нашего представления списка в нижней половине, используя @keyframes для определения анимации. Используя имя анимации внутри определения для имени анимации, анимация привязывается к этому свойству. Если вы хотите узнать больше об анимациях NativeScript, ознакомьтесь с их документацией по анимациям с помощью CSS.

Создание подробного представления для покемона

Давайте закончим наше маленькое приложение, добавив представление сведений о покемонах. Этот компонент получит URL-адрес определенного покемона, поэтому сначала ему необходимо загрузить все данные для него.

Как только мы получили данные, мы извлекаем некоторые данные из ответа, и это то, что мы хотим отобразить (я знаю, что настоящий Pokédex был намного круче, но эй, вы должны продолжить этот пост!).

У нас также снова есть несколько состояний загрузки, просто чтобы интерфейс выглядел лучше. Итак, откройте app/pages/pokemon/pokemon.component.ts и вставьте:

Подробный класс pokemon.component.ts

import { Component } from "@angular/core";
import { Http, HTTP_PROVIDERS } from '@angular/http';
import 'rxjs/add/operator/map';
import {Router, ActivatedRoute} from "@angular/router";
 
@Component({
  selector: "pokemon-page",
  providers: [HTTP_PROVIDERS],
  templateUrl: "pages/pokemon/pokemon.component.html"
})
export class PokemonPage {
  name: string = "";
  pokeimg: string = "";
  weight: number = 0;
  height: number = 0;
 
  isLoading: boolean = false;
  dataLoaded:boolean = false;
 
  constructor(private router: Router, private route: ActivatedRoute, private http: Http) {  }
 
  ngOnInit() {
    this.isLoading = true;
 
    this.route.params
      .map(params => decodeURIComponent(params['url']))
      .subscribe(url => {
        this.http.get(url)
        .map(res => res.json())
        .subscribe((data) => {
          this.name = data["name"];
          this.pokeimg = data["sprites"]["front_default"];
          this.weight = data["weight"];
          this.height= data["height"];
        },
        (err) => {
          console.error(err);
        },
        () => {
          this.isLoading = false;
          this.dataLoaded = true;
        })
    })
  }
 
  navigateBack() {
    this.router.navigate(["/"]);
  }
}

Снова очень стандартный Angular 2, на самом деле нам нечего больше о нем говорить. Логика карты может показаться немного странной, но именно так вы получаете максимальную мощность за минимально возможное количество линий.

Представление внутри app/pages/pokemon/pokemon.component.html должно содержать следующее:
Представление pokemon.component.html для нашей информации

<ActionBar [title]="name">
  <NavigationButton text="Back" android.systemIcon="ic_menu_back" (tap)="navigateBack()"></NavigationButton>
</ActionBar>
<StackLayout class="item-group">
  <Label [text]="'Height:' + height" textWrap="true"></Label>
  <Label [text]="'Weight:' + weight" textWrap="true"></Label>
  <Image [src]="pokeimg"></Image>
 
  <ActivityIndicator
    [busy]="isLoading"
    [visibility]="isLoading ? 'visible' : 'collapse'"
    horizontalAlignment="center"
    verticalAlignment="center">
  </ActivityIndicator>
</StackLayout>

На этот раз мы используем StackLayout в качестве контейнера, который просто размещает каждый объект ниже предыдущего. Мы хотим, чтобы наша панель была вверху, чтобы вернуться к списку, две метки с информацией и, наконец, изображение.

Я нашел немного сложным синтаксис, который на самом деле использовать, поэтому иногда мы обращаемся к свойствам Angular, но иногда они являются обычными свойствами объекта. Также в данный момент документация не очень помогает, что также отметил Рэймонд Камден, когда работал с NativeScript. Но я думаю, что команда знает об этом и будет постоянно это улучшать!

Наконец, мы можем применить некоторые стили к нашему не совсем HTML-представлению с помощью CSS внутри app/pages/pokemon/pokemon.component.css, так что давайте сделаем это:

Стили внутри pokemon.component.css

.item-group {
    padding: 10;
}
 
.item-label {
    font-weight: bold;
}

Вот и все для нашего первого приложения NativeScript!

Теперь запустите свое приложение на iOS или Android и наслаждайтесь детскими воспоминаниями о 150 покемонах и их изображениях.

Почему NativeScript имеет значение

Как было сказано ранее, мои первоначальные мысли были «Зачем мне изучать другой фреймворк?», и до сих пор возникают эти мысли при работе с новыми вещами, такими как React Native.

Но если подумать немного иначе, все становится проще:

Все фреймворки для кроссплатформенной разработки имеют свои сильные и слабые стороны!

Для меня не существует единой наилучшей структуры, все всегда зависит от экономического обоснования и требований, которые у вас есть. Хотя Ionic великолепен во многих областях, это всегда будет веб-просмотр, и вам всегда будет не хватать производительности по сравнению с нативным приложением.

Всегда будут люди, которым вообще не нравится Angular, который лучше подходит для чего-то, что они, возможно, уже знают, например, Xamarin для .NET или React-Native для разработчиков ReactJS, или даже Unity3D, если вы хотите. настроить таргетинг на большее количество устройств.

Вернуться к нативному сценарию

Итак, это были некоторые общие соображения, а теперь вернемся к вопросу о том, что делает NativeScript сильным и кому следует его использовать?

Прежде всего, каждый фреймворк, основанный на Angular 2, является потенциальным кандидатом на то, что мы хотим использовать. Разработка Angular 2 с помощью TypeScript не похожа на старую веб-разработку, а похожа на написание действительно хорошего кода, плюс сообщество огромно.

И, если вы хотите создать мобильное приложение и веб-сайт, шансы очень высоки, вы можете общий код между вашим NativeScript и веб-приложением! Это то, что будет становиться все более и более важным. И поскольку NativeScript не требует от вас наличия каких-то специальных вещей для Angular, вы действительно можете иногда делиться этими частями 1:1, что в настоящее время невозможно с Ionic 2.

Следующим большим плюсом является нативная производительность благодаря нативным скомпилированным компонентам пользовательского интерфейса. Конечно, это делает NativeScript привлекательным для людей, которые полагаются на хорошую производительность и боятся всемогущего Webview в гибридных приложениях.

Одна вещь, которой мы раньше не касались, — это использование нативных API, что довольно просто с NativeScript. Это означает, что вы действительно можете вызвать, скажем, iOS UITableViewController прямо из кода!

Конечно, это означает, что вам нужно иметь какие-то нативные знания, но если они у вас есть, это иногда может предложить вам большие возможности, поскольку вам не нужно ждать, пока какая-то компания напишет оболочку, а просто использовать ее самостоятельно. Для лучшего объяснения вы можете посмотреть видео с объяснением NativeScript здесь.

Наконец, я бы порекомендовал попробовать NativeScript всем, но особенно тем, кто:

  • как Angular 2 и TypeScript
  • хотите повысить производительность кросс-платформенных приложений
  • уже есть некоторый родной опыт

Эти люди могут выиграть от этого больше всего, и это снова причина, по которой я сказал, что есть место для многих фреймворков. То, что этим людям может понравиться в NativeScript, совершенно бесполезно для других, которым лучше подходит другой фреймворк.

Вывод

Мое первое приключение с NativeScript было интересным, я многое узнал о том, как все работает, и я вижу потенциальные преимущества разработки кроссплатформенных приложений с его помощью. Я уделю ему еще немного времени и, возможно, в ближайшем будущем покажу больше руководств, чтобы увидеть, хочу ли я работать с ним чаще.

Каковы ваши первые впечатления от NativeScript? Хотелось бы их услышать!

Еще раз большое спасибо Sebastian Witalec за перенос моего кода с NativeScript 2.1 на 2.2. Вы определенно можете рассчитывать на команду NativeScript, если у вас возникнут какие-либо проблемы!

Предложить

Создавайте приложения с помощью React Native

Ionic на примере: создание мобильных приложений в HTML5

Angular 2 и NodeJS — Практическое руководство по MEAN Stack 2.0