实战分析:评论功能(九)

如今咱们有三个 Dumb 组件,一个控制评论的 reducer。咱们还缺什么?须要有人去 LocalStorage 加载数据,去控制新增、删除评论,去把数据保存到 LocalStorage 里面。以前这些逻辑咱们都是零散地放在各个组件里面的(主要是 CommentApp 组件),那是由于当时咱们还没对 Dumb 和 Smart 组件类型划分的认知,状态和视图之间也没有这么泾渭分明。css

而如今咱们知道,这些逻辑是应该放在 Smart 组件里面的:html

了解 MVC、MVP 架构模式的同窗应该能够类比过去,Dumb 组件就是 View(负责渲染),Smart 组件就是 Controller(Presenter),State 其实就有点相似 Model。其实不能彻底类比过去,它们仍是有很多差异的。可是本质上兜兜转转仍是把东西分红了三层,因此说前端很喜欢炒别人早就玩烂的概念,这话果真不假。废话很少说,咱们如今就把这些应用逻辑抽离到 Smart 组件里面。前端

Smart CommentList

对于 CommentList 组件,能够看到它接受两个参数:comments 和 onDeleteComment。说明须要一个 Smart 组件来负责把 comments 数据传给它,而且还得响应它删除评论的请求。咱们新建一个 Smart 组件 src/containers/CommentList.js 来干这些事情:react

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
// 一个 Smart 组件,负责评论列表数据的加载、初始化、删除评论
// 沟通 CommentList 和 state
class CommentListContainer extends Component {
  static propTypes = {
    comments: PropTypes.array,
    initComments: PropTypes.func,
    onDeleteComment: PropTypes.func
  }

  componentWillMount () {
    // componentWillMount 生命周期中初始化评论
    this._loadComments()
  }

  _loadComments () {
    // 从 LocalStorage 中加载评论
    let comments = localStorage.getItem('comments')
    comments = comments ? JSON.parse(comments) : []
    // this.props.initComments 是 connect 传进来的
    // 能够帮咱们把数据初始化到 state 里面去
    this.props.initComments(comments)
  }

  handleDeleteComment (index) {
    const { comments } = this.props
    // props 是不能变的,因此这里新建一个删除了特定下标的评论列表
    const newComments = [
      ...comments.slice(0, index),
      ...comments.slice(index + 1)
    ]
    // 保存最新的评论列表到 LocalStorage
    localStorage.setItem('comments', JSON.stringify(newComments))
    if (this.props.onDeleteComment) {
      // this.props.onDeleteComment 是 connect 传进来的
      // 会 dispatch 一个 action 去删除评论
      this.props.onDeleteComment(index)
    }
  }

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

// 评论列表从 state.comments 中获取
const mapStateToProps = (state) => {
  return {
    comments: state.comments
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    // 提供给 CommentListContainer
    // 当从 LocalStorage 加载评论列表之后就会经过这个方法
    // 把评论列表初始化到 state 当中
    initComments: (comments) => {
      dispatch(initComments(comments))
    },
    // 删除评论
    onDeleteComment: (commentIndex) => {
      dispatch(deleteComment(commentIndex))
    }
  }
}

// 将 CommentListContainer connect 到 store
// 会把 comments、initComments、onDeleteComment 传给 CommentListContainer
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(CommentListContainer)

代码有点长,你们经过注释应该了解这个组件的基本逻辑。有一点要额外说明的是,咱们一开始传给 CommentListContainer 的 props.comments 实际上是 reducer 里面初始化的空的 comments 数组,由于尚未从 LocalStorage 里面取数据。git

而 CommentListContainer 内部从 LocalStorage 加载 comments 数据,而后调用 this.props.initComments(comments) 会致使 dispatch,从而使得真正从 LocalStorage 加载的 comments 初始化到 state 里面去。github

由于 dispatch 了致使 connect 里面的 Connect 包装组件去 state 里面取最新的 comments 而后从新渲染,这时候 CommentListContainer 才得到了有数据的 props.commentsredux

这里的逻辑有点绕,你们能够回顾一下咱们以前实现的 react-redux.js 来体会一下。数组

Smart CommentInput

对于 CommentInput 组件,咱们能够看到它有三个参数:usernameonSubmitonUserNameInputBlur。咱们须要一个 Smart 的组件来管理用户名在 LocalStorage 的加载、保存;用户还可能点击“发布”按钮,因此还须要处理评论发布的逻辑。咱们新建一个 Smart 组件 src/containers/CommentInput.js 来干这些事情:架构

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
// 负责用户名的加载、保存,评论的发布
class CommentInputContainer extends Component {
  static propTypes = {
    comments: PropTypes.array,
    onSubmit: PropTypes.func
  }

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

  componentWillMount () {
    // componentWillMount 生命周期中初始化用户名
    this._loadUsername()
  }

  _loadUsername () {
    // 从 LocalStorage 加载 username
    // 而后能够在 render 方法中传给 CommentInput
    const username = localStorage.getItem('username')
    if (username) {
      this.setState({ username })
    }
  }

  _saveUsername (username) {
    // 看看 render 方法的 onUserNameInputBlur
    // 这个方法会在用户名输入框 blur 的时候的被调用,保存用户名
    localStorage.setItem('username', username)
  }

  handleSubmitComment (comment) {
    // 评论数据的验证
    if (!comment) return
    if (!comment.username) return alert('请输入用户名')
    if (!comment.content) return alert('请输入评论内容')
    // 新增评论保存到 LocalStorage 中
    const { comments } = this.props
    const newComments = [...comments, comment]
    localStorage.setItem('comments', JSON.stringify(newComments))
    // this.props.onSubmit 是 connect 传进来的
    // 会 dispatch 一个 action 去新增评论
    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)

一样地,对代码的解释都放在了注释当中。这样就构建了一个 Smart 的 CommentInputapp

Smart CommentApp

接下来的事情都是很简单,咱们用 CommentApp 把这两个 Smart 的组件组合起来,把 src/CommentApp.js 移动到 src/containers/CommentApp.js,把里面的内容替换为:

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>
    )
  }
}

本来很复杂的 CommentApp 如今变得异常简单,由于它的逻辑都分离到了两个 Smart 组件里面去了。原来的 CommentApp 确实承载了太多它不该该承担的责任。分离这些逻辑对咱们代码的维护和管理也会带来好处。

最后一步,修改 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')
);

经过 commentsReducer 构建一个 store,而后让 Provider 把它传递下去,这样咱们就完成了最后的重构。

咱们最后的组件树是这样的:

文件目录:

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

全部代码能够在这里找到: comment-app3

相关文章
相关标签/搜索