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 2017:

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.

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.

A typical frontend/JavaScript developer career usually involves some jQuery and associated plugins before moving on to React, Angular or Vue.

2018 marked the end of JavaScript fatigue and the React vs Vue vs Angular (“framework wars”) debate

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 @NgModule and @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 these a, b, c behaviours out of it, anything the component does internally is its own business.

This is the first similarity in the React vs Vue vs Angular debate.

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.

React made the notion of immutable state more widespread in the JavaScript ecosystem. State is not updated by mutating (eg. using 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

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.

In Angular, Input and Output bindings are defined in the component and bound in the template. Output s 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.

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.

Global Stores

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.

The design of these “goto” store solutions reflect some of the idiomatic ways to write code with these frameworks: in Vue it’s object and getter/setter based, in Angular it’s Observables, well-organised effects and so on. React has the most choice, “just JavaScript” + pure functions (redux), reactive state watchers (MobX) or even plain JavaScript objects (unstated).

Types and data validation

Validating the type of data within an application is helpful for development and debugging.

JavaScript isn’t statically typed so keeping track of types while passing data through multiple components and functions can become difficult.

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.

Vue is called “the progressive framework” because it offers different features depending on the size of the application being developed. In the simplest case (Vue globally included using a CDN and script tag), writing templates inline is encouraged. Vue also offers a CLI and packages that integrate with build tools like webpack. The preferred way to write components in this environment is the single-file component a file with a template, a script and a style tag. Vue-loader compiles the template into JavaScript along with the script section and extracts the contents of the style tag into a stylesheet at build time.

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).

In React and Vue, thanks to their use of a Virtual DOM and render functions, testing the template logic is possible using enzyme and vue-test-utils respectively.

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.

This ability to render without a full DOM environment is also useful for server-side rendering of the JavaScript application. Vue has the vue-server-renderer package and React the ReactDOMServer.

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.

Angular has similar compilation and rendering ability although these features are less “drop-in to an existing app” and more “build with these tools in mind” since some of the framework’s features have to be used with caution when using Angular Universal which allows for server-side rendering or Angular’s AoT compiler which compiles templates to JavaScript and render functions.

Conclusion

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.