<p>本篇文章主要的介紹了<a href="http://www.php.cn/course/47.html" target="_blank">angularjs</a>1的深度解析,大家不要覺得難,只要不覺得難,那麼你在angularjs這裡算是學的可以了,這可是考驗大家的一篇文章,現在就讓我們一起來看這篇文章吧</p>
<h2>angular 1 也要以元件為導向</h2>
<p>前端元件化是前端開發模式中一個不可逆的趨勢,三大主要前端框架<code>angular 2</code> <code>react</code> <code>vue</code> 都不約而同的把組件化編程作為自己的一大賣點,<code>angular 1</code> 作為一個歷史相對悠久的框架,在私生子<code>angular 2</code> 的推動下,終於也搭上了組件化編程的末班車,公司裡那些老項目終於也有機會體驗組件化編程的滋味。 </p>
<h2>angular 1 的元件化之路</h2>
<p><code>angular 1</code> 中類似元件化的程式設計思想其實很早就有,只不過那時候不叫元件,而叫指令(Directive),指定<code>restrict: 'E'</code> 後這個指令就與現今元件的用法很相似了。在 <code>angular 1.5</code> 中,又將指令根據 <code>angular 2</code> 的類似概念加以限制,脫胎為如今的元件(Components)。 </p>
<h2>元件的特性</h2>
<p>官方文件列舉了元件和指令的不同點。除此之外,一個規範的組件還應符合以下幾個特點。 </p>
<ol class=" list-paddingleft-2">
<li><p>元件的標籤名稱必須包含中劃線</p></li>
<li><p>#元件擁有良好的生命週期</p></li>
<li><p>元件有自包含性</p></li>
<li><p>元件有自封閉性</p></li>
<li><p> 元件有可重複使用性</p></li>
<li><p>#元件可以被客製化</p></li>
</ol>
<p>下面依序說明。 </p>
<h3>元件的名稱規格</h3>
<p>與指令不同,元件必須是元素,HTML 對於這一點有特殊的規格。 </p>
<p>HTML 規範把帶有中劃線的標籤留給開發者使用,這樣形成的元素又稱為自訂元素(Custom Element)。我們雖然沒有用到自訂元素的概念,但兩者的行為是相似的。我們應該符合這項標準。 </p>
<p>這一點規格對應到 <code>angular 1</code> 中即為:元件名稱必須帶有駝峰形式。 </p>
<p>例如:</p>
<pre class="brush:php;toolbar:false">module.component('dialog', {
// ...
});</pre>
<p>這是不對的。 HTML 規範已經定義了dialog 這個標準元素,重複使用標籤名稱可能導致我們自訂的元件行為和標準元素的行為混雜到一起,導致奇葩bug;而且如果這樣做也間接導致開發者不能使用原生的<code>dialog</code> 標籤。 </p>
<p>另外,就算現在標準沒有定義某個元素,不代表將來不會定義。我們的程式既然跑在瀏覽器裡,就要照規矩辦事。這是一種合法的寫法:</p>
<pre class="brush:php;toolbar:false">module.component('customDialog', {
// ...
});</pre>
<h3>元件的自包含性</h3>
<p>一個設計良好的元件一定有它自己的行為和預設樣式。 </p>
<h4>預設行為</h4>
<p>預設行為在 <code>angular 1</code> 中以控制器(Controller)定義。 </p>
<pre class="brush:php;toolbar:false">function CustomDialogController($service) {
this.someField = 123;
this.someMethod = function someMethod() {
}
}
CustomDialogController.$inject = ['$service'];
module.component('customDialog', {
controller: CustomDialogController,
template: require('./customDialogTemplate.html'),
});</pre>
<p>因為元件預設啟用<code>controllerAs</code>,所有變數和函數都是綁定到<code>this</code> 上的,所以你也可以使用<code>ES2015</code>的<code>class</code> 語法來組織程式碼:</p>
<pre class="brush:php;toolbar:false">class CustomDialogController {
constructor($service) {
}
someMethod() {
}
}
CustomDialogController.$inject = ['$service'];
module.component('customDialog', {
controller: CustomDialogController,
template: require('./customDialogTemplate.html'),
});</pre>
<p>這樣做有一個問題就是其他函數不能使用<code>constructor</code> 裡注入的服務(Service),只能透過<code>this</code> 中轉一次。我個人的做法是這樣:</p>
<pre class="brush:php;toolbar:false">class CustomDialogController {
constructor($service) {
this.services = { $service };
}
someMethod() {
const { $service } = this.services;
}
}
// 下略</pre>
<p>建議對於邏輯相對簡單的元件的控制器使用<code>function</code> 定義,複雜的元件使用<code>class</code> 定義,後者程式碼的層次要更為清晰易讀。 </p>
<h4>預設樣式</h4>
<p>元件的預設樣式直接使用樣式表指定。 </p>
<pre class="brush:php;toolbar:false">custom-dialog {
display: block;
// ...
}</pre>
<p>對於所有瀏覽器不認識的標籤,預設都是內嵌元素(<code>display: inline</code>),對於元件來說通常不是想要的。所以自訂的元件通常至少要有 <code>display: (inline-)block</code> 來改變元素的預設顯示方式。 </p>
<h3>元件的自封閉性</h3>
<p>自封閉性包含兩個面向:資料的自封閉性和樣式的自封閉性。 </p>
<h4>資料的自封閉性</h4>
<p><code>angular 1</code> 中,元件本身的scope 已經是隔離的(isolate),即元件的scope 不繼承自父級scope( <code>__proto__</code> 為<code>null</code>)。除此之外,一個規範的元件不應該直接使用外部的數據,因為這樣會破壞元件的可重複使用性。舉幾個例子:</p>
<ol class=" list-paddingleft-2">
<li><p>$rootScope</p></li>
<li><p>#$root、$parent(範本中)</p></li>
<li><p>路由參數</p></li>
<li><p>localStorage、sessionStorage</p></li>
</ol>
<p>這些資料都應該透過參數綁定<code>binding</code> 傳入。如果元件是路由插件生成,那麼可以用 resolve。 </p>
<p>其次,參數綁定不應使用雙向綁定 <code>=</code>,規範的元件不應(直接)修改元件外部傳入的資料。官方推薦的參數綁定方式有兩種</p>
<ol class=" list-paddingleft-2"><li><p><code><</code> 单向绑定,绑定可变数据。通常用于给组件传递数据</p></li><li><p><code>@</code> 字符串绑定,绑定字符串。通常用于指定组件行为</p></li></ol><p>对于单向绑定对象的情况,由于是引用传递,也不应该修改对象内部的属性。</p><p>遇到要向外部传值的情况,推荐使用 ngModel 或 事件绑定(下面会提到)</p><h4>样式的自封闭性</h4><p>组件间的样式不应该互相干扰,这一点可以简单的通过 <code>scss</code> 的样式嵌套(Nesting)实现:</p><pre class="brush:php;toolbar:false">custom-dialog {
display: block;
// ...
.title {
// ...
}
.body {
// ...
}
}</pre><p>这样可以简单的把组件的内置样式表限制在组件内部,从而避免样式外溢。但是这种方法对在组件内部的其他组件不起效果。如果这个组件的模板中还引用了别的组件,或者这个组件被定义为可嵌入的(transclude),那么可以考虑加类名前缀:</p><pre class="brush:php;toolbar:false">custom-dialog {
display: block;
.custom-dialog {
&-title {
// ..
}
&-body {
}
}
}</pre><h3>组件的可复用性</h3><p>组件为复用而生,拥有良好自封闭性的组件必然是可复用的,因为这个组件不受任何外部因素干扰。组件的复用形式包括</p><ol class=" list-paddingleft-2"><li><p>一个页面中使用多次</p></li><li><p>在多个页面中使用</p></li><li><p><code>ng-repeat</code></p></li><li><p>自己套自己(递归树结构)</p></li><li><p>整个源代码拷贝到其他项目中</p></li></ol><p>等等。一个高度可复用的组件则可以被称为控件,是可以单独投稿 <code>npm</code> 项目库的。</p><p>当然,有些组件(比如单独的页面)可能复用需求没那么高,可以视组件的复用程度不同,从组件的自封闭性和整体代码量做一些取舍。</p><h3>组件的定制化</h3><p>一个高度可复用的组件一定可以被定制。</p><h4>行为的定制化</h4><p>通过参数绑定实现组件行为的定制化。例如:</p><pre class="brush:php;toolbar:false"><custom-dialog x-title="My Dialog" x-modal="true"><!-- 与标签名一样,自定义属性名也应该使用中划线 -->
<!--content -->
</custom-dialog><pre class="brush:php;toolbar:false">module.component('customDialog', {
template: require('./customDialogTemplate.html'),
transclude: true,
bindings: {
title: "@",
modal: '<',
},
});</pre><p>出于使用方便的考虑,定制用的参数都是可选的,组件内部实现应该给每个定制参数设定默认值。(想看更多就到PHP中文网<a href="http://www.php.cn/course/47.html" target="_blank">angularjs学习手册</a>中学习)</p><h4>样式的定制化</h4><p>组件风格定制可以使用 class 判断。</p><pre class="brush:php;toolbar:false">custom-dialog {
display: block;
// ...
.title {
font-size: 16px;
// ...
}
&.big {
.title {
font-size: 24px;
}
}
}</pre><p>使用时</p><pre class="brush:php;toolbar:false"><custom-dialog x-title="My Dialog" class="mydialog big"></custom-dialog></pre>
<p>深度定制样式比较好的方式是 CSS 属性(CSS Variable,注意不是 SCSS 属性)。</p>
<pre class="brush:php;toolbar:false">custom-dialog {
display: block;
// ...
.title {
font-size: 16px;
color: var(--dialog-title-color, #333);
// ...
}
&.big {
.title {
font-size: 24px;
}
}
}</pre>
<p>这时只需要文档中说明标题颜色使用 <code>--dialog-title-color</code> 这个 CSS 变量就好,外部使用不依赖于组件内部 DOM 实现。使用时</p>
<pre class="brush:php;toolbar:false">.mydialog {
--dialog-title-color: red;
}</pre>
<h3>组件的生命周期</h3>
<p>从创建至销毁,组件有自己的生命周期(lifecycle),而不像指令那样把 scope 作为生命周期。常用的回调函数如下:</p>
<ul class=" list-paddingleft-2">
<li><p><code>$onInit()</code>:组件被初始化时调用。与 constructor 不同,<code>angular 1</code> 确保 <code>$onInit</code> 被调用时组件的所有参数绑定都被正确赋值。</p></li>
<li><p><code>$onChanges(changeObj)</code>:组件参数绑定值被改变时调用。用于监听绑定值的变化,初次绑定时也会调用这个函数。</p></li>
<li><p><code>$onDestroy()</code>:组件被销毁时调用。用于清理内部资源如 <code>$interval</code> 等。</p></li>
</ul>
<p>这些函数也是绑定在 <code>this</code> 上的。如果 <code>controller</code> 使用 <code>ES2015</code> 的 <code>class</code> 定义方式,可以这么写:</p>
<pre class="brush:php;toolbar:false">class CustomDialogController {
constructor() {}
onInit() {}
onChanges({ prop1, prop2 }) {}
onDestroy() {}
}</pre>
<h2>组件间的通信</h2>
<p>组件间通信是一个让很多人头疼的问题,通常有这样 3 种情况</p>
<h3>子 -> 父</h3>
<p>这种情况有标准的实现方式:事件绑定。例如</p>
<pre class="brush:php;toolbar:false">class CustomDialogController {
close($value) {
this.hide = true;
this.onClose({ $value });
}
}
module.component('customDialog', {
controller: CustomDialogController,
template: require('./customDialogTemplate.html'),
bindings: {
onClose: '&',
},
});</pre>
<p>使用时:</p>
<pre class="brush:php;toolbar:false"><custom-dialog on-close="$ctrl.handleClose(value)"></custom-dialog></pre>
<p>这种方式也可以用于子组件向父组件传值。</p>
<h3>父 -> 子</h3>
<p>用于触发子组件的某个动作。除了改变某个在子组件内部监听变化的绑定参数值外,行之有效的方式就只有事件广播。</p>
<p>子组件先监听某个事件</p>
<pre class="brush:php;toolbar:false">$scope.$on('custom-dialog--close', () => this.close());</pre>
<p>父组件发送广播</p>
<pre class="brush:php;toolbar:false">$scope.$broadcast('custom-dialog--close');</pre>
<p>切记:事件是全局性的。当有组件复用的情况时请使用标识指定接收对象(BUS 模型);另外最好给事件名添加组件前缀。</p>
<h3>同级组件</h3>
<p>请通过父级组件中转</p>
<h3>子 -> 某全局性组件</h3>
<p>这个显示 Notification 时最常用。遇到这种情况时,可以封装服务(Service)。例如:</p>
<pre class="brush:php;toolbar:false">module.component('globalNotification', {
controller: class GlobalNotificationController {
constructor(notificationService) {
notificationService.component = this;
}
show(props) {
// ...
}
}
});
module.factory('notify', function NotifyService() {
return {
warn(msg) {
this.show({ type: 'warn', text: msg });
}
error(msg) {
this.show({ type: 'error', text: msg });
}
}
});</pre>
<p>方案并不完美。如果有更好的建议欢迎提出。</p>
<h2>结语</h2>
<p>有人可能问既然三大前端框架都是组件化的,何必还要在 <code>angular 1</code> 上实现。殊不知 <code>angular 1</code> 的组件诞生的初衷就是为了减少向 <code>angular 2</code> 迁移的难度。机会总是留给有准备的人,哪天老板大发慈悲表示给你把代码重写的时间,你却看着项目里满屏的 <code>$scope.abc = xxx</code> 不知所措,这岂不是悲剧。。。</p>
<p>這篇文章到這就結束了(想看更多就到PHP中文網<a href="http://www.php.cn/course/47.html" target="_blank">angularjs學習手冊</a>中學習),有問題的可以在下方留言提問</p>
<p class="comments-box-content"></p></code></p></li></ol>
以上是Angularjs1的深度解析之元件化程式設計(內附實例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!