المكونات الزاوي 101 - نظرة عامة
نشرت: 2022-03-11المكونات متوفرة في Angular منذ البداية ؛ ومع ذلك ، لا يزال العديد من الأشخاص يجدون أنفسهم يستخدمون المكونات بشكل غير صحيح. في عملي ، رأيت أشخاصًا لا يستخدمونها على الإطلاق ، بل ينشئون مكونات بدلاً من توجيهات السمات ، والمزيد. هذه هي المشكلات التي يميل مطورو Angular الصغار والكبار على حد سواء إلى طرحها ، بما فيهم أنا. وهكذا ، فإن هذا المنشور مخصص لأشخاص مثلي عندما كنت أتعلم Angular ، حيث لا تشرح الوثائق الرسمية ولا الوثائق غير الرسمية حول المكونات مع حالات الاستخدام كيف ومتى تستخدم المكونات.
في هذه المقالة ، سأحدد الطرق الصحيحة وغير الصحيحة لاستخدام مكونات Angular ، مع أمثلة. يجب أن يمنحك هذا المنشور فكرة واضحة عن:
- تعريف المكونات الزاويّة ؛
- متى يجب إنشاء مكونات زاوية منفصلة ؛ و
- عندما لا يجب إنشاء مكونات زاوية منفصلة.
قبل أن نتمكن من الانتقال إلى الاستخدام الصحيح لمكونات Angular ، أريد أن أتطرق بإيجاز إلى موضوع المكون بشكل عام. بشكل عام ، يحتوي كل تطبيق Angular على مكون واحد على الأقل افتراضيًا - root component
. من هناك ، الأمر متروك لنا في كيفية تصميم تطبيقنا. عادةً ما تقوم بإنشاء مكون لصفحات منفصلة ، وبعد ذلك ستحتوي كل صفحة على قائمة من المكونات المنفصلة. كقاعدة عامة ، يجب أن يستوفي المكون المعايير التالية:
- يجب أن يكون لديك فئة محددة ، والتي تحتفظ بالبيانات والمنطق ؛ و
- يجب أن يقترن بقالب HTML يعرض معلومات للمستخدم النهائي.
دعنا نتخيل سيناريو حيث لدينا تطبيق يتكون من صفحتين: المهام القادمة والمهام المكتملة . في صفحة المهام القادمة ، يمكننا عرض المهام القادمة ، وتمييزها على أنها "تم" ، وأخيرًا إضافة مهام جديدة. وبالمثل ، في صفحة المهام المكتملة ، يمكننا عرض المهام المكتملة وتمييزها بـ "التراجع". أخيرًا ، لدينا روابط تنقل تسمح لنا بالتنقل بين الصفحات. ومع ذلك ، يمكننا تقسيم الصفحة التالية إلى ثلاثة أقسام: مكون الجذر ، والصفحات ، والمكونات القابلة لإعادة الاستخدام.
بناءً على لقطة الشاشة أعلاه ، يمكننا أن نرى بوضوح أن بنية التطبيق ستبدو كما يلي:
── myTasksApplication ├── components │ ├── header-menu.component.ts │ ├── header-menu.component.html │ ├── task-list.component.ts │ └── task-list.component.html ├── pages │ ├── upcoming-tasks.component.ts │ ├── upcoming-tasks.component.html │ ├── completed-tasks.component.ts │ └── completed-tasks.component.html ├── app.component.ts └── app.component.html
لذلك دعونا نربط ملفات المكونات بالعناصر الفعلية على الإطار السلكي أعلاه:
-
header-menu.component
وtask-list.component
عبارة عن مكونات قابلة لإعادة الاستخدام ، ويتم عرضها بحد أخضر في لقطة شاشة الإطار السلكي ؛ -
upcoming-tasks.component
، عنصر ومكونات - مهامcompleted-tasks.component
هي صفحات يتم عرضها بحد أصفر في لقطة شاشة الإطار السلكي أعلاه ؛ و - أخيرًا ،
app.component
هو المكون الجذر ، والذي يتم عرضه بحد أحمر في لقطة شاشة الإطار السلكي.
وبناءً على ذلك ، يمكننا تحديد منطق وتصميم منفصل لكل مكون. استنادًا إلى الإطارات الشبكية أعلاه ، لدينا صفحتان تعيدان استخدام مكون واحد task-list.component
. سيظهر السؤال - كيف نحدد نوع البيانات التي يجب أن نعرضها على صفحة معينة؟ لحسن الحظ ، لا داعي للقلق بشأن ذلك ، لأنه عند إنشاء مكون ، يمكنك أيضًا تحديد متغيرات الإدخال والإخراج .
متغيرات الإدخال
يتم استخدام متغيرات الإدخال داخل المكونات لتمرير بعض البيانات من المكون الرئيسي. في المثال أعلاه ، يمكن أن يكون لدينا معلمتان للإدخال لـ task-list.component
- tasks
و listType
. وفقًا لذلك ، ستكون tasks
عبارة عن قائمة سلاسل تعرض كل سلسلة في صف منفصل ، بينما ستكون listType
إما قادمة أو مكتملة ، مما يشير إلى ما إذا تم تحديد خانة الاختيار. أدناه ، يمكنك العثور على مقتطف رمز صغير لكيفية ظهور المكون الفعلي.
// task-list.component.ts import { Component, Input } from '@angular/core'; @Component({ selector: 'app-task-list', templateUrl: 'task-list.component.html' }) export class TaskListComponent { @Input() tasks: string[] = []; // List of tasks which should be displayed. @Input() listType: 'upcoming' | 'completed' = 'upcoming'; // Type of the task list. constructor() { } }
متغيرات الإخراج
على غرار متغيرات الإدخال ، يمكن أيضًا استخدام متغيرات الإخراج لتمرير بعض المعلومات بين المكونات ، ولكن هذه المرة إلى المكون الرئيسي. على سبيل المثال ، بالنسبة لـ task-list.component
، يمكن أن يكون لدينا itemChecked
متغير ناتج محدد. سيُعلم المكون الأصلي إذا تم تحديد عنصر أو إلغاء تحديده. يجب أن تكون متغيرات الإخراج بواعث للأحداث. أدناه ، يمكنك العثور على مقتطف رمز صغير يوضح كيف يمكن أن يبدو المكون مع متغير الإخراج.
// task-list.component.ts import { Component, Input, Output } from '@angular/core'; @Component({ selector: 'app-task-list', templateUrl: 'task-list.component.html' }) export class TaskListComponent { @Input() tasks: string[] = []; // List of tasks which should be displayed. @Input() listType: 'upcoming' | 'completed' = 'upcoming'; // Type of the task list. @Output() itemChecked: EventEmitter<boolean> = new EventEmitter(); @Output() tasksChange: EventEmitter<string[]> = new EventEmitter(); constructor() { } /** * Is called when an item from the list is checked. * @param selected---Value which indicates if the item is selected or deselected. */ onItemCheck(selected: boolean) { this.itemChecked.emit(selected); } /** * Is called when task list is changed. * @param changedTasks---Changed task list value, which should be sent to the parent component. */ onTasksChanged(changedTasks: string[]) { this.taskChange.emit(changedTasks); } }
task-list.component.ts
بعد إضافة متغيرات الإخراجاستخدام المكونات الفرعية والربط المتغير
دعنا نلقي نظرة على كيفية استخدام هذا المكون داخل المكون الرئيسي وكيفية عمل أنواع مختلفة من الارتباطات المتغيرة. في Angular ، هناك طريقتان لربط متغيرات الإدخال - الربط أحادي الاتجاه ، مما يعني أنه يجب تغليف الخاصية بين أقواس مربعة []
، والربط ثنائي الاتجاه ، مما يعني أنه يجب التفاف الخاصية داخل أقواس مربعة ومستديرة [()]
. ألق نظرة على المثال أدناه واطلع على الطرق المختلفة التي يمكن من خلالها تمرير البيانات بين المكونات.
<h1>Upcoming Tasks</h1> <app-task-list [(tasks)]="upcomingTasks" [listType]="'upcoming'" (itemChecked)="onItemChecked($event)"></app-task-list>
upcoming-tasks.component.html
لنستعرض كل معلمة:
- يتم تمرير معلمة
tasks
باستخدام الربط ثنائي الاتجاه. هذا يعني أنه في حالة تغيير معلمة المهام داخل المكون الفرعي ، سيعكس المكون الرئيسي هذه التغييرات على متغير المهامupcomingTasks
. للسماح بربط البيانات ثنائي الاتجاه ، يجب عليك إنشاء معلمة إخراج ، والتي تتبع القالب "تغيير [inputParameterName]" ، في هذه الحالةtasksChange
. - تم تمرير المعلمة
listType
باستخدام ربط أحادي الاتجاه. هذا يعني أنه يمكن تغييره داخل المكون الفرعي ، لكنه لن ينعكس في المكون الرئيسي. ضع في اعتبارك أنه كان بإمكاني تعيين القيمة'upcoming'
إلى معلمة داخل المكون وتمريرها بدلاً من ذلك - لن يحدث أي فرق. - أخيرًا ، المعلمة
itemChecked
هي وظيفة مستمع ، وسيتم استدعاؤها متى تم تنفيذ onItemCheck على موقعtask-list.component
. إذا تم تحديد عنصر ، فإن$event
سيحتفظ بالقيمةtrue
، ولكن إذا لم يتم تحديدها ، فستحتفظ بالقيمةfalse
.
كما ترى ، بشكل عام ، يوفر Angular طريقة رائعة لتمرير المعلومات ومشاركتها بين مكونات متعددة ، لذلك يجب ألا تخاف من استخدامها. فقط تأكد من استخدامها بحكمة وعدم الإفراط في استخدامها.
متى يتم إنشاء مكون زاوي منفصل
كما ذكرنا سابقًا ، يجب ألا تخاف من استخدام مكونات Angular ، لكن يجب عليك بالتأكيد استخدامها بحكمة.
إذن متى يجب إنشاء مكونات Angular؟
- يجب عليك دائمًا إنشاء مكون منفصل إذا كان من الممكن إعادة استخدام المكون في أماكن متعددة ، كما هو الحال مع
task-list.component
. نسميها مكونات قابلة لإعادة الاستخدام . - يجب أن تفكر في إنشاء مكون منفصل إذا كان المكون سيجعل المكون الرئيسي أكثر قابلية للقراءة ويسمح لهم بإضافة تغطية اختبار إضافية. يمكننا أن نسميها مكونات تنظيم الكود .
- يجب عليك دائمًا إنشاء مكون منفصل إذا كان لديك جزء من الصفحة لا يحتاج إلى التحديث كثيرًا وتريد زيادة الأداء. هذا مرتبط بتغيير استراتيجية الكشف. يمكننا أن نسميها مكونات التحسين .
تساعدني هذه القواعد الثلاثة في تحديد ما إذا كنت بحاجة إلى إنشاء مكون جديد ، وهي تعطيني تلقائيًا رؤية واضحة لدور المكون. من الناحية المثالية ، عند إنشاء مكون ، يجب أن تعرف بالفعل دوره داخل التطبيق.
نظرًا لأننا قد ألقينا بالفعل نظرة على استخدام المكونات القابلة لإعادة الاستخدام ، فلنلقِ نظرة على استخدام مكونات تنظيم الكود . لنتخيل أن لدينا نموذج تسجيل ، وفي أسفل النموذج ، لدينا مربع يحتوي على الشروط والأحكام . عادةً ، تميل العناصر القانونية إلى أن تكون كبيرة جدًا ، وتشغل مساحة كبيرة - في هذه الحالة ، في قالب HTML. لذا ، لنلقِ نظرة على هذا المثال ثم نرى كيف يمكننا تغييره.

