Chapter 8. JavaScript MV* Patterns

Object design and application architecture are the two principal aspects of application design. We have covered patterns that relate to the first in the previous chapter. In this chapter, we’re going to review three fundamental architectural patterns: MVC (Model-View-Controller), MVP (Model-View-Presenter), and MVVM (Model-View-ViewModel). In the past, these patterns were heavily used for structuring desktop and server-side applications. Now they have been adapted for JavaScript too.

As most JavaScript developers currently using these patterns opt to utilize various libraries or frameworks for implementing an MVC/MV*-like structure, we will compare how these solutions differ in their interpretation of MVC compared to classical takes on these patterns.

Note

You can easily distinguish the Model and View layers in most modern browser-based UI design frameworks based on MVC/MVVM. However, the third component varies in both name and function. The * in MV* thus represents whatever form the third component takes in the different frameworks.

MVC

MVC is an architectural design pattern that encourages improved application organization through a separation of concerns. It enforces the isolation of business data (Models) from UIs (Views), with a third component (Controllers) traditionally managing logic and user input. Trygve Reenskaug originally designed the pattern while working on Smalltalk-80 (1979), where it was initially called Model-View-Controller-Editor. MVC was later described in depth in 1995’s Design Patterns: Elements of Reusable Object-Oriented Software (aka the “GoF” book), which played a role in popularizing its use.

Smalltalk-80 MVC

It’s essential to understand what the original MVC pattern was aiming to solve, as it has mutated quite heavily since its origin. Back in the 1970s, GUIs were few and far between. A concept known as Separated Presentation became famous as a means to make a clear division between domain objects that modeled ideas in the real world (e.g., a photo, a person) and the presentation objects that were rendered to the users’ screen.

The Smalltalk-80 implementation of MVC took this concept further and aimed to separate the application logic from the UI. The idea was that decoupling these parts of the application would also allow the reuse of Models for other interfaces in the application. There are some interesting points worth noting about Smalltalk-80’s MVC architecture:

  • A Model represented domain-specific data and was ignorant of the UI (Views and Controllers). When a Model changed, it would inform its observers.

  • A View represented the current state of a Model. The Observer pattern was used to let the View know whenever the Model was updated or modified.

  • The View took care of the presentation, but there wasn’t just a single View and Controller—a View-Controller pair was required for each section or element displayed on the screen.

  • The Controller’s role in this pair was handling user interaction (such as key presses and actions such as clicks) and making decisions for the View.

Developers are sometimes surprised when they learn that the Observer pattern (nowadays commonly implemented as the Publish/Subscribe variation) was included as a part of MVC’s architecture decades ago. In Smalltalk-80’s MVC, the View observes the Model. As mentioned in the bullet point, anytime the Model changes, the Views react. A simple example of this is an application backed by stock market data. For the application to be helpful, any change to the data in our Models should result in the View being refreshed instantly.

Martin Fowler has done an excellent job of writing about the origins of MVC over the years. If you’re interested in some further historical information about Smalltalk-80’s, I recommend reading his work.

MVC for JavaScript Developers

We’ve reviewed the 1970s, but let us return to the present. In modern times, the MVC pattern has been used with a diverse range of programming languages and application types, including those of most relevance to us: JavaScript. JavaScript now has several frameworks boasting support for MVC (or variations of it, which we refer to as the MV* family), allowing developers to add structure to their applications easily.

The first among these frameworks include Backbone, Ember.js, and AngularJS. More recently, React, Angular, and Vue.js ecosystems have been used to implement variations of the MV* family of patterns. Given the importance of avoiding “spaghetti” code, a term that describes code that is very difficult to read or maintain due to its lack of structure, the modern JavaScript developer must understand what this pattern provides. This allows us to effectively appreciate what these frameworks enable us to do differently (Figure 8-1).

ljd2 0801
Figure 8-1. MVC pattern

MVC comprises three core components, described in the following sections.

