從ng1到ng2的平滑升級

如果你想了解如何將現(xiàn)有的AngularJs 1.X(簡稱ng1)的項(xiàng)目平滑升級至Angular 2.X(簡稱ng2),這篇文章或許會(huì)對你有幫助。

本文假設(shè)你原有的ng1項(xiàng)目有如下屬性:

使用的ng1版本為1.2.X+;

使用的Javascript版本為es3或少量es5特性;

暫時(shí)不考慮其他外部依賴,例如第三方ng1插件等;

如果你有一些typescript的知識(shí),閱讀本文將會(huì)更加輕松。

本文的目標(biāo)是:

將ng1升級至ng2,為了實(shí)現(xiàn)平滑升級,我們還將盡量在升級過程中把步子邁小,同時(shí)使升級后的代碼兼容ng1,從而在升級過程中不影響當(dāng)前項(xiàng)目的正常開發(fā)和發(fā)布;

將Javascript升級至Typescript,平滑升級過程中還可以生成兼容ng1的typescript版本代碼;

盡量嘗試使用最佳實(shí)踐。

讓我們開始吧!

我們先來定義一個(gè)在本文中一直會(huì)使用到的ng1項(xiàng)目基礎(chǔ)代碼,后面的代碼實(shí)例都基于這個(gè)基礎(chǔ)代碼開發(fā):

// app.js/* 定義一個(gè)ng1 module,并假設(shè)該module為ng1項(xiàng)目的啟動(dòng)module,

例如在html標(biāo)簽中添加 ng-app="app"

*/var app = angular.module('app', []);

這個(gè)項(xiàng)目至少還要有一個(gè)controller:

// index.controller.js

/* 一個(gè)簡單的controller */

app.controller('IndexCtrl', ['$scope', function ($scope) {

$scope.info = 'hello world';

console.log($scope.info);

}]);

我們還要有一個(gè)html頁面:


...

...

這樣,一個(gè)最簡單的ng1項(xiàng)目就完成了。

我們從最常使用的controller開始下手。首先回顧一下剛才定義的controller:

// index.controller.js

/* 一個(gè)簡單的controller */

app.controller('IndexCtrl', ['$scope', function ($scope) {

$scope.info = 'hello world';

console.log($scope.info);

}]);

仔細(xì)查看代碼,我們可以發(fā)現(xiàn),我們定義controller的方式使用的是angular.Module.controller方法,這樣的定義方式與ng1完全耦合在一起,無法脫離ng1的依賴,因此,第一步我們現(xiàn)在將controller實(shí)體function從該方法中剝離出來。

1. 分離controller的實(shí)體function

分離的方式很簡單,我們定義一個(gè)function變量,再將該function變量寫在angular.Module.controller方法中:

// index.controller.js

/* 一個(gè)簡單的controller */

app.controller('IndexCtrl', ['$scope', indexCtrl]);

var indexCtrl = function ($scope) {

$scope.info = 'hello world';

console.log($scope.info);

};

但是這樣做還是不夠,因?yàn)镮ndex.controller.js文件中還是調(diào)用了app.controller方法,這個(gè)方法是ng1專有的,因此這個(gè)文件還是在依賴ng1,因此我們可以將所有使用app.controller方法注冊controller的代碼集中到一個(gè)app-controller.js中。

這樣,我們將index.controller.js文件拆分成兩個(gè)文件:

// app-controller.js

app.controller('IndexCtrl', ['$scope', indexCtrl])

.controller('LoginCtrl', ['$scope', loginCtrl])

.controller(...);

// index.controller.js

var indexCtrl = function ($scope) {

$scope.info = 'hello world';

console.log($scope.info);

};

修改之后的代碼就清晰多了,app-controller.js專門負(fù)責(zé)注冊項(xiàng)目中所有用到的controller,index.controller.js以及其他的類似login.controller.js等專門負(fù)責(zé)完成controller中的業(yè)務(wù)邏輯和交互邏輯。

這里有一個(gè)問題要注意,因?yàn)閍pp-controller.js中要調(diào)用indexCtrl這個(gè)變量,因此一定要在調(diào)用之前定義這個(gè)變量,否則調(diào)用時(shí)indexCtrl為undefined。避免這個(gè)問題的方法,一種是在html中引用js文件時(shí)首先引用定義indexCtrl的js文件,最后引用app-controller.js文件;第二種是使用typescript中的模塊管理功能管理相互之間的依賴。本文最終會(huì)形成typescript版本的代碼,所以這個(gè)問題僅在改動(dòng)過程中出現(xiàn)。

