React实现B站评论Demo

该Demo涉及的技术点

  • useState函数(数据驱动视图)
  • 子组件的封装
  • 条件判断
  • 回调函数的封装

1、评论数据

json 复制代码
{
  "list": [
    {
      "rpid": 3,
      "user": {
        "uid": "13258165",
        "avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
        "uname": "周杰伦"
      },
      "content": "哎哟,不错哦",
      "ctime": "10-18 08: 15",
      "like": 126
    },
    {
      "rpid": 2,
      "user": {
        "uid": "36080105",
        "avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
        "uname": "许嵩"
      },
      "content": "我寻你千百度 日出到迟暮",
      "ctime": "11-13 11: 29",
      "like": 88
    },
    {
      "rpid": 1,
      "user": {
        "uid": "30009257",
        "avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
        "uname": "前端"
      },
      "content": "前端真好玩",
      "ctime": "10-19 09: 00",
      "like": 66
    }
  ]
}

2、具体实现

  • 获取json中的list数据 (读取本地文件 / 读取接口)
js 复制代码
const JsonReader = () => {
  // 定义一个数组状态来存储 JSON 数据
  const [ commentList, setCommentList ] = useState([]);

  // 使用 useEffect 钩子来加载 JSON 文件
  useEffect(() => {
    // 使用 fetch API 读取 JSON 文件 , 这里需要注意,json文件读取的根路径是public路径
    fetch('_db.json')
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json(); // 解析 JSON 数据
      })
      .then(data => {
        // console.log(data)
        // 将解析后的 JSON 数据存储到 list 数组中
        setCommentList(_.orderBy(data.list, 'like', 'desc'));
      })
      .catch(error => {
        console.error('There has been a problem with your fetch operation:', error);
      });
  }, []); // 空依赖数组表示只在组件挂载时执行一次

  return {
    commentList, 
    setCommentList
  }
}

// 封装请求数据的Hook
function useGetList () {
  // 获取接口数据渲染
  const [commentList, setCommentList] = useState([])

  useEffect(() => {
    // 请求数据
    async function getList () {
      // axios请求数据
      const res = await axios.get(' http://localhost:3004/list')
      setCommentList(res.data)
    }
    getList()
  }, [])

  return {
    commentList,
    setCommentList
  }
}
  • 封装用户信息、导航栏
javascript 复制代码
// 当前登录用户信息
const user = {
  // 用户id
  uid: '30009257',
  // 用户头像
  avatar,
  // 用户昵称
  uname: '黑马前端',
}
// 导航 Tab 数组
const tabs = [
  { type: 'hot', text: '最热' },
  { type: 'time', text: '最新' },
]
  • 封装单个评论组件
javascript 复制代码
// 封装Item组件
function Item ({ item, onDel }) {
  return (
    <div className="reply-item">
      {/* 头像 */}
      <div className="root-reply-avatar">
        <div className="bili-avatar">
          <img
            className="bili-avatar-img"
            alt=""
            src={item.user.avatar}
          />
        </div>
      </div>

      <div className="content-wrap">
        {/* 用户名 */}
        <div className="user-info">
          <div className="user-name">{item.user.uname}</div>
        </div>
        {/* 评论内容 */}
        <div className="root-reply">
          <span className="reply-content">{item.content}</span>
          <div className="reply-info">
            {/* 评论时间 */}
            <span className="reply-time">{item.ctime}</span>
            {/* 评论数量 */}
            <span className="reply-time">点赞数:{item.like}</span>
            {/* 条件:user.id === item.user.id */}
            {user.uid === item.user.uid &&
              <span className="delete-btn" onClick={() => onDel(item.rpid)}>
                删除
              </span>}
          </div>
        </div>
      </div>
    </div>
  )
}
  • 最终实现
