Zona Dart: Kakak Sedang Mengawasi Anda

Halo! Nama saya Dima dan saya adalah pengembang frontend di Wrike. Kami menulis bagian klien dari proyek di Dart, tetapi kami harus bekerja dengan operasi asinkron tidak kurang dari dengan teknologi lainnya. Zona adalah salah satu alat praktis yang disediakan Dart untuk ini. Baru-baru ini saya mulai menganalisis topik ini, dan hari ini saya berencana untuk menunjukkan contoh-contoh penggunaan zona yang tersisa dan fitur penggunaannya yang tidak jelas. Seperti yang dijanjikan, mari kita lihat AngularDart.



Jika Anda ingin memahami fitur dasar zona, baca artikel pertama saya .



gambar



NgZone dan optimalisasi proses deteksi perubahan



: , . over 9000 . !



. , โ€” . , . . , :



class StandardPerformanceCounter {
  final NgZone _zone;

  StandardPerformanceCounter(this._zone) {
    _zone.onMicrotaskEmpty.listen(_countPerformance);
  }
  // ...
}


, : Angular onMicrotaskEmpty , event change detection:



class ApplicationRef extends ChangeDetectionHost {
  ApplicationRef._(
    this._ngZone, // ...
  ) {
    // ...
    _onMicroSub = _ngZone.onMicrotaskEmpty.listen((_) {
      _ngZone.runGuarded(tick);
    });
  }

  // Start change detection
  void tick() {
    _changeDetectors.forEach((detector) {
      detector.detectChanges();
    });
  }
  // ...
}


, , NgZone, . .



NgZone โ€” , โ€” (, Angular ) (, Angular ). NgZone:



class NgZone {
  NgZone._() {
    _outerZone = Zone.current; // Save reference to current zone
    _innerZone = _createInnerZone(
      Zone.current,
      handleUncaughtError: _onErrorWithoutLongStackTrace,
    );
  }

  // Create Angular zone
  Zone _createInnerZone(
    Zone zone, // ...
  ) {
    return zone.fork(
      specification: ZoneSpecification(
        scheduleMicrotask: _scheduleMicrotask,
        run: _run,
        runUnary: _runUnary,
        runBinary: _runBinary,
        handleUncaughtError: handleUncaughtError,
        createTimer: _createTimer,
      ),
      zoneValues: {_thisZoneKey: true, _anyZoneKey: true},
    );
  }
  // ...
}


.



, change detection.







Angular , . , DOM , . Angular : โ€” change detection. , DOM .



โ€” , , . , , - . โ€” .



. Angular , . , , .



event loop :





event loop



, - , โ€” , requestAnimationFrame , , . .



Angular , detectChanges.



, , , , . , detectChanges. .



requestAnimationFrame, ยซยป:



  • Change detection , - .
  • , requestAnimationFrame, , , .
  • change detection . , .


โ€” detectChanges , , . .



, ยซยป Angular :



  • .
  • change detection , .


. innerZone.



:



class NgZone {
  // ...
  // Create Angular zone
  Zone _createInnerZone(
    Zone zone, // ...
  ) {
    return zone.fork(
      specification: ZoneSpecification(
        scheduleMicrotask: _scheduleMicrotask,
        run: _run,
        runUnary: _runUnary,
        runBinary: _runBinary,
        handleUncaughtError: handleUncaughtError,
        createTimer: _createTimer,
      ),
      zoneValues: {_thisZoneKey: true, _anyZoneKey: true},
    );
  }
  // ...
}


, Future , . Angular , Future _run.



:



class NgZone {
  // ...
  R _run<R>(Zone self, ZoneDelegate parent, Zone zone, R fn()) {
    return parent.run(zone, () {
      try {
        _nesting++; // Count nested zone calls
        if (_isStable) {
          _isStable = false; // Set view may change
          // โ€ฆ
        }
        return fn();
      } finally {
        _nesting--;
        _checkStable(); // Check we can try to start change detection
      }
    });
  }
  // ...
}


C run* , , , . NgZone , , . _checkStable , event loop.



change detection , . โ€” scheduleMicrotask:



class NgZone {
  // ...
  void _scheduleMicrotask(Zone _, ZoneDelegate parent, Zone zone, void fn()) {
    _pendingMicrotasks++; // Count scheduled microtasks
    parent.scheduleMicrotask(zone, () {
      try {
        fn();
      } finally {
        _pendingMicrotasks--;
        if (_pendingMicrotasks == 0) {
          _checkStable(); // Check we can try to start change detection
        }
      }
    });
  }
  // ...
}


, . run โ€” , . , . _checkStable , .



, , :



class NgZone {
  // ...
  void _checkStable() {
    // Check task and microtasks are done
    if (_nesting == 0 && !_hasPendingMicrotasks && !_isStable) {
      try {
        // ...
        _onMicrotaskEmpty.add(null); // Notify change detection
      } finally {
        if (!_hasPendingMicrotasks) {
          try {
            runOutsideAngular(() {
              _onTurnDone.add(null);
            });
          } finally {
            _isStable = true; // Set view is done with changes
          }
        }
      }
    }
  }
  // ...
}


