Aurelia vs. Angular 2——代碼比較

已發表: 2022-03-11

Angular 和 Aurelia 是優秀的舊 JavaScript Angular 1 的後代,它們是激烈的競爭者,它們幾乎同時開發和發布,並具有相似的理念,但在許多關鍵方面有所不同。 在本文中,我們將對功能和代碼方面的這些差異進行並排比較。

長話短說,Aurelia 是由被稱為 Durandal 和 Caliburn 的創造者的 Rob Eisenberg 創建的。 他曾在 Google 的 Angular 2 團隊工作,但在 2014 年離開時,他對現代框架應該如何看待的看法與他們的不同。

在技​​術層面上也存在相似之處:模板和與之關聯的組件(或自定義元素)是 Angular 和 Aurelia 應用程序的核心,兩者都要求您擁有一個根組件(即應用程序)。 此外,Angular 和 Aurelia 都大量使用裝飾器進行組件配置。 每個組件都有一個我們可以掛鉤的固定生命週期。

那麼 Aurelia 和 Angular 2 有什麼區別呢?

根據 Rob Eisenberg 的說法,關鍵區別在於代碼:Aurelia 不引人注目。 在開發 Aurelia 應用程序時(在配置之後),您使用 ES6 或 TypeScript 編寫,模板看起來絕對像 HTML,尤其是與 Angular 相比。 Aurelia 是約定優於配置,在 95% 的情況下,使用默認約定(例如模板命名、元素命名等)就可以了,而 Angular 則要求您為基本上所有內容提供配置。

Aurelia 也被認為更符合標準,只是因為它在 HTML 標籤方面不區分大小寫,而 Angular 2 是。 這意味著 Angular 2 不能依賴瀏覽器的 HTML 解析器,所以他們創建了自己的。

在 SPA 框架之間進行選擇時要考慮的另一個因素是它們周圍的社區——生態系統。 Angular 和 Aurelia 都具備所有基礎知識(路由器、模板引擎、驗證等),並且很容易獲得原生模式或使用一些第三方庫,但 Angular 擁有更大的社區和更大的開發也就不足為奇了團隊。

此外,雖然這兩個框架都是開源的,但 Angular 主要由谷歌開發,不打算商業化,而僱傭核心團隊的 Durandal, Inc. 正在通過諮詢和培訓遵循 Ember.js 的貨幣化模式。

Aurelia 與 Angular:代碼比較

讓我們看一下突出每個框架背後的哲學的一些最顯著的特徵。

為 Angular 和 Aurelia 克隆種子項目後,我們分別有一個 ES6 Aurelia 應用程序(您可以將 Jspm/System.js、Webpack 和 RequireJS 與 ES6 或 TypeScript 結合使用)和一個 TypeScript Angular 應用程序(WebPack)。

來吧。

數據綁定

在我們並排比較工作示例之前,我們必須看一下 Aurelia 和 Angular 2 之間的一些語法差異,即從控制器綁定值到視圖的關鍵功能。 Angular 1 對所有內容都使用了“臟檢查”,這是一種掃描更改範圍的方法。 可以理解的是,這導致了許多性能問題。 Angular 2 和 Aurelia 都沒有走這條路。 相反,他們使用事件綁定。

Angular 2 中的數據綁定

在 Angular 中,您使用方括號綁定數據並使用括號綁定事件,如下所示:

 <element [property]="value"></a> <element (someEvent)="eventHandler($event)"></a>

雙向綁定——當您希望應用程序數據的更改反映在視圖上時,反之亦然——是方括號和括號的組合。 因此,對於雙向綁定輸入,這將工作得很好:

 <input type="text" [(ngModel)]="text"> {{text}}

換句話說,括號代表一個事件,而方括號代表一個被推送到輸入的值。

Angular 團隊在分離綁定方向方面做得很好:到 DOM、從 DOM 和雙向。 還有很多與綁定類和样式相關的語法糖。 例如,考慮以下片段作為單向綁定的示例:

 <div [class.red-container]="isRed"></div> <div [style.width.px]="elementWidth"></div>

但是如果我們想將雙向數據綁定到一個組件中呢? 考慮以下基本輸入設置:

 <!-- parent component --> <input type="text" [(ngModel)]="text"> {{ text }} <my-component [(text)]="text"></my-component> import {Component, Input} from '@angular/core'; @Component(/* ... */) export class MyComponent { @Input() text : string; } <!-- child component --> <input [(ngModel)]="text"> Text in child: {{ text }}