Models

Models manage the data for an application. They are concerned with neither the UI nor presentation layers but represent unique data forms that an application may require. When a Model changes (e.g., when it is updated), it will typically notify its observers (e.g., Views, a concept we will cover shortly) that a change has occurred so that it may react accordingly.

To understand Models further, let us imagine we have a photo gallery application. In a photo gallery, the concept of a photo would merit its own Model, as it represents a unique kind of domain-specific data. Such a Model may contain related attributes such as a caption, image source, and additional metadata. You would store a specific photo in an instance of a Model, and a Model may also be reusable.

The built-in capabilities of Models vary across frameworks. However, it is pretty standard for them to support the validation of attributes, where attributes represent the properties of the Model, such as a Model identifier. When using Models in real-world applications, we generally also desire Model persistence. Persistence allows us to edit and update Models with the knowledge that their most recent state will be saved in either memory, local storage, or synchronized with a database.

In addition, a Model may have multiple Views observing it. If, say, our photo Model contained metadata, such as its location (longitude and latitude), friends who were present in the photo (a list of identifiers), and a list of tags, a developer may decide to provide a single View to display each of these three facets.

It is not uncommon for MVC/MV* frameworks to provide a means to group Models as a collection. Managing Models in groups allows us to write application logic based on notifications from the group whenever any of the Models in the group is changed. This avoids the need to observe individual Model instances manually.

Older texts on MVC may also refer to a notion of Models managing application state. In JavaScript applications, the state has a different connotation, typically referring to the current “state”—i.e., view or subview (with specific data) on a user’s screen at a fixed point. The state is regularly discussed when looking at single-page applications (SPAs), where the concept of state needs to be simulated.

So to summarize, Models are primarily concerned with business data.

Views

Views are a visual representation of Models that present a filtered view of their current state. While Smalltalk Views are about painting and maintaining a bitmap, JavaScript Views build and organize a collection of DOM elements.

A View typically observes a Model and is notified when the Model changes, allowing the View to update itself accordingly. Design pattern literature commonly refers to Views as “dumb,” given that their knowledge of Models and Controllers in an application is limited.

Users can interact with Views, including the ability to read and edit (i.e., get or set the attribute values in) Models. Because the View is the presentation layer, we generally present the ability to edit and update in a user-friendly fashion. For example, in the photo gallery application we discussed earlier, we could facilitate Model editing through an “edit” View where a user who selected a specific photo could edit its metadata.

The actual task of updating the Model falls to the Controllers (which we will cover shortly).

Let’s explore Views a little further using a conventional JavaScript sample implementation. Now we can see a function that creates a single photo View, consuming both a Model and a Controller instance.

We define a render() utility within our View, which is responsible for rendering the contents of the photoModel using a JavaScript templating engine (Lodash templating) and updating the contents of our View, referenced by photoEl.

The photoModel then adds our render() callback as one of its subscribers so that we can trigger the View to update when the Model changes using the Observer pattern.

One may wonder where user interaction comes into play here. When users click on any element within the View, it’s not the View’s responsibility to know what to do next. It relies on a Controller to make this decision for it. Our sample implementation achieves this by adding an event listener to photoEl, which will delegate handling the click behavior back to the Controller, passing the Model information along with it in case it’s needed.

The architecture’s benefit is that each component plays its role in making the application function as needed:

const buildPhotoView = (photoModel, photoController) => {
  const base = document.createElement( "div" );
  const photoEl = document.createElement( "div" );

  base.appendChild(photoEl);

  const render = () => {
        // We use Lodash's template method
        // which generates the HTML for our photo entry
        photo entry
        photoEl.innerHTML = _.template("#photoTemplate", {
            src: photoModel.getSrc()
        });
    };

  photoModel.addSubscriber( render );

  photoEl.addEventListener( "click", () => {
    photoController.handleEvent( "click", photoModel );
  });

  const show = () => {
    photoEl.style.display = "";
  };

  const hide = () => {
    photoEl.style.display = "none";
  };

  return {
    showView: show,
    hideView: hide
  };
};