في البداية ، لدينا مكون واحد - Registration.component - والذي يحمل كل شيء ، بما في ذلك استمارة registration.component
وكذلك الشروط والأحكام نفسها.
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <div class="terms-and-conditions-box"> Text with very long terms and conditions. </div> <button (click)="onRegistrate()">Registrate</button>
يبدو القالب الآن صغيرًا ، لكن تخيل ما إذا كنا سنستبدل "نص بشروط وأحكام طويلة جدًا" بنص فعلي يحتوي على أكثر من 1000 كلمة - سيجعل تحرير الملف أمرًا صعبًا وغير مريح. لدينا حل سريع لذلك - يمكننا اختراع مكون جديد terms-and-conditions.component
المكون ، والذي سيشمل كل ما يتعلق بالشروط والأحكام. لذلك دعونا نلقي نظرة على ملف HTML للحصول على عنصر terms-and-conditions.component
.
<div class="terms-and-conditions-box"> Text with very long terms and conditions. </div>
والآن يمكننا تعديل كوبون registration.component
واستخدام terms-and-conditions.component
بداخله.
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <app-terms-and-conditions></app-terms-and-conditions> <button (click)="onRegistrate()">Registrate</button>
تهانينا! لقد قللنا للتو حجم registration.component
بمئات الأسطر وجعلنا قراءة الكود أكثر سهولة. في المثال أعلاه ، قمنا بإجراء تغييرات على قالب المكون ، ولكن يمكن تطبيق نفس المبدأ على منطق المكون.
أخيرًا ، بالنسبة لمكونات التحسين ، أود أن أقترح بشدة أن تتصفح هذا المنشور ، لأنه سيوفر لك جميع المعلومات المطلوبة لفهم اكتشاف التغيير ، والحالات المحددة التي يمكنك تطبيقها عليها. لن تستخدمه كثيرًا ، ولكن قد تكون هناك بعض الحالات ، وإذا كان بإمكانك تخطي الفحوصات المنتظمة على مكونات متعددة عندما لا يكون ذلك ضروريًا ، فهذا يعد فوزًا للأداء.
ومع ذلك ، لا ينبغي دائمًا إنشاء مكونات منفصلة ، لذلك دعونا نلقي نظرة عندما يجب تجنب إنشاء مكون منفصل.
متى تتجنب إنشاء مكون زاوي منفصل
هناك ثلاث نقاط رئيسية بناءً عليها أقترح إنشاء مكون Angular منفصل ، ولكن هناك حالات أتجنب فيها إنشاء مكون منفصل أيضًا.
مرة أخرى ، دعنا نلقي نظرة على النقاط التي ستتيح لك أن تفهم بسهولة متى لا يجب عليك إنشاء مكون منفصل.
- لا يجب عليك إنشاء مكونات لعمليات التلاعب في DOM. بالنسبة لهؤلاء ، يجب عليك استخدام توجيهات السمات.
- يجب ألا تنشئ مكونات إذا كان ذلك سيجعل شفرتك أكثر فوضوية. هذا هو عكس مكونات تنظيم الكود .
الآن ، دعنا نلقي نظرة فاحصة ونفحص كلتا الحالتين في الأمثلة. دعنا نتخيل أننا نريد أن يكون لدينا زر يسجل الرسالة عند النقر عليها. قد يكون هذا خاطئًا ويمكن إنشاء مكون منفصل لهذه الوظيفة المحددة ، والتي من شأنها أن تحتوي على زر وإجراءات محددة. دعنا أولاً نتحقق من النهج غير الصحيح:
// log-button.component.ts import { Component, Input, Output } from '@angular/core'; @Component({ selector: 'app-log-button', templateUrl: 'log-button.component.html' }) export class LogButtonComponent { @Input() name: string; // Name of the button. @Output() buttonClicked: EventEmitter<boolean> = new EventEmitter(); constructor() { } /** * Is called when button is clicked. * @param clicked - Value which indicates if the button was clicked. */ onButtonClick(clicked: boolean) { console.log('I just clicked a button on this website'); this.buttonClicked.emit(clicked); } }
log-button.component.ts
غير صحيحوبناءً على ذلك ، يكون هذا المكون مصحوبًا بطريقة عرض html التالية.
<button (click)="onButtonClick(true)">{{ name }}</button>
log-button.component.html
كما ترى ، سيعمل المثال أعلاه ، ويمكنك استخدام المكون أعلاه خلال طرق العرض الخاصة بك. ستفعل ما تريد ، تقنيًا. لكن الحل الصحيح هنا هو استخدام التوجيهات. سيسمح لك ذلك ليس فقط بتقليل مقدار التعليمات البرمجية التي يجب عليك كتابتها ولكن أيضًا لإضافة إمكانية تطبيق هذه الوظيفة على أي عنصر تريده ، وليس الأزرار فقط.
import { Directive } from '@angular/core'; @Directive({ selector: "[logButton]", hostListeners: { 'click': 'onButtonClick()', }, }) class LogButton { constructor() {} /** * Fired when element is clicked. */ onButtonClick() { console.log('I just clicked a button on this website'); } }
logButton
التوجيه ، والذي يمكن تخصيصه لأي عنصر الآن عندما ننشئ التوجيه الخاص بنا ، يمكننا ببساطة استخدامه عبر تطبيقنا وتخصيصه لأي عنصر نريده. على سبيل المثال ، دعنا نعيد استخدام كوبون registration.component
الخاص بنا.
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <app-terms-and-conditions></app-terms-and-conditions> <button (click)="onRegistrate()" logButton>Registrate</button>
logButton
المستخدم في زر نماذج التسجيل الآن ، يمكننا أن نلقي نظرة على الحالة الثانية ، والتي يجب ألا ننشئ فيها مكونات منفصلة ، وهذا هو عكس مكونات تحسين الكود . إذا كان المكون الذي تم إنشاؤه حديثًا يجعل الكود الخاص بك أكثر تعقيدًا وأكبر ، فلا داعي لإنشائه. لنأخذ كمثال اشتراكنا في registration.component
. تتمثل إحدى هذه الحالات في إنشاء مكون منفصل للتسمية وحقل الإدخال مع عدد كبير من معلمات الإدخال. دعونا نلقي نظرة على هذه الممارسة السيئة.
// form-input-with-label.component.ts import { Component, Input} from '@angular/core'; @Component({ selector: 'app-form-input-with-label', templateUrl: 'form-input-with-label.component.html' }) export class FormInputWithLabelComponent { @Input() name: string; // Name of the field @Input() id: string; // Id of the field @Input() label: string; // Label of the field @Input() type: 'text' | 'password'; // Type of the field @Input() model: any; // Model of the field constructor() { } }
form-input-with-label.component
ويمكن أن تكون هذه وجهة نظر هذا المكون.
<label for="{{ id }}">{{ label }}</label><br /> <input type="{{ type }}" name="{{ name }}" [(ngModel)]="model" /><br />
form-input-with-label.component
بالتأكيد ، سيتم تقليل مقدار الكود في registration.component
، ولكن هل سيجعل المنطق العام للكود أكثر سهولة في الفهم والقراءة؟ أعتقد أنه يمكننا أن نرى بوضوح أنه يجعل الكود أكثر تعقيدًا بشكل غير ضروري مما كان عليه من قبل.
الخطوات التالية: مكونات الزاوي 102؟
للتلخيص: لا تخف من استخدام المكونات ؛ فقط تأكد من أن لديك رؤية واضحة حول ما تريد تحقيقه. السيناريوهات التي ذكرتها أعلاه هي الأكثر شيوعًا ، وأنا أعتبرها الأكثر أهمية والأكثر شيوعًا ؛ ومع ذلك ، قد يكون السيناريو الخاص بك فريدًا والأمر متروك لك لاتخاذ قرار مستنير. آمل أن تكون قد تعلمت ما يكفي لاتخاذ قرار جيد.
إذا كنت ترغب في معرفة المزيد حول استراتيجيات الكشف عن التغيير في Angular واستراتيجية OnPush ، فإنني أوصي بقراءة Angular Change Detection واستراتيجية OnPush. إنه مرتبط ارتباطًا وثيقًا بالمكونات ، وكما ذكرت بالفعل في المنشور ، يمكنه تحسين أداء التطبيق بشكل كبير.
نظرًا لأن المكونات ليست سوى جزء من التوجيهات التي يوفرها Angular ، فسيكون من الرائع أيضًا التعرف على توجيهات السمات والتوجيهات الهيكلية . من المرجح أن يسهل فهم جميع التوجيهات على المبرمج كتابة تعليمات برمجية أفضل تمامًا.