Frontend Architecture
Photo by Arturo Castaneyra on Unsplash

Frontend Architecture

Architecture is the design of the structure of something. It is that simple.

How do we structure a frontend application to adapt to product changes, new feature implementation, backend changes and reskinning of a specific part of the application?

There are various kinds of frontend architectural patterns and the pattern depends on the team. However, I will be sharing a common architectural pattern that I use and that you might find familiar. I hope this guide helps you.

Every frontend application is largely made up of HTML, CSS, and JavaScript/TypeScript.

HTML (Hyper Text Markup Language) provides the structure of every page on the web.

No alt text provided for this image

CSS (Cascading Style Sheets) adds styles to HTML and makes webpages more appealing and functional to users. The grey background of those elements in the image above was done with CSS.

nav, div, aside {
   background-color: gray;
}         

JavaScript enables us to interact with the webpages and communicate with the server. I will stop here. How JavaScript enables this interaction is beyond the scope of this guide.

Back to structuring our frontend application. I will be referencing React.js in this guide. If you are not familiar with React, read more about it here.

The frontend application can be broken down into various parts, namely:

  • Component
  • Container
  • HOC
  • Service
  • Store


Components

This is a unit view element on a page.


No alt text provided for this image

A component doesn't contain a state nor have business logic. However, it can dispatch an event to update the parent container and props can be passed to it from the parent container.

No alt text provided for this image

Container

Like the name, contains several components. An example is a modal. A modal contains some modal-specific styles, logic, and state. The container helps break down a webpage into groups of components that achieve a specific goal. It could contain state and business logic. The modal container for example makes a group of components visible to a user when they perform certain actions without taking them to another page.

No alt text provided for this image

From above, you can see how the modal container is declared, and other components are passed to it as properties (footer, title, and children).

From the view of our entire application, the modal can also be considered a view component. That means the Page could also be considered as a container if we pass it as a child component in a layout container. See the code block below.

No alt text provided for this image

In other words, you determine what a container or component is in your frontend application.

Component is a unit view element in our application and Container is a grouped components that achieve a specific goal on a page.

HOC

A Higher Order Component is a pure component that takes a component as an argument and returns a new component. It does not mutate the state of the component passed into it. Like a pure JavaScript function, it causes no side effects to the component it takes. HOC enables the reusability of component logic in our frontend application. A typical example would be passing an authenticated user to a page.

No alt text provided for this image

Let's take a closer look at what we have in the `withAuth` function.

No alt text provided for this image


From the image, our page component is returned if we are authenticated, else the ErrorView Component is returned. You can also add the `sign in` functionality from the `withAuth` HOC.


A real-world HOC example is the somewhat magical Redux connect

connect(mapStateToProps, mapDispatchToProps)(Page)        

And react-router

withRouter(Page)        
I would recommend using hooks instead of HoC except for advanced cases.

Services

Services are functions that contain our application logic. They do not contain any application view. The service application layer glues the view with the data from the database and other business logic.

You can couple your view with business logic, but that would be unmaintainable and unscalable. For example,

No alt text provided for this image

In this example, we have just two functions for fetching the list of products and adding a product to our wish list. However, we might want to add a product to the cart, checkout, sort products by price range, and filter products by categories. Some or most of these actions might require us to make requests to the server. This is where the service layer comes to the rescue, to separate these logics from the view, so other developers can easily maintain the Product component and add more functionality to it.

Let's refactor our Product Component.

No alt text provided for this image

As you can see in our refactored example, the logic for manipulating the products is no longer in our Product components. We can easily swap out the API endpoints or add a new product transformation function easily without having our Product component file grow into 1000 lines of code. You don't need to call it services, but it's an abstraction that helps you modularize your application business logic.

Nevertheless, if you watch the service layer of the previous example closely, you will see that we repeated the use of `fetch` web API. Whenever we make a POST request, we would need to pass the content type to the header in order to send JSON data to the server. A better approach would be to create a reusable API service.

Let's refactor the example above with TypeScript. TypeScript will enable us to define the interface through which other parts of the application can interact with the API.

No alt text provided for this image

Here is the api.ts file

No alt text provided for this image

This approach helps make the project much more maintainable, and scalable and reduces WTFs when you look at your code base three weeks after.

Store

A store holds shared states of your application. e.g. How does the admin dashboard know that the user is authenticated after he logs in? Sometimes, states are located close to a group of components that can retrieve/update those states.

No alt text provided for this image

In the image above, if I put the states inside the Container, I would be hiding it from the other Components.

In React world, typical store library that you can find are Redux, Zustand, MobX, and React Context, etc.

Let's see an example with React Context,

No alt text provided for this image

A React Context holds and provides context (state) of part or whole of the application that components can consume or update. One key advantage of using React Context is minimizing props drilling.

React Context is not a fixed solution to props drilling. I would recommend you try building your components following the bottom-up approach.

Let's continue with our earlier example and wrap our Page Component with the theme provider and also toggle the theme in a component down the Page tree.

No alt text provided for this image

Take a guess where we will be toggling the theme mode—in the Nav Component!

Let's see how we would do that.

No alt text provided for this image
I had to change the background colour so you can see my moon!

You can see how Page's children have access to the same state without having them passed as props.

Phew! We have come to the end. How does this all work in the real world?

These are concepts that make codebase maintainable and ensure multiple developers follows the same paradigm as the product scale. They also make refactoring easier. Below is what my project codebase usually looks like.

No alt text provided for this image

Thanks for reading!

What do you think I left out and how do you think I should improve this article?

要查看或添加评论,请登录

Emmanuel Ugwuoke的更多文章

社区洞察

其他会员也浏览了