Templating

It would be worthwhile to briefly touch upon JavaScript templating while discussing JavaScript frameworks that support MVC/MV*. As mentioned in the previous section, templating is related to Views.

It has long been considered (and proven) a performance bad practice to manually create large blocks of HTML markup in memory through string concatenation. Developers have fallen prey to inefficient iterations through their data, wrapping it in nested divs and using outdated techniques such as document.write to inject the generated “template” into the DOM. This typically means including scripted markup inline with our standard markup. The markup can quickly become difficult to read, and, more importantly, a nontrivial application with such code can be a maintenance disaster.

Modern JavaScript templating solutions have moved toward using tagged template literals, which are a powerful feature of ES6 (ECMAScript 2015). Tagged template literals allow you to create reusable templates using JavaScript’s template literal syntax, along with a custom processing function that can be used to manipulate and populate the template with data. This approach eliminates the need for additional templating libraries and provides a clean, maintainable way to create dynamic HTML content.

Variables within tagged template literals can be easily interpolated using the ${variable} syntax, which is more concise and easier to read than traditional variable delimiters like {{name}}. This makes it simpler to maintain clean Models and templates while allowing the framework to handle most of the work for populating templates from Models. This has many benefits, particularly when opting to store templates externally. This can give way to templates being dynamically loaded on an as-needed basis when building larger applications.

Examples 8-1 and 8-2 are two examples of JavaScript templates. One has been implemented using tagged template literals, and another using Lodash’s templates.

Example 8-1. Tagged template literals code
// Sample data
const photos = [
  {
    caption: 'Sample Photo 1',
    src: 'photo1.jpg',
    metadata: 'Some metadata for photo 1',
  },
  {
    caption: 'Sample Photo 2',
    src: 'photo2.jpg',
    metadata: 'Some metadata for photo 2',
  },
];

// Tagged template literal function
function photoTemplate(strings, caption, src, metadata) {
  return strings[0] + caption + strings[1] + src + strings[2] + metadata
     + strings[3];
}

// Define the template as a tagged template literal string
const template = (caption, src, metadata) => photoTemplate`<li class="photo">
  <h2>${caption}</h2>
  <img class="source" src="${src}"/>
  <div class="metadata">
    ${metadata}
  </div>
</li>`;

// Loop through the data and populate the template
const photoList = document.createElement('ul');
photos.forEach((photo) => {
  const photoItem = template(photo.caption, photo.src, photo.metadata);
  photoList.innerHTML += photoItem;
});

// Insert the populated template into the DOM
document.body.appendChild(photoList);
Example 8-2. Lodash.js templates
<li class="photo">
  <h2><%- caption %></h2>
  <img class="source" src="<%- src %>"/>
  <div class="metadata">
    <%- metadata %>
  </div>
</li>

Note that templates are not themselves Views. A View is an object that observes a Model and keeps the visual representation up to date. A template might be a declarative way to specify part or even all of a View object so that the framework may generate it from the template specification.

It is also worth noting that in classical web development, navigating between independent Views required the use of a page refresh. In single-page JavaScript applications, however, once data is fetched from a server, it can be dynamically rendered in a new View within the same page without any such refresh being necessary. The navigation role thus falls to a router, which assists in managing application state (e.g., allowing users to bookmark a particular View they have navigated to). However, as routers are neither a part of MVC nor present in every MVC-like framework, I will not be going into them in greater detail in this section.

To summarize, Views represent our application data visually, and templates may be used to generate Views. Modern templating techniques, like tagged template literals, provide a clean, efficient, and maintainable way to create dynamic HTML content in JavaScript applications.

Controllers

Controllers are intermediaries between Models and Views, which are classically responsible for updating the Model when the user manipulates the View. They manage the logic and coordination between Models and Views in an application.

