Practical analysis: comment function (9)

Now we have three Dumb components, a reducer that controls comments. What are we missing? Someone needs to go to LocalStorage to load data, to control adding and deleting comments, and to save data to LocalStorage. Before, we put these logics scattered in each component (mainly  CommentApp components), that is because at that time we didn't have the understanding of the type of Dumb and Smart components, and there was no such clear distinction between the state and the view.

And now we know that these logic should be placed in the Smart component:

Students who understand the MVC and MVP architectural patterns should be able to compare to the past. The Dumb component is the View (responsible for rendering), the Smart component is the Controller (Presenter), and the State is actually a bit similar to the Model. In fact, it cannot be completely compared to the past, and there are still many differences between them. But in essence, it still divides things into three layers, so it is true that the front end likes to fry the concept that others have already played badly. Without further ado, we will now extract these application logics into Smart components.

Smart CommentList

For the  CommentList component, you can see that it accepts two parameters: comments and  onDeleteComment. The description needs a Smart component that is responsible for passing  comments data to it and also responding to its requests to delete comments. We create a new Smart component  src/containers/CommentList.js to do these things:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import CommentList from '../components/CommentList'
import { initComments, deleteComment } from '../reducers/comments'

// CommentListContainer 
// A Smart component responsible for loading, initializing, and deleting comments of comment list data 
// Communicate CommentList and state 
class CommentListContainer extends Component {
  static propTypes = {
    comments: PropTypes.array,
    initComments: PropTypes.func,
    onDeleteComment: PropTypes.func
  }

  componentWillMount () {
    // Initialize comments in componentWillMount lifecycle 
    this ._loadComments()
  }

  _loadComments () {
    // Load comments from LocalStorage 
    let comments = localStorage.getItem('comments' )
    comments = comments ? JSON.parse(comments) : []
     // this.props.initComments is passed in from connect 
    // It can help us initialize data into state 
    this .props.initComments(comments)
  }

  handleDeleteComment (index) {
    const { comments } = this .props
     // props cannot be changed, so here is a new list of comments with a specific subscript removed 
    const newComments = [
      ...comments.slice(0, index),
      ...comments.slice(index + 1)
    ]
    // Save the latest comment list to LocalStorage 
    localStorage.setItem('comments' , JSON.stringify(newComments))
     if ( this .props.onDeleteComment) {
       // this.props.onDeleteComment is passed in from connect 
      // will dispatch one action to delete the comment 
      this .props.onDeleteComment(index)
    }
  }

  render () {
    return (
      <CommentList
        comments={this.props.comments}
        onDeleteComment={this.handleDeleteComment.bind(this)} />
    )
  }
}

// The list of comments is obtained from state.comments 
const mapStateToProps = (state) => {
   return {
    comments: state.comments
  }
}

const mapDispatchToProps = (dispatch) => {
   return {
     // provide to CommentListContainer 
    // this method will be used after loading the comment list from LocalStorage 
    // initialize the comment list into state 
    initComments: (comments) => {
      dispatch (initComments (comments))
    },
    // Delete the comment 
    onDeleteComment: (commentIndex) => {
      dispatch(deleteComment(commentIndex))
    }
  }
}

// Connect CommentListContainer to store 
// Pass comments, initComments, onDeleteComment to CommentListContainer 
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(CommentListContainer)

The code is a bit long, and you should understand the basic logic of this component through comments. One thing to note is that what we initially passed to  CommentListContainer was  props.comments the empty array initialized in the reducer  comments , because the data has not been fetched from LocalStorage.

The  CommentListContainer internal  comments data is loaded from LocalStorage, and then the call  this.props.initComments(comments) will cause  the  initialization to be dispatchactually loaded from LocalStorage  into the state.comments

Because  dispatch the   packaged components inside go to the state to get the latest one  connect and   then re-render, only at this time   can the data be obtained  .ConnectcommentsCommentListContainerprops.comments

The logic here is a bit confusing, you can review what we have implemented before  react-redux.js to experience it.

Smart CommentInput

For the  CommentInput component, we can see that it has three parameters: username, onSubmit, onUserNameInputBlur. We need a Smart component to manage the loading and saving of user names in LocalStorage; the user may also click the "Publish" button, so we also need to deal with the logic of comment publishing. We create a new Smart component  src/containers/CommentInput.js to do these things:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import CommentInput from '../components/CommentInput'
import { addComment } from '../reducers/comments'

// CommentInputContainer 
// Responsible for loading and saving user names and publishing comments 
class CommentInputContainer extends Component {
  static propTypes = {
    comments: PropTypes.array,
    onSubmit: PropTypes.func
  }

  constructor () {
    super()
    this.state = { username: '' }
  }

  componentWillMount () {
    // Initialize username in componentWillMount lifecycle 
    this ._loadUsername()
  }

  _loadUsername () {
    // Load username from LocalStorage 
    // Then it can be passed to CommentInput in the render method 
    const username = localStorage.getItem('username' )
     if (username) {
       this .setState({ username })
    }
  }

  _saveUsername (username) {
    // Look at the onUserNameInputBlur of the render method 
    // This method will be called when the username input box is blurred and save the username 
    localStorage.setItem('username' , username)
  }

  handleSubmitComment (comment) {
    // Verification of comment data 
    if (!comment) return 
    if (!comment.username) return alert('Please enter the username' )
     if (!comment.content) return alert('Please enter the comment content' )
     // Added Comments are saved to LocalStorage 
    const { comments } = this .props
    const newComments = [...comments, comment]
    localStorage.setItem( 'comments' , JSON.stringify(newComments))
     // this.props.onSubmit is passed in from connect 
    // will dispatch an action to add a comment 
    if ( this .props.onSubmit) {
       this .props. onSubmit(comment)
    }
  }

  render () {
    return (
      <CommentInput
        username={this.state.username}
        onUserNameInputBlur={this._saveUsername.bind(this)}
        onSubmit={this.handleSubmitComment.bind(this)} />
    )
  }
}

const mapStateToProps = (state) => {
  return {
    comments: state.comments
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onSubmit: (comment) => {
      dispatch(addComment(comment))
    }
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(CommentInputContainer)

Likewise, explanations of the code are placed in comments. This builds a Smart one  CommentInput.

Smart CommentApp

The next thing is very simple, we use  CommentApp to combine these two Smart components,  src/CommentApp.js move to  src/containers/CommentApp.js, and replace the content inside with:

import React, { Component } from 'react'
import CommentInput from './CommentInput'
import CommentList from './CommentList'

export default class CommentApp extends Component {
  render() {
    return (
      <div className='wrapper'>
        <CommentInput />
        <CommentList />
      </div>
    )
  }
}

What was originally complicated  CommentApp has become extremely simple, because its logic is separated into two Smart components. The original one  CommentApp does carry too many responsibilities that it shouldn't. Separating this logic will also bring benefits to the maintenance and management of our code.

The last step, modify  src/index.js:

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import CommentApp from './containers/CommentApp'
import commentsReducer from './reducers/comments'
import './index.css'

const store = createStore(commentsReducer)

ReactDOM.render(
  <Provider store={store}>
    <CommentApp />
  </Provider>,
  document.getElementById('root')
);

By  commentsReducer building one  store, and then letting  Provider pass it down, we're done with the final refactoring.

Our final component tree looks like this:

File Directory:

src
├── components
│   ├── Comment.js
│   ├── CommentInput.js
│   └── CommentList.js
├── containers
│   ├── CommentApp.js
│   ├── CommentInput.js
│   └── CommentList.js
│   reducers
│     └── comments.js
├── index.css
└── index.js

All code can be found here:  comment-app3 .

Guess you like

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