Artikel ini akan membawa anda melalui pengesanan perubahan dalam Angular Kami akan mulakan dengan contoh kecil, dan kemudian secara beransur-ansur membincangkan pengesanan perubahan secara mendalam.
Pengesanan perubahan dalam Angular ialah mekanisme yang digunakan untuk menyegerakkan keadaan UI aplikasi dengan keadaan data. Apabila logik aplikasi mengubah data komponen, nilai terikat pada sifat DOM dalam paparan juga berubah. Pengesan perubahan bertanggungjawab untuk mengemas kini paparan untuk mencerminkan model data semasa. [Tutorial berkaitan yang disyorkan: "tutorial sudut"]
Apa yang anda pelajari di atas kertas adalah cetek, tetapi anda tahu bahawa anda perlu melakukannya secara terperinci. Untuk memudahkan pembaca memahami, artikel ini dimulakan dengan contoh kecil dan kemudian dikembangkan langkah demi langkah. Contohnya adalah seperti berikut:
// app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'aa'; handleClick() { this.title = 'bb'; }} // app.componnet.html <div (click)="handleClick()">{{title}}</div>
Contohnya agak mudah, iaitu mengikat acara klik pada elemen div
Mengklik pada elemen akan mengubah nilai pembolehubah title
, dan paparan antara muka juga akan dikemas kini dengan sewajarnya. Bagaimanakah rangka kerja mengetahui masa ia perlu mengemas kini paparan, dan bagaimana ia mengemas kini paparan? Mari kita ketahui.
Apabila kita mengklik pada elemen div
, fungsi handleClick
akan dilaksanakan. Jadi bagaimanakah fungsi ini dicetuskan dalam aplikasi Sudut? Jika anda telah membaca artikel saya sebelum ini tentang pengenalan zone.js
, anda akan tahu bahawa peristiwa klik dalam aplikasi Angular telah diambil alih oleh zone.js
. Berdasarkan jawapan ini, jelas bahawa pelaksanaan mesti dicetuskan oleh zone.js
pada mulanya, tetapi di sini kita perlu menganalisis lagi hubungan panggilan langsung dan mengembangkannya lapisan demi lapisan. Kod yang paling hampir dengan panggilan fungsi handleClick
ialah kod berikut:
function wrapListener(listenerFn, ...) { return function wrapListenerIn_markDirtyAndPreventDefault(e) { let result = executeListenerWithErrorHandling(listenerFn, ...); } }
Dalam kod di atas, fungsi listenerFn
menghala ke handleClick
, tetapi ia juga merupakan parameter bagi wrapListener
fungsi. Dalam contoh, elemen terikat pada acara klik Produk kompilasi templat yang berkaitan mungkin seperti berikut:
function AppComponent_Template(rf, ctx) { ...... i0["ɵɵlistener"]("click", function AppComponent_Template_div_click_0_listener() { return ctx.handleClick(); }) }
Apabila memuatkan aplikasi buat kali pertama, ia akan melaksanakan renderView
dalam urutan, dan kemudian laksanakan executeTemplate
, dan kemudian fungsi templat di atas akan dicetuskan Dengan cara ini, fungsi klik elemen dihantar ke parameter listenerFn
. Pada ketika ini, kami memahami bahawa sumber pencetus fungsi klik ialah zone.js
, tetapi penghantaran fungsi klik sebenar dilaksanakan oleh Angular Jadi bagaimanakah hubungan zone.js
dan Angular? zone.js
akan mengatur tugasan untuk setiap acara tak segerak Berdasarkan contoh dalam artikel ini, invokeTask
dipanggil oleh kod berikut:
function forkInnerZoneWithAngularBehavior(zone) { zone._inner = zone._inner.fork({ name: 'angular', properties: { 'isAngularZone': true }, onInvokeTask: (delegate, current, target, task, applyThis, applyArgs) => { try { onEnter(zone); return delegate.invokeTask(target, task, ...); } finally { onLeave(zone); } } }) }
Anda akan berasa biasa apabila anda melihatnya, kerana dalam Terdapat coretan kod yang serupa dalam artikel yang diperkenalkan oleh zone.js
sebelum ini. Fungsi forkInnerZoneWithAngularBehavior
dipanggil oleh pembina kelas NgZone. Setakat ini kami telah memperkenalkan NgZone, protagonis pengesanan perubahan Sudut, yang merupakan enkapsulasi ringkas zone.js
.
Sekarang kita tahu bagaimana fungsi klik dalam contoh dilaksanakan, jika data aplikasi berubah selepas fungsi itu dilaksanakan, bagaimanakah paparan boleh dikemas kini dalam masa? Mari kembali ke fungsi forkInnerZoneWithAngularBehavior
yang dinyatakan di atas dalam blok pernyataan try finally
, melaksanakan fungsi invokeTask
akhirnya akan melaksanakan fungsi onLeave(zone)
. Analisis lanjut dapat melihat bahawa fungsi onLeave
akhirnya memanggil fungsi checkStable
:
function checkStable(zone) { zone.onMicrotaskEmpty.emit(null); }
Sejajar dengan itu, acara ApplicationRef
ini dilanggan dalam kelas emit
pembina:
class ApplicationRef { /** @internal */ constructor() { this._zone.onMicrotaskEmpty.subscribe({ next: () => { this._zone.run(() => { this.tick(); }); } }); }
Dalam fungsi panggil balik berkaitan langganan, adakah this.tick()
kelihatan biasa? Jika anda telah membaca artikel saya sebelum ini tentang fungsi kitaran hayat Angular, maka anda pasti akan mempunyai tanggapan bahawa ia adalah panggilan utama untuk mencetuskan kemas kini paparan. Walaupun fungsi ini disebut dalam artikel pengenalan kitaran hayat, fokus artikel ini adalah pengesanan perubahan Oleh itu, walaupun fungsinya sama, fokusnya telah berubah sedikit. this.tick
Jujukan panggilan yang berkaitan adalah kira-kira seperti ini:
this.tick() -> view.detectChanges() -> renderComponentOrTemplate() -> refreshView()
Di sini refreshView
adalah lebih penting dan dianalisis secara berasingan:
function refreshView(tView, lView, templateFn, context) { ...... if (templateFn !== null) { // 关键代码1 executeTemplate(tView, lView, templateFn, ...); } ...... if (components !== null) { // 关键代码2 refreshChildComponents(lView, components); } }
Dalam proses ini, refreshView
fungsi akan dipanggil Kali kedua, kali pertama anda masukkan ialah cawangan kod kunci 2, dan kemudian anda memanggil fungsi berikut untuk memasukkan semula fungsi refreshView
:
refreshChildComponents() -> refreshChildComponents() -> refreshComponent() -> refreshView()
Kali kedua anda masukkan fungsi refreshView
ialah kod kunci 1 Bercabang, iaitu, fungsi executeTemplate
dilaksanakan. Apa yang akhirnya dilaksanakan oleh fungsi ini ialah fungsi AppComponent_Template
dalam produk kompilasi templat:
function AppComponent_Template(rf, ctx) { if (rf & 1) { // 条件分支1 i0["ɵɵelementStart"](0, "div", 0); i0["ɵɵlistener"]("click", function AppComponent_Template_div_click_0_listener() { return ctx.handleClick(); }); i0["ɵɵtext"](1); i0["ɵɵelementEnd"](); } if (rf & 2) { // 条件分支2 i0["ɵɵadvance"](1); i0["ɵɵtextInterpolate"](ctx.title); } }
Jika masih ada pembaca yang tidak pasti dari mana datangnya fungsi dalam produk kompilasi templat di atas, adalah disyorkan untuk membaca artikel sebelumnya tentang suntikan kebergantungan Artikel yang menerangkan prinsip tidak akan diulang kerana keterbatasan ruang. Pada masa ini, fungsi AppComponent_Template
melaksanakan kod dalam cawangan bersyarat 2. Fungsi fungsi ɵɵadvance
adalah untuk mengemas kini nilai indeks yang berkaitan untuk memastikan elemen yang betul ditemui. Tumpuan di sini adalah pada fungsi ɵɵtextInterpolate
, yang akhirnya memanggil fungsi ɵɵtextInterpolate1
:
function ɵɵtextInterpolate1(prefix, v0, suffix) { const lView = getLView(); // 关键代码1 const interpolated = interpolation1(lView, prefix, v0, suffix); if (interpolated !== NO_CHANGE) { // 关键代码2 textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate1; }
值得指出的是,该函数名末尾是数字1,这是因为还有类似的ɵɵtextInterpolate2
、ɵɵtextInterpolate3
等等,Angular 内部根据插值表达式的数量调用不同的专用函数,本文示例中文本节点的插值表达式数量为1,因此实际调用的是ɵɵtextInterpolate1
函数。该函数主要做了两件事,关键代码1作用是比较插值表达式值有没有更新,关键代码2则是更新文本节点的值。先来看看关键代码1的函数interpolation1
,它最终调用的是:
function bindingUpdated(lView, bindingIndex, value) { const oldValue = lView[bindingIndex]; if (Object.is(oldValue, value)) { return false; } else { lView[bindingIndex] = value; return true; } }
变更检测前的文本节点值称之为oldValue
, 该值存储在lView
中,lView
我在之前的文章中也提到过,忘记了的读者可以去看看lView
的作用。bindingUpdated
首先会比较新值和旧值,比较的方法便是Object.is
。如果新值旧值没有变化,则返回false
。如果有变化,则更新lView
中存储的值,并返回true
。关键代码2的函数textBindingInternal
最终调用的是下述函数:
function updateTextNode(renderer, rNode, value) { ngDevMode && ngDevMode.rendererSetText++; isProceduralRenderer(renderer) ? renderer.setValue(rNode, value) : rNode.textContent = value; }
走完上述流程,我们点击div
元素时,界面显示内容便会由aa
变为bb
,即完成了从应用数据的变更到 UI 状态的同步更新,这便是 Angular 最基本的变更检测过程了。
因篇幅限制,本文所举示例比较简单,但 Angular 的变更检测还有很多没有讲到。比如,如果应用是由若干个组件组成的,父子组件间的变更检测如何进行,以及如何通过策略优化变更检测等等。如果有对这方面感兴趣的朋友,欢迎关注我的个人公众号【朱玉洁的博客】,后续将在那里分享更多前端知识。
更多编程相关知识,请访问:编程学习!!
Atas ialah kandungan terperinci Mari kita bincangkan tentang pengesanan perubahan dalam Angular melalui contoh. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!