2. 不再使用丑陋的$scope

index.controller.js中的代碼使用了$scope,而$scope是使用DI的方式注入到方法中,因此這里并沒有對ng1產(chǎn)生直接的依賴,這也是依賴注入的一大好處。

但是controller方法中到處充滿了$scope,不得不說代碼會(huì)顯得很混亂和丑陋。如果你對ng2有一些了解,那么應(yīng)該知道ng2中是沒有$scope概念的。為了升級至ng2,我們從現(xiàn)在開始對$scope說NO!

ng1中提供了一種可以避免使用$scope的方式,那就是as語法,我們先來看一下如何使用:


...

...

// index.controller.js

var indexCtrl = function () {

this.info = 'hello world';

console.log(this.info);

};

// app-controller.js

app.controller('IndexCtrl', [indexCtrl])

.controller('LoginCtrl', [loginCtrl])

.controller(...);

使用as語法,相當(dāng)于在$scope中定義了一個(gè)屬性$ctrl,并將這個(gè)屬性指向indexCtrl方法作用域的this上,具體可以參見官方文檔(https://docs.angularjs.org/api/ng/directive/ngController)。

因此,使用as語法后,controller方法就可以使用this來操作其中的變量,這樣幾乎與ng2中Component類的操作方法一致,在注冊controller時(shí)也不用注入$scope依賴了。

同時(shí)在html中,調(diào)用controller方法中的變量時(shí),需要使用$ctrl.foobar來實(shí)現(xiàn)(因?yàn)?ctrl為$scope的一個(gè)屬性)。

這里使用的$ctrl屬性可以根據(jù)需要調(diào)整名稱,如ctrl、indexCtrl等,但統(tǒng)一使用$ctrl是最佳實(shí)踐,一方面可以在所有html的controller中統(tǒng)一使用相同的屬性名調(diào)用controller中的變量,另一方面使用$前綴可以作為ng1的一個(gè)標(biāo)記,避免出現(xiàn)變量名沖突的情況。

這時(shí)我們再重新觀察一下index.controller.js文件,它已經(jīng)完全純凈,只是一個(gè)非常普通的方法,一點(diǎn)ng1的影子都看不出來了:

// index.controller.js

var indexCtrl = function () {

this.info = 'hello world';

console.log(this.info);

};

3. 向typescript進(jìn)發(fā)

我們現(xiàn)在的js代碼還停留在es3階段,要靠近ng2,我們還需要使用typescript。

