|
为了提高团队协作效率,提高代码的可读性、可复用性和可移植性,便于前端输出高质量的代码以及后期优化维护,所以需要一定的规范,接下来我们一起学习并检查一下,这些规范我们有做到了吗?
规范1️⃣:项目目录结构规范化!
一个项目目录下的文件结构:Angular开发中,项目的主要代码都是放在app目录下的,规范的代码都很容易辨识,组件文件是带.component,模块文件是带.module,管道文件是带.pipe,枚举文件是带.enum,服务文件是带.service,指令文件是带.directive,所以:

image.png
// 电脑端app中的主要代码
app
|-components // 其它组件存储位置
|-pages // 页面组件存储位置
|-directives // 指令文件存储位置
|-enums // 枚举文件存储位置
|-pipes // 管道文件存储位置
|-services // 服务:比如api服务、公用逻辑服务
|-shared // 共享模块:共享组件,这里也可以直接把directive,enums,pipes放在当前目录。
|-components // 共享组件存储位置
|-shared.module.ts // 定义共享模块特性
|-third.module.ts // 定义第三方组件引入的模块
|-app-routing.module.ts // 路由模块
|-app.component.ts // 路由导航入口
|-app.module.ts // 项目根模块
多个项目目录下的文件结构:一般都是一个项目的门户端,移动端,管理端放在同一个目录中共同维护。
项目根目录
|-pageage.json
|-projects
|-project1 // 项目一
|- src
|-app
|-project2 // 项目二
|- src
|-app
规范2️⃣:文件规范化命名!
- 符号名与文件名:
- 为所有东西使用一致的命名约定,以它们所代表的东西命名!
- 使用大写驼峰命名法来命名类。符号名匹配它所在的文件名!
- 坚持在文件名后面追加约定的类型后缀(.component.ts、.directive.ts、.pipe.ts、.service.ts、.enum.ts、。module.ts)
- 服务名:
- -使用一致的规则命名服务,以它们的特性来命名。
- 为服务的类名加上Service后缀。例如,获取数据或图表应该命名为DataService或ChartService。
- 有些词汇显然就是服务,比如那些以"-er"后缀结尾的,直接是Logger就行。
规范3️⃣:短代码,尽量实现css、ts、html文件三文件合一。
首先我们都知道angular创建组件的时候,默认生成四个文件:
~.component.html // html文件
~.component.less // 样式文件
~.component.spec.ts // 测试文件
~.component.ts // ts逻辑文件
然后分别在ts文件中引入:
@Component({
selector: 'app-~', // 组件名称,规范使用都是带app-前缀
templateUrl: './~.component.html',
styleUrls: ['./~.component.less'],
})
当代码比较少的时候,正常是低于3行代码直接在ts文件中定义less、html,然后删掉组件的less、html两个文件:
// 在反引号``里写内联代码,注意一下这里与独立分离文件的引用区别
@Component({
selector: 'app-personal',
template:``,
styles:[``]
})
如果超过三行,尽量单独使用,为什么不直接都是在ts文件中定义?因为:
- 巨大的、内联和样式表会遮盖组件的意图和实现方式,削弱可读性和可维护性。
- 在多数编辑器中,编写内联的模板和样式表时都无法使用语法提示和代码片段功能。
规范4️⃣:用ngSwitch替换ngIf!
- ngIf:该指令会根据指定表达式返回的boolean类型值对该元素做添加/y删除DOM树的操作。每一次的切换对性能都有一定损耗。
<p *ngIf=&#34;true&#34;>我在这</p>
<p *ngIf=&#34;false&#34;>你在哪</p>
- ngSwitch:该指令表达式在模板上按条件切换DOM结构,元素内使用ngSwitch会被保存在模板中的。也就是说不会删除DOM树,对元素的显示有缓存,所以推荐使用。
<span [ngSwitch]=&#34;true&#34;>
<p *ngSwitchCase=&#34;true&#34;>我在这</p>
<p *ngSwitchCase=&#34;false&#34;>你在哪</p>
<p *ngSwitchDefault>我是默认</p>
</span>规范5️⃣:使用get和set需要注意什么?
当父传子组件中,我们需要在修改的值中做处理的时候,我们需要做什么?
private _isEdit:boolean;
@Input() set isEdit(value:boolean){
this._isEdit = value;
}
get isEdit(){
return this._isEdit;
}
注意地,我们需要先声明一个带_前缀的私有变量,然后把改变的值赋给整个私有变量。
规范6️⃣:ngOnInit周期函数不要写长逻辑!
尽量不要在ngOnInit()周期中写复杂逻辑,因为ngOnInit周期函数中都是用来初始化数据的,元素没有挂载,其实不管是它,其它周期函数也是,尽量把周期内需要的逻辑分离出来用函数编写,然后在周期函数中调用:
ngOnInit(){
this.initData();
...
}
initData(){
}
规范7️⃣:表单错误验证使用ng-template!
表单错误验证信息出现错误的时候,尽量书写验证提示信息在ng-template中。这样使那些有比较多验证的表单项代码更清晰,更容易维护。
<nz-form-item>
<nz-form-label nzSm=&#34;4&#34; nzRequired>标题</nz-form-label>
<nz-form-control nzSm=&#34;18&#34; nzHasFeedback [nzErrorTip]=&#34;titleTpl&#34;>
<input nz-input formControlName=&#34;Title&#34; placeholder=&#34;标题&#34;>
</nz-form-control>
<ng-template #titleTpl let-control>
<ng-container *ngIf=&#34;control.hasError(&#39;required&#39;)&#34;>请填写标题</ng-container>
<ng-container *ngIf=&#34;control.hasError(&#39;maxlength&#39;)&#34;>最多填写200个字符</ng-container>
</ng-template>
</nz-form-item>
规范8️⃣:创建shared目录统一管理共用组件。
- 创建共享模块shared.module.ts。 > 注意地,imports导入模块是,共享组件中需要用到的模块,如果不是共享组件,但又有其他多个组件用到,务必需要在exports中导出。但共享组件必须要导出
@NgModule({
imports: [], // 导入全局共享模块
declarations: [],// 声明共享组件、管道、指令等
exports: [],// 导出imports
providers: [],// 共享服务的注入 })
- 创建third.module.ts,按需引入第三方组件。
// 按需引入最好是精确到组件路径
import { NzTreeModule } from &#39;ng-zorro-antd/tree&#39;;
export const SharedThirdModule = [
NzTreeModule,
]规范9️⃣:优雅地声明嵌套对象表单初始化!
constructor(private fb: FormBuilder) {}
form: any;
ngOnInit() {
this.initData();
}
initData() {
// 表单定义
this.form = this.fb.group({
Attribute:this.fb.array([])
});
// 嵌套对象引入
this.Attribute.push(
this.fb.group({
MaxScore: [null],
MinScore: [null],
})
)
this.form.control[&#39;Attribute&#39;].controls[&#39;MaxScore&#39;].setValue();//
}
规范 :使用 ngTemplateOutlet 自定义指令创建动态模板
<ng-container [ngTemplateOutlet]=&#34;commonTpl&#34;></ng-container>
<ng-template #commonTpl>
// 公共html
</ng-template>
规范1️⃣1️⃣:angular.json中需要注意什么?
- 全局样式的引入,不需要在各个组件css文件中单独引入
- UI组件样式的引入。
- 打包限制。
&#34;build&#34;: {
&#34;builder&#34;: &#34;@angular-builders/custom-webpack:browser&#34;,
&#34;options&#34;: {
...
&#34;aot&#34;: true,
&#34;assets&#34;: [&#34;src/favicon.ico&#34;, &#34;src/assets&#34;],
&#34;styles&#34;: [&#34;src/styles.less&#34;,&#39;UI组件样式的引入&#39;],
&#34;scripts&#34;: [],
...
&#34;customWebpackConfig&#34;: {
...
}
},
&#34;configurations&#34;: {
...
&#34;budgets&#34;: [
{
// 打包限制初始化警告和错误体积大小
&#34;type&#34;: &#34;initial&#34;,
&#34;maximumWarning&#34;: &#34;5mb&#34;,
&#34;maximumError&#34;: &#34;10mb&#34;
},
{
// 限制任何组件样式警告和错误体积大小
&#34;type&#34;: &#34;anyComponentStyle&#34;,
&#34;maximumWarning&#34;: &#34;6kb&#34;,
&#34;maximumError&#34;: &#34;10kb&#34;
}
]
}
}
},规范1️⃣2️⃣:pageage.json尽量不要定义太多字段属性!
因为pageage文件主要是用来定义依赖相关属性,当然也可以把eslint、prettier、ts配置等文件也可以放进来,但是一般是不必要,尽可能把这些文件单独分离,这样方便后期维护,一般是包含下面几个字段属性就可以了:
{
... // 项目基本信息
&#34;scripts&#34;: {
// 脚本文件
},
&#34;private&#34;: true,
&#34;dependencies&#34;: {
// 生产依赖
},
&#34;devDependencies&#34;: {
// 开发依赖
}
}
规范1️⃣3️⃣:防止多次导入CoreModule!
- 只有AppModule才允许导入CoreModule。
- 防范多次导入CoreModule,通过添加Guards逻辑来实现,Guards可以阻止对CoreModule的多次导入,Guards会禁止创建单例服务的多个实例。
// app/core/module-import-guard.ts
export function throwIfAlreadyLoaded(parentModule:any,moduleName:string){
if(parentModule){
throw new Error(`提示信息`)
}
}
// 在app/core/core.module.ts中的constructor中引入使用
throwIfAlreadyLoaded(throwIfAlreadyLoaded,&#39;CoreModule&#39;)
规范1️⃣4️⃣:把逻辑放到服务中!
- 在组件中只包含与视图相关逻辑。其它逻辑应该放到服务中。
- 坚持把可重用的逻辑放到服务中,保持组件简单,聚焦于它们预期目的。
规范1️⃣5️⃣:不要把输出属性添加前缀!
- 坚持命名事件时,不要带前缀on。
- 坚持把事件处理器方法命名为on前缀之后紧跟着事件名。
// child子组件中
@Output() save = new EventEmitter<boolean>()
// parent父组件中
<child (save)=&#34;onSave($event)&#34; /> |
|