What Does MVC Give Us?

This separation of concerns in MVC facilitates simpler modularization of an application’s functionality and enables:

  • Easier overall maintenance. When the application needs to be updated, it is obvious whether the changes are data-centric, meaning changes to Models and possibly Controllers, or merely visual, meaning changes to Views.

  • Decoupling Models and Views means that writing unit tests for business logic is significantly more straightforward.

  • Duplication of low-level Model and Controller code (i.e., what we may have been using instead) is eliminated across the application.

  • Depending on the size of the application and the separation of roles, this modularity allows developers responsible for core logic and developers working on the UIs to work simultaneously.

Smalltalk-80 MVC in JavaScript

Most modern-day JavaScript frameworks attempt to evolve the MVC paradigm to fit the differing needs of web application development. However, there has been one framework that tried to adhere to the pure form of the pattern found in Smalltalk-80. Maria.js by Peter Michaux offers an implementation that is faithful to MVC’s origins: Models are Models, Views are Views, and Controllers are nothing but Controllers. While some developers might feel an MV* framework should address more concerns, this is a valuable reference to be aware of in case you would like a JavaScript implementation of the original MVC.

Summary of MVC

Having reviewed the classical MVC pattern, we should now understand how it allows us to cleanly separate concerns in an application. We should also appreciate how JavaScript MVC frameworks may differ in their interpretation of the MVC pattern. Although quite open to variation, they still share some fundamental concepts the original pattern offers.

When reviewing a new JavaScript MVC/MV* framework, remember: it can be helpful to step back and examine how it has opted to approach the architecture (specifically, how it supports implementing Models, Views, Controllers, or other alternatives), as this can better help us grok the best way to use the framework.

MVP

Model-View-Presenter (MVP) is a derivative of the MVC design pattern that focuses on improving presentation logic. It originated at a company named Taligent in the early 1990s while they were working on a Model for a C++ CommonPoint environment. While both MVC and MVP target the separation of concerns across multiple components, there are some fundamental differences between them.

Here, we will focus on the version of MVP most suitable for web-based architectures.

Models, Views, and Presenters

The P in MVP stands for Presenter. It’s a component that contains the UI business logic for the View. Unlike MVC, invocations from the View are delegated to the Presenters, which are decoupled from the View and instead talk to it through an interface. This has many advantages, such as being able to mock Views in unit tests (MVP pattern) (Figure 8-2).

ljd2 0802
Figure 8-2. MVP pattern

The most common implementation of MVP is one that uses a passive View (a View which is, for all intents and purposes, “dumb”), containing little to no logic. MVC and MVP are different because the roles played by C and P are diverse. In MVP, the P observes Models and updates Views when Models change. The P effectively binds Models to Views, a Controller’s responsibility in MVC.

Solicited by a View, Presenters perform any work related to user requests and pass data back to them. In this respect, they retrieve data, manipulate it, and determine how the data should be displayed in the View. In some implementations, the Presenter also interacts with a service layer to persist data (Models). Models may trigger events, but it’s the Presenter’s role to subscribe to them so that it can update the View. In this passive architecture, we have no concept of direct data binding. Views expose setters that Presenters can use to set data.

The benefit of this change from MVC is that it increases our application’s testability and provides a cleaner separation between the View and the Model. This isn’t, however, without its costs, as the lack of data-binding support in the pattern can often mean having to take care of this task separately.

Although a common implementation of a passive View is for the View to implement an interface, there are variations on it, including the use of events that can decouple the View from the Presenter a little more. As we don’t have the interface construct in JavaScript, we use more of a protocol than an explicit interface here. It’s technically still an API, and it’s probably fair for us to refer to it as an interface from that perspective.

There is also a Supervising Controller variation of MVP, closer to the MVC and MVVM patterns, as it provides data binding from the Model directly from the View.

MVP or MVC?

