VUE3:封装一个评论回复组件

之前用React封装的评论回复组件,里面有三个主要部分:CommentComponent作为主组件,CommentItem处理单个评论项,CommentInput负责输入框。现在需要将这些转换为Vue3的组件。

Vue3和React在状态管理上有所不同,Vue3使用ref和reactive来管理响应式数据,而React用useState。我需要将React的useState转换为Vue3的ref。比如activeReplyId和comments在React中是使用useState,在Vue3中应该用ref来声明。

接下来,处理组件结构。在Vue3中,通常使用单文件组件(.vue文件),所以需要将每个部分拆分成单独的组件。可能需要将CommentItem和CommentInput作为子组件,或者作为主组件的一部分。不过根据用户的需求,可能需要保持组件的可复用性,所以拆分成单独的组件是合理的。

然后,Props的处理。在React中使用PropTypes,而Vue3中使用defineProps。需要将原来的PropTypes转换为Vue3的props定义。例如,initialComments和currentUser作为props传入,需要在子组件中正确声明。

递归渲染在Vue中如何处理?React中CommentItem递归调用自身,Vue中也需要类似的方式。在Vue的单文件组件中,组件可以在模板中直接引用自己,但需要注意组件名称是否正确注册。可能需要使用name属性来允许递归调用。

另外,事件的处理。在React中,通过props传递回调函数,比如onReply、setActiveReplyId。在Vue中,子组件通过$emit触发事件,父组件监听这些事件。所以需要将原来的回调函数转换为Vue的事件发射机制。例如,当用户点击回复按钮时,CommentItem应该emit一个事件,父组件监听到后更新activeReplyId。

状态提升的问题。在React中,activeReplyId和comments状态提升到主组件,Vue中同样需要将这些状态放在父组件中,通过props传递给子组件,或者使用provide/inject,但props更直接。这里应该保持状态在主组件,通过props传递下去。

输入框组件CommentInput需要处理提交和取消。在React中,通过父组件传递onSubmit和onCancel。在Vue中,子组件应该emit事件,比如submit和cancel,父组件监听并处理。

样式部分,用户之前提供了CSS,现在需要将这些样式转换到Vue的单文件组件中的style标签内。注意scoped属性,避免样式污染。或者用户可能使用CSS预处理器,但用户没有特别说明,所以保持普通CSS。

然后,考虑TypeScript的支持。用户没有明确提到,但Vue3支持TS,所以如果需要,可以添加类型定义。不过用户可能只需要基本的JS实现,所以暂时使用JS。

接下来,实现递归评论项。在Vue中,组件需要能够引用自身,所以需要在组件内部使用name属性,比如name: ‘CommentItem’,然后在模板中使用来递归渲染子评论。

测试一下功能是否完整:多级回复、最大深度限制、显示回复对象、时间生成、当前用户处理。确保在Vue中响应式数据更新正确,比如添加回复后,comments数组的更新会触发视图重新渲染。

最后,示例数据和用法。在Vue中,父组件传入initial-comments和current-user作为props,确保props命名在Vue中使用kebab-case。比如initialComments在props中定义为initial-comments。

可能遇到的陷阱:递归组件的注册,确保组件可以在模板中正确引用自己。另外,在Vue中,直接修改props是不允许的,所以所有状态变更都应该通过父组件的事件来触发,保持单向数据流。

总结步骤:转换React组件结构到Vue3的单文件组件,处理响应式状态,转换事件处理机制,确保递归渲染正常工作,调整样式,提供使用示例和props验证。

以下是基于 Vue 3 封装的评论回复组件实现(使用 Composition API + <script setup> 语法):

