翻译外国朋友的一篇文章:https://codepen.io/martinmcwhorter/post/angularjs-1-x-with-typescript-or-es6-best-practices
写在前面,有些翻译便于理解用了自己的语言,望见谅。
在用angularjs和TypeScript开发的过去几年里,有一些比较好的经验并没有在大多数项目中落实。
这些最佳实践适用于很多场景,不管你是普通的JavaScript(ES5)开发者,或是ES6(ES2015),亦或是TypeScript。
代码不应该是对未来封闭的;取而代之的,代码是要被设计成可扩展的;跟随着我总结的这些最佳实践将帮助你写出的代码非常容易的升级到angular2,而angular2则更加容易维护。
下面是最佳实践:
一、使用外部模块
使用外部模块将会为你做两件事。
1、当构建你的应用时,这些外部模块的使用将自动的为你添加各依赖文件。
2、外部模块将避免你频繁的使用///注解。
举例来说:
// app.ts
import {module} from ‘angular’;
export let app = module(‘app’, [
require(‘angular-ui-router’),
require(‘angular-animate’),
require(‘angular-ui-bootstrap’),
require(‘angular-translate’)
]);
// PersonComponent.ts
import {app} from ‘./app’;
export class PersonComponent {
// …
}
app.component(‘PersonComponent’, PersonComponent);
二、不要使用TypeScript内部的模块/命名空间
在外部模块被普遍使用之前,TypeScript的内部模块使用是很常见的,现在改名叫名字空间了。
TypeScript名字空间是基于JavaScript内部模块模式的。这种模式的出现主要是因为在JavaScript中模块封装的缺乏。随着CommonJS和ES6模块以及模块语法的介绍,这种内部模块模式是时候要被避免了。
记住,使用外部commonJS/ES6模块,而不是TypeScript名字空间!!!
三、不要使用IIFE(Immediatly-Invoked Function Expression:直接调用函数表达式)
IIFEs在JavaScript开发过程中是非常常见的,因为这样允许你去封装你的代码。
(function() {
// encapsulated closure protected from other code
})();
那我们使用外部模块方式就可以消除以IIFE包装你的代码的方式。相反构建任务系统(browserify,jspm或webpack)和模块系统将捆绑和加载你的代码到一个闭包。
IIFEs也将会导致你的模块会有导出类型相关的bug。
四、仅仅只为一个目的创建的AngularJS模块
请保持你的angularJS模块的最小数量,当你需要创建一个angularjs模块时,请保证有一个充足的理由,比如:
1、需要在不同应用之间复用代码。
2、为了方便测试。
随着ES6模块、commonJS模块的出现,angularjs是时候该成为过去式了。
五、将services定义为classes
定义你的service为class,这有,angularjs的依赖注入机制将会把你的service实例化为单例类,这将带来一系列好处:使你的代码更干净,更容易阅读,更容易维护以及更容易测试。
最后呢,将你的service定义为class会使得你的代码对TypeScript的静态类型更加友好。
import {app} from ‘../app’;
export class UserProxy {
static $inject = ['$http'];
constructor(private $http: ng.IHttpService) {}
login(login: UserLogin) {
return this.$http.post('/api/login', login);
}
logout() {
return this.$http.post('/api/logout', {});
}
}
app.service(‘userProxy’, UserProxy);
import {app} from ‘../app’;
import {UserProxy} from ‘./userProxy’;
export default class LoginController {
static $inject = ['userProxy'];
constructor(private userProxy: UserProxy) {}
model: UserLogin = {};
submit(model: UserLogin) {
this.userProxy.login(model).then(
() => {
// handle success
},
() => {
// handle error
});
}
}
app.controller(‘LoginController’, LoginController);
在之前的例子中,我们证明可以使用imports进行干净的封装。
1、我们可以通过import引入类型:import {UserProxy} from ‘./userProxy’;
2、然后我们在构造器中使用到了被引用的类型:constructor(private userProxy: UserProxy) {}
3、最后我们验证了静态类型的使用:this.userProxy.login(model).then(
经验表明:这是一种比TypeScript内置模块/名字空间更易于管理的一种方法。
六、当需要时,尽量使用factory方法
大部分情况下,你的服务会是单例的,通过angularjs的依赖注入使用service方法去注册这些:app.service(‘userProxy’, UserProxy).
使用factory方法的最显而易见的一个情景就是当你使用工厂模式时,让我们以之前的UserProxy作为例子。在这个例子中,让我们假设有多个JSON/HTTP端点实现这个相同的API,我们将使得我们使用factory创建的class更有复用性。
下面我们更新我们的代码如下:
// userProxy.ts
import {app} from ‘../app’;
export class UserProxy {
constructor(private $http: ng.IHttpService,
private basePath: string) {}
login(login: UserLogin) {
return this.$http.post(this.basePath + '/login', login);
}
logout() {
return this.$http.post(this.basePath + '/logout', {});
}
}
现在我们使用factory来创建这个类的示例:
// userProxyFactory.ts
import {app} from ‘../app’;
import {UserProxy} from ‘./userProxy’;
export UserProxy;
export type userProxyFactory = (basePath: string) => UserProxy;
userProxy. inject=[′ http’];
function userProxy($http: ng.IHttpService): userProxyFactory {
return (basePath: string) => {
return new UserProxy($http, basePath);
}
}
app.factory(‘userProxy’, userProxy);
以上可以在controller中类似于如下方式使用:
import {app} from ‘../app’;
import {userProxyFactory, UserProxy} from ‘./userProxyFactory’;
export default class LoginController {
private userProxy: UserProxy
static $inject = ['userProxy'];
constructor(userProxyFactory: userProxyFactory) {
this.userProxy = userProxyFactory('/api1');
}
model: UserLogin = {};
submit(model: UserLogin) {
this.userProxy.login(model).then(
() => {
// handle success
},
() => {
// handle error
});
}
}
app.controller(‘LoginController’, LoginController);
七、不能直接绑定controller中的属性和方法
许多案例呢仍是在controller内部直接绑定方法和属性到注入的$scope。
// DON’T DO THIS
export class PersonController {
static $inject = ['$scope'];
constructor(private $scope: ng.IScope) {
$scope.name = "Person's Name";
$scope.save = () => {
// . . .
}
}
}
这真的是很糟糕的实践,有以下原因:
1、这个对内存是有影响的,这个controller中的每一个实例将会为每一个方法都有一份绑定到scope的拷贝,而如果这个方法是被定义为实例方法,它的实现将被整个实例共享。
2、这个类将不会是可导出的且在单元测试中无法使用。
3、在大型的嵌套应用中,你将继承作用域冲突,这些冲突将会导致一些列奇怪的现象,他们通常很难以调试跟踪。
4、当我们使用带有父对象的引用时,通过引用传值将会变得简单。
解决以上的方式如下:
export class PersonController {
name = "Person's Name";
save() {
// . . .
}
}
因为有ng-router和ui-router,你只需要通过controllerAs配置属性为你的controller实例命名。
八、使用TypeScript做单元测试
使用TypeScript的一个很主要的优势可以通过类型注解来给你想要测试的类做测试。对于零散的测试的害怕不应组织我们去重构糟透了的代码。在你的测试用例中使用类型将帮助你的测试用例保持干净与可读性。
九、总结
TypeScript将会是让你代码稳定且容易被重构,你的服务将会有固定的接口供你controller和conponents调用,bug将会在编译阶段就被发现而不是在质量检测或是上线环节。
即使没有TypeScript,这些实践中的很多同样在ES6或ES5中适用。在ES5,你将只需要适用commonJS的require语法来替换ES6的import语法;而JavaScript的原型模式也将代替class关键字。
最后两个观点:
1、请使用构建系统:gulp或grunt。
2、请使用NPM(node package manage):不要使用bower,你所需要的是only一个JavaScript包管理系统,bower是多余的。
谢谢!!
此致,敬礼!!