失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 从零搭建个人博客(4)-留言评论区

从零搭建个人博客(4)-留言评论区

时间:2018-09-20 02:57:02

相关推荐

从零搭建个人博客(4)-留言评论区

代码:/MSChuan/Blog_Comment

Demo:https://mschuan.github.io/Blog_Comment/dist_prod/index.html

本文将实现一个简单的留言评论区,评论分为两种,如上图,这里使用parent comment称呼”test”和”沙发”这两条评论,”你个2货”是对”沙发”的回复,这里称为child comment。最上面的部分是添加parent comment的区域,每一条留言都有一个回复和赞按钮,用于回复产生child comment以及点赞,对应数目需要在点击后+1。

State

由于react+redux会维护一个全局状态树,所以我们首先要定义它,才能继续实现后续的代码编写。首先当然是评论的内容存储,每一条parent comment+related child comments看作一个package,于是我们可以用packages这样一个数组就能存储整个内容,每个package也是一个数组,每个元素都是一个object,包括评论的内容,回复数,点赞数,id,创建时间等。Besides,点击回复后需要弹出回复框,回复框出现的位置也需要在state中定义。

const initialState = {CommentState: {packages: [],replyBoxIndex: {packageIndex: -1,commentIndex: -1}}};

这里定义了初始状态,回复框的出现位置需要两个索引,一个是package的索引,另一个就是package中哪条评论下方出现回复框,初始的-1表示没有回复框。

Action

从功能上看只有四个action,添加parent comment,添加child comment,点击回复按钮,点赞,于是定义这四个action,首先新建一个types.js文件,用于定义所有的action。

const types = {AddParentComment: 'AddParentComment',AddReplyBox: 'AddReplyBox',PraiseComment: 'PraiseComment',AddChildComment: 'AddChildComment',};export default types;

定义actionFactory,用于产生所有的action。

import types from './Types';const actionFactory = {AddParentComment: (text, articleId) => ({type: types.AddParentComment,text: text,articleId: articleId}),AddReplyBox: (packageIndex, commentIndex) => ({type: types.AddReplyBox,packageIndex: packageIndex,commentIndex: commentIndex}),PraiseComment: (packageIndex, commentIndex, id) => ({type: types.PraiseComment,packageIndex: packageIndex,commentIndex: commentIndex,id: id}),AddChildComment: (packageIndex, id, text) => ({type: types.AddChildComment,packageIndex: packageIndex,text: text,id: id}),};export default actionFactory;

AddParentComment需要两个参数,一个是内容,另一个是文章id,当文章id为0时,我们认为是在留言板的留言。其余action的参数就不一一解释了,都较为简单。

Reducer

有了state和action,就能很清晰的写出reducer。

const packages = (state = mentState.packages, action) => {switch(action.type) {case types.AddChildComment:if(action.id !== state[action.packageIndex][0].id) {return state;}return ([...state.slice(0, action.packageIndex),[Object.assign({},state[action.packageIndex][0],{replyCount: state[action.packageIndex][0].replyCount + 1}),...state[action.packageIndex].slice(1), {text: action.text, created_at: '', praiseCount: 0, replyCount: 0, id: 0 }],...state.slice(action.packageIndex + 1)]);case types.AddParentComment:return ([[{text: action.text, created_at: '', praiseCount: 0, replyCount: 0, id: 0}], ...state]);case types.PraiseComment:return ([...state.slice(0, action.packageIndex),[...state[action.packageIndex].slice(0, mentIndex),Object.assign({}, state[action.packageIndex][mentIndex], {praiseCount: state[action.packageIndex][mentIndex].praiseCount + 1}),...state[action.packageIndex].slice(mentIndex + 1)],...state.slice(action.packageIndex + 1)]);default:return state;}};

首先是packages,这里写的比较丑,比较好的做法是把那些很长很复杂的逻辑封装成函数,可以复用,也会让switch case更加清晰。

const replyBoxIndex = (state = mentState.replyBoxIndex, action) => {switch(action.type) {case types.AddReplyBox:if(state.packageIndex === action.packageIndex && mentIndex === mentIndex) {return mentState.replyBoxIndex;}return ({packageIndex: action.packageIndex,commentIndex: mentIndex});default:return state;}};

对于回复框,这里实现了toggle,即点击同一个回复按钮可以开关对应的回复框。

React实现

整个结构也比较清楚,用一个Container把评论区包起来,下面定义两个Component,一个是添加parent comment,一个显示packages中的内容。

