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