<p>이 글은 주로 <a href="http://www.php.cn/course/47.html" target="_blank">angularjs</a>에 대한 심층적인 분석을 소개합니다. 어렵다고 생각하지 않는 한 여기에서 Anglejs를 배울 수 있습니다. 이제 이 글을 함께 읽어봅시다</p>
<h2>angular 1에도 컴포넌트 지향 프로그래밍이 필요합니다</h2>
<p>프런트엔드 컴포넌트화는 프런트엔드 개발 모델에서 돌이킬 수 없는 추세입니다. 세 가지 주요 프런트엔드 프레임워크인 <code>Angular 2 code> <code>react </code> <code>vue</code> 모두 구성 요소 프로그래밍을 주요 판매 포인트 중 하나로 간주합니다. <code>angular 1</code> 상대적으로 오랜 역사를 가진 프레임워크입니다. 사생아 <code>angular 2</code>는 마침내 컴포넌트 기반 프로그래밍의 마지막 열차에 올랐고, 회사의 오래된 프로젝트들은 마침내 컴포넌트 기반 프로그래밍의 맛을 경험할 기회를 얻었습니다. <code>angular 2</code> <code>react</code> <code>vue</code> 都不约而同的把组件化编程作为自己的一大卖点,<code>angular 1</code> 作为一个历史相对悠久的框架,在私生子 <code>angular 2</code> 的推动下,终于也搭上了组件化编程的末班车,公司里那些老项目终于也有机会体验组件化编程的滋味。</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>angular 1의 컴포넌트화로 가는 길🎜🎜<code>angular 1</code> 컴포넌트화와 유사한 프로그래밍 아이디어는 실제로 오랫동안 존재해 왔지만 당시에는 컴포넌트라고 부르지 않고 <code>를 지정하는 지시문이라고 불렀습니다. 제한: 'E'</code> 이 지시문은 오늘날의 구성 요소 사용법과 매우 유사합니다. <code>angular 1.5</code>에서는 <code>angular 2</code>와 유사한 개념을 바탕으로 명령어를 제한하여 오늘날의 구성요소로 재탄생시켰습니다. 🎜🎜구성 요소의 특징🎜🎜공식 문서에는 구성 요소와 지침의 차이점이 나열되어 있습니다. 또한, 표준 부품은 다음과 같은 특성도 충족해야 합니다. 🎜<ol class=" list-paddingleft-2">
<li>🎜구성 요소의 라벨 이름에는 밑줄이 포함되어야 합니다🎜</li>
<li>🎜구성 요소의 수명 주기가 양호합니다🎜</li>
<li>🎜 구성 요소는 자체 포함되어 있습니다🎜</li>
<li>🎜구성 요소는 자체 포함되어 있습니다🎜</li>
<li>🎜구성 요소는 재사용이 가능합니다🎜</li>
<li>🎜구성 요소는 사용자 정의 가능🎜</li>
</ol>🎜다음을 순서대로 설명합니다. 🎜<h3>구성 요소 이름 지정</h3>🎜지시문과 달리 구성 요소는 요소여야 하며 HTML에는 이에 대한 특별한 사양이 있습니다. 🎜🎜HTML 사양에서는 개발자가 사용할 수 있도록 밑줄이 포함된 태그를 남겨 둡니다. 이러한 방식으로 형성된 요소를 맞춤 요소라고도 합니다. 커스텀 요소의 개념을 사용하지는 않았지만 둘의 동작은 유사합니다. 우리는 이 기준을 충족해야 합니다. 🎜🎜이 사양은 <code>angular 1</code>에 해당합니다. 구성 요소 이름은 카멜 표기법이어야 합니다. 🎜🎜예: 🎜<pre class="brush:php;toolbar:false">custom-dialog {
display: block;
// ...
.title {
// ...
}
.body {
// ...
}
}</pre>🎜 이것은 옳지 않습니다. HTML 사양에서는 표준 요소 대화 상자를 정의했습니다. 태그 이름을 재사용하면 사용자 정의 구성 요소 동작이 표준 요소의 동작과 혼합되어 이상한 버그가 발생할 수 있으며, 그렇게 하면 간접적으로 개발자가 기본 <code>를 사용하지 못하게 됩니다. 대화상자</code> 태그. 🎜🎜또한, 현재 표준에 특정 요소가 정의되어 있지 않다고 해서 앞으로도 정의되지 않는다는 의미는 아닙니다. 우리 프로그램은 브라우저에서 실행되므로 규칙을 따라야 합니다. 이것은 합법적인 작성 방법입니다: 🎜<pre class="brush:php;toolbar:false">custom-dialog {
display: block;
.custom-dialog {
&-title {
// ..
}
&-body {
}
}
}</pre>
<h3>구성 요소의 자체 포함성</h3>🎜잘 디자인된 구성 요소는 고유한 동작과 기본 스타일을 가져야 합니다. 🎜<h4>기본 동작</h4>🎜기본 동작은 컨트롤러를 사용하여 <code>angular 1</code>에 정의됩니다. 🎜<pre class="brush:php;toolbar:false"><custom-dialog x-title="My Dialog" x-modal="true"><!-- 与标签名一样,自定义属性名也应该使用中划线 -->
<!--content -->
</custom-dialog></pre>🎜구성 요소는 기본적으로 <code>controllerAs</code>를 활성화하기 때문에 모든 변수와 함수는 <code>this</code>에 바인딩되므로 <code>ES2015</code> <code>를 사용할 수도 있습니다. class</code> 구문을 사용하여 코드 구성: 🎜<pre class="brush:php;toolbar:false">module.component('customDialog', {
template: require('./customDialogTemplate.html'),
transclude: true,
bindings: {
title: "@",
modal: '<',
},
});</pre>🎜이 문제는 다른 함수가 <code>constructor</code>에 삽입된 서비스를 사용할 수 없으며 <code>this</code> 전송을 한 번만 사용할 수 있다는 것입니다. . 내 개인적인 접근 방식은 다음과 같습니다. 🎜<pre class="brush:php;toolbar:false">custom-dialog {
display: block;
// ...
.title {
font-size: 16px;
// ...
}
&.big {
.title {
font-size: 24px;
}
}
}</pre>🎜 상대적으로 간단한 로직을 사용하는 구성 요소의 컨트롤러는 <code>함수</code>를 사용하여 정의하고, 복잡한 구성 요소는 <code>클래스</code>를 사용하여 정의하는 것이 좋습니다. 코드 수준 더 명확하고 읽기 쉬워집니다. 🎜<h4>기본 스타일</h4>🎜구성 요소의 기본 스타일은 스타일 시트를 사용하여 직접 지정됩니다. 🎜<pre class="brush:php;toolbar:false"><custom-dialog x-title="My Dialog" class="mydialog big"></custom-dialog></pre>🎜브라우저가 인식하지 못하는 모든 태그의 경우 기본값은 인라인 요소(<code>display: inline</code>)이며 일반적으로 구성 요소에는 바람직하지 않습니다. 따라서 사용자 정의 구성 요소에는 일반적으로 요소의 기본 표시 모드를 변경하기 위한 최소한 <code>display: (inline-)block</code>이 있습니다. 🎜<h3>구성요소의 자체 폐쇄성</h3>🎜자체 폐쇄에는 데이터의 자체 폐쇄성과 스타일의 자체 폐쇄성이라는 두 가지 측면이 포함됩니다. 🎜<h4>데이터 자체 폐쇄성</h4>🎜<code>angular 1</code>, 구성 요소 자체 범위는 이미 격리되어 있습니다. 즉, 구성 요소의 범위는 상위 범위(<code>__proto__)에서 상속되지 않습니다. </code>는 <code>null</code>입니다). 또한 정식 구성 요소는 외부 데이터를 직접 사용해서는 안 됩니다. 이렇게 하면 구성 요소의 재사용성이 손상되기 때문입니다. 몇 가지 예: 🎜<ol class=" list-paddingleft-2">
<li>🎜$rootScope🎜</li>
<li>🎜$root, $parent(템플릿 내)🎜</li>
<li> 🎜 라우팅 매개변수🎜</li>
<li>🎜localStorage, sessionStorage🎜</li>
</ol>🎜이 데이터는 매개변수 바인딩 <code>바인딩</code>을 통해 전달되어야 합니다. 구성 요소가 라우팅 플러그인에 의해 생성된 경우 확인을 사용할 수 있습니다. 🎜🎜두 번째로, 매개변수 바인딩은 양방향 바인딩 <code>=</code>을 사용해서는 안 되며, 표준 구성 요소는 구성 요소 외부에 전달된 데이터를 (직접) 수정해서는 안 됩니다. 공식적으로 권장되는 두 가지 매개변수 바인딩 방법이 있습니다🎜<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>
<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>
위 내용은 Angularjs1 구성 요소 프로그래밍에 대한 심층 분석(예제 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!