Container

class CommentContainer extends ponent {constructor(props) {super(props);// 0 means this comment container is not related to any articlethis.articalId = props.articalId || 0;}componentDidMount() {// TODO: send request to fetch comments content}render() {const { state, actions } = this.props;let commentPackageList = state.packages.map((p, index) => {return (<div className="commentPackageBox" ><CommentListcomments={p}packageIndex={index}replyBoxIndex={(index === state.replyBoxIndex.packageIndex) ? mentIndex : -1}actions={actions}/></div>);});return (<div><AddParentComment actions={actions} articalId={this.articalId} />{commentPackageList}</div>);}}

从store中拿到state,action,经过处理有传递给子组件,map函数在处理数组时特别好用。

Component

class AddParentComment extends ponent {constructor(props) {super(props);this.editor = null;}componentDidMount() {const textbox = ReactDOM.findDOMNode(this.refs.AddParentCommentBox);this.editor = new Simditor({textarea: $(textbox),toolbar: ['title', 'bold', 'italic', 'underline', 'strikethrough', 'color', 'ol', 'ul', 'link', 'alignment', 'emoji'],emoji: {imagePath: config.emojiUrl}});}render() {const {actions, articalId} = this.props;return (<form><FormGroup controlId="AddParentCommentBox"><FormControl componentClass="textarea" placeholder="留言" ref={'AddParentCommentBox'} /><Button type="submit" onClick={(e) => {e.preventDefault();if(!!this.editor && this.editor.getValue() !== '') {actions.AddParentComment(this.editor.getValue(), articalId);this.editor.setValue('');}}}>提交</Button></FormGroup></form>);}}

组件的内容就是一个编辑框+提交按钮,使用react-bootstrap很容易就能实现。显示评论内容部分稍微复杂一点,首先是根据packages render出parent comment和child comment,若是需要显示回复框,则在对应的位置插入回复框,更好的做法是复用AddParentComment这个组件,因为回复框其实和AddParentComment是同构的。

class CommentList extends ponent {constructor(props) {super(props);this.editor = null;}componentDidUpdate() {if(!!mentReplyBox) {const textbox = ReactDOM.findDOMNode(mentReplyBox);this.editor = new Simditor({textarea: textbox,toolbar: ['title', 'bold', 'italic', 'underline', 'strikethrough', 'color', 'ol', 'ul', 'link', 'alignment', 'emoji'],emoji: {imagePath: config.emojiUrl}});}}render() {const {comments, packageIndex, replyBoxIndex, actions} = this.props;let list = comments.map((comment, index) =>{return (<ListGroupItem className="commentOutline"><div className="commentContent" dangerouslySetInnerHTML={{__html: comment.text}}></div><Form horizontal><FormGroup><ControlLabel>{comment.created_at}</ControlLabel><Button bsStyle="link" eventKey={3} href="#" onClick={(e) => {e.preventDefault();actions.AddReplyBox(packageIndex, index);}}>{'回复 ' + (index === 0 ? comment.replyCount : '')}</Button><Button bsStyle="link" eventKey={2} href="#" onClick={(e) => {e.preventDefault();actions.PraiseComment(packageIndex, index, comments[index].id);}} >{'赞 ' + comment.praiseCount}</Button></FormGroup></Form></ListGroupItem>);});if(replyBoxIndex >= 0) {list.splice(replyBoxIndex + 1, 0,<ListGroupItem id="commentReplyOutline"><FormGroup controlId="commentReplyBox"><FormControl componentClass="textarea" placeholder="回复" ref={'commentReplyBox'} /><Button className="commentReplyBoxReplyButton" type="submit" onClick={(e) => {e.preventDefault();if(!!this.editor && this.editor.getValue() !== '') {actions.AddChildComment(packageIndex, comments[0].id, this.editor.getValue());actions.AddReplyBox(-1, -1);}}}>回复</Button></FormGroup></ListGroupItem>);}return <ListGroup className="commentList">{list}</ListGroup>;}}

写代码之前对于组件的封装考虑不是很到位,没有最大程度的复用组件,由于时间关系,就先mark一下。

最后加一下css,让child comment显示时能够有left margin,同时调整编辑框的高度。

.commentList {& > li:not(:first-child) {margin-left: 10%;}}.simditor-body {min-height: 100px !important;}

如果觉得《从零搭建个人博客(4)-留言评论区》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。