Foreword
This is my first post on Habré, so do not judge too harshly (well, or judge, but constructively ).
I would like to note that in this approach, the main advantage for me is that we clearly delineate and delegate business logic by modules. One module is responsible for one thing and for something very specific. That is, with this approach, during the development process, there is no thought: "where would I better (more correctly) do this here?" With this approach, it is immediately clear where exactly the task / problem should be solved.
File structure
Project structure + is standard. I will try to tell you briefly what is used from what the assembly itself consists of
- To prepare asynchronous actions, I use saga middleware - a much more flexible and powerful thing regarding thunk middleware
- I have css-modules styling components. In fact, the concept is the same as that of styled components, but somehow it's more convenient for me
- An interlayer between containers and components that controls the props thrown from the store into the component (filtering, memoization and the ability to conveniently manipulate the field names of any piece of application state) - reselect
Redux sagas
Redux sagas documentation
The main essence of sagas (and not only) is that we separate the business logic of working with requests by modules. Each saga is responsible for its own piece of the API. If I needed to get user data and call an action on their successful receipt - I would do it in a separate module usersSagas.js
The main factors (for me) in favor of the sagas are:
UPD (added a point about promise-hell)
- 1) The fact that actions, in an amicable way, should be just functions that give an object. If some of the actions look different (and this is how it happens with the use of thunk), I would like to somehow bring them to one form, because somehow this does not fit into a single concept. I would like to move the logic of requests and work with data for requests into a separate module - this is what sagas are for
- 2) As a rule, if we need to make several requests that use data from responses to previous requests (and we don't want to store intermediate data in the store?), Redux-thunk invites us to make a new action in which we will call another async-action request, in which 2 more (for example) the same iterations are made. It smacks of promise-hell and usually looks messy and, in principle, inconvenient to use (too many nestings), as for me
css-modules / styled components
I often see on projects that people write rules for styling components in some common css modules.
I am totally against it. The component is an isolated system. It should be as reusable as possible . Therefore, it would be nice to style it separately.
Reselect
Selectors have several goals:
- , . , , ( , ). , , . - getFilteredItems,
- ,
- . - . - . friends. , . , . , , friends contacts. reselect , , . — .
It is because of the last point that I am drowning for the fact that we make a reset for every sneeze. Reselect - not only for memoization, but also for more convenient work with the drawing of the application state tree
Components and Containers
In the classic approach to React (without using libraries for storing the store as a separate entity), there are two types of components - Presentational and Container-Components. Usually (as I understand it) this is not a strict folder division, but rather a conceptual division.
Presentational components are stupid. They are, in fact, only the layout and display of data that was thrown into them as props. (an example of such a component can be found above in css-modules)
Container-Components - components that encapsulate the logic of working with, for example, a component's life cycle. They are responsible for invoking an action that is responsible for requesting data, for example. They return a minimum of layout, because layout is isolated in Presentational-Components.
Example + -Container-Component:
Redux Containers are, in fact, a layer between the redax side and the react components. In them, we call selectors and throw actions into the component's react props.
I AM FOR having its own container for every sneeze. Firstly, it gives you more control over the props thrown into the component, and secondly, it gives you control over the performance using reselect.
It often happens that we need to reuse one component, but with different parts of the store. It turns out that for this we just need to write another container and return it where necessary. That is, the relationship Many to one (many are containers, one is a component. For me, it is convenient and conceptually)
I would also like to give a more frequent example in favor of containerizing most of the components.
We have an array of data (an array of users, for example), which we receive from some API. We also have an infinite scroll, which the customer is not going to refuse. We scrolled down for a very long time and loaded about 10k + data. And now we have changed some property of one user. Our application will slow down a lot because:
- We attached the global container to the entire page with the list of users
- When changing one field of one element of the users array, a NEW array with new elements and indices returned in the users reducer
- All components placed in the tree of the UsersPage component will be redrawn. Including each User component (array element)
How can you avoid this?
We make containers on
- array with users
- array element (one user)
After that, in the component, which is wrapped in an "array with users" container, we return the "array element (one user)" container with the key (react required prop) thrown there, index
In the "array element (one user)" container in mapStateToProps we are the second as an argument, we take the ownProps of the component that the container returns (among them index). By index, we pull out directly from the store only one element of the array.
Further, it will become much easier to optimize the redrawing of only the changed element (the entire array is redrawn, because in the reducer we made some kind of map that returns a NEW array with new indices for each element) - here we will be helped by reselecting the
container array:
the container element:
element-selector:
If there are any additions, I will read them with pleasure in the comments.