請注意,要使用ngModel ,您的模塊必須從@angular/forms導入 FormsModule。 現在我們有了一些有趣的東西。 更新父輸入中的值會更改各處的值,但修改子輸入只會影響該子。 如果我們希望它更新父值,我們需要一個事件通知父值。 此事件的命名約定是property name + 'Change' ,如下所示:

 import {Component, Input, Output, EventEmitter} from '@angular/core'; @Component(/* ... */) export class MyComponent { @Input() text : string; @Output() textChange = new EventEmitter(); triggerUpdate() { this.textChange.emit(this.text); } }

在我們綁定到ngModelChange事件後,雙向綁定開始正常工作:

 <!-- child component --> <input [(ngModel)]="text" (ngModelChange)="triggerUpdate($event)">

當你有效地告訴框架忽略綁定值即使它們發生變化時,一次性綁定又如何呢?

在 Angular 1 中,我們使用 {{::value}} 綁定一次。 在 Angular 2 中,一次性綁定變得複雜:文檔說您可以在組件的配置中使用changeDetection: ChangeDetectionStrategy.OnPush屬性,但這將使您的所有綁定一次性完成。

數據綁定:Aurelia 方式

與 Angular 2 相比,在 Aurelia 中綁定數據和事件非常簡單。 您可以使用任何一種插值,就像 Angular 的property="${value}"一樣,或者使用以下綁定類型之一:

 property.one-time="value" property.one-way="value" property.two-way="value"

這些名稱是不言自明的。 此外,還有property.bind="value" ,它是一種語法糖,可以自我檢測綁定是單向還是雙向。 考慮:

 <!-- parent--> <template bindable="text"> <input type="text" value.bind="text"/> <child text.two-way="text"></child> </template> <!-- child custom element --> <template bindable="text"> <input type="text" value.bind="text"/> </template>

在上面的代碼片段中, @bindable@Input都是可配置的,因此您可以輕鬆更改被綁定屬性的名稱等內容。

事件呢? 要綁定到 Aurelia 中的事件,您可以使用.trigger.delegate 。 例如,要讓子組件觸發事件,您可以執行以下操作:

 // child.js this.element.dispatchEvent(new CustomEvent('change', { detail: someDetails }));

然後,在父母那裡聽:

 <child change.trigger="myChangeHandler($event)"></child> <!-- or --> <child change.delegate="myChangeHandler($event)"></child>

這兩者之間的區別在於.trigger在該特定元素上創建一個事件處理程序,而.delegatedocument上添加一個偵聽器。 這可以節省資源,但顯然不適用於非冒泡事件。

Aurelia 與 Angular 的基本示例

現在我們已經介紹了綁定,讓我們創建一個渲染可縮放矢量圖形 (SVG) 的基本組件。 它會很棒,因此我們稱它為awesome-svg 。 本練習將說明 Aurelia 和 Angular 2 的基本功能和理念。本文的 Aurelia 代碼示例可在 GitHub 上獲得。

Aurelia 中的 SVG 矩形示例

讓我們首先構建 JavaScript 文件:

 // awesome-svg.js import {bindable} from 'aurelia-framework'; export class AwesomeSvgCustomElement { @bindable title; @bindable colors = []; }

現在是 HTML。

在 Aurelia 中,您可以使用註釋@template@inlineView甚至@noView指定模板(或使用內聯模板),但開箱即用,它會搜索與.js同名的.html文件文件。 自定義元素的名稱也是如此——您可以使用@customElement('awesome-svg')設置它,但如果不這樣做,Aurelia 會將標題轉換為 dash-case 並查找匹配項。

由於我們沒有另外指定,該元素將被稱為awesome-svg並且 Aurelia 將在同一目錄中搜索與js文件(即awesome-svg.html )同名的模板:

 <!-- awesome-svg.html --> <template> <h1>${title}</h1> <svg> <rect repeat.for="color of colors" fill.bind="color" x.bind="$index * 100" y="0" width="50" height="50"></rect> </svg> </template>

注意到<template>標籤了嗎? 所有模板都需要包裝在<template>標記中。 另外值得注意的是,您使用 ` for ... of and the string interpolation ${title}`,就像在 ES6 中一樣。

