React删除评论逻辑:1、客户端立即更新UI(乐观更新)2、后台调用删除评论API

发布于:2025-06-14 ⋅ 阅读:(23) ⋅ 点赞:(0)

https://gitee.com/arnold_s/my-learning-test/blob/master/20250520_reactTest/01_easyReact/react-basic_pro/src/App_Day1-14.%E8%AF%84%E8%AE%BA%E6%A1%88%E4%BE%8B-%E5%88%A0%E9%99%A4%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0.js

原代码

import './App.scss'
import avatar from './images/bozai.png'
import { useState } from 'react'

/**
 * 评论列表的渲染和操作
 *
 * 1. 根据状态渲染评论列表
 * 2. 删除评论
 */

// 评论列表数据
const defaultList = [
	{
		// 评论id
		rpid: 3,
		// 用户信息
		user: {
			uid: '13258165',
			avatar: '',
			uname: '周杰伦',
		},
		// 评论内容
		content: '哎哟,不错哦',
		// 评论时间
		ctime: '10-18 08:15',
		like: 88,
	},
	{
		rpid: 2,
		user: {
			uid: '36080105',
			avatar: '',
			uname: '许嵩',
		},
		content: '我寻你千百度 日出到迟暮',
		ctime: '11-13 11:29',
		like: 88,
	},
	{
		rpid: 1,
		user: {
			uid: '30009257',
			avatar,
			uname: '黑马前端',
		},
		content: '学前端就来黑马',
		ctime: '10-19 09:00',
		like: 66,
	},
]
// 当前登录用户信息
const user = {
	// 用户id
	uid: '30009257',
	// 用户头像
	avatar,
	// 用户昵称
	uname: '黑马前端',
}

/**
 * 导航 Tab 的渲染和操作
 *
 * 1. 渲染导航 Tab 和高亮
 * 2. 评论列表排序
 *  最热 => 喜欢数量降序
 *  最新 => 创建时间降序
 */

// 导航 Tab 数组
const tabs = [
	{ type: 'hot', text: '最热' },
	{ type: 'time', text: '最新' },
]


// 2、使用useEffect 监听评论列表数据的变化
// 3、使用useRef 管理评论列表的滚动位置
// 4、使用useCallback 管理评论列表的滚动位置
// 5、使用useMemo 管理评论列表的滚动位置
// 6、使用useContext 管理评论列表的滚动位置
// 7、使用useReducer 管理评论列表的滚动位置

const App = () => {
	// 渲染评论列表
	// 1、使用useState 管理评论列表数据
	const [commentList, setCommentList] = useState(defaultList)


	// 删除评论
	const handleDelete = (rpid) => {
		// 过滤掉要删除的评论
		// 使用filter 方法过滤掉要删除的评论
		// filter 方法会返回一个新数组,新数组中不包含要删除的评论
		// 新数组中的元素是原数组中除了要删除的评论以外的所有元素
		// 使用setCommentList 方法更新评论列表数据
		setCommentList(commentList.filter(item => item.rpid !== rpid))
	}

	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 */}
						<span className='nav-item'>最新</span>
						<span className='nav-item'>最热</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="发一条友善的评论"
						/>
						{/* 发布按钮 */}
						<div className="reply-box-send">
							<div className="send-text">发布</div>
						</div>
					</div>
				</div>
				{/* 评论列表 */}
				<div className="reply-list">
					{/* 评论项 */}
					{commentList.map(item => (
						<div key={item.rpid} className="reply-item">
							{/* 头像 */}
							<div className="root-reply-avatar">
								<div className="bili-avatar">
									<img
										className="bili-avatar-img"
										alt=""
										src={item.user.avatar
											|| 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f464.png'}
									/>
								</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>
										{/* 删除按钮 */}
										{/* 判断是否是当前登录用户 */}
										{item.user.uid === user.uid && (
											<span className="delete-btn" onClick={() => handleDelete(item.rpid)}>
												删除
											</span>
										)}

									</div>
								</div>
							</div>
						</div>
					))}

				</div>
			</div>
		</div>
	)
}

export default App

问:为什么删除评论是客户端调用过滤评论,而不是请求数据库删除,然后再从数据库获取最新评论列表呢?

这是一个很好的问题!让我来解释一下这两种删除评论的实现方式:

当前代码的实现方式(本地状态管理)

const handleDelete = (rpid) => {
    setCommentList(commentList.filter(item => item.rpid !== rpid))
}

这种方式是客户端状态管理,直接修改本地的状态数据。

两种方式的对比

1. 本地过滤方式(当前代码)

优点:

  • 🚀 响应速度快 - 无需等待网络请求
  • 💰 节省服务器资源 - 减少API调用次数
  • 🎯 用户体验好 - 立即反馈,无加载等待

缺点:

  • ⚠️ 数据不同步 - 只是前端展示删除,后端数据库还存在
  • 🔄 页面刷新后恢复 - 数据会重新从服务器加载

2. API删除方式(你提到的方式)

const handleDelete = async (rpid) => {
    try {
        // 1. 调用删除API
        await deleteCommentAPI(rpid)
        // 2. 重新获取评论列表
        const newList = await getCommentsAPI()
        setCommentList(newList)
    } catch (error) {
        console.error('删除失败:', error)
    }
}

优点:

  • 数据真实同步 - 前后端数据一致
  • 🔒 数据持久化 - 删除后数据真正消失

缺点:

  • 🐌 响应较慢 - 需要等待两次网络请求
  • 💸 服务器负担重 - 每次操作都需要API调用

实际项目中的最佳实践

在真实项目中,通常会采用混合方式

const handleDelete = async (rpid) => {
    try {
        // 1. 立即更新UI(乐观更新)
        setCommentList(commentList.filter(item => item.rpid !== rpid))
        
        // 2. 后台调用删除API
        await deleteCommentAPI(rpid)
        
        // 3. 如果删除失败,恢复UI状态
    } catch (error) {
        // 恢复删除的评论
        setCommentList(originalList)
        alert('删除失败,请重试')
    }
}

总结

当前代码可能是:

  1. 学习Demo - 专注于React状态管理学习
  2. 原型阶段 - 还未连接后端服务
  3. 离线应用 - 数据只在本地存储

在实际生产环境中,建议使用乐观更新的方式,既保证用户体验,又确保数据一致性!