📜 ⬆️ ⬇️

Structuring React Applications

The material, the translation of which we publish today, reveals the approaches used by its author when structuring React applications. In particular, we will discuss here the folder structure used, the naming of entities, the places where the test files are located, and other similar things.

One of the most pleasant features of React is that this library does not force the developer to strictly observe certain conventions regarding the structure of the project. Much of this remains at the discretion of the programmer. This approach is different from that, say, adopted in the Ember.js or Angular frameworks. They give developers more standard features. These frameworks provide for conventions regarding the structure of projects and rules for naming files and components.


')
Personally, I like the approach adopted by React. The fact is that I prefer to control something myself, without relying on certain “agreements”. However, there are many advantages to the approach to structuring projects that Angular offers. The choice between freedom and more or less rigid rules comes down to what is closer to you and your team.

Over the years of working with React, I have tried many different ways to structure applications. Some of the ideas I applied turned out to be more successful than others. Therefore, here I am going to talk about everything that has proven itself in practice. I hope you find something here that is useful to you.

I’m not trying to show here some “only right” way of structuring applications. You can take some of my ideas and change them to fit your needs. You may well disagree with me by continuing to work as you did before. Different teams create different applications and use different means to achieve their goals.

It is important to note that if you look at the Thread website, which I participate in the development, and look at the device of its interface, you will find places in which those rules that I will talk about are not respected. The fact is that any “rules” in programming should be taken only as recommendations, and not as comprehensive standards that are valid in any situation. And if you think that some kind of “rules” do not suit you, you, for the sake of improving the quality of what you are working on, should find the strength to deviate from these “rules”.

Actually, now, without further ado, I offer you my story about structuring React applications.

Don't worry too much about the rules.


Perhaps you decide that the recommendation that you don’t worry too much about the rules looks strange at the beginning of our conversation. But I mean exactly this when I say that the main mistake that programmers have in terms of observing the rules is that the programmers attach too much importance to the rules. This is especially true at the beginning of work on a new project. At the time of the creation of the first index.jsx simply impossible to know what is best for this project. As the project develops, you will naturally come to some kind of file and folder structure, which is likely to be quite good for this project. If during the continuation of the work it turns out that the existing structure is somewhat unsuccessful, it can be improved.

If you read this and catch yourself thinking that there is nothing in your application that is being discussed, then this is not a problem. Each application is unique, there are no two absolutely identical development teams. Therefore, each team, working on a project, comes to some agreements regarding its structure and methods of working on it. This helps team members work productively. Do not strive to, having learned about how someone is doing something, immediately introduce this to yourself. Do not try to introduce into your work what is called in certain materials, and even in this, the "most effective way" to solve a problem. I have always adhered to and adhere to the following strategy regarding such recommendations. I have my own set of rules, but, reading about how others act in certain situations, I choose what seems to me successful and suitable for me. This leads to the fact that over time, my working methods improve. At the same time, I do not have any shocks and there is no desire to rewrite everything from scratch.

Important components are located in separate folders


The approach to placing component files in folders that I came to is that those components that can be considered “important”, “basic”, “basic” in the application context are placed in separate folders. These folders, in turn, are located in the components folder. For example, if we are talking about an application for an electronic store, then the <Product> component used to describe the product can be recognized as a similar component. Here is what I mean:

 - src/  - components/    - product/      - product.jsx      - product-price.jsx    - navigation/      - navigation.jsx    - checkout-flow/      - checkout-flow.jsx 

In this case, the "secondary" components that are used only by certain "main" components are located in the same folder as these "main" components. This approach has proven itself in practice. The fact is that due to its application, a certain structure appears in the project, but the level of folder nesting is not too large. Its application does not lead to the appearance of something like ../../../ in the component import commands, it does not make it difficult to move around the project. This approach allows you to build a clear hierarchy of components. That component, whose name matches the name of the folder, is considered to be “basic”. Other components located in the same folder serve the purpose of dividing the "base" component into parts, which simplifies working with the code of this component and its support.

Although I am a supporter of the presence of a certain folder structure in the project, I believe that the most important thing is the selection of good file names. Folders themselves are less important.

Using subfolders for subcomponents


