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;