Practical analysis: comment function (7)

From this section, we start to use Redux, React-redux to refactor the comment function in the second stage. Product requirements are the same as before, but will use Redux, React-redux to help manage application state instead of "state improvement". Make the whole application closer to the real project.

You can modify comment-app2 (non-high-level component version) on the code of the second stage. If you have forgotten the comment function in the second stage, you can briefly review its functional requirements, practical analysis: comment function (4). The actual code for the first, second and third stages can be found here: react-naive-book-examples.

We first install the dependencies. Now comment-app2 needs to depend on Redux and React-redux. Enter the project directory and execute the command to install the dependencies:

npm install redux react-redux --save

Then, without further src ado create three empty directories: components, containers, reducers.

Build a reducer for comments

Our previous reducers were written directly in the  src/index.js file, which is a bad practice. Because as the application becomes more complex, more reducers may be needed to help us manage the application (which will be mentioned in later chapters here). So it's better to extract all reducers and put them in one directory  src/reducers.

The comment function is actually relatively simple. Let’s review what is the status of our continuous improvement in the status improvement chapter? Actually the shared state between the components of the comment function is only  comments. We can simply  src/reducers create a new reducer  comments.js to manage it.

Think about what the comment function does to comments? We can write reducers by thinking clearly, because reducers are used to describe the shape of data and corresponding changes. The two operations of adding and deleting comments are the most obvious, and everyone should be able to easily think of them. Another, our comment function will actually read data from LocalStorage, and after reading the data, it needs to be saved to the application state. So we also have an action that initializes the comment. So there are three things I can think of right now:

// action types
const INIT_COMMENTS = 'INIT_COMMENTS'
const ADD_COMMENT = 'ADD_COMMENT'
const DELETE_COMMENT = 'DELETE_COMMENT'

We use three constants to store  action.type the type, so it will be more convenient for us to modify it later. Write a reducer based on these three operations:

// reducer
export default function (state, action) {
  if (!state) {
    state = { comments: [] }
  }
  switch (action.type) {
     case INIT_COMMENTS:
       // Initialize comments 
      return { comments: action.comments }
     case ADD_COMMENT:
       // Add comments 
      return {
        comments: [...state.comments, action.comment]
      }
    case DELETE_COMMENT:
       // delete comment 
      return {
        comments: [
          ...state.comments.slice(0, action.commentIndex),
          ...state.comments.slice(action.commentIndex + 1)
        ]
      }
    default:
      return state
  }
}

We only store the  comments state of one, initialized to an empty array. When  INIT_COMMENTS an action is encountered, a new object will be created, and then  the properties in it will be action.comments overwritten  . commentsThis is the initial comment operation.

Similarly, the new comment operation  ADD_COMMENT will also create a new object, then create a new array, then  state.comments copy all the original contents into the new array, and finally append it to the new array  action.comment. This way the fairly new array will have one more comment than the original. (Don't worry about the performance issues of array copying here, it [...state.comments] is a shallow copy, and all they copy are object references.)

For deleting comments, what we actually need to do is to create an array with the contents of a specific subscript deleted. We know that the array  slice(from, to) will copy the contents of a specific range into the new array according to the subscript you pass in. So we can   copy the content before the subscript  slice in the original comment array  to an array, and  copy the content after the coordinates to another array. Then the two arrays are combined, which is equivalent to the "deleted"   comment.action.commentIndexaction.commentIndexaction.commentIndex

In this way, the reducer related to the comment is written.

action creators

When we used  dispatch it before, we built the object directly and manually:

dispatch({ type: 'INIT_COMMENTS', comments })

It is actually quite troublesome to write every time  type , and it is also a burden to memorize the name of the action type. We can encapsulate actions into a function, and let them help us build this action, which we call action creators.

// action creators
export const initComments = (comments) => {
  return { type: INIT_COMMENTS, comments }
}

export const addComment = (comment) => {
  return { type: ADD_COMMENT, comment }
}

export const deleteComment = (commentIndex) => {
  return { type: DELETE_COMMENT, commentIndex }
}

The so-called action creators are actually functions that return actions, so that we  dispatch only need to pass in the data:

dispatch (initComments (comments))

The additional benefit of action creators is that they can help us to uniformly process incoming data; and with action creators, the code will be more convenient to test. These contents can be experienced later in the actual project.

The whole  src/reducers/comments.js code is:

// action types
const INIT_COMMENTS = 'INIT_COMMENTS'
const ADD_COMMENT = 'ADD_COMMENT'
const DELETE_COMMENT = 'DELETE_COMMENT'

// reducer
export default function (state, action) {
  if (!state) {
    state = { comments: [] }
  }
  switch (action.type) {
     case INIT_COMMENTS:
       // Initialize comments 
      return { comments: action.comments }
     case ADD_COMMENT:
       // Add comments 
      return {
        comments: [...state.comments, action.comment]
      }
    case DELETE_COMMENT:
       // delete comment 
      return {
        comments: [
          ...state.comments.slice(0, action.commentIndex),
          ...state.comments.slice(action.commentIndex + 1)
        ]
      }
    default:
      return state
  }
}

// action creators
export const initComments = (comments) => {
  return { type: INIT_COMMENTS, comments }
}

export const addComment = (comment) => {
  return { type: ADD_COMMENT, comment }
}

export const deleteComment = (commentIndex) => {
  return { type: DELETE_COMMENT, commentIndex }
}

Some friends may find that our reducer is not the same as other reducer examples on the Internet. Some people like to cut the action out of a separate directory  actions, so that the action and reducer are separate. Personally, I think this approach may be a bit over-optimized. In fact, in most cases, a specific action will only affect a specific reducer. Putting them together can make it clearer what kind of reducer this action actually affects. Separating it would put an extra and unnecessary burden on us to maintain and understand the code, which is kind of overkill. However, there is no one-size-fits-all rule here. You can refer to and try more to find a solution that suits your project needs.

Personal habit of writing reducer files, for reference only:

  1. define action types
  2. write reducers
  3. Action creators associated with this reducer

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325102755&siteId=291194637