📜 ⬆️ ⬇️

How to organize a large React-application and make it scalable



One of the best features of React is that it does not impose any restrictions on the file structure of the project. Therefore, on StackOverflow and similar resources there are so many questions about how to structure React applications. This is a very controversial topic. There is no one right way. We propose to understand this issue using the article by Jack Franklin , in which he talks about the approach to structuring large React-applications. Here you will learn what decisions you can make when building React-applications: about choosing tools, structuring files and splitting components into smaller parts.

Build and code validation tools


Webpack is a great tool for collecting projects. Despite its complexity, the fact that the team did a great job on version 2 and the new documentation site greatly simplifies the matter. As soon as you take a Webpack, with a clear concept in your head, you really have an incredibly powerful tool. Babel can be used to compile the code, including transformations specific to React: for example, JSX and webpack-dev-server for local “hosting” of the site. Perhaps HMR will not give any great benefit, so it will suffice to use webpack-dev-server with its automatic page refresh.

We will also use the syntax of ES2015 modules (which is transported by Babel) to import and export dependencies. This syntax has been around for a long time, and although the Webpack supports CommonJS (import syntax in Node style), it’s better to use the latest and the best. In addition, the Webpack can remove the dead code from the bundle using ES2015 modules, which, although not perfect, is a very convenient feature that will become more useful when the community moves to publishing code to npm in ES2015.
')

Configuring the resolution of Webpack modules


The only thing that can be frustrating when working with large projects with an embedded file structure is the definition of relative paths between files. You will find that you have a lot of code that looks like this:

import foo from './foo' import bar from '../../../bar' import baz from '../../lib/baz' 

When you create your application using a Webpack, you can specify the directory in which the Webpack should look for the file if it cannot find it. This allows you to determine the base folder to which all imports belong. For example, you can always put your code in the src directory. And you can make the Webpack always look in this directory. This is done in the same place where you inform the Webpack of any other file extensions you may be using, for example jsx:

 // inside Webpack config object { resolve: { modules: ['node_modules', 'src'], extensions: ['.js', '.jsx'], } } 

The default value for resolve.modules is ['node_modules'] , so you also need to add it, otherwise Webpack will not be able to import files installed using npm or yarn.

After that, you can always import files relative to the src directory:

 import foo from './foo' import bar from 'app/bar' // => src/app/bar import baz from 'an/example/import' // => src/an/example/import 

Although it ties up your application code on a Webpack, this is probably a profitable compromise, because it makes it easier for you to execute the code and makes it easier to add imports.

Directory structure


There is no single correct directory structure for all React applications. As with the rest of this article, you should change the structure to suit your preferences. The following describes one of the examples of a well-functioning structure.

The code lives in src


To make it all organized, put all the application code in a directory called src. It contains only the code that is reduced to the final bundle, and nothing else. This is useful because you can tell Babel (or any other code processing tool) to simply look in one directory and make sure that it does not process any code that it does not need. Other code, such as Webpack configuration files, is located in the appropriate directory. For example, the top-level directory structure may contain:

 - src => app code here - webpack => webpack configs - scripts => any build scripts - tests => any test specific code (API mocks, etc) 

Typically, the only files at the top level are index.html, package.json, and any dotfiles, such as .babelrc. Some people prefer to include the Babel configuration in package.json, but in large projects with many dependencies, these files may become too large, so it’s advisable to use .eslintrc, .babelrc, etc.

By resolve.modules application code in src, you can also use the resolve.modules setting mentioned above, which simplifies the import.

React components


Having defined the src directory, you need to decide how to structure the components. If you put them all in one big folder, such as src / components, then in large projects it is very quickly cluttered.

The general tendency is to have separate folders for “smart” and “silly” components (also known as container and presentation components), but this explicit division is not always useful. And although you probably have components that can be classified as “smart” and “stupid” (see below), it is not necessary to create folders for each of these categories.

We grouped components based on the areas of the application in which they are used, along with the core directory for common components that are used everywhere (buttons, headers and footers are components that are universal and reusable). The remaining directories correspond to specific areas of the application. For example, we have a directory called cart, which contains all the components associated with a shopping cart, and a directory called listings, which contains the code for lists of things that users can buy on the page.

Grouping by catalogs also means that you can avoid unnecessary prefixes pointing to the application area in which components are used. For example, if we have a component that displays the total cost of the user's cart, we can call it Total, not CartTotal, because it is imported from the cart directory:

 import Total from 'src/cart/total' // vs import CartTotal from 'src/cart/cart-total' 

This rule can sometimes be violated: sometimes an additional prefix can bring additional clarity, especially if you have 2-3 similarly named components. But often this method avoids duplicate names.

Jsx extension instead of capital letters


Many people use capital letters in the names of files with React components to distinguish them from regular JavaScript files. Thus, in the above import files will be called CartTotal.js, or Total.js. But you can stick to lowercase letters with hyphens as separators, that is, use the .jsx file extension to distinguish React components: cart-total.jsx.

This gives a slight additional advantage: you can easily search only your React files, limiting your search in .jsx files, and you can even apply specific Webpack plugins to them if necessary.

Whichever file naming convention you choose, it is important to adhere to it. Having a combination of several symbols in your application will quickly become a nightmare in which you have to somehow navigate.

Only one component per file


