【综合案例】使用React编写B站评论案例

一、效果展示

默认效果,一开始默认按照最热进行排序
发布了一条评论
按照最新进行排序
按照最新进行排序

二、效果说明

页面上默认有3条评论,且一开始进入页面的时候是按照点赞数量进行倒序排列展示,可以点击【最热 、最新】进行排序的切换。

在文本框中输入要评论的文本,然后点击【发布】按钮,即可将评论添加到下方的评论列表当中进行展示;如果没有输入任何文本的时候直接点击【发布】按钮会弹出提示对话框。

点击删除按钮可以将对应的评论从评论列表中移除。

三、涉及知识点

3.1 useState

3.1.1 基础使用

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果。

🚩 语法:

const [state, setState] = useState(initialState)
  1. useState是一个函数,返回值是一个数组 
  2. 数组中的第一个参数是状态变量;第二个参数是set函数,用来修改状态变量
  3. useState的参数将作为state的初始值

🚩 本质:

和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)。

注意事项:

  • useState 是一个 Hook,因此你只能在 组件的顶层 或自己的 Hook 中调用它。你不能在循环或条件语句中调用它。如果你需要这样做,请提取一个新组件并将状态移入其中。
  • 在严格模式中,React 将 两次调用初始化函数,以 帮你找到意外的不纯性。这只是开发时的行为,不影响生产。如果你的初始化函数是纯函数(本该是这样),就不应影响该行为。其中一个调用的结果将被忽略。
3.1.2 修改状态的规则

🚩 状态不可变

在React中,状态被认为是只读的,我们应该始终 替换它而不是修改它, 直接修改状态不能引发视图更新。

🚩 修改对象状态

规则:对于对象类型的状态变量,应该始终传给set方法一个 全新的对象 来进行修改。

3.2  classnames优化类名控制

classnames是一个简单的JS库,可以非常方便的 通过条件动态控制class类名的显示。

 

现在的问题:字符串的拼接方式不够直观,也容易出错。
3.3 受控表单绑定

 概念:使用React组件的状态(useState)控制表单的状态。

 1. 准备一个React状态值

const [value, setValue] = useState('')
2. 通过value属性绑定状态,通过onChange属性绑定状态同步的函数
// 通过value属性绑定react状态
// 绑定onChange事件,通过事件参数e拿到输入框最新的值,反向修改到react状态
<input
    type="text"
    value={value}
    onChange={(e) => setValue(e.target.value)}

/>
3.4  获取DOM

在React组件中获取 / 操作DOM,需要使用useRef React Hook钩子函数,分为两步:

1. 使用useRef创建 ref 对象,并与 JSX 绑定

2. 在DOM可用时,通过 inputRef.current 拿到 DOM 对象

四、代码实现

4.1 逻辑渲染层

