Comparison of change detection of top 3 frontend framework
I have written about VueJs's change detection before. Recently, I created a simplified version here. In my opinion, it's an exemplary usage of observer/observable pattern in the frontend.
In this article, I want to talk about change detection in other frameworks, namely angular and react.
React doesn't do any active detection of changes. It's just a view layer. Its view components forms a tree structure. A property is passed into each component.
Whenever you have some property changed, you have to call the
render function of the component, so React knows that there is a possible change in UI. The render function will be called and propagated down the tree. The
shouldComponentUpdate hook is a way to stop rendering for some node and all its descendants.
The render function will build the VirtualDOM (POJO) in memory and compare with the existing VirutalDOM to see if anything is changed. If the rendered element is swapped, the node will be destructed and re-rendered. In other case, small changes will be done to the real DOM. E.g. the attribute change, or prepending of an element. React doc emphasizes on having a key for every element, because during the shuffling of a list of same element, it's the fastest way to match an element.
The process is simple to understand. But it really sounds tedious. Data is changed, you don't know which part of UI will be potentially affected, so in order to keep everything in sync, you have to run the render function for the entire tree, and carefully compare the virtually rendered tree with the existing tree. There is no presumed structure for all the properties. The advent of Flux/Redux is to solve this problem. So it segregates the data from UI, and uses callback to bind property changes to render functions. All the properties are managed in the same tree structure as React and the component can better leverage
shouldComponentUpdate to optimize the app.
Angular1's change detection is no better than React's. It builds a tree of scopes and attaches a bunch of watchers and listeners to each scope node, so it has two-way data binding. Change detections is automatically triggered at appropriate points by calling
$scope.$digest(). This digest cycle can be repeated multiple time in one row until nothing else is changed in the cycle. This is very inefficient because it never tracks changes but simply do a check all operation.
Angular2 is improved by delegating the change detection to a separate library called Zone.js. Angular2 assumes change detection should be only done at the end of a frame. And some series of frames are results of the same action and can be viewed as a single thread of actions, thus the check can be done at the end of the thread. Eg. firing a http request, may invoke an UI change; but two of such frames are really results of the same button click. Zone.js easily makes this possible. By creating a zone for an async action, any other async action stemmed from this root action is contained in one zone. And the Zone.js has various hooks that tell you things like when a zone entered or exited. Zone.js enable this by patching all browser based async API's. The list is here.
Another way to optimize Angular2 is when the component state is dependent on the
@Inputs, you can set the change detection strategy to
Push. So the change detector won't be triggered by the state change.
Whether should we use
Redux in Angular2, I think it depends on project. It does the top-down approach, but may not always be optimal.
By using Zone.js, the change detection is much more efficient. React 1.6 rewrote the reconciler (a.k.a renderer) to allow defer rendering. I think it's quite similar to Angular's thread of async action. The intermediate changes of an action is non-important and can be deferred. Just that, with zone.js angular is able to achieve this much easier.
Out of the three approaches of change detections, I like VueJS's most, for its simplicity and efficiency. It's the only framework tracks data dependencies explicitly.