現在要使用該組件,我們應該使用<require from="path/to/awesome-svg"></require>將其導入模板中,或者,如果它在整個應用程序中使用,則在框架的配置函數中全球化資源與aurelia.use.globalResources('path/to/awesome-svg'); ,這將一勞永逸地導入awesome-svg組件。

[注意,如果你不做這些, <awesome-svg></awesome-svg>將被視為任何其他 HTML 標籤,沒有錯誤。]

您可以使用以下方式顯示組件:

 <awesome-svg colors.bind="['#ff0000', '#00ff00', '#0000ff']"></awesome-svg>

這將呈現一組 3 個矩形:

Aurelia 中的 SVG 矩形示例

Angular 2 中的 SVG 矩形示例

現在讓我們在 Angular 2 中做同樣的例子,也可以在 GitHub 上找到。 Angular 2 要求我們同時指定模板和元素名稱:

 // awesome-svg.component.ts import {Component, Input} from '@angular/core'; @Component({ selector: 'awesome-svg', templateUrl: './awesome-svg.component.html' }) export class AwesomeSvgComponent { @Input() title : string; @Input() colors : string[] = [] }

視圖是事情變得有點複雜的地方。 首先,Angular 以與瀏覽器相同的方式默默地處理未知的 HTML 標籤:它會觸發一個錯誤,說明類似於my-own-tag的內容是未知元素。 它對你綁定的任何屬性都是一樣的,所以如果你在代碼的某個地方有錯字,它會引起很大的關注,因為應用程序會崩潰。 聽起來不錯,對吧? 是的,因為如果您破壞了應用程序,您會立即註意到,不,因為這只是一種糟糕的形式。

考慮這個片段,它在綁定語法方面非常好:

 <svg> <rect [fill]="color"></rect> </svg>

即使它讀起來很好,你也會得到一個錯誤,比如“不能綁定到 'fill',因為它不是 ':svg:rect' 的已知屬性。” 要解決此問題,您需要改用語法[attr.fill]="color" 。 另請注意,需要在<svg/>: <svg:rect>內的子元素中指定命名空間,以讓 Angular 知道這不應被視為 HTML。 讓我們擴展我們的片段:

 <!-- awesome-svg.component.html--> <h1>{{ title }}</h1> <svg> <rect *ngFor="let color of colors; let i = index" [attr.fill]="color" [attr.x]="i * 100" y="0" width="50" height="50" ></rect> </svg>