javascript 复制代码
const App = () => {
  // 渲染评论列表
  // 1. 使用useState维护list
  // const [commentList, setCommentList] = useState(_.orderBy(List, 'like', 'desc'))
  // const { commentList, setCommentList } = useGetList()
  const { commentList, setCommentList } = JsonReader()


  // 删除功能
  const handleDel = (id) => {
    console.log(id)
    // 对commentList做过滤处理
    setCommentList(commentList.filter(item => item.rpid !== id))
  }

  // tab切换功能
  // 1. 点击谁就把谁的type记录下来
  // 2. 通过记录的type和每一项遍历时的type做匹配 控制激活类名的显示
  const [type, setType] = useState('hot')
  const handleTabChange = (type) => {
    console.log(type)
    setType(type)
    // 基于列表的排序
    if (type === 'hot') {
      // 根据点赞数量排序 
      // lodash
      setCommentList(_.orderBy(commentList, 'like', 'desc'))
    } else {
      // 根据创建时间排序
      setCommentList(_.orderBy(commentList, 'ctime', 'desc'))
    }
  }

  // 发表评论
  const [content, setContent] = useState('')
  const inputRef = useRef(null)
  const handlPublish = () => {
    setCommentList([
      ...commentList,
      {
        rpid: uuidV4(), // 随机id
        user: {
          uid: '30009257',
          avatar,
          uname: '黑马前端',
        },
        content: content,
        ctime: dayjs(new Date()).format('MM-DD hh:mm'), // 格式化 月-日 时:分
        like: 66,
      }
    ])
    // 1. 清空输入框的内容
    setContent('')
    // 2. 重新聚焦  dom(useRef) - focus
    inputRef.current.focus()
  }

  return (
    <div className="app">
      {/* 导航 Tab */}
      <div className="reply-navigation">
        <ul className="nav-bar">
          <li className="nav-title">
            <span className="nav-title-text">评论</span>
            {/* 评论数量 */}
            <span className="total-reply">{10}</span>
          </li>
          <li className="nav-sort">
            {/* 高亮类名: active */}
            {tabs.map(item =>
              <span
                key={item.type}
                onClick={() => handleTabChange(item.type)}
                className={classNames('nav-item', { active: type === item.type })}>
                {item.text}
              </span>)}
          </li>
        </ul>
      </div>

      <div className="reply-wrap">
        {/* 发表评论 */}
        <div className="box-normal">
          {/* 当前用户头像 */}
          <div className="reply-box-avatar">
            <div className="bili-avatar">
              <img className="bili-avatar-img" src={avatar} alt="用户头像" />
            </div>
          </div>
          <div className="reply-box-wrap">
            {/* 评论框 */}
            <textarea
              className="reply-box-textarea"
              placeholder="发一条友善的评论"
              ref={inputRef}
              value={content}
              onChange={(e) => setContent(e.target.value)}
            />
            {/* 发布按钮 */}
            <div className="reply-box-send">
              <div className="send-text" onClick={handlPublish}>发布</div>
            </div>
          </div>
        </div>
        {/* 评论列表 */}
        <div className="reply-list">
          {/* 评论项 */}
          {commentList.map(item => <Item key={item.id} item={item} onDel={handleDel} />)}
        </div>
      </div>
    </div>
  )
}

最终效果:

相关推荐
鬼手点金6 小时前
nanoGPT/nanochat 能做出 DeepSeek/Qwen 这样的大模型吗?
服务器·前端·javascript·bug·openclaw
蛋蛋的就会顺顺的6 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
程序员Sunday6 小时前
TypeScript 的 any、unknown、never 有什么区别?
前端·面试·typescript
朱涛的自习室6 小时前
70 美金的 Beyond Compare,我让 Gemini 4 和 DeepSeek 造个开源免费版(Day 2)
android·前端·人工智能
码艺-Alimjan7 小时前
Tauri 源码备份 · 打包 & 解压 总结,源码收集之路
前端·javascript·vue.js
小马不起床7 小时前
零基础玩转 FastAPI_11:前端后台管理页面
前端·状态模式·fastapi
子非鱼a8 小时前
【文件上传】一个普普通通的edu16
前端
美狐美颜SDK开放平台9 小时前
直播APP如何实现美白滤镜功能?视频美颜SDK技术开发方案详解
前端·数据库·人工智能·音视频·美颜sdk·直播美颜sdk·视频美颜sdk
pride.li9 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
屏什么录10 小时前
电脑如何录屏幕视频和声音?从工具到代码,7 种方案帮你彻底搞定
前端·音视频开发