Author | Li Jun (Nirvana) Source |Erda public account
The introduction
Hello everyone, this is the Erda open source project front-end technical team, today to talk about front-end state management.
When it comes to the state management library, I think the front end of the students can easily name several to the community wheel after wheel. Today I’m not going to talk about the technical details of a particular library, but I’m going to talk about the process of implementing a state management library, and some of the thoughts I’ve had along the way.
background
The front-end state management of Erda projects, from redux at the beginning, to DVA, and now to Cube – State, is also gradually developing with the trend of the community. Redux aside, DVA seems to me to be an excellent library with a very personal design philosophy. The reason for abandoning it for a self-developed state management library is that DVA is a wrapper based on Redux, and redux’s string-matched Dispatch action is inherently hard to support. As the project grows to hundreds of pages and nearly 2000 files, not having a complete and reliable type definition is a disaster for subsequent development and maintenance.
For example, a common reducer in dVA looks like this:
dispatch({ type: 'products/delete', payload: id });
Copy the code
The interrogation of the soul came:
- How do you make sure that type is right?
- How do I know whether the reducer delete under Products still exists?
- How do I know that the payload type matches the reducer type?
- How does the data connected to the component know to be of the type that the component needs?
The target
We were eager to find a state management library that supported type definitions because the above problems could not be solved without type definitions, and we were afraid to delete obsolete code because we were worried about where it was still in use. At the same time, we needed to be able to smooth and gradual transformation in order to avoid the transformation too much affecting normal business development.
Our goals are clear:
- Obtain data from the API, put data into store, components obtain data from store, and components call the effect or reducer of store. The whole link has a type.
- Compatible with DVA, do incremental modifications, the best architecture is similar to the API, no additional learning costs.
- Easy to extend, put project-related logic in extensions, and keep the library itself simple and reliable.
process
Exploring open Source Libraries
At that time, many libraries were researched, but few supported a complete type definition link, or to switch to another system, the transformation was very difficult, and the business risk was too high. We also thought it would be better to build a wheel ourselves, but encountered some problems in the process of trying, until one day we found the Stamen library, which uses React Hooks for listening and unlistening, which inspired us.
Stamen architecture diagram
The above is the architecture diagram of stamen repository. You can see that the structure is similar to DVA. Each store is divided into three parts: state, Effects, and reducers. The listener is removed when unmount is performed. Because hooks are normal functions that are easy to type, they are typescript-friendly. The types of the state structure, effects and reducers functions in the store can be easily obtained. If the component is also a function, then the entire type link is already connected.
Why didn’t we use it directly, but based on it? Because we made some enhancements based on DVA in our project, Stamen couldn’t meet them, and the logic didn’t apply to every team, so it wasn’t suitable for someone else’s library.
Can not meet the following points:
- There is no key or name attribute and must be introduced by name, which is inconvenient in some scenarios.
- It doesn’t provide the same kind of SUBSCRIPTIONS capability as DVA, and route monitoring is a very common feature in our project.
- When using dispatch(“action”, payload), the payload type cannot be determined, thus causing the link type to be interrupted. In addition, using dispatch(“action”, payload) is less intuitive and convenient than calling effect or Reducer directly.
- You cannot add hook functions before and after effect and Reducer. Therefore, you cannot support middleware such as Loading.
- Extensions to Store, such as adding a custom field, or custom enhancements to Effect, are not supported.
- The props type of the class component needs to be redefined.
Based on open source transformation
Therefore, in terms of the implementation idea of Stamen, we need to make a transformation in combination with our own project.
First of all, the structure and API were closer to DVA, and we added the name and THE SUBSCRIPTIONS field, which will be discussed later. After adding the name field, we can put route listens, WS connections and so on in this field, for example, the common route listens in our project:
In API, DVA puts payload in the first parameter, and calls, puts and other methods in the second parameter. In this way, all data can only be passed in payload. However, some data may not be required by the interface, such as API path parameters and special logical markers. It is troublesome to pull it out during transparent transmission, as shown in the figure below:
Therefore, in the cube-state, we put the first parameter of call, select and other methods, and the second parameter is payload. After that, other parameters can be passed, but the call is still in the ordinary form, as shown in the figure below:
We then transformed from dispatch mode can directly call to form, such as countStore. Effects. AddLater (content), type definitions is perfect, so must be introduced into effect define the type of execution place, In addition, effect can also directly call the Reducer of store itself. In order to facilitate the invocation, we also support the creation of the flat form, using effects and reducers as the root properties, namely the form of CountStore.addLater (Payload).
Next, we add hooks to effect and Reducer to support the middleware system by allowing some logic to be executed before and after. For example, loading middleware automatically updates the state of loadingStore before and after the execution of each effect. This is where the name field of the store is used.
Next, support for store extensions. This is to support some custom logic. In our project, the back end of the request return structure is encapsulated as follows:
{
success: true,
data: {},
err: {}
}
Copy the code
Generally, only the data field needs to be processed. If each effect extracts data from the structure, there will be a lot of redundant code, which is best handled by default during the call to the service. Therefore, extendEffect is supported to extend or override the first parameter of effet provided by default. For example, our project extended getParams and getQuery methods, overriding the original call method, processing the request return structure internally, and some logic for making hints.
After the extension, you can easily obtain path parameters, query parameters, and some success and error messages in effect. For example, in the following figure, the appId on the path is obtained as the request parameter, and the user is prompted after the request succeeds:
Finally, we expose the state type so that we don’t have to define it again when working with class components:
At this point, the general structure is pretty much the same, but what will be added is some requirements that are more in-depth to specific business scenarios, such as support for extending one store to another, support for global singletons, and so on. The overall architecture is shown as follows:
Improve testing and documentation
To make a stable and reliable library, on the one hand, it is as simple as possible, on the other hand, test cases are indispensable, so we also add a more comprehensive test, basically covering every logic. We also add new test cases when bugs are discovered, which is another topic that we won’t go into here.
As for the documentation, it’s pretty simple, with few apis in total, so it goes straight into the README. The documentation provides basic and advanced usage instructions, as well as an online demo for experience.
conclusion
After the first release of Cub-State was completed, we gradually started to do migration changes in the project, because the usage was similar, and in addition to adding a lot of type definitions, it was a lot of mechanical work. After getting through the complete link of the type definition, the development and maintenance of the project is no longer as before, and can avoid a lot of errors caused by the type.
There are still some issues, such as the extended getParams method not having a type definition, and having to wrap the source object directly in the createStore method, which is not appropriate in some scenarios. We hope to address these issues over time or find a better way to upgrade.
Later, I also saw some very simple, excellent and similar libraries, but only you know what your project is suitable for, this is not to build wheels for the sake of building wheels, but to better support the development and maintenance of the project. So, we don’t do meaningless things.
Before listening to the jade “in sharing mentioned: in fact in the field of the front, there are many things to do, such as packaging tools, such as webpack although already very perfect, but the bloated difficult problem difficult to solve, if who can continue to” make the wheel “, in the process of exploring the different road, is very meaningful. Finally, I hope you can explore your own wonderful on the road to the front.
Welcome to open source
Erda is an open source, one-stop cloud-native PaaS platform with platform level capabilities such as DevOps, micro-service observation governance, multi-cloud management, and fast data governance. Click on the link below to participate in open source, discuss and communicate with many developers, and build an open source community. You are welcome to pay attention, contribute code and Star!
- Erda Githubhttps://github.com/erda-project/erda
- Erda Cloudhttps://www.erda.cloud/