One of the drawbacks of the above approach is that its use can lead to the appearance of folders of “basic” components containing a lot of files. Consider, for example, the <Product> component. CSS files will be attached to it (we'll talk about them later), test files, many subcomponents, and, possibly, other resources - such as images and SVG icons. The list of “additions” is not limited to this. All this will fall into the same folder as the "base" component.

I really don’t really care about that. This suits me if the files have well-thought-out names, and if they can be easily found (using the file search tools in the editor). If this is the case, then the folder structure fades into the background. Here is a tweet on this topic.

However, if you prefer your project to have a more extensive structure, there is nothing difficult in moving subcomponents to their own folders:

 - src/  - components/    - product/      - product.jsx      - ...      - product-price/        - product-price.jsx 

Test files are located in the same place as the files of the components under test.


We begin this section with a simple recommendation, which is that the test files should be placed in the same place as the files with the code that they are checked with their help. I will also talk about how I prefer to structure the components, trying to ensure that they would be close to each other. But now I can say that I find it convenient to place the test files in the same folders as the component files. In this case, the names of the files with the tests are identical to the names of the files with the code. To the test names, before the file name extension, the suffix .test only added:


This approach has several strengths:


If we have certain data used during the test, for example, something like API request mocks, we put them in the same folder where the component and its test already lie. When everything that may be needed lies in one folder, this contributes to the growth of productivity. For example, if you use a branched folder structure and the programmer is sure that a certain file exists, but cannot remember its name, the programmer will have to look for this file in many subdirectories. With the proposed approach, just look at the contents of one folder and everything will become clear.

CSS Modules


I am a big fan of CSS modules . We found that they are great for creating modular CSS rules for components.

In addition, I really like styled-components technology. However, in the course of work on projects in which many developers participated, it turned out that the presence of real CSS files in the project increases the usability.

As you probably already guessed, our CSS-files are located, like other files, next to the component files, in the same folders. This greatly simplifies the movement between files when you need to quickly understand the meaning of a class.

A more general recommendation, the essence of which permeates all this material, is that all the code related to a certain component should be kept in the same folder in which this component is located. Gone are the days when separate folders were used to store CSS and JS code, test code and other resources. The use of complex folder structures complicates the movement between files and does not have any obvious benefit, except that it helps to "organize the code." Keep interconnected files in the same folder - this means spending less time moving between folders during work.

We even created a Webpack loader for CSS, the capabilities of which correspond to the features of our work. It checks the declared class names and throws an error in the console if we refer to a class that does not exist.

Almost always, only one component code is placed in one file


My experience shows that programmers are usually too strict in adhering to the rule that one and only one React component code should be in one file. At the same time, I fully support the idea that it is not worth placing too many components in one file (just imagine the difficulty of naming such files!). But I believe that there is nothing wrong with putting in the same file in which the code of a certain "large" component is located, and the code of the "small" component associated with it. If such a move helps preserve the purity of the code, if the "small" component is not too large to be placed in a separate file, then this will not harm anyone.

For example, if I create a <Product> component, and I need a small piece of code to display the price, then I can do this:

 const Price = ({ price, currency }) => (  <span>    {currency}    {formatPrice(price)}  </span> ) const Product = props => {  // ,      !  return (    <div>      <Price price={props.price} currency={props.currency} />      <div>loads more stuff...</div>    </div>  ) } 

The good thing about this approach is that I did not have to create a separate file for the <Price> component, and that this component is available exclusively to the <Product> component. We do not export this component, so it cannot be imported elsewhere in the application. This means that when asked whether to put <Price> in a separate file, you can give a clear, positive answer if you need to import it somewhere else. Otherwise, you can do without putting the <Price> code into a separate file.

Separate folders for universal components


We have recently been using universal components. They, in fact, form our design system (which we plan to publish someday), but so far we have started small - with components like <Button> and <Logo> . A component is considered to be “universal” if it is not tied to a specific part of the site, but is one of the building blocks of the user interface.

Similar components are located in your own folder ( src/components/generic ). This greatly simplifies the work with all universal components. They are in one place - it is very convenient. Over time, as the project grows, we plan to develop a style guide (we are big fans of react-styleguidist ) in order to further simplify the work with universal components.

Using Aliases to Import Entities


The relatively flat folder structure in our projects ensures that the import commands do not have too long structures like ../../ . But it’s hard to do without them. Therefore, we used babel-plugin-module-resolver to configure aliases that simplify import commands.

You can do the same with Webpack, but thanks to the use of the Babel plugin, the same import commands can also work in tests.

We configured this with a pair of aliases:

 {  components: './src/components',  '^generic/([\\w_]+)': './src/components/generic/\\1/\\1', } 

The first is extremely simple. It allows you to import any component, starting the command with the word components . In the normal approach, import commands look something like this:

 import Product from '../../components/product/product' 

Instead, we can write them like this:

 import Product from 'components/product/product' 

Both commands import the same file. This is very convenient, as it allows you not to think about the folder structure.

The second alias is a little more complicated:

 '^generic/([\\w_]+)': './src/components/generic/\\1/\\1', 

We use regex here. It finds import commands that begin with generic (the ^ sign at the beginning of the expression allows you to select only those commands that begin with generic ), and captures what is after generic/ into the group. After that, we use the captured fragment ( \\1 ) in the ./src/components/generic/\\1/\\1 construct.

As a result, we can use the import commands for universal components of this kind:

 import Button from 'generic/button' 

They are converted to the following commands:

 import Button from 'src/components/generic/button/button' 

This command, for example, serves to import a JSX file describing a universal button. We did all this because this approach greatly simplifies the import of universal components. In addition, it will serve us well if we decide to change the structure of the project files (this, since our design system is growing, is quite possible).

Here I would like to note that you should be careful when working with pseudonyms. If you have only a few of them, and they are designed to solve standard import problems, then everything is fine. But if you have a lot of them, they can bring more confusion than good.

Universal lib folder for utilities


I would like to regain all the time that I spent on finding the perfect place for code that is not component code. I shared this all according to different principles, highlighting the code of utilities, services, auxiliary functions. All this has so many names that I won’t mention all of them. Now I'm not trying to figure out the difference between the "utility" and the "auxiliary function" in order to find the right place for a certain file. Now I use a much simpler and more understandable approach: all this falls into a single lib folder.

In the long run, the size of this folder may turn out to be so large that you have to structure it somehow, but this is completely normal. It is always easier to equip something with a certain structure than to get rid of errors of excessive structuring.

In our Thread project, the lib folder contains about 100 files. They are divided approximately equally into files containing the implementation of certain features, and into test files. It did not cause difficulties in finding the necessary files. Thanks to the intelligent search lib/name_of_thing built into most editors, I almost always have to enter something like lib/name_of_thing , and what I need is found.

In addition, we have an alias that simplifies import from the lib folder, allowing you to use commands of this kind:

 import formatPrice from 'lib/format_price' 

Do not be alarmed by the flat folder structures that can cause multiple folders to be stored in one folder. Usually such a structure is all that is needed for a certain project.

Hiding third-party libraries behind native APIs


I really like the Sentry bug monitoring system. I often used it when developing server and client parts of applications. With its help, you can catch exceptions and receive notifications about their occurrence. This is a great tool that allows us to keep abreast of the problems encountered on the site.

Whenever I use a third-party library in my project, I think about how to make it so that, if necessary, it can be replaced as easily as possible with something else. Often, as with the same Sentry system that we really like, this is not necessary. But, just in case, it never hurts to think out a way to avoid using a certain service or a way to change it to something else.

The best solution to this problem is to develop your own API that hides other people's tools. This is something like creating a lib/error-reporting.js module that exports the reportError() function. The core of this module uses Sentry. But Sentry is directly imported only in this module and nowhere else. This means that replacing Sentry with another tool will look very simple. To do this, it will be enough to change one file in one place. As long as the public API of this file remains unchanged, the rest of the project will not even know that when calling reportError() , it is not Sentry that is used, but something else.

Please note that the module’s public API is called the functions it exports and their arguments. They are also called the public interface of the module.

Using PropTypes (or tools such as TypeScript or Flow)


When I do programming, I think of three versions of myself:


It may sound weird, but I found it useful, thinking about how to write code, ask the following question: “How will it be perceived in six months?”.

One simple way to make yourself present and yourself more productive is to specify the types of properties ( PropTypes ) used by the components. This will save time searching for possible typos. This will save you from situations where, using the component, properties of the wrong types are applied, or they completely forget about the transfer of properties. In our case, the eslint-react / prop-types rule is a good reminder of the need to use PropTypes .

If you go even further, it is recommended to describe the properties as accurately as possible. For example, you can do this:

 blogPost: PropTypes.object.isRequired 

But it would be much better to do this:

 blogPost: PropTypes.shape({  id: PropTypes.number.isRequired,  title: PropTypes.string.isRequired,  //    }).isRequired 

In the first example, the minimum necessary check is performed. In the second, the developer is given much more useful information. They will come in very handy, for example, if someone forgets about a certain field used in the object.

Third-party libraries are used only when they are truly needed.


This tip is more relevant than ever with the advent of React hooks . For example, I was engaged in a large alteration of one of the parts of the Thread site and decided to pay special attention to the use of third-party libraries. I suggested that using hooks and some of my own developments, I can do a lot of things without using someone else's code.My guess (which was a pleasant surprise) turned out to be true. You can read about this here in the material about managing the state of React-applications. If you are attracted to such ideas, consider that these days, thanks to the React and Context API hooks, you can go very far in implementing these ideas.

Of course, some things, like Redux, are necessary in certain circumstances. I would advise you not to strive to completely avoid such decisions (and you should not give priority to avoiding them if you already use them). But, if you have a thought about including a new library in the project, you should know that this is far from always the only solution to a certain problem.


— , , . .

 //     emitter.send('user_add_to_cart') //     emitter.on('user_add_to_cart', () => {  //  -  }) 

, . , . , « ». , , , . . «» - , . , , .

Redux. . , . , , user_add_to_cart , . . , , Redux, . , Redux , .

, , , , :


- , . , , . , , , «» .

, , API Context - .


, (, , ). : , .

, , , . :

 const wrapper = mount(  <UserAuth.Provider value=>    <ComponentUnderTest />  </UserAuth.Provider> ) 

:

 const wrapper = mountWithAuth(ComponentUnderTest, {  name: 'Jack',  userId: 1, }) 

:


, test-utils.js . , — . .

Summary


. , , , , . , , . , : . . - , .

Dear readers! React-?

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


All Articles