<!-- CommentComponent.vue -->
<template><div class="comment-container"><!-- 主评论输入框 --><CommentInput @submit="addComment" /><!-- 评论列表 --><div class="comments-list"><CommentItemv-for="comment in comments":key="comment.id":comment="comment":depth="0":current-user="currentUser":active-reply-id="activeReplyId"@reply="handleReply"@set-active-reply="setActiveReplyId"/></div></div>
</template><script setup>
import { ref } from 'vue'
import CommentItem from './CommentItem.vue'
import CommentInput from './CommentInput.vue'const props = defineProps({initialComments: {type: Array,default: () => []},currentUser: {type: String,default: '匿名用户'}
})const comments = ref([...props.initialComments])
const activeReplyId = ref(null)// 添加新评论/回复
const addComment = (content, parentId = null) => {const newComment = {id: Date.now(),author: props.currentUser,content,time: new Date().toLocaleString(),replies: []}if (!parentId) {comments.value.push(newComment)} else {const updateComments = (items) => items.map(item => {if (item.id === parentId) {return {...item,replies: [...item.replies, newComment]}}return {...item,replies: updateComments(item.replies)}})comments.value = updateComments(comments.value)}
}// 处理回复事件
const handleReply = ({ parentId, content }) => {addComment(content, parentId)activeReplyId.value = null
}const setActiveReplyId = (id) => {activeReplyId.value = id
}
</script><style scoped>
/* 样式与 React 版本保持一致 */
.comment-container {max-width: 800px;margin: 0 auto;padding: 20px;
}
</style>
<!-- CommentItem.vue -->
<template><div class="comment-item":style="{ marginLeft: depth * 32 + 'px' }"><div class="comment-header"><span class="comment-author">{{ comment.author }}</span><span v-if="parentAuthor" class="reply-to">回复 @{{ parentAuthor }}</span><span class="comment-time">{{ comment.time }}</span></div><div class="comment-content">{{ comment.content }}</div><button v-if="canReply"class="reply-btn"@click="setActiveReply(comment.id)">回复</button><!-- 回复输入框 --><CommentInputv-if="activeReplyId === comment.id"@submit="handleSubmit"@cancel="setActiveReply(null)"/><!-- 递归渲染子评论 --><CommentItemv-for="reply in comment.replies":key="reply.id":comment="reply":depth="depth + 1":current-user="currentUser":active-reply-id="activeReplyId":parent-author="comment.author"@reply="$emit('reply', $event)"@set-active-reply="$emit('set-active-reply', $event)"/></div>
</template><script setup>
import { computed } from 'vue'
import CommentInput from './CommentInput.vue'const props = defineProps({comment: Object,depth: Number,currentUser: String,activeReplyId: Number,parentAuthor: String
})const emit = defineEmits(['reply', 'set-active-reply'])const maxDepth = 4
const canReply = computed(() => props.depth < maxDepth)const handleSubmit = (content) => {emit('reply', { parentId: props.comment.id,content })
}const setActiveReply = (id) => {emit('set-active-reply', id)
}
</script><style scoped>
/* 保持与 React 版本相同的样式 */
.comment-item {margin: 16px 0;padding: 12px;border: 1px solid #eee;border-radius: 4px;
}.comment-header {display: flex;align-items: center;margin-bottom: 8px;color: #666;
}.comment-author {font-weight: bold;margin-right: 8px;
}.reply-to {color: #999;margin-right: 8px;
}.comment-time {font-size: 0.9em;
}.reply-btn {background: none;border: none;color: #1890ff;cursor: pointer;padding: 4px 8px;
}
</style>
<!-- CommentInput.vue -->
<template><div class="comment-input-container"><textareav-model="content"placeholder="输入评论内容..."rows="3"></textarea><div class="input-buttons"><button @click="handleSubmit">提交</button><button @click="$emit('cancel')">取消</button></div></div>
</template><script setup>
import { ref } from 'vue'const content = ref('')
const emit = defineEmits(['submit', 'cancel'])const handleSubmit = () => {if (content.value.trim()) {emit('submit', content.value)content.value = ''}
}
</script><style scoped>
.comment-input-container {margin: 16px 0;
}.comment-input-container textarea {width: 100%;padding: 8px;margin-bottom: 8px;
}.input-buttons button {margin-right: 8px;padding: 4px 12px;
}
</style>

组件特性说明:

  1. Vue3 实现特点
  • 使用 Composition API + <script setup> 语法
  • 通过 defineProps 定义组件参数
  • 通过 defineEmits 声明自定义事件
  • 响应式系统使用 refcomputed
  • 样式使用 scoped CSS
  1. 使用方式
<template><CommentComponent:initial-comments="comments"current-user="当前用户"/>
</template><script setup>
import CommentComponent from './components/CommentComponent.vue'const comments = [{id: 1,author: '用户A',content: '第一条评论',time: '2023-08-01 10:00',replies: [{id: 2,author: '用户B',content: '第一条回复',time: '2023-08-01 10:05'}]}
]
</script>
  1. 与 React 版本的差异处理
  • 状态管理:用 ref 替代 useState
  • 事件处理:用 emit 替代 props 回调
  • 递归组件:通过组件自身名称实现递归
  • 响应式更新:直接操作数组触发响应式更新
  • 样式处理:使用 scoped CSS 避免样式污染
  1. 扩展建议
