實戰分析:評論功能(六)

刪除評論

如今發佈評論,評論不會消失,評論愈來愈多並非什麼好事。因此咱們給評論組件加上刪除評論的功能,這樣就能夠刪除不想要的評論了。修改 src/Comment.js 的 render 方法,新增一個刪除按鈕:css

...
  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 定義了樣式,因此鼠標放到特定的評論上纔會顯示刪除按鈕,讓用戶體驗好一些。html

咱們知道評論列表數據是放在 CommentApp 當中的,而這個刪除按鈕是在 Comment當中的,如今咱們要作的事情是用戶點擊某條評論的刪除按鈕,而後在 CommentApp中把相應的數據刪除。可是 CommentApp 和 Comment 的關係是這樣的:前端

Comment 和 CommentApp 之間隔了一個 CommentListComment 沒法直接跟 CommentApp 打交道,只能經過 CommentList 來轉發這種刪除評論的消息。修改 Comment 組件,讓它能夠把刪除的消息傳遞到上一層:react

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 的時候,能夠傳入 onDeleteComment 和 index 兩個參數。index 用來標誌這個評論在列表的下標,這樣點擊刪除按鈕的時候咱們才能知道你點擊的是哪一個評論,才能知道怎麼從列表數據中刪除。用戶點擊刪除會調用 handleDeleteComment ,它會調用從上層傳入的 props. onDeleteComment 函數告知上一層組件刪除的消息,而且把評論下標傳出去。如今修改 src/CommentList.js 讓它把這兩個參數傳進來:git

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 組件會調用 props.onDeleteComment,也就是 CommentList 的 handleDeleteComment 方法。而 handleDeleteComment 會調用 CommentList 所接受的配置參數中的 props.onDeleteComment,而且把下標傳出去。github

也就是說,咱們能夠在 CommentApp 給 CommentList 傳入一個 onDeleteComment 的配置參數來接受這個刪除評論的消息,修改 CommentApp.js正則表達式

...
  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 把下標 index 傳給 Comment。點擊刪除按鈕的時候,Comment 把 index 傳給了 CommentListCommentList 再把它傳給 CommentApp。如今能夠在 CommentApp 中刪除評論了:redux

...
  handleDeleteComment (index) {
    const comments = this.state.comments
    comments.splice(index, 1)
    this.setState({ comments })
    this._saveComments(comments)
  }
...

咱們經過 comments.splice 刪除特定下標的評論,而且經過 setState 從新渲染整個評論列表;固然了,還須要把最新的評論列表數據更新到 LocalStorage 中,因此咱們在刪除、更新之後調用了 _saveComments 方法把數據同步到 LocalStorage 中。安全

如今就能夠愉快地刪除評論了。可是,你刪除評論之後 5 秒鐘後就會在控制檯中看到報錯了:app

這是由於咱們忘了清除評論的定時器,修改 src/Comment.js,新增生命週期 commentWillUnmount 在評論組件銷燬的時候清除定時器:

...
  componentWillUnmount () {
    clearInterval(this._timer)
  }
...

這纔算完成了第 5 個需求。

顯示代碼塊

用戶在的輸入內容中任何以 `` 包含的內容都會用 <code> 包含起來顯示到頁面上。<code> 這是一個 HTML 結構,須要往頁面動態插入 HTML 結構咱們只能用 dangerouslySetInnerHTML 了,修改 src/Comment.js,把原來 render() 函數中的:

<p>{comment.content}</p>

修改爲:

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

咱們把通過 this._getProcessedContent 處理的評論內容以 HTML 的方式插入到 <p>元素中。this._getProcessedContent 要把 `` 包含的內容用 <code> 包裹起來,一個正則表達式就能夠作到了:

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

可是這樣作會有嚴重的 XSS 漏洞,用戶能夠輸入任意的 HTML 標籤,用 <script>執行任意的 JavaScript 代碼。因此在替換代碼以前,咱們要手動地把這些 HTML 標籤進行轉義:

...
  _getProcessedContent (content) {
    return content
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;")
      .replace(/`([\S\s]+?)`/g, '<code>$1</code>')
  }
...

前 5 個 replace 其實是把相似於 <> 這種內容替換轉義一下,防止用戶輸入 HTML 標籤。最後一行代碼纔是實現功能的代碼。

這時候在評論框中輸入:

這是代碼塊 `console.log`,這是 <h1>正常內容</h1>。

而後點擊發布,看看效果:

咱們安全地完成了第 6 個需求。到目前爲止,第二階段的實戰已經所有完成,你能夠在這裏找到完整的代碼。

總結

到這裏第二階段已經所有結束,咱們已經掌握了所有 React.js 實戰須要的入門知識。接下來咱們會學習兩個相對比較高級的 React.js 的概念,而後進入 React-redux 的世界,讓它們配合 React.js 來構建更成熟的前端頁面。

相關文章
相關標籤/搜索