摘要:風(fēng)格指南的最佳實踐涵蓋體系結(jié)構(gòu)文件結(jié)構(gòu)組件單向數(shù)據(jù)流和生命周期。模塊概述模塊的設(shè)計直接反應(yīng)了我們的文件結(jié)構(gòu)從而保持了可維護性和可預(yù)測性。指令不應(yīng)再聲明模板和控制器,或者通過綁定接收數(shù)據(jù)。指令應(yīng)該只是用來和互交。
# AngularJS 風(fēng)格指南 (ES2015)
AngularJs1.6.x的最佳實踐.涵蓋體系結(jié)構(gòu),文件結(jié)構(gòu),組件,單向數(shù)據(jù)流和生命周期。 目錄
模塊化體系結(jié)構(gòu)
概述
Root module
Component module
Common module
Low-level modules
文件命名約定
可伸縮文件結(jié)構(gòu)
組件
概述
支持的屬性
控制器
單項數(shù)據(jù)流和事件
有狀態(tài)組件/容器型組件
無狀態(tài)組件/展示型組件
路由組件
指令
概述
推薦的屬性
Constants or Classes
服務(wù)
概述
Classes for Service
樣式
ES2015 and Tooling
狀態(tài)管理
模塊化體系結(jié)構(gòu)Angular應(yīng)用程序中的每個模塊都應(yīng)該是組件模塊。組件模塊用來封裝邏輯、模板、路由和子組件。
模塊概述模塊的設(shè)計直接反應(yīng)了我們的文件結(jié)構(gòu), 從而保持了可維護性和可預(yù)測性。我們最好有三個高級模塊: root、component和common。root模塊是用來啟動我們應(yīng)用和相應(yīng)模板的基礎(chǔ)模塊,root模塊中導(dǎo)入component和common模塊作為依賴模塊。component和common模塊則負責(zé)引入Low-level modules,Low-level modules通常包含可重用的組件,控制器,服務(wù),指令,過濾器和測試.
Back to top
Root 模塊root模塊中定義整個應(yīng)用的根組件,根組件中通常包含路由組件,比如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模塊中注冊根組件,你可能注意到樣式也被引入到了根組件,我們將在后面的章節(jié)介紹這一點.
Back to top
Components 模塊所有的可重用組件應(yīng)該注冊在component模塊上。上面例子中展示了我們是如何導(dǎo)入 ComponentsModule 并將它們注冊在root模塊中。這樣做可以輕松的將component模塊移動到任何其他應(yīng)用程序中,因為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所有我們不希望用在其他應(yīng)用中的組件應(yīng)該注冊在common模塊上。比如布局、導(dǎo)航和頁腳之類的內(nèi)容。
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是包含獨立功能的組件模塊,將會被導(dǎo)入到像component module或者是 common module這樣的higher-level module中,下面是一個例子。一定要記住在每個export的模塊最后添加.name。你可能注意到路由定義也在這個模塊中,我們將在本指南后面的章節(jié)中介紹這一點。
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 標(biāo)示組件,使用 *.module.js 標(biāo)示模塊
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
可伸縮文件結(jié)構(gòu)文件結(jié)構(gòu)是非常重要的,這描述了一個可伸縮和可預(yù)測的結(jié)構(gòu),下面是一個例子。
├── 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 定義數(shù)據(jù)或是事件的輸入和輸出,你可以將組件視為完整的代碼段,而不僅僅是 .component() 定義的對象,讓我們探討一些關(guān)于組件的最佳實踐和建議, 然后深入了解如何通過有狀態(tài)的、無狀態(tài)的和路由組件的概念來構(gòu)造它們。
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
控制器控制器應(yīng)該只和組件一起使用,如果你感覺需要一個控制器,可能你需要的是一個管理這個特定行為的組件。
這是一些使用 Class 定義controllers的建議:
使用 controller: class TodoComponent {...} 這種寫法以應(yīng)對未來向Angular遷移
始終使用 constructor 來進行依賴注入
使用 babel-plugin-angularjs-annotate 的 "ngInject";語法
如果需要訪問詞法作用域,請使用箭頭函數(shù)
除了箭頭函數(shù) let ctrl = this; 也是可以接受的
將所有的公共函數(shù)綁定到 class{}上
適當(dāng)?shù)氖褂?$onInit, $onChanges, $postLink and $onDestroy 等生命周期函數(shù)
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
單向數(shù)據(jù)流和事件AngularJS 1.5中引入了單向數(shù)據(jù)流,這重新定義了組件之間的通信.
這里有一些使用單向數(shù)據(jù)流的建議:
在組件中接受數(shù)據(jù)時,總是使用單向數(shù)據(jù)綁定語法 "<"
任何時候都不要在使用雙向綁定語法 "="
使用 bindings 綁定傳入數(shù)據(jù)時,在 $onChanges 生命周期函數(shù)中深復(fù)制傳入的對象以解除父組件的引用
在父組件的方法中使用 $event 作為函數(shù)的參數(shù)(查看下面有狀態(tài)組件的例子$ctrl.addTodo($event))
從無狀態(tài)組件的方法中傳遞回 $event: {} 對象(查看下面的無狀態(tài)組件例子 this.onAddTodo)
Back to top
有狀態(tài)組件讓我們定義一個有狀態(tài)組件
獲取狀態(tài),實質(zhì)上是通過服務(wù)與后臺API通信
不要直接變化狀態(tài)
一個包括low-level module定義的有狀態(tài)組件的例子(為了簡潔省略了一些代碼)
/* ----- 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;
這個例子展示了一個有狀態(tài)組件,在控制器中通過服務(wù)獲取數(shù)據(jù),然后傳遞給無狀態(tài)子組件。注意我們在模板中并沒有使用ng-repeat 之類的指令,而是委托給
Back to top
無狀態(tài)組件讓我們第一我們所謂的無狀態(tài)組件:
使用 bindings: {} 明確的定義輸入和輸出
通過屬性綁定傳遞數(shù)據(jù)
數(shù)據(jù)通過事件離開組件
不要關(guān)心數(shù)據(jù)來自哪里 - 它是無狀態(tài)的
是高度可重用的組件
也被稱為展示型組件
一個無狀態(tài)組件的例子(
/* ----- 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 ----- */
注意,
Back to top
路由組件讓我們定義一個路由組件。
一個帶有路由定義的有狀態(tài)組件
沒有 router.js 文件
使用路由組件來定義他們自己的路由邏輯
組件數(shù)據(jù)的輸入是通過路由的 resolve塊
在這個例子中,我們將使用路由定義和 bindings重構(gòu)
/* ----- 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 和很多其他特性。在基于組件的應(yīng)用中應(yīng)該注意他們的使用。指令不應(yīng)再聲明模板和控制器,或者通過綁定接收數(shù)據(jù)。指令應(yīng)該只是用來和DOM互交。簡單來說,如果你需要操作DOM,那就寫一個指令,然后在組件的模板中使用它。如果您需要大量的DOM操作,還可以考慮$ postLink生命周期鉤子,但是,這不是讓你將所有DOM操作遷移到這個函數(shù)中。
這有一些使用指令的建議:
不要在使用 templates, scope, bindToController or controllers
指令總是使用 restrict: "A"
必要時使用編譯和鏈接函數(shù)
記住在 $scope.$on("$destroy", fn); 中銷毀和解除綁定事件處理程序
Back to top
推薦的屬性由于指令支持.component()所做的大多數(shù)事情(指令是原始組件),我建議將指令對象定義限制為僅限于這些屬性,以避免錯誤地使用指令:
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中有幾種方式使用指令,要么通過箭頭函數(shù),要么使用 Class,選擇你認為合適的。
下面是一個使用箭頭函數(shù)的例子:
/* ----- 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 創(chuàng)建一個對象(注意在注冊指令時手動調(diào)用 "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
服務(wù) 服務(wù)概述服務(wù)本質(zhì)上是業(yè)務(wù)邏輯的容器。服務(wù)包含其他內(nèi)置或外部服務(wù)例如$http,我們可以在應(yīng)用的其他位置注入到控制器中。我們有兩種使用服務(wù)的方式,.service() 和 .factory()。如果要使用ES2015的 Class,我們應(yīng)該使用.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 導(dǎo)入我們的 .scss 文件,這樣做可以讓我們的組件在功能和樣式上都是隔離的。
如果你有一些變量或全局使用的樣式,比如表單輸入元素,那么這些文件仍然應(yīng)該放在根目錄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
狀態(tài)管理考慮使用redux管理你應(yīng)用的狀態(tài).
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
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/93368.html
摘要:組件還包含數(shù)據(jù)事件的輸入與輸出,生命周期鉤子和使用單向數(shù)據(jù)流以及從父組件上獲取數(shù)據(jù)的事件對象備份。 說明:參照了Angular1.x+es2015的中文翻譯,并將個人覺得不合適、不正確的地方進行了修改,歡迎批評指正。 架構(gòu),文件結(jié)構(gòu),組件,單向數(shù)據(jù)流以及最佳實踐 來自@toddmotto團隊的實用編碼指南 Angular 的編碼風(fēng)格以及架構(gòu)已經(jīng)使用ES2015進行重寫,這些在Angul...
摘要:是文檔的一種表示結(jié)構(gòu)。這些任務(wù)大部分都是基于它。這個實踐的重點是把你在前端練級攻略第部分中學(xué)到的一些東西和結(jié)合起來。一旦你進入框架部分,你將更好地理解并使用它們。到目前為止,你一直在使用進行操作。它是在前端系統(tǒng)像今天這樣復(fù)雜之前編寫的。 本文是 前端練級攻略 第二部分,第一部分請看下面: 前端練級攻略(第一部分) 在第二部分,我們將重點學(xué)習(xí) JavaScript 作為一種獨立的語言,如...
摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個最重要的技術(shù)點常用整理網(wǎng)頁性能管理詳解離線緩存簡介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實現(xiàn)的大排序算法一怪對象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個最重要的技術(shù)點 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...
閱讀 1337·2021-11-11 11:00
閱讀 3069·2021-09-24 09:47
閱讀 5004·2021-09-22 15:53
閱讀 976·2021-09-10 10:50
閱讀 3219·2021-09-01 11:40
閱讀 1178·2019-08-30 15:55
閱讀 480·2019-08-30 12:49
閱讀 1063·2019-08-29 17:12