Zonas de dardo: o Big Brother está observando você

Olá! Meu nome é Dima e sou desenvolvedor front-end da Wrike. Escrevemos a parte do cliente do projeto no Dart, mas temos que trabalhar com operações assíncronas não menos que com outras tecnologias. As zonas são uma das ferramentas úteis que o Dart fornece para isso. Recentemente, comecei a analisar esse tópico e hoje pretendo mostrar os exemplos do uso de zonas que me restam e os recursos não óbvios de seu uso. Como prometido, vamos dar uma olhada no AngularDart.



Se você quiser entender os recursos básicos das zonas, leia meu primeiro artigo .



imagem



NgZone e otimização do processo de detecção de alterações



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