简单的评论组件

870 阅读5分钟

花了半天时间用React,Redux和Material-UI撸了个简单的评论组件,也是为了加深对redux的理解,所以总结于此。

整个评论组件又分为三个组件,评论发送组件和评论流组件,以及最外层的父组件

//CommentField.js

import React from 'react'import { connect } from 'react-redux'import TextField from '@material-ui/core/TextField'import PostIcon from '@material-ui/icons/SendSharp'import Button from '@material-ui/core/Button'import { postComment } from './commentsmiddleware'

export class CommentField extends React.Component{  constructor(props) {    super(props)    this.state = {      value: ''    }  }  _handlePostComment = () => {    // console.log(this.state.value)    if(this.state.value !== ''){      this.props.postComment(this.state.value)      this.setState({        value: ''      })    }  }  render() {    return(      <div>        <TextField          id='multiline-comments'          multiline          label='Comment'          rows='8'          margin='normal'          variant='outlined'          type='text'          fullWidth={true}          value={this.state.value}          onChange={(e) => this.setState({value: e.target.value})}        />        <Button variant='contained' color='secondary' onClick={this._handlePostComment}>          Post          <PostIcon style={{marginLeft: '10'}}/>        </Button>      </div>    )  }}

//CommentFlow.js

import React from 'react'import { connect } from 'react-redux'import IconButton from '@material-ui/core/IconButton';import ThumbUpIcon from '@material-ui/icons/ThumbUpRounded'import Paper from '@material-ui/core/Paper';import Badge from '@material-ui/core/Badge';import Typography from '@material-ui/core/Typography';import { fetchData, update } from './commentsmiddleware'export class CommentFlow extends React.Component{  constructor(props) {    super(props)  }  componentDidMount() {    this.props.fetchData()  }  _thumbUpHandle(user){    this.props.update(user)  }    render() {    return(      <div className='comment-flow-container'>        {          this.props.comments.map((item, index) => {            let id= item.id            return (            <Paper key={id} style={{marginTop: 20}}>              <div className='title-bar'>                <Typography variant='h6' component='h6'>                  {item.nickname}                </Typography>                <Typography variant='h6' component='h6'>                  {item.date}                </Typography>              </div>              <div>                <Typography component='p'>                  {item.content}                </Typography>              </div>              <div className='like-bar'>                <IconButton aria-label='thumb-up' onClick={() => this._thumbUpHandle(item)}>                  <Badge badgeContent={item.thumbupcount >= 100 ? 99+'+' : item.thumbupcount} color='secondary'>                    <ThumbUpIcon/>                  </Badge>                </IconButton>              </div>            </Paper>            )          })        }      </div>    )  }}

//Comments.js

import React from 'react'import CommentField  from './CommentField'import CommentFlow  from './CommentFlow'import './comments.css'export class CommentComponet extends React.Component{  constructor(props){    super(props)  }  render(){    return(      <div className='container'>        <CommentField/>        <CommentFlow/>      </div>    )  }}


数据这块,我用json-server事先写入了一部分数据,评论对象里包含id,用户名,日期,内容和点赞数这些属性

{
    "comments": [
     {      "id": 1,      "nickname": "Red",      "date": "2019-08-15 17:14",      "content": "Hello",      "thumbupcount": 18     },     {      "id": 2,      "nickname": "Red",      "date": "2019-08-15 17:15",      "content": "thanks",      "thumbupcount": 6     },     {      "id": 3,      "nickname": "Red",      "date": "2019-08-15 17:16",      "content": "嘿嘿嘿",      "thumbupcount": 11     }     ]
}


action有五种,分别是获取server评论数据的LOAD_COMMENTS,网络请求成功的SUCCESS,请求失败的FAILED,点赞当前评论的THUMB_UP,以及发送一篇评论POST_COMMENT

//actions.js

const LOAD_COMMENTS = 'LOAD_COMMENTS'const SUCCESS = 'SUCCESS'const FAILED = 'FAILED'const THUMB_UP = 'THUMB_UP'const POST_COMMENT = 'POST_COMMENT'export const load = () => ({  type: LOAD_COMMENTS})export const scuccess = (res) => ({  type: SUCCESS,  res})export const failed = (err) => ({  type: FAILED,  err})export const thumbup = (acomment) => ({  type: THUMB_UP,  acomment})export const newcomment = (anewcomment) => ({  type: POST_COMMENT,  anewcomment})


此外,我还用到了中间件,目的是在acton和reducer之间实现网络请求,将返回的数据再dispatch给reducer处理,包括请求评论数据,点赞评论和发送评论

//middleware.js

/** * 中间件 * 用来处理网络请求过程中的状态 */import { load, newcomment, thumbup, scuccess, failed} from './actions'export const fetchData = () => {  return async (dispatch) => {    dispatch(load)    try {      let response = await fetch(`http://localhost:3001/comments`)      let resTxt = await response.text()      let resJson = await JSON.parse(resTxt)      dispatch(scuccess(resJson))    } catch (error) {      dispatch(failed(error))    }  }}/** * 更新点赞数 * 更新成功后dispatch给thumbup action */export const update = (acomment) => {  return async (dispatch) => {    let acommentId = acomment.id    let count = acomment.thumbupcount + 1    let updatecomment = {      "id": acommentId,      "nickname": acomment.nickname,      "date": acomment.date,      "content": acomment.content,      "thumbupcount": count    }    try{      await fetch(`http://localhost:3001/comments/${acommentId}`,{        method: 'PUT',        headers: {          "Content-Type": "application/json"        },        body: JSON.stringify(updatecomment)      })      dispatch(thumbup(updatecomment))    }catch(error){      dispatch(failed(error))    }  }}/** * 发送一篇评论 * @param {*} content  */export const postComment = (content) => {  return async (dispatch) => {    let date = new Date(Date.now())    let newComment = {      "id": Math.random(),      "nickname": "Red",      "date": date.getFullYear() + '-' + (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-' +         date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes(),      "content": content,      "thumbupcount": 0    }    try {      await fetch(`http://localhost:3001/comments`,{        method: 'POST',        headers: {          "Content-Type": "application/json"        },        body: JSON.stringify(newComment)      })      dispatch(newcomment(newComment))    } catch (error) {      dispatch(failed(error))    }  }}


再来就是reducer,分别处理上述五个acton

//reducer.js

const initialState = {  comments: [],  success: false,  failed: ''}export const result = (state=initialState, action) => {  switch (action.type) {    case 'LOAD_COMMENTS':      console.log('loading comments...')      return {        ...state,        success: false,        failed: ''      }    case 'SUCCESS':      return {        ...state,        comments: state.comments.concat(action.res),        success: true,        failed: ''      }    case 'FAILED':      return {        ...state,        success: false,        failed: action.err      }    case 'THUMB_UP':      let updateComment = state.comments.map(obj => {        return obj.id === action.acomment.id ? action.acomment : obj      })      return {        ...state,        comments: updateComment,         success: true,        failed: ''      }    case 'POST_COMMENT':      return {        ...state,        comments: state.comments.concat(action.anewcomment),        success: true,        failed: ''      }    default:      return state  }}

对于THUMB_UP这个action,reducer的处理逻辑是要判断当前点击的是那条评论,即该条评论的id是否与传递过来的comment对象的id相同,若相同才更新数据。因为评论流就是一个list,里面的所有对象都会有一个独一无二id(通过Math.random()生成),react内部就是通过判断这些id来实现re-render的。

然后,就是映射状态(state)和事件分发(dispatch)给props,再通过react-redux提供的connect组件封装这两个函数再导出组件

//CommentFlow.js

const mapStateToProps = (state) => {  return {    comments: state.result.comments,    success: state.result.success,    failed: state.result.failed,  }}//分发给中间件函数处理const mapDispatchToProps = (dispatch) => {  return {    fetchData: () => dispatch(fetchData()),    update: (acomment) => dispatch(update(acomment))  }}export default connect(mapStateToProps, mapDispatchToProps)(CommentFlow)

//CommentField.jsconst mapDispatchToProps = (dispatch) => {  return {    postComment: (val) => dispatch(postComment(val))  }}export default connect(mapDispatchToProps)(CommentField)


最后就是再index.js里引入store和中间件了

//index.js

import React from 'react';import ReactDOM from 'react-dom';import './index.css';import { Provider } from 'react-redux'import { createStore, applyMiddleware, combineReducers } from 'redux'import thunk from 'redux-thunk'import reducer from './reducer'import { result } from './commentsreducer'import { CommentComponet } from './Comments'const rootReducer = combineReducers({result})const store = createStore(rootReducer, applyMiddleware(thunk))const App = () => (  <Provider store={store}>    <CommentComponet />  </Provider>)ReactDOM.render(  <App/>,  document.getElementById('root'));


运行出来的效果是这样


回头来梳理一下其中的不足:

1. 没有做到容器组件和展示组件的分离

2. 对state的定义不够准确,以comment作为一个state,其余两个success和failed都没用上,说白了就是对state的颗粒度划分的不清晰。comment对象既包含了评论内容、时间这些基本信息,同时还有点赞数这样的记录信息,如何合理的定义state也是开发过程中需要细致考量的。

Redux的出现使得React如虎添翼,并不是在于它有多强大,而是因为”单向数据流“的思想,把复杂的东西给简单化了,对于开发人员而言,最忌讳的就是在一个点上钻牛角,如果理解并运用Redux的这种思想到实际项目中时,往往会事倍功半,减轻了开发人员的思考压力。