(本文未审核)

删除评论

现在发布评论,评论不会消失,评论越来越多并不是什么好事。所以我们给评论组件加上删除评论的功能,这样就可以删除不想要的评论了。修改 src/Comment.js 的 render 方法,新增一个删除按钮:

  1. ...
  2. render () {
  3. const { comment } = this.props
  4. return (
  5. <div className='comment'>
  6. <div className='comment-user'>
  7. <span className='comment-username'>
  8. {comment.username}
  9. </span>:
  10. </div>
  11. <p>{comment.content}</p>
  12. <span className='comment-createdtime'>
  13. {this.state.timeString}
  14. </span>
  15. <span className='comment-delete'>
  16. 删除
  17. </span>
  18. </div>
  19. )
  20. }
  21. ...

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

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

Comment 和 CommentApp 之间隔了一个 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. )
  22. }

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

  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. onDeleteComment={this.handleDeleteComment.bind(this)} />
  23. )}
  24. </div>
  25. )
  26. }
  27. }

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

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

  1. ...
  2. handleDeleteComment (index) {
  3. console.log(index)
  4. }
  5. render() {
  6. return (
  7. <div className='wrapper'>
  8. <CommentInput onSubmit={this.handleSubmitComment.bind(this)} />
  9. <CommentList
  10. comments={this.state.comments}
  11. onDeleteComment={this.handleDeleteComment.bind(this)} />
  12. </div>
  13. )
  14. }
  15. }
  16. ...

现在点击删除按钮,可以在控制台看到评论对应的下标打印了出来。其实这就是这么一个过程:CommentList 把下标 index 传给 Comment。点击删除按钮的时候,Comment 把 index 传给了 CommentListCommentList 再把它传给 CommentApp。现在可以在 CommentApp 中删除评论了:

  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 秒钟后就会在控制台中看到报错了:

这是因为我们忘了清除评论的定时器,修改 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>{comment.content}</p>

修改成:

  1. <p dangerouslySetInnerHTML={{
  2. __html: this._getProcessedContent(comment.content)
  3. }} />

我们把经过 this._getProcessedContent 处理的评论内容以 HTML 的方式插入到 <p> 元素中。this._getProcessedContent 要把 `` 包含的内容用 <code> 包裹起来,一个正则表达式就可以做到了:

  1. ...
  2. _getProcessedContent (content) {
  3. return content
  4. .replace(/`([\S\s]+?)`/g, '<code>$1</code>')
  5. }
  6. ...

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

  1. ...
  2. _getProcessedContent (content) {
  3. return content
  4. .replace(/&/g, "&amp;")
  5. .replace(/</g, "&lt;")
  6. .replace(/>/g, "&gt;")
  7. .replace(/"/g, "&quot;")
  8. .replace(/'/g, "'")
  9. .replace(/`([\S\s]+?)`/g, '<code>$1</code>')
  10. }
  11. ...

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

这时候在评论框中输入:

这是代码块 `console.log`,这是 <h1>正常内容</h1>。

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

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

总结

到这里第二阶段已经全部结束,我们已经掌握了全部 React.js 实战需要的入门知识。接下来我们会学习两个相对比较高级的 React.js 的概念,然后进入 React-redux 的世界,让它们配合 React.js 来构建更成熟的前端页面。

React.js 小书 Lesson27 - 实战分析:评论功能(六)的更多相关文章

  1. React.js 小书 Lesson25 - 实战分析:评论功能(四)

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson25 转载请注明出处,保留原文链接和作者信息. (本文未审核) 目前为止,第二阶段知识已经基本 ...

  2. React.js 小书 Lesson16 - 实战分析:评论功能(三)

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson16 转载请注明出处,保留原文链接和作者信息. 接下来的代码比较顺理成章了.修改 Commen ...

  3. React.js 小书 Lesson14 - 实战分析:评论功能(一)

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson14 转载请注明出处,保留原文链接和作者信息. 课程到这里大家已经掌握了 React.js 的 ...

  4. React.js 小书 Lesson26 - 实战分析:评论功能(五)

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson26 转载请注明出处,保留原文链接和作者信息. (本文未审核) 持久化评论 同样地,可以通过类 ...

  5. React.js 小书 Lesson15 - 实战分析:评论功能(二)

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson15 转载请注明出处,保留原文链接和作者信息. 上一节我们构建了基本的代码框架,现在开始完善其 ...

  6. 【React.js小书】动手实现 React-redux(五):Provider - 方志

    我们要把 context 相关的代码从所有业务组件中清除出去,现在的代码里面还有一个地方是被污染的.那就是 src/index.js 里面的 Index: 1234567891011121314151 ...

  7. React.js 小书介绍

    React.js 小书 Github 关于作者 这是一本关于 React.js 的小书. 因为工作中一直在使用 React.js,也一直以来想总结一下自己关于 React.js 的一些知识.经验.于是 ...

  8. React.js 小书 Lesson20 - 更新阶段的组件生命周期

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson20 转载请注明出处,保留原文链接和作者信息. 从之前的章节我们了解到,组件的挂载指的是将组件 ...

  9. React.js 小书 Lesson24 - PropTypes 和组件参数验证

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson24 转载请注明出处,保留原文链接和作者信息. 我们来了到了一个非常尴尬的章节,很多初学的朋友 ...

随机推荐

  1. Intel Galileo development documentation

    Intel Galileo development Documentation Author:Liutianchen 1552227, Department of Computer Science,E ...

  2. 从hive将数据导出到mysql(转)

    从hive将数据导出到mysql http://abloz.com 2012.7.20 author:周海汉 在上一篇文章<用sqoop进行mysql和hdfs系统间的数据互导>中,提到s ...

  3. Web开发工具箱

    1.打印1 Web打印组件jatoolsPrinter 2.打印2Lodop 3.web前端利器 Web Essentials

  4. 基于.net standard 的动态编译实现

    在前文[基于.net core 微服务的另类实现]结尾处,提到了如何方便自动的生成微服务的客户端代理,使对于调用方透明,同时将枯燥的东西使用框架集成,以提高使用便捷性.在尝试了基于 Emit 中间语言 ...

  5. bitcoin script

    P2PK P2PKH,MS,P2SH,OP_RETURN 等的区别 1.P2PK pay_to_public_key pubkey script: <pubkey> OP_CHECKSIG ...

  6. HttpRunnerManager接口自动化测试框架在win环境下搭建教程

    近几日一直在研究如何把接口自动化做的顺畅,目前用的是轻量级jmeter+ant+Jenkins自动化测试框架,目前测试界的主流是python语言,所以一直想用搭建一个基于python的HttpRunn ...

  7. 4.Python的版本

    Python2: 英文支持没问题,中文报错,默认编码:ascii码 显示中午需要添加代码在首行:# -*- encoding:utf -8 -*- 用户交互  raw_input python2 里还 ...

  8. BZOJ2430 chocolate

    有一个显然的想法是因为最后要花分成n*m个小块,所以每条边一定是要被切开的. 所以直接排序就可以了qwq,按照代价从大到小切一定是最优的. #include<iostream> #incl ...

  9. spring的包大概作用(备忘)

    1.spring.jar 是包含有完整发布模块的单个jar 包. 2. org.springframework.aop 包含在应用中使用Spring的AOP特性时所需的类. 3. org.spring ...

  10. JQ的ready()方法与window.onload()的区别与联系

    JQ的ready()与window.onload()方法都是在文档加载完毕之后才会被触发的方法,但它们之间的区别也是很明显的. 1.区别与联系:   $(document).ready() windo ...