import { useRef, useState } from "react";
import './App.scss'
import avatar from './image/bozai.png'
import dayjs from 'dayjs'
import { v4 as uuidV4 } from 'uuid'
import _ from 'lodash'
import classNames from 'classnames'
function App() {
  // 当前登录用户信息
  const user = {
    // 用户id
    uid: '30009257',
    // 用户头像
    avatar,
    // 用户昵称
    uname: '嘟嘟嘟',
  }
  // 评论列表数据
  const defaultList = [
    {
      // 评论id
      rpid: 3,
      // 用户信息
      user: {
        uid: '13258165',
        avatar: require('./image/panda.jpg'),
        uname: '周杰伦',
      },
      // 评论内容
      content: '哎哟,不错哦',
      // 评论时间
      ctime: '10-18 08:15',
      like: 88,
    },
    {
      rpid: 2,
      user: {
        uid: '36080105',
        avatar: require('./image/panda.jpg'),
        uname: '许嵩',
      },
      content: '我寻你千百度 日出到迟暮',
      ctime: '11-13 11:29',
      like: 88,
    },
    {
      rpid: 1,
      user: {
        uid: '30009257',
        avatar,
        uname: '黑马前端',
      },
      content: '学前端就来黑马',
      ctime: '10-19 09:00',
      like: 66,
    },
  ]
  // 导航 Tab 数组
  const tabs = [
    { type: 'hot', text: '最热' },
    { type: 'time', text: '最新' },
  ]
  // 最初的时候先按照最热进行倒叙排列
  const [remarkList, setRemark] = useState(_.orderBy(defaultList, 'hot', 'desc'))
  const [content, setContent] = useState('')
  const inputRef = useRef(null)
  // 发布评论
  function handleSubmit() {
    // 阻止提交空数据
    if (!content) return alert('请输入评论内容')
    setRemark([
      {
        rpid: uuidV4(),//随机id
        user,
        content,
        ctime: dayjs(new Date()).format('MM-DD HH:mm'),
        like: 0,
      },
      ...remarkList
    ])
    setContent('')//清空输入框中的内容
    inputRef.current?.focus()//重新聚焦
  }
  // 删除评论
  function handleDel(item) {
    setRemark(remarkList.filter(v => v.rpid !== item.rpid))
  }
  const [type, setType] = useState('hot')
  /**
   * tab切换
   *  1.点击谁就把谁的type记录下来
   *  2.通过记录的type和每一项遍历时的type做匹配,控制激活类名的显示
   */
  function handleTabChange(type) {
    setType(type)
    if (type === 'time') {
      // 按照时间倒序
      setRemark(_.orderBy(remarkList, 'ctime', 'desc'))
    } else {
      // 按照点赞数倒序
      setRemark(_.orderBy(remarkList, 'like', 'desc'))
    }

  }
  return (
    <div className="App">
      <div className="top">
        <div className="left">
          <span className="l-title">评论</span>
          <span>{remarkList.length}</span>
        </div>
        <div className="right">
          {/* 高亮类名 */}
          {/* 
            classnames优化类名控制
            classnames是一个简单的JS库,可以非常方便的通过条件动态控制class类型的显示
          */}
          {tabs.map((item) => {
            return (
              <span
                className={classNames('nav-item', { active: type === item.type })}
                key={item.type}
                onClick={() => handleTabChange(item.type)}>{item.text}</span>
            )
          })}
        </div>
      </div>
      <div className="push">
        <img className="avatar" src={user.avatar} />
        <textarea className="textarea" placeholder="发一条友善的评论" ref={inputRef} value={content} onChange={(e) => setContent(e.target.value)}></textarea>
        <button className="pushBtn" onClick={handleSubmit}>发布</button>
      </div>
      <div className="main">
        {/* 评论项 */}
        {remarkList.map((item) => {
          return (
            <div className="m-item" key={item.rpid}>
              <img className="avatar" src={item.user.avatar} />
              <div className="mi-right">
                <div>{item.user.uname}</div>
                <div className="text">{item.content}</div>
                <div >
                  <span>{item.ctime}</span>
                  <span className="like">点赞数:{item.like}</span>
                  <span onClick={() => handleDel(item)}>删除</span>
                </div>
              </div>
            </div>
          )
        })}
      </div>
    </div>
  );
}

export default App;

4.2 样式层

.App{
  margin: 10px;
}
.top{
  margin-bottom: 20px;
  display: flex;
  align-items: baseline;
  font-size: 15px;
  color: #999;
.left{
  margin-right: 50px;
.l-title{
  font-size: 20px;
  font-weight: 700;
  color: #000;
  margin-right: 5px;
}
}
.right{
  display: flex;
  flex-direction: row;
  align-items: center;
  .nav-item {
    cursor: pointer;
  
    &:hover {
      color: #00aeec;
    }
  
    &:last-child::after {
      display: none;
    }
    &::after {
      content: ' ';
      display: inline-block;
      height: 10px;
      width: 1px;
      margin: -1px 12px;
      background-color: #9499a0;
    }
  }
  .nav-item.active{
    color: #000;
  }
}
}
.push{
  margin-bottom: 20px;
  display: flex;
.textarea{
  margin: 0 10px;
  padding: 10px;
  flex: 1;
  min-height: 30px;
  max-height: 100px;
  border-radius: 10px;
  border: none;
  outline: none;
  background-color: #ebebeb;
}
.pushBtn{
  width: 100px;
  height: 50px;
  font-size: 16px;
  color: #fff;
  border: none;
  border-radius: 5px;
  background-color: rgba(0,174,236,0.5);
  &:hover{
    background-color: rgba(0,174,236);
  }
}
}
.avatar{
  width: 40px;
  height: 40px;
  border-radius: 50%;
}

