React.js 小书 Lesson27 - 实战分析:评论功能(六)
转载请注明出处,保留原文链接以及作者信息
在线阅读:http://huziketang.com/books/react
{%raw%}
删除评论
现在发布评论,评论不会消失,评论越来越多并不是什么好事。所以我们给评论组件加上删除评论的功能,这样就可以删除不想要的评论了。修改 src/Comment.js@H_404_21@ 的
render@H_404_21@ 方法,新增一个删除按钮:
... render () { const { comment } = this.props return ( <div className='comment'> <div className='comment-user'> <span className='comment-username'> {comment.username} </span>: </div> <p>{comment.content}</p> <span className='comment-createdtime'> {this.state.timeString} </span> <span className='comment-delete'> 删除 </span> </div> ) } ...
我们在后面加了一个删除按钮,因为 index.css@H_404_21@ 定义了样式,所以鼠标放到特定的评论上才会显示删除按钮,让用户体验好一些。
我们知道评论列表数据是放在 CommentApp@H_404_21@ 当中的,而这个删除按钮是在
Comment@H_404_21@ 当中的,现在我们要做的事情是用户点击某条评论的删除按钮,然后在
CommentApp@H_404_21@ 中把相应的数据删除。但是
CommentApp@H_404_21@ 和
Comment@H_404_21@ 的关系是这样的:
Comment@H_404_21@ 和
CommentApp@H_404_21@ 之间隔了一个
CommentList@H_404_21@,
Comment@H_404_21@ 无法直接跟
CommentApp@H_404_21@ 打交道,只能通过
CommentList@H_404_21@ 来转发这种删除评论的消息。修改
Comment@H_404_21@ 组件,让它可以把删除的消息传递到上一层:
class Comment extends Component { static propTypes = { comment: PropTypes.object.isrequired,onDeleteComment: PropTypes.func,index: PropTypes.number } ... handleDeleteComment () { if (this.props.onDeleteComment) { this.props.onDeleteComment(this.props.index) } } render () { ... <span onClick={this.handleDeleteComment.bind(this)} className='comment-delete'> 删除 </span> </div> ) }
现在在使用 Comment@H_404_21@ 的时候,可以传入
onDeleteComment@H_404_21@ 和
index@H_404_21@ 两个参数。
index@H_404_21@ 用来标志这个评论在列表的下标,这样点击删除按钮的时候我们才能知道你点击的是哪个评论,才能知道怎么从列表数据中删除。用户点击删除会调用
handleDeleteComment@H_404_21@ ,它会调用从上层传入的
props. onDeleteComment@H_404_21@ 函数告知上一层组件删除的消息,并且把评论下标传出去。现在修改
src/CommentList.js@H_404_21@ 让它把这两个参数传进来:
class CommentList extends Component { static propTypes = { comments: PropTypes.array,onDeleteComment: PropTypes.func } static defaultProps = { comments: [] } handleDeleteComment (index) { if (this.props.onDeleteComment) { this.props.onDeleteComment(index) } } render() { return ( <div> {this.props.comments.map((comment,i) => <Comment comment={comment} key={i} index={i} onDeleteComment={this.handleDeleteComment.bind(this)} /> )} </div> ) } }
当用户点击按钮的时候,Comment@H_404_21@ 组件会调用
props.onDeleteComment@H_404_21@,也就是
CommentList@H_404_21@ 的
handleDeleteComment@H_404_21@ 方法。而
handleDeleteComment@H_404_21@ 会调用
CommentList@H_404_21@ 所接受的配置参数中的
props.onDeleteComment@H_404_21@,并且把下标传出去。
也就是说,我们可以在 CommentApp@H_404_21@ 给
CommentList@H_404_21@ 传入一个
onDeleteComment@H_404_21@ 的配置参数来接受这个删除评论的消息,修改
CommentApp.js@H_404_21@:
... handleDeleteComment (index) { console.log(index) } render() { return ( <div className='wrapper'> <CommentInput onSubmit={this.handleSubmitComment.bind(this)} /> <CommentList comments={this.state.comments} onDeleteComment={this.handleDeleteComment.bind(this)} /> </div> ) } } ...
现在点击删除按钮,可以在控制台看到评论对应的下标打印了出来。其实这就是这么一个过程:CommentList@H_404_21@ 把下标
index@H_404_21@ 传给
Comment@H_404_21@。点击删除按钮的时候,
Comment@H_404_21@ 把
index@H_404_21@ 传给了
CommentList@H_404_21@,
CommentList@H_404_21@ 再把它传给
CommentApp@H_404_21@。现在可以在
CommentApp@H_404_21@ 中删除评论了:
... handleDeleteComment (index) { const comments = this.state.comments comments.splice(index,1) this.setState({ comments }) this._saveComments(comments) } ...
我们通过 comments.splice@H_404_21@ 删除特定下标的评论,并且通过
setState@H_404_21@ 重新渲染整个评论列表;当然了,还需要把最新的评论列表数据更新到 LocalStorage 中,所以我们在删除、更新以后调用了
_saveComments@H_404_21@ 方法把数据同步到 LocalStorage 中。
现在就可以愉快地删除评论了。但是,你删除评论以后 5 秒钟后就会在控制台中看到报错了:
这是因为我们忘了清除评论的定时器,修改 src/Comment.js@H_404_21@,新增生命周期
commentWillUnmount@H_404_21@ 在评论组件销毁的时候清除定时器:
... componentWillUnmount () { clearInterval(this._timer) } ...
这才算完成了第 5 个需求。
显示代码块
用户在的输入内容中任何以 `` 包含的内容都会用 <code>@H_404_21@ 包含起来显示到页面上。
<code>@H_404_21@ 这是一个 HTML 结构,需要往页面动态插入 HTML 结构我们只能用
dangerouslySetInnerHTML@H_404_21@ 了,修改
src/Comment.js@H_404_21@,把原来
render()@H_404_21@ 函数中的:
<p>{comment.content}</p>
修改成:
<p dangerouslySetInnerHTML={{ __html: this._getProcessedContent(comment.content) }} />
我们把经过 this._getProcessedContent@H_404_21@ 处理的评论内容以 HTML 的方式插入到
<p>@H_404_21@ 元素中,
this._getProcessedContent@H_404_21@ 是这样实现的:
... _getProcessedContent (content) { return content .replace(/&/g,"&") .replace(/</g,"<") .replace(/>/g,">") .replace(/"/g,""") .replace(/'/g,"'") .replace(/`([\S\s]+?)`/g,'<code>$1</code>') } ...
看起来很复杂,其实前 5 行是用来处理 HTML 内容转义的,最后一行是用来插入 <code>@H_404_21@ 标签的。如果我们把用户输入的内容全部以 HTML 显示到页面上,那么就会造成跨站脚本攻击。所以前 5 个
replace@H_404_21@ 实际上是把类似于
<@H_404_21@、
>@H_404_21@ 这种内容替换转义一下。而最后一行才是真正实现需求的代码,把 `` 包含的内容用
<code>@H_404_21@ 包裹起来。
输入:
看看效果:
我们安全地完成了第 6 个需求。到目前为止,第二阶段的实战已经全部完成,你可以在@L_403_3@找到完整的代码。
总结
到这里第二阶段已经全部结束,我们已经掌握了全部 React.js 实战需要的入门知识。接下来我们会学习两个相对比较高级的 React.js 的概念,然后进入 React-router 和 Redux 的世界,让它们配合 React.js 来构建更成熟的前端页面。
{%endraw%}
下一节中我们将介绍《React.js 小书 Lesson28 - 高阶组件(Higher-Order Components)》。