<!-- 增加点赞功能示例 -->
<template><button class="like-btn":class="{ liked: comment.liked }"@click="toggleLike">{{ comment.likes || 0 }} 👍</button>
</template><script setup>
// 在 CommentItem 中添加
const toggleLike = () => {context.emit('like', props.comment.id)
}
</script>

这个 Vue3 实现保留了 React 版本的核心功能,同时遵循 Vue 的响应式模式和组件通信方式,更适合 Vue 技术栈项目使用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

制作一款打飞机游戏27:精灵编辑器UI

继续开发我们的编辑器——Sprit Edit。我们已经创建了这个小编辑器&#xff0c;它可以显示很多精灵&#xff08;sprites&#xff09;&#xff0c;并且我们可以遍历所有精灵。这真的很棒&#xff0c;我们可以创建新的精灵&#xff0c;这也不错。但是&#xff0c;唉&#xff0c;我…

k8s(9) — zookeeper集群部署(亲和性、污点与容忍测试)

一、部署思路 1、前期设想 zookeeper集群至少需要运行3个pod集群才能够正常运行&#xff0c;考虑到节点会有故障的风险这个3个pod最好分别运行在&#xff13;个不同的节点上(为了实现这一需要用到亲和性和反亲和性概念)&#xff0c;在部署的时候对zookeeper运行的pod打标签加…

WXT+Vue3+sass+antd+vite搭建项目开发chrome插件

WXTVue3sassantdvite搭建项目开发chrome插件 前言一、初始化项目二、项目配置调整三、options页面配置四、集成antd五、集成sass六、环境配置七、代码注入 vue3&#xff1a;https://cn.vuejs.org/ axios&#xff1a;https://www.axios-http.cn/docs/api_intro antd&#xff1a;…

JSAPI2.4——正则表达式

一、语法 const str 一二三四五六七八九十 //判断内容 const reg /二/ //判断条件 console.log(reg.test(str)); //检查 二、test与exec方法的区别 test方法&#xff1a;用于判断是否符合规则的字符串&#xff0c;返回值是布尔值 exec方法&…

燃气用户检修工考试精选题

燃气用户检修工考试精选题&#xff1a; 我国国家标准规定民用天然气中硫化氢含量最高允许浓度是&#xff08; &#xff09;。 A. 20mg/m B. 15mg/m C. 5mg/m D. 50mg/m 答案&#xff1a;A 城市燃气应具有可以察觉的臭味&#xff0c;当无毒燃气泄漏到空气中&#xff0c;达到爆炸…

【前端】1h 搞定 TypeScript 教程_只说重点

不定期更新&#xff0c;建议关注收藏点赞。 目录 简介使用基本类型、类型推断和类型注解接口、类型别名、联合类型类与继承泛型GenericsReact 与 TS 进阶高级类型装饰器Decorators模块系统TypeScript 编译选项 简介 TypeScript&#xff08;简称 TS&#xff09;是一种由微软开发…

MyBatis 参数绑定

一、MyBatis 参数绑定机制 1.1 核心概念 当 Mapper 接口方法接收多个参数时&#xff0c;MyBatis 提供三种参数绑定方式&#xff1a; 默认参数名&#xff1a;arg0、arg1&#xff08;Java 8&#xff09;或 param1、param2Param 注解&#xff1a;显式指定参数名称POJO/DTO 对象…

【解决方案】Linux解决CUDA安装过程中GCC版本不兼容

Linux解决CUDA安装过程中GCC版本不兼容 目录 问题描述 解决方法 安装后配置 问题描述 Linux环境下安装 CUDA 时&#xff0c;运行sudo sh cuda_10.2.89_440.33.01_linux.run命令出现 “Failed to verify gcc version.” 的报错&#xff0c;提示 GCC 版本不兼容&#xff0c;查…

人工智能数学基础(一):人工智能与数学

在人工智能领域&#xff0c;数学是不可或缺的基石。无论是算法的设计、模型的训练还是结果的评估&#xff0c;都离不开数学的支持。接下来&#xff0c;我将带大家深入了解人工智能数学基础&#xff0c;包括微积分、线性代数、概率论、数理统计和最优化理论&#xff0c;并通过 P…