升級至typescript后,需要使用typescript compiler(http://www.typescriptlang.org/)編譯為es3或es5版本。

注意:雖然代碼升級為typescript,但仍然可以在編譯后兼容ng1。

這里,我們使用typescript中的class來定義indexCtrl:

// index.controller.ts

// 為變量增加類型聲明,同時(shí)也就不用再判斷變量類型了

export default class IndexCtrl {

info : string = 'hello world';

constructor () {

console.log(this.info);

}

// 這里我們?yōu)閏ontroller添加一個(gè)可以在html中調(diào)用的方法

hasInfo () : boolean {

// 使用了typescript,這里就可以省略類型判斷了

return this.info.length > 0;

}

}

我們使用以下幾種方式實(shí)現(xiàn)IndexCtrl:

之前使用this定義的變量,我們在class的最頂端使用foo = 'bar'的方式聲明為類屬性;

初始化工作在class中的constructor中完成;

需要在html中調(diào)用的方法使用foo () {}的方式生命為類方法。

代碼中export default使用了typescript中的模塊管理功能,將類導(dǎo)出,并可以在其他文件中調(diào)用。

類似的,我們將其他文件也做相應(yīng)的優(yōu)化:

// app.ts

export default angular.module('app', []);

// app-controller.ts

// 這里使用import調(diào)用其他模塊,從此就不用擔(dān)心變量是否已定義的問題啦

import app from './app';

import IndexCtrl from './index.controller';

import LoginCtrl from './login.controller';

...

app.controller('IndexCtrl', [indexCtrl])

.controller('LoginCtrl', [loginCtrl])

.controller(...);

// 這個(gè)文件不需要其他文件調(diào)用,因此不需要export模塊

...

?
? ?

ng-if="$ctrl.hasInfo()"> ?...

雖然到此為止,代碼在typescript下編譯完成后,仍然可以生成兼容ng1的es3版本js代碼。

4. 根據(jù)最佳實(shí)踐進(jìn)一步優(yōu)化

這里有一些最佳實(shí)踐,我們可以用來參考。

最佳實(shí)踐:我們建議在constructor中,只對一些變量的值進(jìn)行初始化,不執(zhí)行具體的業(yè)務(wù)邏輯(例如本文中的console.log(this.info)),而將涉及到業(yè)務(wù)邏輯的初始化,則放到單獨(dú)的初始化方法中完成。

這個(gè)最佳實(shí)踐也正符合ng2的理念,在ng2中,每個(gè)component都有一系列生命周期方法,并通過ng2框架自動(dòng)調(diào)用運(yùn)行,我們涉及到業(yè)務(wù)邏輯的初始化工作在這里拆分到單獨(dú)的初始化方法中,將更方便我們的代碼升級至ng2:

// index.controller.ts

// 接下來優(yōu)化typescript版本

export default class IndexCtrl {

info : string = 'hello world';

constructor () {

this._onInit();

}

// typescript中,類的方法默認(rèn)為public類型,因此不需要顯式聲明

hasInfo () : boolean {

return this.info.length > 0;

}

// 這里我們將初始化方法同意命名為_onInit

// 這里用typescript中的private關(guān)鍵字將方法聲明為私有方法

private _onInit () : void {

console.log(this.info);

}

}

最佳實(shí)踐:初始化方法并不需要在網(wǎng)頁中或其他模塊中調(diào)用,因此我們將其設(shè)定為私有方法(以_開頭),并寫在所有公開方法后面。

5. 升級為ng2

到此為止,雖然我們對項(xiàng)目的修改已經(jīng)如此巨大,但它依然可以兼容ng1(在typescript compiler幫忙編譯的情況下),而代碼卻已經(jīng)變得整潔而先進(jìn),對你現(xiàn)有的項(xiàng)目的運(yùn)行完全沒有任何影響。

準(zhǔn)備工作做的這么充分,升級至ng2就指日可待啦:

// index.component.ts

// ng2中沒有controller概念,我們將controller改造為component

import { Component, OnInit } from '@angular/core';

@Component({

selector: 'index-ctrl',

template: '{{ info }}'

})

export class IndexComponent implements OnInit {

info : string = 'hello world';

constructor () {

// 初始化方法為生命周期方法,由ng2框架自動(dòng)調(diào)用,這里不需要手動(dòng)調(diào)用

}

hasInfo () : boolean {

return this.info.length > 0;

}

// 這里用ng2中的生命周期方法

ngOnInit () : void {

console.log(this.info);

}

}

改到這里,index.component.ts的ng2升級就完成了,我們可以看到,除了添加一些annotation以外,component的核心代碼幾乎沒有變動(dòng)。

這說明經(jīng)過優(yōu)化的、完全兼容ng1的typescript版本核心代碼(也就是此步驟之前的所有重構(gòu)工作),可以直接升級至ng2,之要添加一些必要的annotation就可以了。有木有很平滑!

ngModule的ng2升級就留給你做練習(xí)吧。

原文:http://mp.weixin.qq.com/s?__biz=MzI5MDM2NjY5Nw==&mid=2247483843&idx=1&sn=90a1ac29272a0791464c934857ba0ee7&chksm=ec21b445db563d539ce014a1309cca36ed9c4c4d964fa4003a01f23396ea3da915f53107b883&mpshare=1&scene=1&srcid=1112L0HzskOj2DolrEDLs0vI#rd

(未完待續(xù))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,612評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,141評論 25 708
  • 通過AngularJS仿豆瓣一刻的案例:https://github.com/zhongxiaolian/doub...
    中小戀閱讀 1,888評論 1 21
  • AngularJS是什么?AngularJs(后面就簡稱ng了)是一個(gè)用于設(shè)計(jì)動(dòng)態(tài)web應(yīng)用的結(jié)構(gòu)框架。首先,它是...
    200813閱讀 1,788評論 0 3
  • 下回還吃嗎! 吃~
    Asue不2閱讀 209評論 0 0

友情鏈接更多精彩內(nèi)容