摘要:风格指南的最佳实践涵盖体系结构文件结构组件单向数据流和生命周期。模块概述模块的设计直接反应了我们的文件结构从而保持了可维护性和可预测性。指令不应再声明模板和控制器,或者通过绑定接收数据。指令应该只是用来和互交。
# AngularJS 风格指南 (ES2015)
AngularJs1.6.x的最佳实践.涵盖体系结构,文件结构,组件,单向数据流和生命周期。 目录
模块化体系结构
概述
Root module
Component module
Common module
Low-level modules
文件命名约定
可伸缩文件结构
组件
概述
支持的属性
控制器
单项数据流和事件
有状态组件/容器型组件
无状态组件/展示型组件
路由组件
指令
概述
推荐的属性
Constants or Classes
服务
概述
Classes for Service
样式
ES2015 and Tooling
状态管理
模块化体系结构Angular应用程序中的每个模块都应该是组件模块。组件模块用来封装逻辑、模板、路由和子组件。
模块概述模块的设计直接反应了我们的文件结构, 从而保持了可维护性和可预测性。我们最好有三个高级模块: root、component和common。root模块是用来启动我们应用和相应模板的基础模块,root模块中导入component和common模块作为依赖模块。component和common模块则负责引入Low-level modules,Low-level modules通常包含可重用的组件,控制器,服务,指令,过滤器和测试.
Back to top
Root 模块root模块中定义整个应用的根组件,根组件中通常包含路由组件,比如ui-router 中的 ui-view
// app.component.js export const AppComponent = { template: `Hello world ` };
// app.module.js import angular from "angular"; import uiRouter from "angular-ui-router"; import { AppComponent } from "./app.component"; import { ComponentsModule } from "./components/components.module"; import { CommonModule } from "./common/common.module"; import "./app.scss"; export const AppModule = angular .module("app", [ ComponentsModule, CommonModule, uiRouter ]) .component("app", AppComponent) .name;
使用 .component("app", AppComponent) 方法在root模块中注册根组件,你可能注意到样式也被引入到了根组件,我们将在后面的章节介绍这一点.
Back to top
Components 模块所有的可重用组件应该注册在component模块上。上面例子中展示了我们是如何导入 ComponentsModule 并将它们注册在root模块中。这样做可以轻松的将component模块移动到任何其他应用程序中,因为root模块与component模块是分离的。
import angular from "angular"; import { CalendarModule } from "./calendar/calendar.module"; import { EventsModule } from "./events/events.module"; export const ComponentsModule = angular .module("app.components", [ CalendarModule, EventsModule ]) .name;
Back to top
Common module所有我们不希望用在其他应用中的组件应该注册在common模块上。比如布局、导航和页脚之类的内容。
import angular from "angular"; import { NavModule } from "./nav/nav.module"; import { FooterModule } from "./footer/footer.module"; export const CommonModule = angular .module("app.common", [ NavModule, FooterModule ]) .name;
Back to top
Low-level modulesLow-level module是包含独立功能的组件模块,将会被导入到像component module或者是 common module这样的higher-level module中,下面是一个例子。一定要记住在每个export的模块最后添加.name。你可能注意到路由定义也在这个模块中,我们将在本指南后面的章节中介绍这一点。
import angular from "angular"; import uiRouter from "angular-ui-router"; import { CalendarComponent } from "./calendar.component"; import "./calendar.scss"; export const CalendarModule = angular .module("calendar", [ uiRouter ]) .component("calendar", CalendarComponent) .config(($stateProvider, $urlRouterProvider) => { "ngInject"; $stateProvider .state("calendar", { url: "/calendar", component: "calendar" }); $urlRouterProvider.otherwise("/"); }) .name;
Back to top
文件命名约定保持文件名简单,并且使用小写字母,文件名使用 " - "分割,比如 calendar-grid.*.js 。使用 *.component.js 标示组件,使用 *.module.js 标示模块
calendar.module.js calendar.component.js calendar.service.js calendar.directive.js calendar.filter.js calendar.spec.js calendar.html calendar.scss
Back to top
可伸缩文件结构文件结构是非常重要的,这描述了一个可伸缩和可预测的结构,下面是一个例子。
├── app/ │ ├── components/ │ │ ├── calendar/ │ │ │ ├── calendar.module.js │ │ │ ├── calendar.component.js │ │ │ ├── calendar.service.js │ │ │ ├── calendar.spec.js │ │ │ ├── calendar.html │ │ │ ├── calendar.scss │ │ │ └── calendar-grid/ │ │ │ ├── calendar-grid.module.js │ │ │ ├── calendar-grid.component.js │ │ │ ├── calendar-grid.directive.js │ │ │ ├── calendar-grid.filter.js │ │ │ ├── calendar-grid.spec.js │ │ │ ├── calendar-grid.html │ │ │ └── calendar-grid.scss │ │ ├── events/ │ │ │ ├── events.module.js │ │ │ ├── events.component.js │ │ │ ├── events.directive.js │ │ │ ├── events.service.js │ │ │ ├── events.spec.js │ │ │ ├── events.html │ │ │ ├── events.scss │ │ │ └── events-signup/ │ │ │ ├── events-signup.module.js │ │ │ ├── events-signup.component.js │ │ │ ├── events-signup.service.js │ │ │ ├── events-signup.spec.js │ │ │ ├── events-signup.html │ │ │ └── events-signup.scss │ │ └── components.module.js │ ├── common/ │ │ ├── nav/ │ │ │ ├── nav.module.js │ │ │ ├── nav.component.js │ │ │ ├── nav.service.js │ │ │ ├── nav.spec.js │ │ │ ├── nav.html │ │ │ └── nav.scss │ │ ├── footer/ │ │ │ ├── footer.module.js │ │ │ ├── footer.component.js │ │ │ ├── footer.service.js │ │ │ ├── footer.spec.js │ │ │ ├── footer.html │ │ │ └── footer.scss │ │ └── common.module.js │ ├── app.module.js │ ├── app.component.js │ └── app.scss └── index.html
顶级文件夹仅包含index.html and app/,其余的模块在app/中
Back to top
组件 组件概述组件是带有控制器的模板,组件可以通过 bindings 定义数据或是事件的输入和输出,你可以将组件视为完整的代码段,而不仅仅是 .component() 定义的对象,让我们探讨一些关于组件的最佳实践和建议, 然后深入了解如何通过有状态的、无状态的和路由组件的概念来构造它们。
Back to top
支持的属性这些是 .component() 支持的属性
Property | Support |
---|---|
bindings | Yes, use "@", "<", "&" only |
controller | Yes |
controllerAs | Yes, default is $ctrl |
require | Yes (new Object syntax) |
template | Yes |
templateUrl | Yes |
transclude | Yes |
Back to top
控制器控制器应该只和组件一起使用,如果你感觉需要一个控制器,可能你需要的是一个管理这个特定行为的组件。
这是一些使用 Class 定义controllers的建议:
使用 controller: class TodoComponent {...} 这种写法以应对未来向Angular迁移
始终使用 constructor 来进行依赖注入
使用 babel-plugin-angularjs-annotate 的 "ngInject";语法
如果需要访问词法作用域,请使用箭头函数
除了箭头函数 let ctrl = this; 也是可以接受的
将所有的公共函数绑定到 class{}上
适当的使用 $onInit, $onChanges, $postLink and $onDestroy 等生命周期函数
Note: $onChanges is called before $onInit, see resources section for articles detailing this in more depth
Use require alongside $onInit to reference any inherited logic
Do not override the default $ctrl alias for the controllerAs syntax, therefore do not use controllerAs anywhere
Back to top
单向数据流和事件AngularJS 1.5中引入了单向数据流,这重新定义了组件之间的通信.
这里有一些使用单向数据流的建议:
在组件中接受数据时,总是使用单向数据绑定语法 "<"
任何时候都不要在使用双向绑定语法 "="
使用 bindings 绑定传入数据时,在 $onChanges 生命周期函数中深复制传入的对象以解除父组件的引用
在父组件的方法中使用 $event 作为函数的参数(查看下面有状态组件的例子$ctrl.addTodo($event))
从无状态组件的方法中传递回 $event: {} 对象(查看下面的无状态组件例子 this.onAddTodo)
Back to top
有状态组件让我们定义一个有状态组件
获取状态,实质上是通过服务与后台API通信
不要直接变化状态
一个包括low-level module定义的有状态组件的例子(为了简洁省略了一些代码)
/* ----- todo/todo.component.js ----- */ import templateUrl from "./todo.html"; export const TodoComponent = { templateUrl, controller: class TodoComponent { constructor(TodoService) { "ngInject"; this.todoService = TodoService; } $onInit() { this.newTodo = { title: "", selected: false }; this.todos = []; this.todoService.getTodos().then(response => this.todos = response); } addTodo({ todo }) { if (!todo) return; this.todos.unshift(todo); this.newTodo = { title: "", selected: false }; } } }; /* ----- todo/todo.html ----- *//* ----- todo/todo.module.js ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; import "./todo.scss"; export const TodoModule = angular .module("todo", []) .component("todo", TodoComponent) .name;
这个例子展示了一个有状态组件,在控制器中通过服务获取数据,然后传递给无状态子组件。注意我们在模板中并没有使用ng-repeat 之类的指令,而是委托给
Back to top
无状态组件让我们第一我们所谓的无状态组件:
使用 bindings: {} 明确的定义输入和输出
通过属性绑定传递数据
数据通过事件离开组件
不要关心数据来自哪里 - 它是无状态的
是高度可重用的组件
也被称为展示型组件
一个无状态组件的例子(
/* ----- todo/todo-form/todo-form.component.js ----- */ import templateUrl from "./todo-form.html"; export const TodoFormComponent = { bindings: { todo: "<", onAddTodo: "&" }, templateUrl, controller: class TodoFormComponent { constructor(EventEmitter) { "ngInject"; this.EventEmitter = EventEmitter; } $onChanges(changes) { if (changes.todo) { this.todo = Object.assign({}, this.todo); } } onSubmit() { if (!this.todo.title) return; // with EventEmitter wrapper this.onAddTodo( this.EventEmitter({ todo: this.todo }) ); // without EventEmitter wrapper this.onAddTodo({ $event: { todo: this.todo } }); } } }; /* ----- todo/todo-form/todo-form.html ----- *//* ----- todo/todo-form/todo-form.module.js ----- */ import angular from "angular"; import { TodoFormComponent } from "./todo-form.component"; import "./todo-form.scss"; export const TodoFormModule = angular .module("todo.form", []) .component("todoForm", TodoFormComponent) .value("EventEmitter", payload => ({ $event: payload })) .name;
注意,
Back to top
路由组件让我们定义一个路由组件。
一个带有路由定义的有状态组件
没有 router.js 文件
使用路由组件来定义他们自己的路由逻辑
组件数据的输入是通过路由的 resolve块
在这个例子中,我们将使用路由定义和 bindings重构
/* ----- todo/todo.component.js ----- */ import templateUrl from "./todo.html"; export const TodoComponent = { bindings: { todoData: "<" }, templateUrl, controller: class TodoComponent { constructor() { "ngInject"; // Not actually needed but best practice to keep here incase dependencies needed in the future } $onInit() { this.newTodo = { title: "", selected: false }; } $onChanges(changes) { if (changes.todoData) { this.todos = Object.assign({}, this.todoData); } } addTodo({ todo }) { if (!todo) return; this.todos.unshift(todo); this.newTodo = { title: "", selected: false }; } } }; /* ----- todo/todo.html ----- *//* ----- todo/todo.service.js ----- */ export class TodoService { constructor($http) { "ngInject"; this.$http = $http; } getTodos() { return this.$http.get("/api/todos").then(response => response.data); } } /* ----- todo/todo.module.js ----- */ import angular from "angular"; import uiRouter from "angular-ui-router"; import { TodoComponent } from "./todo.component"; import { TodoService } from "./todo.service"; import "./todo.scss"; export const TodoModule = angular .module("todo", [ uiRouter ]) .component("todo", TodoComponent) .service("TodoService", TodoService) .config(($stateProvider, $urlRouterProvider) => { "ngInject"; $stateProvider .state("todos", { url: "/todos", component: "todo", resolve: { todoData: TodoService => TodoService.getTodos() } }); $urlRouterProvider.otherwise("/"); }) .name;
Back to top
指令 指令概述指令有 template, scope 绑定, bindToController, link 和很多其他特性。在基于组件的应用中应该注意他们的使用。指令不应再声明模板和控制器,或者通过绑定接收数据。指令应该只是用来和DOM互交。简单来说,如果你需要操作DOM,那就写一个指令,然后在组件的模板中使用它。如果您需要大量的DOM操作,还可以考虑$ postLink生命周期钩子,但是,这不是让你将所有DOM操作迁移到这个函数中。
这有一些使用指令的建议:
不要在使用 templates, scope, bindToController or controllers
指令总是使用 restrict: "A"
必要时使用编译和链接函数
记住在 $scope.$on("$destroy", fn); 中销毁和解除绑定事件处理程序
Back to top
推荐的属性由于指令支持.component()所做的大多数事情(指令是原始组件),我建议将指令对象定义限制为仅限于这些属性,以避免错误地使用指令:
Property | Use it? | Why |
---|---|---|
bindToController | No | Use bindings in components |
compile | Yes | For pre-compile DOM manipulation/events |
controller | No | Use a component |
controllerAs | No | Use a component |
link functions | Yes | For pre/post DOM manipulation/events |
multiElement | Yes | See docs |
priority | Yes | See docs |
require | No | Use a component |
restrict | Yes | Defines directive usage, always use "A" |
scope | No | Use a component |
template | No | Use a component |
templateNamespace | Yes (if you must) | See docs |
templateUrl | No | Use a component |
transclude | No | Use a component |
Back to top
Constants or Classes在ES2015中有几种方式使用指令,要么通过箭头函数,要么使用 Class,选择你认为合适的。
下面是一个使用箭头函数的例子:
/* ----- todo/todo-autofocus.directive.js ----- */ import angular from "angular"; export const TodoAutoFocus = ($timeout) => { "ngInject"; return { restrict: "A", link($scope, $element, $attrs) { $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => { if (!newValue) { return; } $timeout(() => $element[0].focus()); }); } } }; /* ----- todo/todo.module.js ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; import { TodoAutofocus } from "./todo-autofocus.directive"; import "./todo.scss"; export const TodoModule = angular .module("todo", []) .component("todo", TodoComponent) .directive("todoAutofocus", TodoAutoFocus) .name;
或者使用 ES2015 Class 创建一个对象(注意在注册指令时手动调用 "new TodoAutoFocus")
/* ----- todo/todo-autofocus.directive.js ----- */ import angular from "angular"; export class TodoAutoFocus { constructor($timeout) { "ngInject"; this.restrict = "A"; this.$timeout = $timeout; } link($scope, $element, $attrs) { $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => { if (!newValue) { return; } this.$timeout(() => $element[0].focus()); }); } } /* ----- todo/todo.module.js ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; import { TodoAutofocus } from "./todo-autofocus.directive"; import "./todo.scss"; export const TodoModule = angular .module("todo", []) .component("todo", TodoComponent) .directive("todoAutofocus", ($timeout) => new TodoAutoFocus($timeout)) .name;
Back to top
服务 服务概述服务本质上是业务逻辑的容器。服务包含其他内置或外部服务例如$http,我们可以在应用的其他位置注入到控制器中。我们有两种使用服务的方式,.service() 和 .factory()。如果要使用ES2015的 Class,我们应该使用.service(),并且使用$inject自动完成依赖注入。
Back to top
Classes for Service这是一个使用 ES2015 Class的例子:
/* ----- todo/todo.service.js ----- */ export class TodoService { constructor($http) { "ngInject"; this.$http = $http; } getTodos() { return this.$http.get("/api/todos").then(response => response.data); } } /* ----- todo/todo.module.js ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; import { TodoService } from "./todo.service"; import "./todo.scss"; export const TodoModule = angular .module("todo", []) .component("todo", TodoComponent) .service("TodoService", TodoService) .name;
Back to top
样式使用 Webpack 我们可以在*.module.js 中用 import 导入我们的 .scss 文件,这样做可以让我们的组件在功能和样式上都是隔离的。
如果你有一些变量或全局使用的样式,比如表单输入元素,那么这些文件仍然应该放在根目录scss文件夹中。 例如 SCSS / _forms.scss。这些全局样式可以像通常那样被 @importe.
Back to top
ES2015 and Tooling使用Babel编译你写的ES2015+代码
考虑使用TypeScript
如果想支持组件路由,那么使用 ui-router latest alpha
使用 Webpack 编译你的ES2015+代码和样式
使用webpack的ngtemplate-loader
使用babel的babel-plugin-angularjs-annotate
Back to top
状态管理考虑使用redux管理你应用的状态.
Angular Redux
Back to top
资源Stateful and stateless components, the missing manual
Understanding the .component() method
Using "require" with $onInit
Understanding all the lifecycle hooks, $onInit, $onChanges, $postLink, $onDestroy
Using "resolve" in routes
Redux and Angular state management
Sample Application from Community
Back to top
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/93368.html
摘要:组件还包含数据事件的输入与输出,生命周期钩子和使用单向数据流以及从父组件上获取数据的事件对象备份。 说明:参照了Angular1.x+es2015的中文翻译,并将个人觉得不合适、不正确的地方进行了修改,欢迎批评指正。 架构,文件结构,组件,单向数据流以及最佳实践 来自@toddmotto团队的实用编码指南 Angular 的编码风格以及架构已经使用ES2015进行重写,这些在Angul...
摘要:是文档的一种表示结构。这些任务大部分都是基于它。这个实践的重点是把你在前端练级攻略第部分中学到的一些东西和结合起来。一旦你进入框架部分,你将更好地理解并使用它们。到目前为止,你一直在使用进行操作。它是在前端系统像今天这样复杂之前编写的。 本文是 前端练级攻略 第二部分,第一部分请看下面: 前端练级攻略(第一部分) 在第二部分,我们将重点学习 JavaScript 作为一种独立的语言,如...
阅读 1326·2021-11-11 11:00
阅读 3049·2021-09-24 09:47
阅读 4957·2021-09-22 15:53
阅读 963·2021-09-10 10:50
阅读 3209·2021-09-01 11:40
阅读 1163·2019-08-30 15:55
阅读 476·2019-08-30 12:49
阅读 1051·2019-08-29 17:12