【react案例】实现评论列表

1. 需求

  1. 展示评论列表
  2. 实现删除功能
    2.1 只有自己的评论才展示删除按钮
    2.2 点击删除按钮,删除当前评论
  3. tab切换(点击对应tab,对tab文案高亮处理)
  4. 评论高亮
  5. 评论排序(最新、最热)

2. 实现思路

  1. useState维护评论列表
  2. map方法遍历渲染
  3. 删除显示------条件渲染
  4. 删除功能------拿到当前评论项的id,然后对原有评论列表进行过滤,根据id剔除要删除的评论项
  5. tab高亮------记录点击的tab的type,根据type去匹配高亮样式
  6. 评论排序------根据tab的type对评论列表状态数据进行不同的排序处理,当成新值传给set方法重新渲染视图UI(注意:排序后不要修改评论列表状态数据的原始值,教程里推荐使用lodash包的orderBy方法)

3. 代码

  • 样式是我自己随便写的,具体可以参考b站本身的css样式
3.1 App.js
javascript 复制代码
import { useState } from "react"
import _ from 'lodash'
import './App.css'

function App() {
  // 评论列表
  const initReviewList = [
    {
      rid: 2,
      user: {
        uid: '002',
        avator: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.gC14cncyeUsZYKNwXOyBtgHaHa?rs=1&pid=ImgDetMain',
        uname: '小猫爱吃鱼'
      },
      content: '喵喵喵',
      ctime: '09-22 17:55',
      like: 90
    },
    {
      rid: 1,
      user: {
        uid: '001',
        avator: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.qCys2C8LjF8c3_UHbGOooAAAAA?rs=1&pid=ImgDetMain',
        uname: '小狗爱吃骨头'
      },
      content: '汪汪汪',
      ctime: '09-22 14:55',
      like: 100
    },
    {
      rid: 0,
      user: {
        uid: '000',
        avator: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.pL9aeO50HMujMSzGcOPhKwAAAA?rs=1&pid=ImgDetMain',
        uname: '二郎神'
      },
      content: '666',
      ctime: '09-30 12:23',
      like: 88
    }
  ]
  // 当前登录的用户信息
  const user = {
    uid: '000',
    avator: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.pL9aeO50HMujMSzGcOPhKwAAAA?rs=1&pid=ImgDetMain',
    uname: '二郎神'
  }
  // tab
  const tabs = [
    {
      type: 'hot',
      text:'最热',
    },
    {
      type: 'new',
      text:'最新',
    }
  ]
  const [reviewList, setReviewList] = useState(_.orderBy(initReviewList, 'like', 'desc'))
  function handleDeleteReview(currentRid) {
    setReviewList(reviewList.filter(item=> item.rid !== currentRid))
  }
  const [currTabType, steCurrTabType] = useState('hot')
  function handleClickTab(type) {
    steCurrTabType(type)
    if (type === 'hot') {
      // 根据点赞数量,降序排序
      setReviewList(_.orderBy(reviewList, 'like', 'desc'))
    } else {
      // 根据评论时间,降序排序
      setReviewList(_.orderBy(reviewList, 'ctime', 'desc'))
    }
  }
  return (
    <div className="App">
      {/* 导航栏 */}
      <div className="review-header">
        {/* 标题 */}
        <div className="review-title-container">
          <span className="review-title">评论</span>
          {/* 评论总数 */}
          <div className="review-total">{reviewList.length ?? 0}</div>
        </div>
        <div className="review-tabs-container">
          {tabs.map((item, index) => {
            return <div key={item.type} className="tabs-item">
              <span className={`tabs-text ${item.type === currTabType && 'tabs-active'}`} onClick={() => handleClickTab(item.type)}>{item.text}</span>
              {index < tabs.length - 1 && <div className="tabs-idot">|</div>}
            </div>
          })}
        </div>
      </div>
      {/* 评论 */}
      <div className="review-wrap">
        {/* 发表评论 */}
        <div className="box-normal">
          <img className="normal-avator" src={user.avator}></img>
          <input className="review-input" placeholder="发一条友善的评论"></input>
          <button className="add-review">发布</button>
        </div>
        {/* 评论列表 */}
        <div className="review-list">
          {reviewList.map((item) => {
            {/* 每一条评论 */ }
            return <div className="review-item" key={item.rid}>
              {/* 头像 */}
              <img className="review-avator" src={item.user.avator}></img>
              <div className="review-item-content">
                {/* 用户名 */}
                <div className="user-name">{item.user.uname}</div>
                {/* 评论内容 */}
                <span className="review-content">{item.content}</span>
                {/* 评论相关信息 */}
                <div className="review-msg">
                  {/* 评论时间 */}
                  <span className="review-date">{item.ctime}</span>
                  {/* 点赞数量 */}
                  <span className="good-count">点赞数:{item.like}</span>
                  {/* 删除评论按钮 */}
                  {item.user.uid === user.uid && <span className="review-delete" onClick={() => handleDeleteReview(item.rid)}>删除</span>}
                </div>
                {/* 分割线 */}
                <div className="part-line"></div>
              </div>
            </div>
          })}
        </div>
      </div>
    </div>
  );
}

export default App;
3.2 App.css
javascript 复制代码
.review-header {
    display: flex;
    .review-title-container {
        display: flex;
        align-items: center;
        margin-right: 40px;
        .review-title {
            font-weight: bold;
            margin-right: 2px;
        }
        .review-total {
            font-size: 12px;
            color: gray;
        }
    }
    .review-tabs-container {
        display: flex;
        align-items: center;
        font-size: 12px;
        font-weight: bold;
        .tabs-item {
            color: gray;
            display: flex;
            .tabs-text {
                cursor: pointer;
            }
            .tabs-active {
                color: black;
            }
            .tabs-idot {
                margin: 0px 5px;
            }
        }
    }
}

.box-normal {
    display: flex;
    margin: 16px 0px 30px 0px;
    .normal-avator {
        width: 50px;
        height: 50px;
        border-radius: 50%;
        margin-right: 20px;
    }
    .review-input {
        border: 1px solid #F1F2F3;
        background-color: #F1F2F3;
        border-radius: 6px;
        padding-left: 10px;
    }
    .add-review {
        background-color: skyblue;
        border: transparent;
        border-radius: 6px;
        color: #fff;
        margin-left: 6px;
        padding: 0px 10px;
    }
}

.review-list {
    .review-item {
        display: flex;
        margin-bottom: 15px;
        .review-avator {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            margin-right: 20px;
        }
        .review-item-content {
            .user-name {
                font-size: 12px;
                font-weight: bold;
                color: gray;
                margin-bottom: 6px;
            }
            .review-content {
                font-size: 14px;
            }
            .review-msg {
                font-size: 10px;
                color: gray;
                .review-date {
                    margin-right: 15px;
                }
                .good-count {
                    margin-right: 15px;
                }
                .review-delete {
                    cursor: pointer;
                }
            }
            .part-line {
                margin-top: 5px;
                height: 0.5px;
                background-color: #E3E5E7;
            }
        }
    }
}
3.3 效果图

参考

黑马程序员react教程

相关推荐
真的很上进10 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
web1309332039816 分钟前
vue elementUI form组件动态添加el-form-item并且动态添加rules必填项校验方法
前端·vue.js·elementui
NiNg_1_23437 分钟前
Echarts连接数据库,实时绘制图表详解
前端·数据库·echarts
如若1231 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~2 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语2 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport2 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg2 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww2 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254882 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui