Redux is a very popular solution for state management. Any moment during the execution of a Redux application is a reflection of the state. State drives the Redux logic, so to speak. Designing a good State is not easy. This article starts with two of the most common mistakes you can make when designing a State, and then shows you how to design a State properly.
Mistake 1: Designing State based on an API
States are designed based on apis, often one API for each sub-state, and the structure of the State is consistent (or nearly identical) with the data structure returned by the API. For example, for a blog application, the /posts interface returns a list of blogs with the following data structure:
[{"id": 1,
"title": "Blog Title"."create_time": "The 2017-01-10 T23:07:43. 248 z"."author": {
"id": 81,
"name": "Mr Shelby"}}... ]Copy the code
/posts/{id}/comments; /posts/{id}/comments;
{
"id": 1,
"title": "Blog Title"."create_time": "The 2017-01-10 T23:07:43. 248 z"."author": {
"id": 81,
"name": "Mr Shelby"
},
"content": "Some really short blog content. "
}
Copy the code
[{"id": 41."author": "Jack"."create_time": "The 2017-01-11 T23:07:43. 248 z"."content": "Good article!"}... ]Copy the code
The data of the above three interfaces are respectively used as three sub-states, which constitute the State of the global application:
{
"posts": [{"id": 1,
"title": "Blog Title"."create_time": "The 2017-01-10 T23:07:43. 248 z"."author": {
"id": 81,
"name": "Mr Shelby"}},... ] ."currentPost": {
"id": 1,
"title": "Blog Title"."create_time": "The 2017-01-10 T23:07:43. 248 z"."author": {
"id": 81,
"name": "Mr Shelby"
},
"content": "Some really short blog content. "
},
"currentComments": [{"id": 1,
"author": "Jack"."create_time": "The 2017-01-11 T23:07:43. 248 z"."content": "Good article!"},... ] }Copy the code
In this State, posts and currentPosts have a lot of duplicate information, and posts and currentComments are array types, so it’s not easy to find, and every time you look for a record, you have to go through the array. These problems are essentially because the API is designed based on server-side logic, not application state. For example, while the list of blogs is obtained with basic information such as the title and author of each blog, the API for obtaining blog details is designed to include basic information about the blog, rather than just return the content of the blog. For example, posts and currentComments return an array structure because of the order of data, paging and other factors.
Mistake # 2: Design State based on the page UI
Since you can’t design State based on the API, many people go the other way and design State based on the UI of the page. State is designed to be whatever data and data format the page UI needs. Let’s take the Todo application as an example. The page will have three states: show all items, obviously all done items, and show all to-do items. Using the page UI as the basis for designing State, the State would look like this:
{
"all": [{"id": 1,
"text": "todo 1"."completed": false
},
{
"id": 2."text": "todo 2"."completed": true}]."uncompleted": [{"id": 1,
"text": "todo 1"."completed": false}]."completed": [{"id": 2."text": "todo 2"."completed": false}}]Copy the code
This State is very handy for components that display the UI, rendering the UI with the array type of data that corresponds to the current State of the application, without any intermediate data transitions. However, it is also easy to find the problems of this State. First, this State still has the problem of data duplication. Second, when you add or modify a record, you need to modify more than one place. For example, when a new record is added, both the All and uncompleted arrays are added to the new record. This type of State creates both a waste of storage and a risk of data inconsistency.
In fact, these two ways of designing State are two extreme ways of designing. In actual projects, few developers design State completely according to these two ways, but the vast majority of people will be affected by these two ways of designing State. Remember, have you ever returned data from an API as part of State? Have you ever defined a State for a component’s UI for the sake of component rendering?
Reasonable design of State
Let’s take a look at how State should be properly designed. The most important and core principle is to design State like a database. Think of State as a database. Each part of State is a table in the database. Each field in the State corresponds to a field in the table. When designing a database, you should follow these three principles:
- Data is classified by Domain and stored in different tables. The column data stored in different tables cannot be the same.
- The data for each column in a table depends on the primary key of that table.
- Columns in a table other than the primary key cannot have direct dependencies on each other.
These three principles can be translated into the principles of designing State:
- The State of the entire application is divided into several sub-states according to Domain. Repeat data cannot be saved among sub-states.
- State stores data in a key-value pair structure, with the key/ID of the record as the index of the record, and all other fields in the record depend on the index.
- State does not hold data that can be calculated from existing data, that is, the fields in State do not depend on each other.
Following these three principles, we redesigned the State of our blog application. By domain, State can be split into three sub-states: Posts, comments, authors, and posts take the blog ID as the key value and include title, create_time, author, and comments. The final State structure is as follows:
{
"posts": {
"1": {
"id": 1,
"title": "Blog Title"."content": "Some really short blog content."."created_at": "The 2016-01-11 T23:07:43. 248 z"."author": 81,
"comments": [352]}... },"comments": {
"352": {
"id": 352,
"content": "Good article!"."author": 41},... },"authors": {
"41": {
"id": 41."name": "Jack"
},
"81": {
"id": 81,
"name": "Mr Shelby"},... }}Copy the code
Now does this State look a lot like a database with three tables? However, this State does not meet the application’s requirements: the key-value pairs are stored in a way that does not guarantee the order of the blog list data, but in the case of the blog list, order is clearly required. To solve this problem, we can define another state postIds to store the blog ids as an array:
{
"posts": {
"1": {
"id": 1,
"title": "Blog Title"."content": "Some really short blog content."."created_at": "The 2016-01-11 T23:07:43. 248 z"."author": 81,
"comments": [352]}... },"postIds": 1, [...]. ."comments": {
"352": {
"id": 352,
"content": "Good article!"."author": 41},... },"authors": {
"41": {
"id": 41."name": "Jack"
},
"81": {
"id": 81,
"name": "Mr Shelby"},... }}Copy the code
So when you display a list of blogs, you get the list order based on the postIds, and then you get the blog information from posts based on the blog ID. Some of you in this area may be confused, and think that posts and postIds both store ID data, which violates the principle that you can’t have duplicate data between different states. However, this is not duplicate data. The data stored in postIds is the order of the list of blogs, but the “order” of the data is reflected in the blog ID. This is in the same way that a primary key of one table can be used as a foreign key of another table. It’s also important to note that both posts and postId states change when a new blog is added. This may seem like a hassle, but when you need to modify the data of a particular blog post, this structure has obvious advantages, and using an array to store the state directly can lead to a deeper level of object nesting. Imagine accessing the content of a comment. It needs to be obtained through a three-tier structure like posts[0].comments[0].content. When the business is more complex, this problem is more prominent. Flat State provides better flexibility and extensibility.
So far, our State has been designed based on the domain data returned by the backend API, but in reality, the application State, not only contains the domain data, but also needs to contain the application’S UI logic data, such as processing the loading effect of the page based on whether the server is currently communicating with it; When an application fails to run, error information is displayed. In this case, the structure of State is as follows:
{
"isFetching": false."error": ""."posts": {... },"postIds": 1, [...]. ."comments": {... },"authors": {... }}Copy the code
As the application business logic increases, the first tier of State becomes more and more numerous. In this case, we tend to consider merging node data with strong correlation, and then split the reducer to allow each child reducer to process the state logic of one node. In this example, we can merge posts and postIds and change the status name to isFetching and error as global UI logic:
{
"app": {"isFetching": false."error": "",},"posts": {"byId": {
"1": {... },... },"allIds": 1, [...]. ,}"comments": {... },"authors": {... }}Copy the code
In this way, we can define four reducers, appReducer, postsReducer, commentsReducer, and authorsReducer, to handle four sub-states respectively. At this point, the structural design of State is complete.
In summary, the key to designing Redux State is to design the State as if it were a database. Consider State as a database applied in memory, and action and Reducer as SQL statements that operate on this database.
Welcome to pay attention to my public number: senior cadres of the front end, get 21 front end selected books!