Typescript是一門擁有可選靜態類型系統、基於類的編譯型語言。這話若是你覺着怪,那嘗試這麼理解一下,她是JavaScript
的超集,也就是說,理論上她支持JavaScript
的全部特性,而後又提供了額外的優點。javascript
舉幾個小栗子說明其優點:css
範型支持html
錯誤提示前端
這類快速提示,在原生的JavaScript
中幾乎是沒法想象的,若是你的眼力不夠好,那問題只能等到運行時再發現了,浪費時間、浪費精力。html5
那既然Typescript
這麼屌,並且angular2就推薦使用Typescript
編寫應用,那AngularJS能不能也用Typescript
編寫應用?編寫的過程又是怎樣一種感覺!?答案是確定的,不然我我在這幹什麼^^。但使用起來是怎樣的感覺,請容許我先上一張動圖表達個人心情:java
到處有提示,糾錯不是夢!有沒有很風騷?jquery
但在一切開始以前,先讓我強烈建議各位安裝由Github主持開發的超強編輯器atom,再配合她的atom-typescript插件,絕對亮瞎曾經身爲JSer的你!!!webpack
OK,和以前用ES6編寫AngularJS程序是怎樣一種體驗同樣,咱們也代碼未動,工具先行,誰讓咱們前端界又被稱爲「帶薪搭環境界」,環境,如今成了每個入行前端朋友的夢魘!那麼一個快速、簡潔的、跟得上時代的腳手架就顯得尤其重要,因而我要介紹的是generator-ts-angular。git
yo
(若是你還沒安過的話)npm install -g yo
請注意前綴
sudo
,若是你使用的是unix
like操做系統的話angularjs
generator-ts-angular
npm install -g generator-ts-angular
請注意前綴
sudo
,若是你使用的是unix
like操做系統的話
generator-ts-angular
建立項目先找個你喜歡的目錄,而後運行下面的命令,由於一會新項目會直接建立在該目錄下。
yo ts-angular
上面命令回車後,生成器會問你以下問題,老實做答便可(注意: 對單頁應用沒經驗的孩紙,在Use html5 model
這個問題上,請選擇No
; 當問你Which registry would you use?
時,國內用戶選擇第一個淘寶鏡像安裝速度會快不少)
當命令執行完畢後,你就能在當前目錄下看到剛纔建立的項目了,本例中我使用的project name
是ngType
。
#進入剛建立的項目目錄 cd ngType #啓動調試服務 npm start
而後你就能夠在http://localhost:8080下,看到剛建立的項目的運行效果了:
ngType ├── css ├── etc ├── img ├── ts │ ├── features │ │ ├──common │ │ │ ├── directive │ │ │ └── listener │ │ └── todos │ │ ├── controller │ │ ├── model │ │ ├── partials │ │ └── service │ ├── fw │ │ ├── config │ │ ├── ext │ │ ├── init │ │ ├── lib │ │ └── service │ │ │ └── typings │ ├── angularjs │ ├── es6-collections │ ├── es6-promise │ └── jquery │ ├── index.html_vm ├── package.json ├── require.d.ts ├── tsconfig.json ├── tsd.json ├── webpack.config.dev.js ├── webpack.config.prod.js
css
, 這個不用多說吧,裏面有個main.css
,本身隨便改改看嘛。我這裏沒有默認引入less
或者sass
理由很是簡單,留給開發人員選擇本身喜好的工具
etc
, 一些公共配置性內容,能夠放在這裏,方便查找、通用
img
, 用我多說麼?放圖片的啦
ts
, 分爲features
和fw
兩大部分。這個內容略多,我後面詳述吧。
typings
, 這裏放着那些非Typescript
編寫的庫、原始類型的Declaration Files
,沒有這個,Typescript
的靜態分析工具就沒辦法提供那些強大提示和檢查了。
index.html_vm
, 單頁應用html
模版,最終的html
會由webpack
根據這個模版生成
package.json
, 項目的npm
描述文件,那些具體的工具命令(譬如剛纔用過的npm start
,都在這裏面定義好了)
require.d.ts
, 這個Declaration File
仍是因爲webpack
的緣故,詳情看這裏
tsconfig.json
, 這個是Typescript
須要的配置文件,裏面包含的編譯後的ECMAScript
版本,已經模塊規範...
tsd.json
, 這裏定義了本項目須要哪些額外的Declaration Files
,根據這個文件下載的定義文件,就放在前面提到的typings
目錄裏
webpack.config.dev.js
, 開發、調試環境使用的webpack
配置
webpack.config.prod.js
, 正式運行環境使用的webpack
配置。npm run release
命令會用這個配置,生成的結果都會給文件名加hash
,javascript
文件也會壓縮。
npm start
, 啓動調試服務器,使用webpack.config.dev.js
做爲webpack
配置,不直接生成物理文件,直接內存級別響應調試服務器資源請求。並且內置hot reload
,不用重啓服務,修改源碼,瀏覽器便可刷新看到新效果
npm run release
, 使用webpack.config.prod.js
做爲webpack
配置,生成壓縮、去緩存化的bundle
文件到ngType/build
目錄下。也就是說,若是你要發佈到生產環境或者其它什麼測試環境,你應該提供的是ngType/build
目錄下生成的那堆東西,而不是源碼。
npm run dev
, 使用webpack.config.dev.js
做爲webpack
配置,生成物理文件。
ts
目錄介紹common
那些通用的邏輯、UI組件能夠統統放在這裏,譬如爲了演示方便,我已經在features/common/directive
裏寫了一個Autofocus.ts
的指令。
//引入基類 import FeatureBase from '../../../fw/lib/FeatureBase'; class Autofocus extends FeatureBase { constructor() { //設置名稱,會在ts/main.ts裏的findDependencies中用到 super('AutofocusModule'); } _autoFocus() { return { restrict: 'A', //注意看,有了類型,當你"點"的時候,有提示出現哦 link: function($scope: angular.IScope, element: angular.IAugmentedJQuery) { element[0].focus(); } }; } execute() { //註冊該指令到當前feature this.directive('autofocus', this._autoFocus); } } //默認導出便可 export default Autofocus;
todos
這是一個單純的演示feature,裏面的內容咱們後面詳解
這裏面都是些所謂"框架"級別的設置,有興趣的話挨個兒打開瞧瞧嘛,沒什麼大不了的。
特別注意,大部分時候,你的開發都應該圍繞
features
目錄展開,之因此叫fw
,就是和具體業務無關,除非你須要修改框架啓動邏輯,路由控制系統。。。,不然不須要動這裏的內容
入口文件
/** * * 這裏連用兩個ensure,是webpack的特性,能夠強制在bundle時將內容拆成兩個部分 * 而後兩個部分還並行加載 * */ //第一個部分是一個很小的spinner,在並行加載兩個chunk時,這個很是小的部分90%會競速成功 //因而你就看到了傳說中的loading動畫 require.ensure(['splash-screen/dist/splash.min.css', 'splash-screen'], function(require) { //這裏的強轉any類型,是由於我使用的功能是webpack的特性,因此Typescript並不知道 //因此要強制忽略Typescript的提示 (<any>require('splash-screen/dist/splash.min.css')).use(); (<any>require('splash-screen')).Splash.enable('circular'); }); //因爲這裏是真正的業務,代碼多了太多,因此體積也更大,加載也更慢,因而在這個chunk加載完成前 //有個美好的loading動畫,要比生硬的白屏更優雅。 //放心,這個chunk加載完後,loading動畫也會被銷燬 require.ensure(['../css/main.css', './main'], function(require) { (<any>require('../css/main.css')).use(); //這裏啓動了真正的「框架」 var App = (<any>require('./main')).default; (new App()).run(); });
「框架」啓動器
//引入依賴部分 import * as ng from 'angular'; import Initializers from './fw/init/main'; import Extensions from './fw/ext/main'; import Configurators from './fw/config/main'; import Services from './fw/service/main'; import Features from './features/main'; import {Splash} from 'splash-screen'; import FeatureBase from './fw/lib/FeatureBase'; class App { //聲明成員變量及其類型 appName: string; features: Array<FeatureBase>; depends: Array<string>; app: angular.IModule; constructor() { //這裏至關於ng-app的名字 this.appName = 'ngType'; //實例化全部features Features.forEach(function(Feature) { this.push(new Feature()); }, this.features = []); } //從features實例中提取AngularJS module name //並將這些name做爲ngType的依賴 //會在下面createApp時用到 findDependencies() { this.depends = Extensions.slice(0); var featureNames = this.features .filter(feature => !!feature.export) .map(feature => feature.export); this.depends.push(...featureNames); } //激活初始化器,個別操做但願在AngularJS app啓動前完成 beforeStart() { Initializers.forEach((Initializer) => (new Initializer(this.features)).execute()); this.features.forEach(feature => feature.beforeStart()); } //建立ngType應用實例 createApp() { this.features.forEach(feature => feature.execute()); this.app = ng.module(this.appName, this.depends); } //配置ngType configApp() { Configurators.forEach((Configurator) => (new Configurator(this.features, this.app)).execute()); } //註冊fw下的「框架」級service registerService() { Services.forEach((Service) => (new Service(this.features, this.app)).execute()); } //看到了麼,這裏我會銷燬loading動畫,並作了容錯 //也就是說,即使你遇到了那微乎其微的情況,loading動畫比業務的chunk加載還慢 //我也會默默的把它收拾掉的 destroySplash(): void { var _this = this; Splash.destroy(); (<any>require('splash-screen/dist/splash.min.css')).unuse(); setTimeout(function() { if (Splash.isRunning()) { _this.destroySplash(); } }, 100); } //啓動AngularJS app launch() { ng.bootstrap(document, [this.appName], { strictDi: true }); } run(): void { this.findDependencies(); this.beforeStart(); this.createApp(); this.configApp(); this.registerService(); this.destroySplash(); this.launch(); } } export default App;
ts/features/todos/main.ts
//一個feature的main.ts負責管理該feature所用到的全部模塊 import FeatureBase from '../../fw/lib/FeatureBase'; //引入路由定義 import Routes from './Routes'; //引入controller定義,和service定義 import TodosController from './controller/TodosController'; import TodosService from './service/TodosService'; class Feature extends FeatureBase { constructor() { //指定feature名字 super('todos'); //設置路由 this.routes = Routes; } execute() { //註冊controler到本feature this.controller('TodosController', TodosController); //註冊service到本feature this.service('TodosService', TodosService); } } //導出本feature,別擔憂,再上一級調用方會正確處理的 export default Feature;
簡單到沒朋友
//引入路由對應的模版,仍是由於webpack,將模版 //做爲字符串引入,就是這麼easy //仍是由於webpack特性的緣故,這裏只能經過強轉的形式讓IDE忽略檢查 var tpl = (<string>require('./partials/todos.html')); import Route from '../../fw/lib/Route'; const routes: Route[] = [{ id: 'todos', isDefault: true, when: '/todos', controller: 'TodosController', controllerAs: 'todos', template: tpl }]; export default routes;
這裏路由的內容你能夠寫錯試試看,會有錯誤提示哦!
import * as angular from 'angular'; import InternalService from '../../../fw/service/InternalService'; import TodosService from '../service/TodosService'; import Todo from '../model/Todo'; //定義一個表示狀態的類型,能夠約束輸入哦! interface IStatusFilter { completed?: boolean } //自定義TodosScope,由於Scope自己沒有todolist屬性 //須要自定義添加 interface ITodosScope extends angular.IScope { todolist?: Array<Todo>; } class TodosController { //聲明成員,並賦予初始值 todolist: Array<Todo> = []; statusFilter: IStatusFilter = {}; remainingCount: number = 0; filter: string = ''; editedTodo: Todo; newTodo: string = ''; //屌炸天的ng-annotate插件,媽媽不再用擔憂我手寫什麼inline annotation, 或者$inject屬性了 /*@ngInject*/ constructor(public $scope: ITodosScope, public TodosService: TodosService, public utils: InternalService) { this._init_(); this._destroy_(); } _init_() { this.$scope.todolist = this.todolist; //從service中獲取初始值,並放入this.todolist this.TodosService .getInitTodos() .then(data => { this.todolist.push(...data); }); //監視todolist的變化 this.$scope.$watch('todolist', this.onTodosChanged.bind(this), true); } onTodosChanged() { this.remainingCount = this.todolist.filter((todo) => !todo.completed).length; } addTodo() { this.todolist.push({ title: this.newTodo, completed: false }); this.newTodo = ''; } editTodo(todo) { this.editedTodo = todo; } doneEditing(todo: Todo) { this.editedTodo = undefined; if (!todo.title.trim()) { this.removeTodo(todo); } } removeTodo(todo: Todo) { this.$scope.todolist = this.todolist = this.todolist.filter((t) => t !== todo); } markAll(checked: boolean) { this.todolist.forEach(todo => todo.completed = checked); } toggleFilter(e: MouseEvent, filter: string) { this.utils.stopEvent(e); this.filter = filter; this.statusFilter = !filter ? {} : filter === 'active' ? { completed: false } : { completed: true }; } clearDoneTodos() { this.$scope.todolist = this.todolist = this.todolist.filter((todo) => !todo.completed); } _destroy_() { this.$scope.$on('$destroy', () => { }); } } export default TodosController;