Now that we’ve discussed both MVP and MVC, how do you select the most appropriate pattern for your application?

MVP is generally used in enterprise-level applications where it’s necessary to reuse as much presentation logic as possible. Applications with very complex Views and a great deal of user interaction may find that MVC doesn’t quite fit the bill here, as solving this problem may mean heavily relying on multiple Controllers. In MVP, all of this complex logic can be encapsulated in a Presenter, significantly simplifying maintenance.

As MVP Views are defined through an interface, and the interface is technically the only point of contact between the system and the View (other than a Presenter), this pattern also allows developers to write presentation logic without needing to wait for designers to produce layouts and graphics for the application.

MVP may be easier to unit test than MVC, depending on the implementation. The reason often cited for this is that you can use the Presenter as a complete mock of the UI so it can be unit-tested independent of other components. In my experience, this depends on the languages we are implementing MVP in (there’s quite a difference between opting for MVP for a JavaScript project over one for, say, ASP.NET).

The underlying concerns we may have with MVC will likely hold for MVP, given that the differences between them are mainly semantic. As long as we cleanly separate concerns into Models, Views, and Controllers (or Presenters), we should achieve most of the same benefits regardless of the variation we choose.

Few, if any, JavaScript architectural frameworks claim to implement the MVC or MVP patterns in their classical form. Many JavaScript developers don’t view MVC and MVP as mutually exclusive (we are more likely to see MVP strictly implemented in web frameworks such as ASP.NET or Google Web Toolkit). This is because we can have additional Presenter/View logic in our application and still consider it a flavor of MVC.

MVVM

MVVM (Model-View-ViewModel) is an architectural pattern based on MVC and MVP, which attempts to more clearly separate the development of UIs from that of the business logic and behavior in an application. To this end, many implementations of this pattern make use of declarative data bindings to allow a separation of work on Views from other layers.

This facilitates UI and development work occurring almost simultaneously within the same codebase. UI developers write bindings to the ViewModel within their document markup (HTML), whereas developers working on the logic for the application maintain the Model and ViewModel (Figure 8-3).

ljd2 0803
Figure 8-3. MVVM pattern

History

MVVM (by name) was initially defined by Microsoft for use with Windows Presentation Foundation (WPF) and Silverlight, having been officially announced in 2005 by John Grossman in a blog post about Avalon (the codename for WPF). It also found some popularity in the Adobe Flex community as an alternative to using MVC.

Before Microsoft adopted the MVVM name, there was a movement in the community to go from MVP to MVPM: Model-View PresentationModel. Martin Fowler wrote an article on PresentationModels back in 2004 for those interested in reading more about it. The idea of a PresentationModel had been around much longer than this article. However, it was considered a significant break for the concept and helped popularize it.

There was quite a lot of uproar in the “alt.net” circles after Microsoft announced MVVM as an alternative to MVPM. Many claimed the company’s dominance in the GUI world allowed them to take over the community, renaming existing concepts as they pleased for marketing purposes. A progressive crowd recognized that while MVVM and MVPM were effectively the same ideas, they came in slightly different packages.

MVVM was originally implemented in JavaScript in the form of structural frameworks such as KnockoutJS, Kendo MVVM, and Knockback.js, with an overall positive response from the community.

Let’s now review the three components that compose MVVM:

Model

Representing the domain-specific information

View

The UI

ViewModel

An interface between the Model and the View

Model

As with other members of the MV* family, the Model in MVVM represents domain-specific data or information with which our application will work. A typical example of domain-specific data might be a user account (e.g., name, avatar, email) or a music track (e.g., title, year, album).

Models hold information but typically don’t handle behavior. They don’t format information or influence how data appears in the browser, as this isn’t their responsibility. Instead, the View governs data formatting, while the behavior is considered business logic that you should encapsulate in another layer that interacts with the Model: the ViewModel.

