功能展示 :
1. 用户可输入用户名
2. 输入评论内容
3. 点击发布
4. 用户名和用户评论会被显示在列表里, 第一个评论在最上面
5. 且显示本条评论离现在过去了多长时间
6. 鼠标放在事件上方可显示删除键, 点击删除, 删除当前
总体结构:
1.components 下创建 comment 文件夹
2. 先创建用户输入组件: CommentInput
3. 再创建单条用户评论视图: Comment
4. 接着创建 CommentList 组件, 将用户评论遍历渲染成评论列表
5. 创建 CommentApp 组件, 将 CommentInput 评论组件和 CommentList 列表组件渲染到一起
6. 再 index.JS 中引入展示
用户输入组件: CommentInput 所有内容
1. 创建用户名输入视图模板,
用 ref 获取 input 此 DOM 节点的值,
- import {Component} from "react";
- import React 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.submit){
- this.props.submit({
- username:this.state.username,
- content:this.state.content,
- createTime:+new Date()
- })
- }
- // 点击发布后将 state 中的评论区域重新至为空
- this.setState({
- content:''
- })
- };
- // 用户名框失去焦点
- handleUsernameHold=(event)=>{
- // 将用户名数据存储到本地
- localStorage.setItem('username',event.target.value)
- };
- // 将要装载, 在 render 之前调用;
- componentWillMount(){
- // 改变数据重新渲染前 get 获取用户名
- const username=localStorage.getItem('username');
- if(username){
- this.setState({username})
- }
- }
- //(装载完成), 在 render 之后调用
- componentDidMount(){
- // 用户刷新之后, 用户名输入自动获取焦点
- this.input.focus();
- };
- render(){
- return(
- <div className='comment-input'>
- <div className='comment-field'>
- <span className='comment-field-name'> 用户名:</span>
- <div className='comment-field-input'>
- <input
- ref={(input)=>this.input=input}
- value={this.state.username}
- onBlur={this.handleUsernameHold}
- onChange={this.handleUsernameChange}
- />
- </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}
- />
- </div>
- </div>
- <div className='comment-field-button'>
- <button onClick={this.handleSubmit}>
发布
- </button>
- </div>
- </div>
- )
- }
- }
- export default CommentInput// 给 input 标签绑定 ref={(input)=>{this.input = input}}
- // 用来直接获取 dom 节点的值, 此处将 input 的值赋给 this.input, 让外部能访问
单条用户评论视图: Comment 所有内容
- import {Component} from "react";
- import React from "react";
- class Comment extends Component{
- constructor(){
- super();
- this.state ={
- // 初始化时间
- timeString:''
- }
- }
- // 显示用户评论时间
- handleTimeString=()=>{
- const item=this.props.item;
- // 当前时间减去用户创建时的时间
- const duration=(+Date.now()-item.createTime)/1000;
- // 如果足够 60 就转成分, 不够 60 就转成秒
- return duration>60?`${Math.round(duration/60)} 分钟前 `:`${Math.round(Math.max(duration,1))} 秒前 `;
- };
- // 点击删除事件
- // 将 handleDelete 事件传出, index 传出
- handleDelete=()=>{
- if(this.props.deleteItem){
- this.props.deleteItem(this.props.index)
- }
- };
- render(){
- return(
- <div className='comment'>
- <div className='comment-user'>
- <span className='comment-username'>{this.props.item.username} </span>:
- </div>
- <p>{this.props.item.content}</p>
- <span className="comment-delete" onClick={this.handleDelete}> 删除 </span>
- <span className="comment-createdtime">
- {this.handleTimeString()}
- </span>
- </div>
- )
- }
- }
export default Comment
评论列表 CommentList 所有内容
- import {Component} from "react";
- import React from "react";
- import Comment from './Comment'
- class CommentList extends Component{
- constructor(){
- super();
- this.state ={
- items:[]
- }
- }
- // 将数组列表传出, 将 deleteItem 删除事件传出
- render(){
- return(
- <div>
- {this.props.items.map((item,index)=>
- <Comment deleteItem={this.props.deleteItem}
- item={item} index={index} key={index}
- />)}
- </div>
- )
- }
- }
export default CommentList
总组件 CommentApp 的所有内容
- import {Component} from "react";
- import React from "react";
- import CommentInput from './CommentInput'
- import CommentList from './CommentList'
- class CommentApp extends Component{
- constructor(){
- super();
- this.state ={
- items:[]
- }
- }
- // 点击发送
- handleSubmit=(item)=>{
- // 点击发送时 push 当前那条
- this.state.items.push(item);
- // 更新数据列表
- this.setState({
- items:this.state.items
- });
- // 用户刷新时保存当前数据
- localStorage.setItem('items',JSON.stringify(this.state.items))
- };
- // 删除事件
- handleDelete=(index)=>{
- console.log(index);
- // 点击删除, 删除当前
- this.state.items.splice(index,1);
- // 列表数据更新至最新
- this.setState({
- items:this.state.items
- });
- // 删除后保存当下数据
- localStorage.setItem('items',JSON.stringify(this.state.items))
- };
- // 装载前获取
- componentWillMount(){
- let items=localStorage.getItem('items');
- if(items){
- items=JSON.parse(items);
- this.setState({items})
- }
- };
- render(){
- return(
- <div className="wrapper">
- <CommentInput submit={this.handleSubmit} />
- <CommentList deleteItem={this.handleDelete} items={this.state.items}/>
- </div>
- )
- }
- }
- export default CommentApp
- // Windows.localStorage
- // 保存数据语法:
- // localStorage.setItem("key", "value");
- // 读取数据语法:
- // var lastname = localStorage.getItem("key");
- // 删除数据语法:
- // localStorage.removeItem("key");
index.JS 下所有内容:
- import React from 'react';
- import ReactDOM from 'react-dom';
- import CommentApp from "./components/comment/CommentApp";
- import './index.CSS'
- ReactDOM.render(<CommentApp/>, document.getElementById('root'));
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 {
- position: relative;
- display: flex;
- border-bottom: 1px solid #f1f1f1;
- margin-bottom: 10px;
- padding-bottom: 10px;
- min-height: 50px;
- }
- .comment .comment-user {
- flex-shrink: 0;
- }
- .comment-username {
- color: #00a3cf;
- font-style: italic;
- }
- .comment-createdtime {
- padding-right: 5px;
- position: absolute;
- bottom: 0;
- right: 0;
- padding: 5px;
- font-size: 12px;
- }
- .comment:hover .comment-delete {
- color: #00a3cf;
- }
- .comment-delete {
- position: absolute;
- right: 0;
- top: 0;
- color: transparent;
- font-size: 12px;
- cursor: pointer;
- }
- .comment p {
- margin: 0;
- /*text-indent: 2em;*/
- }
- code {
- border: 1px solid #ccc;
- background: #f9f9f9;
- padding: 0px 2px;
}
来源: https://juejin.im/post/5c42f8b0518825273e732246