ู…ู†ุงุทู‚ Dart: Big Brother ูŠุฑุงู‚ุจูƒ

ู…ุฑุญุจุง! ุงุณู…ูŠ ุฏูŠู…ุง ูˆุฃู†ุง ู…ุทูˆุฑ ุฃู…ุงู…ูŠ ููŠ Wrike. ู†ูƒุชุจ ุฌุฒุก ุงู„ุนู…ูŠู„ ู…ู† ุงู„ู…ุดุฑูˆุน ููŠ Dart ุŒ ูˆู„ูƒู† ุนู„ูŠู†ุง ุงู„ุนู…ู„ ู…ุน ุนู…ู„ูŠุงุช ุบูŠุฑ ู…ุชุฒุงู…ู†ุฉ ู„ุง ุชู‚ู„ ุนู† ุงู„ุชู‚ู†ูŠุงุช ุงู„ุฃุฎุฑู‰. ุงู„ู…ู†ุงุทู‚ ู‡ูŠ ูˆุงุญุฏุฉ ู…ู† ุงู„ุฃุฏูˆุงุช ุงู„ู…ููŠุฏุฉ ุงู„ุชูŠ ูŠูˆูุฑู‡ุง Dart ู„ุฐู„ูƒ. ุจุฏุฃุช ู…ุคุฎุฑู‹ุง ููŠ ุชุญู„ูŠู„ ู‡ุฐุง ุงู„ู…ูˆุถูˆุน ุŒ ูˆุงู„ูŠูˆู… ุฃุฎุทุท ู„ุนุฑุถ ุฃู…ุซู„ุฉ ุนู„ู‰ ุงุณุชุฎุฏุงู… ุงู„ู…ู†ุงุทู‚ ุงู„ุชูŠ ุชุฑูƒุชู‡ุง ูˆู…ูŠุฒุงุช ุบูŠุฑ ูˆุงุถุญุฉ ู„ุงุณุชุฎุฏุงู…ู‡ุง. ูƒู…ุง ูˆุนุฏู†ุง ุŒ ู„ู†ู„ู‚ูŠ ู†ุธุฑุฉ ุนู„ู‰ AngularDart.



ุฅุฐุง ูƒู†ุช ุชุฑูŠุฏ ูู‡ู… ุงู„ู…ูŠุฒุงุช ุงู„ุฃุณุงุณูŠุฉ ู„ู„ู…ู†ุงุทู‚ ุŒ ูุงู‚ุฑุฃ ู…ู‚ุงู„ุชูŠ ุงู„ุฃูˆู„ู‰ .



ุตูˆุฑุฉ



NgZone ูˆุชุญุณูŠู† ุนู…ู„ูŠุฉ ุงู„ูƒุดู ุนู† ุงู„ุชุบูŠูŠุฑ



: , . 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