The only exception to this rule tends to be validation, and it’s acceptable for Models to validate data used to define or update existing Models (e.g., does an email address being input meet the requirements of a particular regular expression?).

View

As with MVC, the View is the only part of the application that users interact with. The View is an interactive UI that represents the state of a ViewModel. In this sense, the View is considered active rather than passive, which is also true for MVC and MVP Views. In MVC, MVP, and MVVM, a View can also be passive, but what does this mean?

A passive View only outputs a display and does not accept any user input. Such a View may also have no real knowledge of the Models in our application and could be manipulated by a Presenter. MVVM’s active View contains the data bindings, events, and behaviors, which requires an understanding of the ViewModel. Although these behaviors can be mapped to properties, the View is still responsible for handling events from the ViewModel.

It’s important to remember that the View isn’t responsible for handling state; it keeps this in sync with the ViewModel.

ViewModel

The ViewModel can be considered a specialized Controller that acts as a data converter. It changes Model information into View information, passing commands from the View to the Model.

For example, let us imagine that we have a Model containing a date attribute in UNIX format (e.g., 1333832407). Rather than our Models being aware of a user’s View of the date (e.g., 04/07/2012 @ 5:00 pm), where it would be necessary to convert the address to its display format, our Model holds the raw format of the data. Our View contains the formatted date, and our ViewModel acts as a middleman between the two.

In this sense, the ViewModel can be seen as more of a Model than a View, but it does handle most of the View’s display logic. The ViewModel may also expose methods for helping to maintain the View’s state, update the Model based on the actions on a View, and trigger events on the View.

In summary, the ViewModel sits behind our UI layer. It exposes data needed by a View (from a Model) and can be the source the View goes to for both data and actions.

Recap: The View and the ViewModel

Views and ViewModels communicate using data bindings and events. As we saw in our initial ViewModel example, the ViewModel doesn’t just expose Model attributes but also provides access to other methods and features, such as validation.

Our Views handle their own UI events, mapping them to the ViewModel as necessary. Models and attributes on the ViewModel are synchronized and updated via two-way data binding.

Triggers (data triggers) also allow us to react further to changes in the state of our Model attributes.

ViewModel Versus Model

While the ViewModel may be entirely responsible for the Model in MVVM, there are some subtleties with this relationship worth noting. The ViewModel can expose a Model or Model attributes for data binding and contain interfaces for fetching and manipulating properties exposed in the View.

Pros and Cons

We now hopefully have a better appreciation for what MVVM is and how it works. Let’s review the advantages and disadvantages of employing this pattern.

Advantages

  • MVVM facilitates easier parallel development of a UI and the building blocks that power it.

  • MVVM abstracts the View and thus reduces the quantity of business logic (or glue) required in the code behind it.

  • The ViewModel can be easier to unit test than in the case of event-driven code.

  • The ViewModel (being more Model than View) can be tested without UI automation and interaction concerns.

Disadvantages

  • For simpler UIs, MVVM can be overkill.

  • While data bindings can be declarative and nice to work with, they can be harder to debug than imperative code, where we simply set breakpoints.

  • Data bindings in nontrivial applications can create a lot of bookkeeping. We also don’t want to end up in a situation where bindings are heavier than the objects being bound.

  • In larger applications, it can be more challenging to design the ViewModel up-front to get the necessary generalization.

MVC Versus MVP Versus MVVM

Both MVP and MVVM are derivatives of MVC. The key difference between MVC and its derivatives is the dependency each layer has on other layers and how tightly bound they are to each other.

In MVC, the View sits on top of our architecture with the Controller beside it. Models sit below the Controller, so our Views know about our Controllers, and Controllers know about Models. Here, our Views have direct access to Models. Exposing the complete Model to the View, however, may have security and performance costs, depending on the complexity of our application. MVVM attempts to avoid these issues.

In MVP, the role of the Controller is replaced with a Presenter. Presenters sit at the same level as Views, listening to events from both the View and Model and mediating the actions between them. Unlike MVVM, there isn’t a mechanism for binding Views to ViewModels, so we instead rely on each View implementing an interface allowing the Presenter to interact with the View.

MVVM consequently allows us to create View-specific subsets of a Model, which can contain state and logic information, avoiding exposing the entire Model to a View. Unlike MVP’s Presenter, a ViewModel is not required to reference a View. The View can bind to properties on the ViewModel, in turn exposing data contained in Models to the View. As we’ve mentioned, the abstraction of the View means there is less logic required in the code behind it.

However, one of the downsides to this is that a level of interpretation is needed between the ViewModel and the View, which can have performance costs. The complexity of this interpretation can also vary: it can be as simple as copying data or as complex as manipulating it to a form we would like the View to see. MVC doesn’t have this problem, as the whole Model is readily available, and such manipulation can be avoided.

Modern MV* Patterns

Frameworks such as Backbone and KnockoutJS used initially to implement MVC and MVVM are no longer popular or updated. They have made way for other libraries and frameworks such as React, Vue.js, Angular, Solid, and many others. Understanding architecture from a Backbone or KnockoutJS perspective may still be relevant because it gives us a sense of where we came from and what changed with modern frameworks.

MV* patterns can always be implemented using the latest vanilla JavaScript as illustrated by this example of a list: ToDo list MVC application. However, developers generally prefer libraries and frameworks for building larger, scalable applications.

Technically modern libraries and frameworks such as React or Vue.js form the View or the presentation layer of applications. In most cases, the frameworks are flexible about how you implement your Model and manage the state in your applications. Vue officially claims to be the ViewModel layer in MVVM. Here are some additional thoughts on MV* in React.

MV* and React.js

To be very clear, React is not an MVC framework. It is a JavaScript library for building UIs and is often used for creating SPAs.

React isn’t considered MVC because it doesn’t map well with how it has been conceived and used on the backend. React is a rendering library that ideally takes care of the View layer. It doesn’t have a central Controller as an orchestrator/router, similar to MVC.

React follows a declarative approach to programming—you describe your application’s desired state, and React renders the appropriate Views based on that state. You don’t use React in an MVC design pattern simply because, with React, the server does not provide a “View” to the browser but “data.” React parses the data on the browser to generate the actual Views. In this sense, you could say that React is a “V” (View) in the MVC pattern, but it is not an MVC framework in the traditional sense.

Another way of looking at it is that React slices the MVC vertically (by concern) instead of horizontally (by technology). You could say Components in React started as small vertically sliced encapsulated MVCs: containing state (Model), rendering (View), and control-flow logic (a localized mini-Controller).

These days, with a lot of component logic extracted into Hooks, you can see Components as Views and Hooks as Controllers. You can also consider “Model ⇒ Suspense resource, View ⇒ Component, Controller ⇒ Hook” if it helps, but don’t take it too seriously.

Next.js is a framework built on top of React that makes it easy to construct server-rendered React applications. It includes features such as automatic code splitting, optimized performance, and easy deployment to production. Like React, Next.js is not an MVC framework, but when you use server-side rendering (SSR) or static site generators (SSGs), it can be like MVC. When Next.js acts as a backend, interacting with a database and providing the View to prerender it, then yes, it’s MVC that is hydrated afterward with the reactive functionalities.

Summary

We have now analyzed the concepts of Model, View, Controller, Presenter, and ViewModel and where they fit in different architectural patterns. Today, we may not see these patterns applied as-is on the frontend where JavaScript is most relevant. However, they may help us figure out the overall architecture of the web application. They may also be applied to individual frontend components where the application sliced vertically may have multiple components, each with a ViewModel or Model to power the View.

By this point, we have now covered a good mix of patterns at the micro (class) as well as macro (architecture) levels. The next chapter will help us design the application flow for a modern JavaScript application. We will look at asynchronous programming patterns that can help us better manage long-running tasks on browsers.