Shell脚本-嵌套循环应用案例

在Shell脚本编程中&#xff0c;嵌套循环是一种强大的工具&#xff0c;可以用于处理复杂的任务和数据结构。通过在一个循环内部再嵌套另一个循环&#xff0c;我们可以实现对多维数组、矩阵操作、文件处理等多种高级功能。本文将通过几个实际的应用案例来展示如何使用嵌套循环解决…

勘破养生伪常识,开启科学养生新篇

​在养生潮流风起云涌的当下&#xff0c;各种养生观点和方法层出不穷。但其中有不少是缺乏科学依据的伪常识&#xff0c;若不加分辨地盲目跟从&#xff0c;不仅难以实现养生目的&#xff0c;还可能损害健康。因此&#xff0c;勘破这些养生伪常识&#xff0c;是迈向科学养生的关…

Nacos-3.0.0适配PostgreSQL数据库

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

机器学习第三篇 模型评估(交叉验证)

Sklearn:可以做数据预处理、分类、回归、聚类&#xff0c;不能做神经网络。原始的工具包文档&#xff1a;scikit-learn: machine learning in Python — scikit-learn 1.6.1 documentation数据集:使用的是MNIST手写数字识别技术&#xff0c;大小为70000&#xff0c;数据类型为7…

如何在 IntelliJ IDEA 中编写 Speak 程序

在当今数字化时代&#xff0c;语音交互技术越来越受到开发者的关注。如果你想在 IntelliJ IDEA&#xff08;一个强大的集成开发环境&#xff09;中编写一个语音交互&#xff08;Speak&#xff09;程序&#xff0c;那么本文将为你提供详细的步骤和指南。 一、环境准备 在开始编…

AI大模型学习十四、白嫖腾讯Cloud Studio AI环境 通过Ollama+Dify+DeepSeek构建生成式 AI 应用-接入DeepSeek大模型

一、说明 需要阅读 AI大模型学习十三、白嫖腾讯Cloud Studio AI环境 通过OllamaDifyDeepSeek构建生成式 AI 应用-安装-CSDN博客https://blog.csdn.net/jiangkp/article/details/147580344?spm1011.2415.3001.5331 我们今天干点啥呢&#xff0c;跟着官网走 模型类型 在 Dify…

《Astro 3.0岛屿架构让内容网站“脱胎换骨”》

内容优先的网站越来越成为主流。无论是新闻资讯、知识博客&#xff0c;还是电商产品展示&#xff0c;用户都希望能快速获取所需内容&#xff0c;这对网站的性能和体验提出了极高要求。而Astro 3.0的岛屿架构&#xff0c;就像是为内容优先网站量身定制的一把神奇钥匙&#xff0c…

在 UniApp 中实现 App 与 H5 页面的跳转及通信

在移动应用开发中&#xff0c;内嵌 H5 页面或与外部网页交互是常见需求。UniApp 作为跨平台框架&#xff0c;提供了灵活的方式实现 App 与 H5 的跳转和双向通信。本文将详细讲解实现方法&#xff0c;并提供可直接复用的代码示例。 文章目录 一、 App 内嵌 H5 页面&#xff08;使…

springboot 实现敏感信息脱敏

记录于2025年4月28号晚上--梧州少帅 1. 定义枚举类&#xff1a; public enum DesensitizeType {NAME, EMAIL } 2. 创建自定义注解&#xff1a; 用于标记需要脱敏的字段及其类型。 Retention(RetentionPolicy.RUNTIME) JacksonAnnotationsInside JsonSerialize(using Desen…

SNMP协议之详解(Detailed Explanation of SNMP Protocol)

SNMP协议之详解 一、前言 SNMP&#xff0c;被形象地喻为网络世界大的工具箱&#xff0c;使他们能的“智慧守护者”&#xff0c;它为网络管理员装备了一套功能强够实现对网络设备状态的实时监控、性能数据的全面收集、远程配置的灵活管理以及故障事件的即时响应。借助SNMP&…

SpeedyAutoLoot

SpeedyAutoLoot自动拾取插件 SpeedyAutoLoot.lua local AutoLoot CreateFrame(Frame)SpeedyAutoLootDB SpeedyAutoLootDB or {} SpeedyAutoLootDB.global SpeedyAutoLootDB.global or {}local BACKPACK_CONTAINER BACKPACK_CONTAINER local LOOT_SLOT_CURRENCY LOOT_SLOT…