- ! , . , _onMicrotaskEmpty. , detectChanges! , change detection . , NgZone , .



:



Angular NgZone. Future , Stream Timer run* scheduleMicrotask, detectChanges.



, . , addEventListener Element , , . โ€” _zone.run() detectChanges, NgZone.



. detectChanges โ€” , , , . Change detection event loop, .



OnPush change detection . . detectChanges, scroll mouseMove . : 1000 200 . , .



Angular , .



Stream runOutsideAngular



runOutsideAngular , , . , onMouseMove Element. , Dart โ€” . Zones :



, .

. , . Angular:



// Part of AngularDart component class
final NgZone _zone;
final ChangeDetectorRef _detector;
final Element _element;

void onSomeLifecycleHook() {
  _zone.runOutsideAngular(() {
    _element.onMouseMove.where(filterEvent).listen((event) {
      doWork(event);
      _zone.run(_detector.markForCheck);
    });
  });
}


โ€” , ? :



// Part of AngularDart component class
final Element _element;

void onSomeLifecycleHook() {
  _element.onMouseMove.where(filterEvent).listen(doWork);
}


, . where . , _WhereStream:



// Part of AngularDart component class
final Element _element;

void onSomeLifecycleHook() {
  _element.onMouseMove // _ElementEventStreamImpl
      .where(filterEvent) // _WhereStream
      .listen(doWork);
}


_WhereStream, . , detectChanges , . .



package:redux_epics



redux_epics. . , , , . change detection , action - , . , , . ?



, , listen redux_epics:



class EpicMiddleware<State> extends MiddlewareClass<State> {
  bool _isSubscribed = false;
  // ...
  @override
  void call(Store<State> store, dynamic action, NextDispatcher next) {
    // Init on first call
    if (!_isSubscribed) {
      _epics.stream
          .switchMap((epic) => epic(_actions.stream, EpicStore(store)))
          .listen(store.dispatch); // Forward all stream actions to dispatch
      _isSubscribed = true; // Set middleware is initialized
    }
    next(action);
    // ...
  }
}


call. ( โ€” ), .



โ€” action . , :



// Part of AngularDart component class
final NgZone _zone;
final AppDispatcher _element;

void onInit() {
  _zone.runOutsideAngular(() {
    // ...
    _dispatcher.dispatch(const InitApp());
  });
}


, null :



// Part of AngularDart component class
final NgZone _zone;
final AppDispatcher _element;

void onInit() {
  _zone.runOutsideAngular(() {
    // ...
    _dispatcher.dispatch(null);
  });
}


, change detection.



change detection



. , , , button:



<!-- parent-component -->
<child-component
  (click)="handleClick()">
</child-component> 

<!-- child-component -->
<button
  type="button"
  (click)="handleClick()">
  Click
</button>


click. . , change detection . , event listeners , addEventListener:



_el_0.addEventListener('click', eventHandler(_handleClick_0));


. addEventListener: , , event loop , . , detectChanges.



Angular , Output:



<!-- parent-component -->
<child-component
  (buttonPress)="handleButtonPress()">
</child-component> 

<!-- child-component -->
<button
  type="button"
  (click)="handleClick()">
  Click
</button>


change detection , Output โ€” , , , , NgZone .



.





โ€” , . - , โ€” .



, . , . โ€” , , . , , .



. โ€” , , . , , issue, . .



Future , . , Dart SDK Future root :



abstract class Future<T> {
  final Future<Null> _nullFuture = Future<Null>.zoneValue(null, Zone.root);

  final Future<bool> _falseFuture = Future<bool>.zoneValue(false, Zone.root);
  // ...
}


, Future . Future then, , , :



  • zone.scheduleMicrotask;
  • zone.registerUnaryCallback;
  • zone.runUnary.


, , then. scheduleMicrotask .



Future โ€” Future , :



// Callbacks doFirstWork and doSecondWork will be called in same microtask
void doWork(Future future) {
  future.then(doFirstWork).then(doSecondWork);
}


scheduleMicrotask. . , :



void doWork(Future future) {
  runZoned(() {
    // First zone
    future.then(doFirstWork);
  }, zoneValues: {#isFirst: true});

  runZoned(() {
    // Second zone
    future.then(doSecondWork);
  }, zoneValues: {#isFirst: false});
}


. โ€” ? ? ? Dart , , Future:



// Zone that is saved in [future] argument will schedule microtask
void doWork(Future future) {
  runZoned(() {
    // First zone
    future.then(doFirstWork);
  }, zoneValues: {#isFirst: true});

  runZoned(() {
    // Second zone
    future.then(doSecondWork);
  }, zoneValues: {#isFirst: false});
}


, , _nullFuture, scheduleMicrotask , root :



final future = Future._nullFuture;
final currentZone = Zone.current;
future.then(doWork);

// currentZone.registerUnaryCallback(...);
// _rootZone.scheduleMicrotask(...);
// currentZone.runUnary(...);


, . FakeAsync: , .



, _nullFuture , :



final controller = StreamController<void>(sync: true);
final subscription = controller.stream.listen(null);
subscription.cancel(); // Returns Future._nullFuture


, . FakeAsync.



, issue, ! , Future Stream, !



. !




All Articles