實戰分析:評論功能(九)

如今咱們有三個 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

相關文章
相關標籤/搜索