我们在后面加了一个删除按钮,因为 index.css 定义了样式,所以鼠标放到特定的评论上才会显示删除按钮,让用户体验好一些。

我们知道评论列表数据是放在 CommentApp 当中的,而这个删除按钮是在 Comment 当中的,现在我们要做的事情是用户点击某条评论的删除按钮,然后在 CommentApp 中把相应的数据删除。但是 CommentAppComment 的关系是这样的:

CommentCommentApp 之间隔了一个 CommentListComment 无法直接跟 CommentApp 打交道,只能通过 CommentList 来转发这种删除评论的消息。修改 Comment 组件,让它可以把删除的消息传递到上一层:

  1. class Comment extends Component {
  2. static propTypes = {
  3. comment: PropTypes.object.isRequired,
  4. onDeleteComment: PropTypes.func,
  5. index: PropTypes.number
  6. }
  7. ...
  8. handleDeleteComment () {
  9. if (this.props.onDeleteComment) {
  10. this.props.onDeleteComment(this.props.index)
  11. }
  12. }
  13. render () {
  14. ...
  15. <span
  16. onClick={this.handleDeleteComment.bind(this)}
  17. className='comment-delete'>
  18. 删除
  19. </span>
  20. </div>
  21. }

现在在使用 Comment 的时候,可以传入 onDeleteCommentindex 两个参数。index 用来标志这个评论在列表的下标,这样点击删除按钮的时候我们才能知道你点击的是哪个评论,才能知道怎么从列表数据中删除。用户点击删除会调用 handleDeleteComment ,它会调用从上层传入的 props. onDeleteComment 函数告知上一层组件删除的消息,并且把评论下标传出去。现在修改 让它把这两个参数传进来:

  1. class CommentList extends Component {
  2. static propTypes = {
  3. comments: PropTypes.array,
  4. onDeleteComment: PropTypes.func
  5. }
  6. static defaultProps = {
  7. comments: []
  8. }
  9. handleDeleteComment (index) {
  10. if (this.props.onDeleteComment) {
  11. this.props.onDeleteComment(index)
  12. }
  13. }
  14. render() {
  15. return (
  16. <div>
  17. {this.props.comments.map((comment, i) =>
  18. <Comment
  19. comment={comment}
  20. key={i}
  21. index={i}
  22. )}
  23. </div>
  24. )
  25. }
  26. }

当用户点击按钮的时候,Comment 组件会调用 props.onDeleteComment,也就是 CommentListhandleDeleteComment 方法。而 handleDeleteComment 会调用 CommentList 所接受的配置参数中的 props.onDeleteComment,并且把下标传出去。

也就是说,我们可以在 CommentAppCommentList 传入一个 onDeleteComment 的配置参数来接受这个删除评论的消息,修改 CommentApp.js

  1. ...
  2. handleDeleteComment (index) {
  3. const comments = this.state.comments
  4. comments.splice(index, 1)
  5. this.setState({ comments })
  6. this._saveComments(comments)
  7. }
  8. ...

我们通过 comments.splice 删除特定下标的评论,并且通过 setState 重新渲染整个评论列表;当然了,还需要把最新的评论列表数据更新到 LocalStorage 中,所以我们在删除、更新以后调用了 _saveComments 方法把数据同步到 LocalStorage 中。

现在就可以愉快地删除评论了。但是,你删除评论以后 5 秒钟后就会在控制台中看到报错了:

React.js 小书实战评论功能图片

这是因为我们忘了清除评论的定时器,修改 src/Comment.js,新增生命周期 commentWillUnmount 在评论组件销毁的时候清除定时器:

  1. ...
  2. componentWillUnmount () {
  3. clearInterval(this._timer)
  4. }
  5. ...

这才算完成了第 5 个需求。

用户在的输入内容中任何以 `` 包含的内容都会用 <code> 包含起来显示到页面上。<code> 这是一个 HTML 结构,需要往页面动态插入 HTML 结构我们只能用 dangerouslySetInnerHTML 了,修改 src/Comment.js,把原来 render() 函数中的:

修改成:

  1. <p dangerouslySetInnerHTML={{
  2. __html: this._getProcessedContent(comment.content)
  3. }} />
  1. ...
  2. _getProcessedContent (content) {
  3. return content
  4. .replace(/`([\S\s]+?)`/g, '<code>$1</code>')
  5. }

但是这样做会有严重的 XSS 漏洞,用户可以输入任意的 HTML 标签,用 <script> 执行任意的 JavaScript 代码。所以在替换代码之前,我们要手动地把这些 HTML 标签进行转义:

前 5 个 replace 实际上是把类似于 <、 这种内容替换转义一下,防止用户输入 HTML 标签。最后一行代码才是实现功能的代码。

这时候在评论框中输入:

然后点击发布,看看效果:

我们安全地完成了第 6 个需求。到目前为止,第二阶段的实战已经全部完成,你可以在这里找到完整的代码。