關於 React 小書:評論功能


Posted by YongChenSu on 2020-12-09

Component 規劃



評論功能整架構



向父元件傳遞數據

數據傳遞順序

當使用可點擊發布按鈕,將 CommentInput 的 state 當中最金的數據傳給父組件 ComponentApp,ComponentApp 會把這組數據傳給 CommentList 進行渲染。

如何傳遞數據

CommentInput 如何向 CommentApp 傳遞數據?父組件 CommentApp 只要透過 props 給子組件 CommentInput 傳入一個 callback function,當使用者點擊發布按鈕時,CommentInput 調用 props 中的 callback function 並將 state 傳入該函數即可。

CommentApp 中的 CommentInput 傳入 onSubmit 屬性,這個屬性值是 CommentApp 的一個方法 handleSubmitComment。這樣 CommentInput 可調用 this.props.onSubmit(...) 把數據傳給 CommentApp

React 小書 評論功能

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import CommentApp from './CommentApp'
import './index.css'

ReactDOM.render(
  <CommentApp />,
  document.getElementById('root')
)

CommentApp.js

import React, { Component } from 'react'
import CommentInput from './CommentInput'
import CommentList from './CommentList'

class CommentApp extends Component {
  constructor() {
    super()
    this.state = {
      comments: []
    }
  }

  handleSubmitComment (comment) {
    console.log(comment)
    if (!comment) return
    if (!comment.username) return alert('請輸入使用者名稱')
    if (!comment.content) return alert('請輸入內容')

    this.state.comments.push(comment)
    this.setState({
      comments: this.state.comments
    })
  }

  render() {
    return (
      <div className='wrapper'>
        <CommentInput onSubmit={this.handleSubmitComment.bind(this)}/>
        <CommentList comments={this.state.comments}/>
      </div>
    )
  }
}

export default CommentApp


CommentInput.js

import React, { Component } from 'react'

class CommentInput extends Component {
  constructor() {
    super()
    this.state = {
      username: '',
      content: ''
    }
  }

  handleUsernameChange (event) {
    this.setState({
      username: event.target.value
    })
  }

  handleContentChange (event) {
    this.setState({
      content: event.target.value
    })
  }

  handleSubmit () {
    if (this.props.onSubmit) {
      const { username, content } = this.state
      this.props.onSubmit({username, content})
    }
    this.setState({ content: '' })
  }

  render () {
    return (
      <div className='comment-input'>
        <div className='comment-field'>
          <span className='comment-field-name'>使用者名稱:</span>
          <div className='comment-field-input'>
            <input
              value={this.state.username} 
              onChange={this.handleUsernameChange.bind(this)} />
          </div>
        </div>
        <div className='comment-field'>
          <span className='comment-field-name'>評論內容:</span>
          <div className='comment-field-input'>
            <textarea 
              value={this.state.content}
              onChange={this.handleContentChange.bind(this)} />
          </div>
        </div>
        <div className='comment-field-button'>
          <button onClick={this.handleSubmit.bind(this)}>
            發布
          </button>
        </div>
      </div>
    )
  }
}

export default CommentInput


CommentList.js

import React, { Component } from 'react'
import Comment from './Comment'

class CommentList extends Component {
  static defaultProps = {
    comments: []
  }

  render() {
    // const comments = [
    //   {username: 'Jerry', content: 'Hello'},
    //   {username: 'Tomy', content: 'World'},
    //   {username: 'Lucy', content: 'Good'}
    // ]
    // return (
    //   <div>
    //     {comments.map((comment, i) => 
    //       <Comment comment={comment} key={i} /> )}
    //   </div>
    // )
    return (
      <div>
        {this.props.comments.map((comment, i) =>
          <Comment comment={comment} key={i} />
        )}
      </div>
    )
  }
}

export default CommentList


Comment.js

import React, { Component } from 'react'

class Comment extends Component {
  render () {
    return (
      <div className='comment'>
        <div className='comment-user'>
          <span>{this.props.comment.username} </span>:
        </div>
        <p>{this.props.comment.content}</p>
      </div>
    )
  }
}

export default Comment


index.css

body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  background-color: #fbfbfb;
}

.wrapper {
  width: 500px;
  margin: 10px auto;
  font-size: 14px;
  background-color: #fff;
  border: 1px solid #f1f1f1;
  padding: 20px;
}

/* 评论框样式 */
.comment-input {
  background-color: #fff;
  border: 1px solid #f1f1f1;
  padding: 20px;
  margin-bottom: 10px;
}

.comment-field {
  margin-bottom: 15px;
  display: flex;
}

.comment-field .comment-field-name {
  display: flex;
  flex-basis: 100px;
  font-size: 14px;
}

.comment-field .comment-field-input {
  display: flex;
  flex: 1;
}

.comment-field-input input,
.comment-field-input textarea {
  border: 1px solid #e6e6e6;
  border-radius: 3px;
  padding: 5px;
  outline: none;
  font-size: 14px;
  resize: none;
  flex: 1;
}

.comment-field-input textarea {
  height: 100px;
}

.comment-field-button {
  display: flex;
  justify-content: flex-end;
}

.comment-field-button button {
  padding: 5px 10px;
  width: 80px;
  border: none;
  border-radius: 3px;
  background-color: #00a3cf;
  color: #fff;
  outline: none;
  cursor: pointer;
}

.comment-field-button button:active {
  background: #13c1f1;
}

/* 评论列表样式 */
.comment-list {
  background-color: #fff;
  border: 1px solid #f1f1f1;
  padding: 20px;
}

/* 评论组件样式 */
.comment {
  display: flex;
  border-bottom: 1px solid #f1f1f1;
  margin-bottom: 10px;
  padding-bottom: 10px;
  min-height: 50px;
}

.comment .comment-user {
  flex-shrink: 0;
}

.comment span {
  color: #00a3cf;
  font-style: italic;
}

.comment p {
  margin: 0;
  /*text-indent: 2em;*/
}


總結

  1. 實現功能前先理解、分析需求、劃分組件。劃分組件的基本原則:可複用性、可維護性。
  2. 受控組件:若 React.js 中的 <input />, <textarea />, <select /> 等元素的值,如果受到 React.js 控制就是受控組件。
  3. 組件之間透過 props 通過父元素傳遞數據的技巧。

參考資源


#程式導師實驗計畫第四期 #前端 #React #react 小書 #comment







Related Posts

Day 71

Day 71

什麼是 Prototype 、繼承與原型鏈 ?

什麼是 Prototype 、繼承與原型鏈 ?

React 部署上 Github pages

React 部署上 Github pages


Comments