Following the previous rule, we adhere to the agreement that we should always have only one component in one file, and the component should always be the default export.

Usually our React files look like this:

 import React, { Component, PropTypes } from 'react' export default class Total extends Component { ... } 

In the case when we have to wrap a component in order to connect it, for example, to the Redux data store, the fully wrapped component becomes the default export:

 import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' export class Total extends Component { ... } export default connect(() => {...})(Total) 

Have you noticed that we are still exporting the original component? This is really useful for testing when you can work with a “simple” component, and not configure Redux in your unit tests.

When exporting a default component, it is easy to import a component and know how to get it, instead of looking for the exact name. One of the drawbacks of this approach is that the importing user can invoke the component in any way. Once again, we have an agreement for this: the import must be done by file name. Therefore, if you import total.jsx, then the component should be named Total. user-header.jsx becomes a UserHeader, and so on.

Smart and Stupid React Components


Above we mentioned the separation of components into “smart” and “stupid”. And although we do not list them in separate directories, you can generally divide the application into these two types of components:


“Stupid” components make up the bulk of our application, and, if possible, you should always give them preference. They are easier to work with, fewer problems, and easier to test.

Even when we have to create “smart” components, we try to keep all the JavaScript logic in a separate file. Ideally, components that manipulate data should pass this data to some JavaScript, which will actually do so. Then the manipulation code can be tested separately from React, and you can do anything with it when testing the React component.

Avoid large render methods


One thing we strive for is to have many small React components, and not a smaller number of larger ones. A good indicator that your component becomes too large is the size of the rendering function. If it becomes cumbersome, or you need to break it up into several smaller functions, then it may be time to think about splitting the component.

This is not a hard and fast rule; You and your team should clearly understand what is considered a “big” component for you before increasing their number. But the size of the component's render function is a good guide. You can also use the number of props or items as another good indicator. If a component accepts seven different props, this may be a sign that it is doing too much.

Always use the prop-type


React allows, using the prop-types package, to document the names and types of properties that you expect to be passed to the component. Note that this is not the case in React 15.5, previously proptypes was part of the React module.

When declaring the names and types of expected properties, and whether they are optional, you should feel confident in working with components, and spend less time debugging if you forgot the property name or assigned the wrong type to it. This can be achieved using the ESLint-React PropTypes rule.

It may seem that the time to add them will be wasted. But if you do this, you will thank yourself for re-using the component that you wrote six months ago.

Redux


We use Redux to manage data in many of our applications, and structuring Redux applications is another very common question that has many different opinions.

The winner for us is Ducks, which puts actions (actions), reducer and action creators for each part of your application in one file.

Instead of having reducers.js and actions.js, each of which contains pieces of code to communicate with each other, the Ducks system claims that it makes sense to group related code into one file. Suppose you have a Redux store with two top-level keys, user and posts . Your folder structure will look like this:

 ducks - index.js - user.js - posts.js 

index.js will contain the code that creates the main reducer, possibly using combineReducers from Redux, and in user.js and posts.js you put all the code for them, which usually looks like this:

 // user.js const LOG_IN = 'LOG_IN' export const logIn = name => ({ type: LOG_IN, name }) export default function reducer(state = {}, action) { .. } 

This eliminates the need to import actions and action creators from different files and allows you to store nearby code for different parts of your repository.

Standalone JavaScript Modules


Although this article focused on React components, you can write a lot of code completely separate from React when creating a React application.

It is recommended to do this each time you find a component with business logic that can be removed from a component. A directory with the name lib or services usually works well - the specific name does not matter, but a directory full of “non-React components” is really what you need.

These services sometimes export a group of functions, or an object of related functions. For example, we have services/local-storage , which provides a small wrapper around the window.localStorage native API:

 // services/local-storage.js const LocalStorage = { get() {}, set() {}, ... } export default LocalStorage 

Keeping your logic separate from such components has some really great advantages:


Tests


The Facebook Jest framework is a great tool for testing. It does a lot of tests very quickly and well, runs quickly in the view mode, quickly gives you feedback and out of the box provides some convenient features for React testing. Consider how to structure tests.

Someone will say that it is better to have a separate directory that contains all the tests for all tasks. If you have src / app / foo.jsx, then there will also be tests / app / foo.test.jsx. But in practice, when the application grows, it makes it difficult to find the necessary files. And if you move files to src, you often forget to move them to test, and the structures lose synchronicity. In addition, if you have a file in tests, in which you need to import a file from src, you will get a very long import. Surely everyone faced:

 import Foo from '../../../src/app/foo' 

It’s hard to work with and difficult to fix if you change the directory structure.

But the placement of each test file along with the source file avoids all these problems. To distinguish them, we add the suffix .spec to our tests, although others use .test or just -test, but they all live next to files with the source code with the same name:

 - cart — total.jsx — total.spec.jsx - services — local-storage.js — local-storage.spec.js 

As the directory structure changes, it is easy to move the correct test files. Also immediately noticeable when the file has no tests. You can quickly identify these problems and fix them.

findings


There are many ways to get things done, it can be said about React. One of the best features of the framework is how it allows you to make the most decisions about tools, build tools, and directory structure. We hope that this article has given you some ideas on how to structure your larger React applications.

Source: https://habr.com/ru/post/329060/


All Articles