.m-item{
  display: flex;
  margin-bottom: 10px;
.mi-right{
  padding-bottom: 10px;
  margin-left: 10px;
  flex: 1;
  font-size: 14px;
  color: #999;
  border-bottom: 1px solid #e4e3e3;
}
.text{
  margin: 10px 0;
  color: #000;
}
.like{
  margin: 0 20px;
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/910155.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SSL证书申请终极指南

SSL验证是确认网站或服务器提供的SSL 证书的真实性和有效性的过程。 SSL证书验证是确认网站或服务器提供的SSL证书的真实性和有效性的过程。SSL证书是用于在客户端&#xff08;例如Web浏览器&#xff09;和服务器之间建立安全连接的数字证书。它们对于确保通过互联网传输的数据…

处理PhotoShopCS5和CS6界面字体太小

处理PhotoShop CS6界面字体太小 背景&#xff1a;安装PhotoShop CS6后发现无法调大字体大小&#xff0c;特别是我的笔记本14寸的&#xff0c;显示的字体小到离谱。 百度好多什么降低该电脑分辨率&#xff0c;更改电脑的显示图标大小&#xff0c;或者PS里的首选项中的界面设置。…

python中t是什么意思

python中t是什么意思&#xff1f; python中t指的是“\r”&#xff1a;回车符&#xff0c;返回到这一行的开头&#xff0c;return的意思。 其他相关&#xff1a; \n&#xff1a;换行符&#xff0c;到下一行的同一位置&#xff0c;纵坐标相同&#xff0c;new line的意思。 \t…

python项目实战---使用图形化界面下载音乐

音乐下载 设计思路&#xff1a; 设计界面编写爬虫代码绑定爬虫打包exe文件 这个是最终的设计成果&#xff0c;所有的下载歌曲都在“下载mp3”文件夹里面 完整代码 逻辑代码 import os.path import reimport requests from PyQt5.QtWidgets import QApplication,QWidget,QM…

Linux(inode + 软硬链接 图片+大白话)

后面也会持续更新&#xff0c;学到新东西会在其中补充。 建议按顺序食用&#xff0c;欢迎批评或者交流&#xff01; 缺什么东西欢迎评论&#xff01;我都会及时修改的&#xff01; 在这里真的很感谢这位老师的教学视频让迷茫的我找到了很好的学习视频 王晓春老师的个人空间…

从0开始深度学习(24)——填充和步幅

1 填充 在上一节中&#xff0c;我们的卷积步骤如下&#xff1a; 可以发现输入是 3 3 3\times3 33&#xff0c;输出是 2 2 2\times2 22&#xff0c;这样可能会导致原始图像的边界丢失了许多有用信息&#xff0c;如果应用多层卷积核&#xff0c;累积丢失的像素就更多了&#…

C++:模拟实现STL的vector

目录 一.vector类 1.vector类的构造及析构 2.定义迭代器 3.size()和capacity() 4.operator [ ] 5.resize()和reserve() 6.插入和删除 二.整体代码 1.vector.h 2.vector.cpp 上一节中了解了vector中部分接口的使用,在这里我们模拟实现vector,为了避免与库中的起冲突,…

砥砺十年风雨路,向新而行创新程丨怿星科技十周年庆典回顾

10月24日&#xff0c;是一年中的第256天&#xff0c;也是程序员节&#xff0c;同时也是怿星的生日。2014年到2024年&#xff0c;年华似水匆匆一瞥&#xff0c;多少岁月轻描淡写&#xff0c;怿星人欢聚一堂&#xff0c;共同为怿星科技的十周年庆生&#xff01; 01.回忆往昔&…

Chrome与火狐哪个浏览器的移动版本更流畅

在当今的数字化时代&#xff0c;移动设备已经成为我们生活中不可或缺的一部分。而浏览器作为我们访问互联网的重要工具&#xff0c;其性能和用户体验直接影响到我们的使用感受。本文将对比Chrome和火狐&#xff08;Firefox&#xff09;两款主流浏览器的移动版本&#xff0c;探讨…

算法练习:1004. 最大连续1的个数 III

题目链接&#xff1a;1004. 最大连续1的个数 III。 题目要求&#xff0c;给定一个数组&#xff0c;这个数组里面只有0或1&#xff0c;然后计算有多少个连续的1的最大长度&#xff0c;同时给了一个条件就是&#xff0c;可以把k个0变成1&#xff0c;然后来计算长度。 暴力解法&a…

【大数据技术基础 | 实验七】HBase实验:部署HBase

文章目录 一、实验目的二、实验要求三、实验原理四、实验环境五、实验内容和步骤&#xff08;一&#xff09;验证Hadoop和ZooKeeper已启动&#xff08;二&#xff09;修改HBase配置文件&#xff08;三&#xff09;启动并验证HBase 六、实验结果七、实验心得 一、实验目的 掌握…

LLMs之LoLCATs:《LoLCATs: On Low-Rank Linearizing of Large Language Models》翻译与解读

LLMs之LoLCATs&#xff1a;《LoLCATs: On Low-Rank Linearizing of Large Language Models》翻译与解读 导读&#xff1a;这篇论文的核心是提出了一种名为 LoLCATs (Low-rank Linear Conversion via Attention Transfer) 的方法&#xff0c;用于高效地将大型语言模型 (LLM) 线性…

linux命令详解,文件管理类

文件管理 stat 显示文件的详细信息&#xff0c;包括时间戳 stat filenametouch 主要用于更新文件的访问时间和修改时间&#xff08;时间戳&#xff09;。如果指定的文件不存在&#xff0c;touch 命令会创建一个新的空文件。 touch newfile参数 -t 更新文件的修改时间为特…

MySQL的其他函数

数学函数&#xff1a; 1.round 四舍五入 select round(1.45);//不管正负数,先将绝对值round,然后加正负号 select round(1.567,2); //表示小数点保留2位 2.ceil 向上取整 select ceil(-1.3); 3.floor 向下取整 4.truncate 截断 select truncate(1.65,1); // 结果保留小数…

@Excel若依导出异常/解决BusinessBaseEntity里面的字段不支持导出

今天发现所有实体类继承BusinessBaseEntity里面的这些通用字段不支持导出&#xff0c;debug时发现是这样&#xff1a; 导出效果 这里我把能查到的方法都汇总了&#xff0c;如果你也遇到这个异常&#xff0c;可以去逐步排查 1.先看库里有没有数据 2.看字段名是否对齐 3.所需要…

云数据中心基础环境-详细设计方案(364页WORD)

文档介绍&#xff1a; 随着云计算技术的飞速发展&#xff0c;云数据中心已成为企业数字化转型的核心基础设施&#xff0c;承载着数据存储、处理、分析和应用的重任。本设计方案旨在构建一个高性能、高可用、高安全性的云数据中心基础环境&#xff0c;以满足企业日益增长的业务需…

在 CSS 中,gap 是 布局容器(flex 或 grid)的属性。它用于设置容器内子元素之间的间距。

在 CSS 中&#xff0c;gap 是 布局容器&#xff08;flex 或 grid&#xff09;的属性。它用于设置容器内子元素之间的间距。以下是 gap 属性在不同布局中的应用&#xff1a; 1. 在 CSS Grid 布局中 gap 定义了网格行和列之间的间距。可以分别使用 row-gap 和 column-gap 设置行…

Python练习9

Python日常练习 题目&#xff1a; 编程序计算形式如&#xff1a;sumaaaaaaaaaa…aaa…aaa的表达式的值。 说明&#xff1a; 补充完整函数fun()&#xff0c;其中a为小于10的自然数&#xff0c;n为项数&#xff0c;给定 变量result作为函数返回值&#xff0c;变量ts作为…

浙江深大智能科技有限公司管控平台服务端存在任意文件上传漏洞

漏洞描述 智游宝是连接景区与分销商(OTA、旅行社)的公正、权威、可信的第三方服务平台。作为国内智慧景区第三方技术服务支撑平台&#xff0c;智游宝为景区提供了可控制分销商的管理环境&#xff0c;安全、便捷、高效地实现了电子票的生产、发送、检票、退换票以及票款回收等技…

Pr 视频过渡:沉浸式视频 - VR 默比乌斯缩放

效果面板/视频过渡/沉浸式视频/VR 默比乌斯缩放 Video Transitions/Immersive Video/VR Mobius Zoom VR 默比乌斯缩放 VR Mobius Zoom用于 VR 视频中的缩放式场景切换&#xff0c;通过缩小或放大的渐变效果在两个场景之间平滑过渡。 自动 VR 属性 Auto VR Properties 默认勾选…