ホームページ  >  記事  >  ウェブフロントエンド  >  Angularjs1 コンポーネント プログラミングの詳細な分析 (例を含む)

Angularjs1 コンポーネント プログラミングの詳細な分析 (例を含む)

寻∝梦
寻∝梦オリジナル
2018-09-07 15:19:001783ブラウズ
<p>この記事では主に <a href="http://www.php.cn/course/47.html" target="_blank">angularjs の詳細な分析を紹介します</a> 難しいと思わない限り、ここで angularjs を学ぶことができます。それでは、一緒にこの記事を読みましょう</p> <h2>angular 1 にもコンポーネント指向プログラミングが必要です</h2> <p> フロントエンドのコンポーネント化は、フロントエンド開発モデルにおける不可逆的な傾向です。 3 つの主要なフロントエンド フレームワーク <code>angular 2 code> <code>react </code> <code>vue</code> はすべて、コンポーネント プログラミングを主要なセールス ポイントの 1 つとみなしています。 <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> を指定するディレクティブと呼ばれていました。 strict: '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 仕様では、開発者が使用できるようにアンダースコア付きのタグが残されています。このようにして形成された要素はカスタム要素とも呼ばれます。カスタム要素の概念は使用していませんが、この 2 つの動作は似ています。私たちはこの基準を満たす必要があります。 🎜🎜この仕様は <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: '<&#39;, }, });</pre>🎜 これに関する問題は、他の関数が <code>constructor</code> に挿入されたサービスを使用できず、 <code>this</code> 転送を 1 回しか使用できないことです。 。私の個人的なアプローチは次のとおりです: 🎜<pre class="brush:php;toolbar:false">custom-dialog { display: block; // ... .title { font-size: 16px; // ... } &.big { .title { font-size: 24px; } } }</pre>🎜 比較的単純なロジックを持つコンポーネントのコントローラーは <code>function</code> を使用して定義し、複雑なコンポーネントは <code>class</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>🎜 自己閉鎖には、データの自己閉鎖とスタイルの自己閉鎖という 2 つの側面が含まれます。 🎜<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>binding</code> を通じて渡す必要があります。コンポーネントがルーティング プラグインによって生成された場合、解決を使用できます。 🎜🎜第二に、パラメーター バインディングでは双方向バインディング <code>=</code> を使用すべきではありません。また、正規コンポーネントは、コンポーネントの外部で渡されたデータを (直接) 変更すべきではありません。公式に推奨されているパラメーターバインド方法は 2 つあります🎜<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: '<&#39;, }, });</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 中国語 Web サイト<a href="http://www.php.cn/course/47.html" target="_blank">angularjs 学習マニュアル</a> にアクセスして学習してください)。ご質問がある場合は、以下にメッセージを残してください</p> <p class="comments-box-content"></p>。

以上がAngularjs1 コンポーネント プログラミングの詳細な分析 (例を含む)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。