我們去吧。 接下來,在模塊配置中導入它:

 @NgModule({ declarations: [ AwesomeSvgComponent ] //... })

現在該組件可以在這個模塊中使用,如下所示:

 <awesome-svg [colors]="['#ff0000', '#00ff00', '#0000ff']" title="Rectangles"></awesome-svg> 

AngularJS 2 中的 SVG 矩形示例

自定義元素

假設現在我們希望我們的矩形代碼是一個具有自己邏輯的自定義組件。

自定義元素:Angular 2 方式

由於 Angular 2 通過匹配其定義的選擇器來呈現組件,因此定義自定義組件非常容易,如下所示:

 @Component({ selector: 'g[custom-rect]', ... })

上面的代碼片段會將自定義元素呈現給任何<g custom-rect></div>標籤,這非常方便。

自定義元素:Aurelia 方式

Aurelia 允許我們創建僅模板的自定義元素:

 <template bindable="colors, title"> <h1>${title}</h1> <svg> <rect repeat.for="color of colors" fill.bind="color" x.bind="$index * 100" y="0" width="50" height="50"></rect> </svg> </template>

自定義元素將根據文件名命名。 與命名其他組件的唯一區別是,在導入時,無論是在配置中還是通過<require>標記,都應該將.html放在末尾。 例如: <require from="awesome-svg.html"></require>

Aurelia 也有自定義屬性,但它們的用途與 Angular 2 中的不同。例如,在 Aurelia 中,您可以在自定義rect元素上使用@containerless註釋。 @containerless也可以與沒有控制器和<compose>的自定義模板一起使用,它基本上將內容渲染到 DOM 中。

考慮以下包含@containerless註釋的代碼:

 <svg> <custom-rect containerless></custom-rect> </svg>

輸出不會包含自定義元素標籤 ( custom-rect ),而是我們得到:

 <svg> <rect ...></rect> </svg>

服務

在服務方面,Aurelia 和 Angular 非常相似,您將在以下示例中看到。 假設我們需要NumberOperator ,它依賴於NumberGenerator

奧里利亞的服務

以下是如何在 Aurelia 中定義我們的兩個服務:

 import {inject} from 'aurelia-framework'; import {NumberGenerator} from './number-generator' export class NumberGenerator { getNumber(){ return 42; } } @inject(NumberGenerator) export class NumberOperator { constructor(numberGenerator){ this.numberGenerator = numberGenerator; this.counter = 0; } getNumber(){ return this.numberGenerator.getNumber() + this.counter++; } }

現在,對於一個組件,我們以相同的方式註入:

 import {inject} from 'aurelia-framework'; import {NumberOperator} from './_services/number-operator'; @inject(NumberOperator) export class SomeCustomElement { constructor(numberOperator){ this.numberOperator = numberOperator; //this.numberOperator.getNumber(); } }

如您所見,通過依賴注入,任何類都可以成為完全可擴展的服務,因此您甚至可以編寫自己的解析器。

奧里利亞的工廠

如果您需要的是工廠或新實例( FactoryNewInstance只是開箱即用的幾個流行的解析器),您可以執行以下操作:

 import { Factory, NewInstance } from 'aurelia-framework'; @inject(SomeService) export class Stuff { constructor(someService, config){ this.someService = someService; } } @inject(Factory.of(Stuff), NewInstance.of(AnotherService)) export class SomethingUsingStuff { constructor(stuffFactory, anotherService){ this.stuff = stuffFactory(config); this.anotherServiceNewInstance = anotherService; } }

角服務

這是 Angular 2 中的同一組服務:

 import { Injectable } from '@angular/core'; import { NumberGenerator } from './number-generator'; @Injectable() export class NumberGenerator { getNumber(){ return 42; } } @Injectable() export class NumberOperator { counter : number = 0; constructor(@Inject(NumberGenerator) private numberGenerator) { } getNumber(){ return this.numberGenerator.getNumber() + this.counter++; } }

@Injectable註解是必需的,要真正注入服務,需要在組件配置或整個模塊配置的提供者列表中指定服務,如下所示:

 @Component({ //... providers: [NumberOperator, NumberGenerator] })

或者,不推薦,您也可以在bootstrap(AppComponent, [NumberGenerator, NumberOperator])調用中指定它。

請注意,無論您如何注入它,都需要同時指定NumberOperatorNumberGenerator

生成的組件將如下所示:

 @Component({ //... providers: [NumberOperator, NumberGenerator], }) export class SomeComponent { constructor(@Inject(NumberOperator) public service){ //service.getNumber(); } }
Angular 2 中的工廠

在 Angular 2 中,您可以使用provide註解創建工廠,該註解也用於別名服務以防止名稱衝突。 創建工廠可能如下所示:

 let stuffFactory = (someService: SomeService) => { return new Stuff(someService); } @Component({ //... providers: [provide(Stuff, {useFactory: stuffFactory, deps: [SomeService]})] })

嵌入

Angular 1 能夠使用嵌入將內容(一個“插槽”)從一個模板包含到另一個模板中。 讓我們看看它的後代能提供什麼。

使用 Angular 2 進行內容投影

在 Angular 2 中,嵌入被稱為“內容投影”,它的工作方式與ng-transclude相同; 即,用 Angular 1 術語來說,被嵌入的內容使用父範圍。 它將根據配置選擇器匹配嵌入的內容標籤。 考慮:

 @Component({ selector: 'child', template: `Transcluded: <ng-content></ng-content>` }) export class MyComponent {}

然後,您可以將組件與<child-component>Hello from Translusion Component</child-component>一起使用,我們將獲得在子組件中呈現的精確嵌入的Yes HTML。

對於多槽嵌入,Angular 2 有選擇器,你可以像@Component配置一樣使用它們:

 <!-- child.component.html --> <h4>Slot 1:</h4> <ng-content select=".class-selector"></ng-content> <h4>Slot 2:</h4> <ng-content select="[attr-selector]"></ng-content>
 <!-- parent.component.html --> <child> <span class="class-selector">Hello from Translusion Component</span> <p class="class-selector">Hello from Translusion Component again</p> <span attr-selector>Hello from Translusion Component one more time</span> </child> 

Angular2 中的插槽

您可以在自定義標籤上使用select ,但請記住,Angular 2 必須知道該標籤。

帶有 Aurelia 的老虎機

還記得我說過 Aurelia 盡可能遵循 Web 標準嗎? 在 Aurelia 中,transclusion 稱為 slot,它只是 Web Components Shadow DOM 的 polyfill。 Shadow DOM尚未為 slot 創建,但它遵循 W3C 規範。

 <!-- child --> <template> Slot: <slot></slot> </template> <!-- parent --> <template> <child>${textValue}</child> </template>

Aurelia 被設計為符合標準,而 Angular 2 不是。 因此,我們可以使用 Aurelia 的插槽做更多很棒的事情,比如使用後備內容(嘗試在 Angular 2 中使用後備內容失敗, <ng-content> element cannot have content )。 考慮:

 <!-- child --> <template> Slot A: <slot name="slot-a"></slot> <br /> Slot B: <slot name="slot-b"></slot> Slot C: <slot name="slot-c">Fallback Content</slot> </template> <!-- parent --> <template> <child> <div slot="slot-a">A value</div> <div slot="slot-b">B value</div> </child> </template>

與 Angular 2 一樣,Aurelia 將根據名稱匹配呈現所有出現的插槽。

Aurelia 中的老虎機

還值得注意的是,在 Aurelia 和 Angular 中,您都可以編譯模板部分並動態渲染組件(在 Aurelia 中使用<compose>view-model ,或在 Angular 2 中使用ComponentResolver )。

影子 DOM

Aurelia 和 Angular 都支持 Shadow DOM。

在 Aurelia 中,只需使用@useShadowDOM裝飾器即可:

 import {useShadowDOM} from 'aurelia-framework'; @useShadowDOM() export class YetAnotherCustomElement {}

在 Angular 中,同樣可以使用ViewEncapsulation.Native來完成:

 import { Component, ViewEncapsulation } from '@angular/core'; @Component({ //... encapsulation: ViewEncapsulation.Native, }) export class YetAnotherComponent {}

記得檢查你的瀏覽器是否支持 Shadow DOM。

服務器端渲染

現在是 2017 年,服務器端渲染非常流行。 您已經可以使用 Angular Universal 在後端渲染 Angular 2,而 Aurelia 將在 2017 年實現這一點,正如其團隊的新年決議所述。 事實上,Aurelia 的 repo 中有一個可運行的演示。

除此之外,Aurelia 已經擁有一年多的漸進增強功能,Angular 2 沒有,因為它的非標準 HTML 語法。

尺寸、性能和接下來會發生什麼

雖然它沒有向我們展示全貌,但使用默認配置和優化實現的 DBMonster 基準測試描繪了一幅很好的對比圖:Aurelia 和 Angular 顯示出相似的結果,每秒大約 100 次重新渲染(在 MacBook Pro 上測試),而 Angular 1 顯示了大約一半的結果。 Aurelia 和 Angular 的性能都比 Angular 1 高出大約 5 倍,並且都比 React 領先 40%。 Aurelia 和 Angular 2 都沒有虛擬 DOM 實現。

在大小問題上,Angular 大約是 Aurelia 的兩倍,但谷歌的人正在努力:Angular 的路線圖包括發布 Angular 4,計劃使其更小、更輕量級,同時改善開發人員體驗。 沒有 Angular 3,實際上,在談論 Angular 時應該刪除版本號,因為主要版本計劃每 6 個月發布一次。 如果您查看 Angular 從 alpha 到其當前版本的路徑,您會發現它並不總是與從構建到構建的屬性重命名等事情保持一致。Angular 團隊承諾破壞性更改將很容易遷移。

截至 2017 年,Aurelia 團隊計劃發布 Aurelia UX,提供更多集成和工具,並實現服務器端渲染(這在路線圖上已經有很長時間了)。

Angular 2 vs. Aurelia:品味問題

Angular 和 Aurelia 都很好,選擇其中一個是個人品味和優先級的問題。 如果您需要更苗條的解決方案,Aurelia 是您的最佳選擇,但如果您需要社區支持,Angular 是您的贏家。 這不是“這個框架允許我……嗎?”的問題。 因為答案很簡單。 它們提供大致相同的功能,同時遵循不同的理念和風格以及完全不同的 Web 標準方法。