如今咱们有三个 Dumb 组件,一个控制评论的 reducer。咱们还缺什么?须要有人去 LocalStorage 加载数据,去控制新增、删除评论,去把数据保存到 LocalStorage 里面。以前这些逻辑咱们都是零散地放在各个组件里面的(主要是 CommentApp
组件),那是由于当时咱们还没对 Dumb 和 Smart 组件类型划分的认知,状态和视图之间也没有这么泾渭分明。css
而如今咱们知道,这些逻辑是应该放在 Smart 组件里面的:html
了解 MVC、MVP 架构模式的同窗应该能够类比过去,Dumb 组件就是 View(负责渲染),Smart 组件就是 Controller(Presenter),State 其实就有点相似 Model。其实不能彻底类比过去,它们仍是有很多差异的。可是本质上兜兜转转仍是把东西分红了三层,因此说前端很喜欢炒别人早就玩烂的概念,这话果真不假。废话很少说,咱们如今就把这些应用逻辑抽离到 Smart 组件里面。前端
对于 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.comments
。redux
这里的逻辑有点绕,你们能够回顾一下咱们以前实现的 react-redux.js
来体会一下。数组
对于 CommentInput
组件,咱们能够看到它有三个参数:username
、onSubmit
、onUserNameInputBlur
。咱们须要一个 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 的 CommentInput
。app
接下来的事情都是很简单,咱们用 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。