يقول أوري شاكيد ، مؤلف المادة التي ننشرها اليوم: "
أعتقد أن المترجمين مثيرون للاهتمام للغاية ". في العام الماضي ، كتب
مقالًا تحدث عن الهندسة العكسية للمترجم الزاوي ومحاكاة بعض مراحل عملية التجميع للمساعدة في فهم ميزات الهيكل الداخلي لهذه الآلية. وتجدر الإشارة إلى أن ما يتحدث عنه مؤلف هذه المادة عادةً باسم "المترجم" يسمى "محرك التقديم".
عندما سمع أوري أنه تم إصدار نسخة جديدة من المترجم Angular ، تسمى Ivy ، أراد على الفور إلقاء نظرة فاحصة ومعرفة ما تغير فيها مقارنة بالإصدار القديم. هنا ، كما كان من قبل ، يتلقى المترجم القوالب والمكونات التي أنشأتها Angular ، والتي يتم تحويلها إلى HTML وشفرة JavaScript عادية ومفهومة لمتصفح Chrome والمتصفحات الأخرى.

إذا قارنت الإصدار الجديد من المترجم بالإصدار السابق ، فقد اتضح أن Ivy تستخدم خوارزمية اهتزاز الأشجار. هذا يعني أن المترجم يحذف تلقائيًا أجزاء الكود غير المستخدمة (وهذا ينطبق أيضًا على الكود الزاوي) ، مما يقلل من حجم حزم المشروع. تحسين آخر يتعلق بحقيقة أنه يتم الآن تجميع كل ملف بشكل مستقل ، مما يقلل من وقت إعادة الترجمة. باختصار ، بفضل
المترجم الجديد
، نحصل على تجميعات أصغر ، وإعادة تجميع سريعة للمشروعات ، وكود أبسط جاهز.
إن فهم كيفية عمل المترجم مثير للاهتمام بحد ذاته (على الأقل مؤلف المادة المادية يأمل ذلك) ، ولكنه يساعد أيضًا على فهم الآليات الداخلية لـ Angular بشكل أفضل. هذا يؤدي إلى تحسين مهارات "التفكير الزاوي" ، والذي بدوره يسمح لك باستخدام هذا الإطار بشكل أكثر فعالية لتطوير الويب.
بالمناسبة ، هل تعرف لماذا سمي المترجم الجديد Ivy؟ والحقيقة هي أن هذه الكلمة تبدو وكأنها مزيج من الحروف "IV" ، مقروءة بصوت عالٍ ، والتي تمثل الرقم 4 ، مكتوبة بأرقام رومانية. "4" هو الجيل الرابع من المجمعات الزاويّة.
تطبيق اللبلاب
Ivy لا تزال في عملية التطوير المكثف ، يمكن ملاحظة هذه العملية
هنا . على الرغم من أن المترجم نفسه ليس مناسبًا للاستخدام القتالي بعد ، إلا أن تجريد RendererV3 ، الذي سيستخدمه ، فعال بالفعل ويأتي مع Angular 6.x.
على الرغم من أن Ivy لا يزال غير جاهز تمامًا ، لا يزال بإمكاننا إلقاء نظرة على نتائج عمله. كيف تفعل ذلك؟ من خلال إنشاء مشروع Angular جديد:
ng new ivy-internals
بعد ذلك ، تحتاج إلى تمكين Ivy عن طريق إضافة الأسطر التالية إلى ملف
tsconfig.json
الموجود في مجلد المشروع الجديد:
"angularCompilerOptions": { "enableIvy": true }
وأخيرًا ، نبدأ المترجم بتنفيذ الأمر
ngc
في مجلد المشروع الذي تم إنشاؤه حديثًا:
node_modules/.bin/ngc
هذا كل شيء. يمكنك الآن فحص الكود الذي تم إنشاؤه في
dist/out-tsc
. على سبيل المثال ، ألق نظرة على الجزء التالي من قالب
AppComponent
:
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="300" alt="Angular Logo" src="…"> </div>
إليك بعض الروابط لمساعدتك في البدء:
يمكن العثور على الرمز الذي تم إنشاؤه لهذا القالب من خلال النظر في ملف
dist/out-tsc/src/app/app.component.js
:
i0.ɵE(0, , _c0); i0.ɵE(1, ); i0.ɵT(2); i0.ɵe(); i0.ɵE(3, , _c1); i0.ɵe(); i0.ɵe(); i0.ɵE(4, ); i0.ɵT(5, ); i0.ɵe();
في هذا النوع من كود JavaScript يقوم Ivy بتحويل قالب المكون. إليك كيفية إجراء نفس الشيء في الإصدار السابق من المحول البرمجي:
كود أنتج بواسطة نسخة سابقة من المترجم الزاويهناك شعور بأن الشفرة التي يولدها Ivy هي أبسط بكثير. يمكنك تجربة قالب المكون (الموجود في
src/app/app.component.html
) ،
src/app/app.component.html
مرة أخرى ومعرفة كيف ستؤثر التغييرات التي تم إجراؤها عليه على الشفرة التي تم إنشاؤها.
تحليل رمز ولدت
دعونا نحاول تحليل الشفرة التي تم إنشاؤها ونرى بالضبط الإجراءات التي يقوم بها. على سبيل المثال ، دعنا نبحث عن إجابة لسؤال حول معنى المكالمات مثل
i0.ɵE
و
i0.ɵT
إذا نظرت إلى بداية الملف الذي تم إنشاؤه ، فسنجد التعبير التالي:
var i0 = require("@angular/core");
لذا فإن
i0
هي مجرد وحدة Angular core ، وكل هذه الوظائف يتم تصديرها بواسطة Angular.
ɵ
استخدام الحرف by من قبل فريق التطوير الزاوي للإشارة إلى أن بعض الطرق تهدف فقط إلى توفير
آليات إطار عمل
داخلية ، أي أنه لا يجب على المستخدمين الاتصال بها مباشرة ، نظرًا لأن ثبات API لهذه الطرق غير مضمون عند إصدار إصدارات جديدة من Angular (في الواقع ، أود أن أقول أن واجهات برمجة التطبيقات الخاصة بهم مضمونة تقريبًا للتغيير).
لذلك ، كل هذه الأساليب هي واجهات برمجة تطبيقات خاصة يتم تصديرها بواسطة Angular. من السهل معرفة وظائفها من خلال فتح المشروع في VS Code وتحليل تلميحات الأدوات:
تحليل الكود في VS Codeعلى الرغم من تحليل ملف JavaScript هنا ، يستخدم VS Code معلومات النوع من TypeScript لتحديد توقيع المكالمة والعثور على وثائق لطريقة معينة. إذا ، بعد تحديد اسم الطريقة ، استخدم تركيبة Ctrl + النقر (Cmd + النقر على Mac) ، اكتشفنا أن الاسم الحقيقي لهذه الطريقة هو
elementStart
.
جعلت هذه التقنية من الممكن معرفة أن اسم الطريقة
ɵT
هو
text
، واسم الطريقة هو
elementEnd
. مسلحين بهذه المعرفة ، يمكننا "ترجمة" الشفرة التي تم إنشاؤها ، وتحويلها إلى شيء سيكون أكثر ملاءمة للقراءة. هنا جزء صغير من مثل هذه "الترجمة":
var core = require("angular/core"); //... core.elementStart(0, "div", _c0); core.elementStart(1, "h1"); core.text(2); core. (); core.elementStart(3, "img", _c1); core.elementEnd(); core.elementEnd(); core.elementStart(4, "h2"); core.text(5, "Here are some links to help you start: "); core.elementEnd();
وكما ذكرنا من قبل ، يتوافق هذا الرمز مع النص التالي من قالب HTML:
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="300" alt="Angular Logo" src="…"> </div>
إليك بعض الروابط لمساعدتك في البدء:
بعد تحليل كل هذا ، من السهل ملاحظة ما يلي:
- تحتوي كل علامة HTML مفتوحة على استدعاء لـ
core.elementStart()
. - تتوافق علامات الإغلاق مع المكالمات إلى
core.elementEnd()
. - تتوافق العقد النصية مع المكالمات إلى
core.text()
.
الوسيطة الأولى
elementStart
و
text
هي رقم تزداد قيمته مع كل مكالمة. من المحتمل أنه يمثل فهرسًا في بعض المصفوفات يخزن فيه Angular روابط للعناصر التي تم إنشاؤها.
يتم تمرير الوسيطة الثالثة أيضًا إلى طريقة
elementStart
. بعد دراسة المواد المذكورة أعلاه ، يمكننا أن نستنتج أن الحجة اختيارية وتحتوي على قائمة السمات للعقدة DOM. يمكنك التحقق من ذلك من خلال النظر إلى قيمة
_c0
ومعرفة أنها تحتوي على قائمة السمات وقيمها لعنصر
div
:
var _c0 = ["style", "text-align:center"];
NgComponentDef ملاحظة
حتى الآن ، قمنا بتحليل جزء من الشفرة التي تم إنشاؤها والتي تكون مسؤولة عن عرض القالب للمكون. يقع هذا الرمز في الواقع في جزء أكبر من التعليمات البرمجية الذي تم تعيينه لـ
AppComponent.ngComponentDef
- خاصية ثابتة تحتوي على جميع البيانات الوصفية حول المكون ، مثل محددات CSS وإستراتيجية الكشف عن التغيير (إذا تم تحديد واحد) والقالب. إذا كنت تشعر برغبة شديدة في المغامرة - يمكنك الآن معرفة كيفية عملها بشكل مستقل ، على الرغم من أننا سنتحدث عنها أدناه.
اللبلاب محلية الصنع
الآن بعد أن فهمنا بشكل عام كيف يبدو الرمز الذي تم إنشاؤه ، يمكننا محاولة إنشاء مكون خاص بنا من الصفر باستخدام نفس RendererV3 API التي تستخدمها Ivy.
سيكون الرمز الذي سنقوم بإنشائه مشابهًا للكود الذي ينتجه المترجم ، لكننا سنجعله بحيث يكون من الأسهل قراءته.
لنبدأ بكتابة مكون بسيط ، ثم ترجمته يدويًا إلى رمز مشابه لما تم الحصول عليه بواسطة Ivy:
import { Component } from '@angular/core'; @Component({ selector: 'manual-component', template: '<h2><font color="#3AC1EF">Hello, Component</font></h2>', }) export class ManualComponent { }
يأخذ المترجم مدخلات الديكور
@component
، وينشئ تعليمات ، ثم يرتبها كلها كخاصية ثابتة لفئة المكونات. لذلك ، من أجل محاكاة نشاط Ivy ، نقوم بإزالة الديكور
@component
واستبداله
ngComponent
الثابتة:
import * as core from '@angular/core'; export class ManualComponent { static ngComponentDef = core.ɵdefineComponent({ type: ManualComponent, selectors: [['manual-component']], factory: () => new ManualComponent(), template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => {
نحدد البيانات الوصفية للمكون
ɵdefineComponent
عن طريق استدعاء
ɵdefineComponent
. تتضمن البيانات الوصفية نوع المكون (المستخدم سابقًا لتنفيذ التبعية) ، ومحدد CSS (أو المحددات) الذي سيستدعي هذا المكون (في حالتنا ،
manual-component
هو اسم المكون في قالب HTML) ، المصنع الذي يعيد المثيل الجديد المكون ، ثم الوظيفة التي تحدد القالب للمكون. يعرض هذا القالب تمثيلاً مرئيًا للمكون ويقوم بتحديثه عندما تتغير خصائص المكون. لإنشاء هذا القالب ، سنستخدم الأساليب التي وجدناها أعلاه:
ɵE
،
ɵe
و
ɵT
.
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { core.ɵE(0, 'h2'); // h2 core.ɵT(1, 'Hello, Component'); // core.ɵe(); // h2 },
في هذه المرحلة ، لا نستخدم معلمات
rf
أو
ctf
التي توفرها وظيفة القالب. سنعود لهم. ولكن أولاً ، دعنا نلقي نظرة على كيفية عرض أول مكون محلي الصنع على الشاشة.
التطبيق الأول
لعرض المكونات على الشاشة ، تقوم Angular بتصدير طريقة تسمى
ɵrenderComponent
. كل ما عليك فعله هو التحقق من أن ملف
index.html
يحتوي على علامة HTML تتوافق مع محدد العنصر
<manual-component>
، ثم أضف ما يلي إلى نهاية الملف:
core.ɵrenderComponent(ManualComponent);
هذا كل شيء. الآن لدينا الحد الأدنى من تطبيق الزاوي العصامي الذي يتكون من 16 سطرًا فقط من التعليمات البرمجية. يمكنك تجربة التطبيق النهائي على
StackBlitz .
آلية الكشف عن التغيير
لذا ، لدينا مثال عملي. هل يمكنك إضافة تفاعل إليها؟ قل ، ماذا عن شيء مثير للاهتمام ، مثل استخدام نظام Angular للكشف عن التغيير هنا؟
قم بتغيير المكون بحيث يمكن للمستخدم تخصيص نص الترحيب. أي ، بدلاً من أن يعرض المكون دائمًا النص
Hello, Component
، سنسمح للمستخدم بتغيير جزء النص الذي يأتي بعد
Hello
.
نبدأ بإضافة خاصية
name
وأسلوب لتحديث قيمة هذه الخاصية إلى فئة المكون:
export class ManualComponent { name = 'Component'; updateName(newName: string) { this.name = newName; }
في حين أن كل هذا لا يبدو مثيرًا للإعجاب بشكل خاص ، ولكن الأكثر إثارة للاهتمام هو المستقبل.
بعد ذلك ، سنقوم بتحرير وظيفة القالب بحيث يعرض محتويات خاصية
name
بدلاً من النص غير الثابت:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { // : core.ɵE(0, 'h2'); core.ɵT(1, 'Hello, '); core.ɵT(2); // <-- name core.ɵe(); } if (rf & 2) { // : core.ɵt(2, ctx.name); // ctx - } },
ربما لاحظت أننا قمنا بلف تعليمات القالب في
if
التي تتحقق من قيم
rf
. يتم استخدام هذه المعلمة من قبل Angular للإشارة إلى ما إذا كان المكون يتم إنشاؤه لأول مرة (سيتم
تعيين البت الأقل أهمية) ، أو نحتاج فقط إلى تحديث المحتوى الديناميكي في عملية الكشف عن التغييرات (هذا هو ما
if
الهدف الثاني من
if
).
لذلك ، عندما يتم عرض المكون لأول مرة ، نقوم بإنشاء جميع العناصر ، وبعد ذلك ، عندما يتم الكشف عن التغييرات ، نقوم فقط بتحديث ما يمكن تغييره. الطريقة الداخلية
ɵt
مسؤولة عن هذا (لاحظ الحرف الصغير
t
) ، الذي يتوافق مع وظيفة
textBinding
التي يتم تصديرها بواسطة Angular:
الدالة textBindingلذلك ، المعلمة الأولى هي فهرس العنصر المراد تحديثه ، والثاني هو القيمة. في هذه الحالة ، نقوم بإنشاء عنصر نص فارغ باستخدام الفهرس 2 باستخدام
core.ɵT(2);
الأوامر.
core.ɵT(2);
. يعمل كعنصر نائب
name
. نقوم بتحديثه باستخدام الأمر
core.ɵt(2, ctx.name);
عند الكشف عن تغيير في المتغير المقابل.
في الوقت الحالي ، سيظل إخراج هذا المكون يعرض النص
Hello, Component
، على الرغم من أنه يمكننا تغيير قيمة خاصية
name
، مما سيؤدي إلى تغيير في النص على الشاشة.
من أجل أن يصبح التطبيق تفاعليًا حقًا ، سنضيف هنا حقل إدخال بيانات مع مستمع للأحداث يستدعي
updateName()
طريقة المكون
updateName()
:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { core.ɵE(0, 'h2'); core.ɵT(1, 'Hello, '); core.ɵT(2); core.ɵe(); core.ɵT(3, 'Your name: '); core.ɵE(4, 'input'); core.ɵL('input', $event => ctx.updateName($event.target.value)); core.ɵe(); }
يتم تنفيذ ربط الأحداث في سطر
core.ɵL('input', $event => ctx.updateName($event.target.value));
. أي أن طريقة
ɵL
مسؤولة عن تعيين مستمع الأحداث
ɵL
العناصر المعلنة. الوسيطة الأولى هي اسم الحدث (في هذه الحالة ،
input
هو الحدث الذي يتم رفعه عند تغيير محتويات عنصر
<input>
) ، الوسيطة الثانية هي رد اتصال. يقبل رد الاتصال هذا بيانات الحدث كوسيطة. ثم نقوم باستخراج القيمة الحالية من العنصر المستهدف للحدث ، أي من عنصر
<input>
، ونقوم بتمريرها إلى الوظيفة في المكون.
الرمز أعلاه يكافئ كتابة HTML التالي في قالب:
Your name: <input (input)="updateName($event.target.value)" />
يمكنك الآن تحرير محتويات عنصر
<input>
ومراقبة كيفية تغير النص في المكون. ومع ذلك ، لا يتم ملء حقل الإدخال عند تحميل المكون. لكي يعمل كل شيء بهذه الطريقة ، تحتاج إلى إضافة تعليمات أخرى إلى رمز وظيفة القالب ، يتم تنفيذها عند اكتشاف التغيير:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { ... } if (rf & 2) { core.ɵt(2, ctx.name); core.ɵp(4, 'value', ctx.name); } }
هنا نستخدم طريقة أخرى مضمنة لنظام التقديم ،
ɵp
، والتي تقوم بتحديث خاصية عنصر بفهرس معين. في هذه الحالة ، يتم تمرير الفهرس 4 إلى الطريقة ، وهو الفهرس الذي تم تعيينه لعنصر
input
،
ctx.name
الطريقة لوضع قيمة
ctx.name
في خاصية
value
لهذا العنصر.
الآن مثالنا جاهز أخيرًا. نفذنا ، من البداية ، ربط بيانات ثنائي الاتجاه باستخدام واجهة برمجة تطبيقات Ivy rendering system. هذا رائع.
هنا يمكنك تجربة الكود النهائي.
نحن الآن على دراية بمعظم اللبنات الأساسية لمجمع Ivy الجديد. نحن نعرف كيفية إنشاء العناصر وعقد النص ، وكيفية ربط الخصائص وتكوين مستمعي الأحداث ، وكيفية استخدام نظام الكشف عن التغيير.
حول * ngIf و * ngFor للكتل
قبل أن ننتهي من دراسة Ivy ، دعنا نلقي نظرة على موضوع آخر مثير للاهتمام. وبالتحديد ، لنتحدث عن كيفية عمل المترجم مع الأنماط الفرعية. هذه هي الأنماط المستخدمة
*ngIf
أو
*ngFor
. يتم معالجتها بطريقة خاصة. دعونا نلقي نظرة على كيفية استخدام
*ngIf
في رمز القالب محلي الصنع.
تحتاج أولاً إلى تثبيت حزمة npm
@angular/common
- هذا هو المكان الذي يتم فيه الإعلان عن
*ngIf
. بعد ذلك ، تحتاج إلى استيراد التوجيه من هذه الحزمة:
import { NgIf } from '@angular/common';
الآن ، لكي تكون قادرًا على استخدام
NgIf
في القالب ، تحتاج إلى تزويده ببعض البيانات الوصفية ، حيث لم يتم تجميع الوحدة النمطية
@angular/common
باستخدام Ivy (على الأقل أثناء كتابة المادة ، وربما سيتغير هذا في المستقبل من إدخال
ngcc ).
ɵdefineDirective
طريقة
ɵdefineDirective
، المرتبطة بطريقة
ɵdefineComponent
المألوفة. تحدد البيانات الوصفية للتوجيهات:
(NgIf as any).ngDirectiveDef = core.ɵdefineDirective({ type: NgIf, selectors: [['', 'ngIf', '']], factory: () => new NgIf(core.ɵinjectViewContainerRef(), core.ɵinjectTemplateRef()), inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'} });
لقد وجدت هذا التعريف في
التعليمات البرمجية المصدر الزاوي ، جنبا إلى جنب مع
ngFor
. الآن بعد أن قمنا بإعداد
NgIf
للاستخدام في Ivy ، يمكننا إضافة ما يلي إلى قائمة التوجيهات للمكون:
static ngComponentDef = core.ɵdefineComponent({ directives: [NgIf], // ... });
بعد ذلك ، نحدد النمط الفرعي فقط للقسم الذي يحده
*ngIf
.
افترض أنك بحاجة إلى عرض صورة. لنقم بتعيين وظيفة جديدة لهذا القالب داخل وظيفة القالب:
function ifTemplate(rf: core.ɵRenderFlags, ctx: ManualComponent) { if (rf & 1) { core.ɵE(0, 'div'); core.ɵE(1, 'img', ['src', 'https://pbs.twimg.com/tweet_video_thumb/C80o289UQAAKIqp.jpg']); core.ɵe(); } }
لا تختلف وظيفة القالب هذه عن تلك التي كتبناها بالفعل. ويستخدم نفس التركيبات لإنشاء عنصر
img
داخل عنصر
div
.
وأخيرًا ، يمكننا تجميع كل ذلك عن طريق إضافة توجيه
ngIf
إلى قالب المكون:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { // ... core.ɵC(5, ifTemplate, null, ['ngIf']); } if (rf & 2) { // ... core.ɵp(5, 'ngIf', (ctx.name === 'Igor')); } function ifTemplate(rf: core.ɵRenderFlags, ctx: ManualComponent) { // ... } },
لاحظ استدعاء الأسلوب الجديد في بداية الكود (
core.ɵC(5, ifTemplate, null, ['ngIf']);
). يعلن عن عنصر حاوية جديد ، أي عنصر يحتوي على قالب. الحجة الأولى هي فهرس العنصر ، لقد رأينا بالفعل مثل هذه الفهارس. الحجة الثانية هي وظيفة نمط فرعي حددناها للتو. سيتم استخدامه كقالب لعنصر الحاوية. المعلمة الثالثة هي اسم العلامة للعنصر ، وهو أمر لا معنى له هنا ، وأخيرًا ، هناك قائمة بالتوجيهات والسمات المرتبطة بهذا العنصر. هذا هو المكان الذي يأتي فيه
ngIf
.
في قلب الخط.
core.ɵp(5, 'ngIf', (ctx.name === 'Igor'));
يتم تحديث حالة العنصر
ngIf
السمة
ngIf
بقيمة التعبير المنطقي
ctx.name === 'Igor'
. يتحقق هذا لمعرفة ما إذا كانت خاصية
name
المكون تساوي
Igor
.
الرمز أعلاه يعادل رمز HTML التالي:
<div *ngIf="name === 'Igor'"> <img align="center" src="..."> </div>
هنا يمكن ملاحظة أن المترجم الجديد لا ينتج الكود الأكثر ضغطًا ، ولكنه ليس سيئًا جدًا مقارنة بما هو عليه الآن.
يمكنك تجربة مثال جديد
هنا . لرؤية قسم
NgIf
قيد التشغيل ، أدخل اسم
Igor
في حقل
Your name
.
الملخص
لقد سافرنا إلى حد كبير حول قدرات مترجم Ivy. نأمل أن تكون هذه الرحلة قد أثارت اهتمامك بمزيد من استكشاف Angular. إذا كان الأمر كذلك ، فلديك الآن كل ما تحتاجه لتجربة Ivy. الآن أنت تعرف كيفية "ترجمة" القوالب إلى JavaScript ، وكيفية الوصول إلى نفس الآليات الزاويّة التي تستخدمها Ivy بدون استخدام هذا المترجم. أفترض أن كل هذا سوف يمنحك الفرصة لاستكشاف آليات Angular الجديدة بالعمق الذي تريده.
هنا ،
هنا وهنا - ثلاث مواد يمكنك أن تجد فيها معلومات مفيدة عن اللبلاب.
وإليك رمز المصدر لـ Render3.
أعزائي القراء! ما هو شعورك حول الميزات الجديدة لـ Ivy؟
