How components won the “framework wars”
Hugo Di Francesco Apr 22 Originally published at codewithhugo.com on Apr 22, 2018
React vs Angular vs Vue: Why it doesn’t matter.
Having experienced React, Vue and Angular, it seems they solve similar problems in a similar way. Where they differ is the setup experience and best-practices. In more ways than one, the mental model of these frameworks/libraries have converged to the component model.
This is a win for the developers since the mental model is the same for all the most widespread frameworks. Which means going from one to the other does not pose as big a challenge as it used to.
If you came here to read a more thorough review and have more background to the Vue, React and Angular ecosystems, I recommend the following from last summer:
The Component and composition model
All 3 of the frameworks’ unit of work are the component. For React you’re extending
React.Component, in Angular we’re setting up a Module to wrap some component(s) with
@Component decorators, in Vue you’re using
Vue.component() to register your components on the Vue instance.
Everything is based around Components, nesting them within each other, passing data between them and so on.
As a composition model, components are meant to be self-contained sections or "bits" of your application that you can then reuse in more specific contexts. The great thing they allow is a way to encapsulate logic, providing API guarantees: you pass x, y and z into this component and you will get this foo behaviour out of it, anything the component does internally is its own business.
State and mutation
The problem that all these frameworks tackle is binding data to the DOM somehow. This is something that the developer would have to do manually in jQuery for example.
This means that the most basic application (that uses a framework/library) will hold some sort of state. The models that Vue, Angular and React themselves (ie. not user-land libraries) expose are actually quite different.
Angular has the belief that state should be mutable. It also has affordances for passing services across components and modules usually keeping that service as a quasi-singleton through dependency injection. A developer can therefore easily write a data-sharing container that will update the relevant components, usually through the service returning Observables and components storing subscriptions to them.
Vue uses a reactivity system to notify the other parts of the application that a change has happened in state. This gives it a performance edge since using
this.property is actually using a setter under the hood, in that setter,
Vue can send off updates wherever they’re required, and not just send them everywhere. The preferred mechanism for composing state to render in the template is computed properties.
state.myProperty), instead the component’s
setState method is called with the data to update.
The encapsulation that components provide however, means that the difference between the specifics of state management is not so obvious when using all these frameworks.
The preferred pattern in all 3 frameworks is to avoid directly mutating data passed from a parent in favour of informing said parent that a state change should happen.
Data passing patterns are simplified with a component-based application: the communication is only done from the parent to the child and vice-versa.
In React, props are passed to pass data but also functions that allow you to update the parent state from the child.
Output bindings are defined in the component and bound in the template.
Outputs behave much like events in that they are emitted by the child and listened to by the parent.
In Vue, props are passed from parent to child and the child can emit events back to the parent.
The way to pass data between “sibling” components is solved in the same way in all these frameworks by finding the nearest common parent in the tree and encapsulating state and state updates there.
Lifecycles, updates and re-render
Components in React, Vue and Angular update if local state or props (inputs) change. If you don’t store any state locally, you could force components to only change when their props change.
Functional components do that for React and Vue, and
ChangeDetection.OnPush change detection strategy can be used in Angular.
The component lifecycles are provided under different method names in each framework.
All three provide a mounted/dismount which refers to the component being initialised in the DOM and it not being needed any more. It's also possible to listen to updates to data and props which usually trigger a re-render of the template.
When an application needs shared data in components that are quite far apart in the component tree, it's time to use a store.
This was initially popularised by the React ecosystem with Facebook’s flux architecture. Flux consists in passing
actions to the store which knows how to update the store's state depending on the action type.
In React the options are redux or MobX, Vue has the officially supported Vuex and Angular has ngrx/store.
This single global store pattern is therefore supported by all these frameworks. The difference is that the libraries are from the ecosystem for Angular and React whereas Vue's store is supported by the core team.
Types and data validation
Validating the type of data within an application is helpful for development and debugging.
Vue and React solve this with prop type validation. A component and its props is defined with typed props, in development mode, the library will check that passed props match the set prop types. These annotations and checks get stripped in production builds of the app/library. The overhead of the checks therefore disappears where performance is most crucial.
Angular doesn’t have such a prop validation mechanism but has the advantage of generally being written in TypeScript. The development experience of having statically typed inputs and outputs is great. These types however get stripped at build time. Detecting type mismatches at runtime is not possible. The majority of these type mismatches end up happening in the IDE or compiler.
React and Vue can also be tooled to leverage type systems such as TypeScript and Flow, which gives them similar guarantees as developing in Angular and TypeScript.
Templates, styles and tooling
Best practices around file structure of an application differ between Angular, Vue and React.
Angular leans towards one folder per Module/Component where your TypeScript, template and style files live. Templates and styles can be written inline in the Angular component inline but the best practice is to have separate files. This is a good idea for large single page applications.
In React, since logic and JSX templates cannot be separated, there is just the question of styles. There are multiple answers: handle styles separately, use webpack to extract your
import ‘my.css' statements into a stylesheet or use CSS-in-JS libraries.
For small projects Vue has the nicer ergonomics, for the largest enterprise use-case, Angular has the most structure, React sits somewhere in between, where the component structure is left as an exercise to the developer but a build tool is recommended (at least to compile JSX).
Testing and server side rendering
Unit testing in Angular is mainly done on the TypeScript components classes. To be able to test the template logic would require a full DOM environment (eg. a headless browser).
Shallow rendering of components means that only the first "layer" of the component's children is rendered, ie. all components that are in the children are not fully evaluated (rendered to HTML), instead they are left as
ComponentName in the component tree.
These allow a Node application to convert a hydrated Vue or React app to markup (HTML) usually to be sent back as a HTML response for the first page load.
It's very possible today to double down on component-based frameworks and to move seamlessly between them.
The concepts around components and how to build an app with them are the same, with framework-specific names which have a clear mapping across frameworks.
The only thing still separating React, Vue and Angular is the underlying philosophies and primitives on which they are built. React leans heavily on developers to pick and choose their preferred tools from the ecosystem (or build them if necessary). Vue is happy to start off as a simple
script tag include but also provides a coherent toolset to build larger applications
(single file components, Vuex, vue-router, documentation and even a style guide). Angular is aimed at large applications and immediately comes with copious amounts of structure and verbosity (using TypeScript) as well as deeply embracing RxJS and Observables.
Originally published at codewithhugo.com on April 22, 2018.