跳到主要内容位置

Vue 3.0 留言板项目添加事件交互

给 Vue 项目添加交互,主要是通过事件来响应用户的操作,还有使用生命周期回调,在组件加载或卸载的时候,做一些数据的处理。这期视频我们就给之前的留言板项目,添加一些事件交互。 大体的交互流程是这样的:

  1. 用户先在输入框中输入留言,点击提交按钮发表留言。
  2. 在表单提交的事件处理函数中,组装一个新的留言对象,并追加到留言列表数组中。
  3. 清空输入框。
  4. 对于回复留言,当用户点击回复按钮的时候,在它下面自动显示留言表单,之后的操作和发表留言一样,只是它会回复到当前留言 的下边。
  5. 我们这里假设回复只有一层,只能回复顶层的留言,不能对留言回复进行回复。

接下来我们看一下具体的事件处理流程。

传递数据到父组件

用户在 textarea 这个输入框中输入的留言,需要保存到一个响应式的对象中,这样就能在表单提交的时候,获取它的值,我们在 CommentBox 组件中,引入 ref 函数,然后定义一个空的状态,保存到 content 中:

import { ref } from "vue";
const content = ref("");

之后在 textarea 里,使用 v-model 绑定 content,这样用户在输入内容时,可以使用 content 来获取输入值:

<textarea v-model="content"></textarea>

用户在写完留言之后,点击提交按钮,这个表单会触发 submit 事件,这里因为我们的留言列表数据在 CommentsApp 这个父组件中,而表单在 CommentBox 组件中,是个子组件,我们需要把输入框的数据传递给父组件,再由父组件去组装新的留言对象,追加到留言列表中。 我们可以利用 Vue 的 emit 特性,把表单数据通过事件的方式,传递给父组件。emit 就是告诉父组件,我这个组件会触发什么样的事件,需要父组件传递回调函数或者 JavaScript 代码来进行处理。 在 CommentBox 组件里,我们用 defineEmits()定义这个组件会触发的事件,它接收一个数组作为参数,数组中是组件会触发的事件名,这个名字可以自定义,我们给它一个名字叫 submit:

const emit = defineEmits(["submit"]);

defineEmits() 会返回一个函数,我们在真正触发事件的地方调用它,它就会把事件转发到使用这个组件的其他组件中。 在 CommentBox 组件里,真实触发事件的是表单 form 元素,我们监听它的 submit 事件,传递一个 handleSubmit 事件处理函数,先在组件自身预处理一下事件:

<form class="grid" @submit="handleSubmit">

在事件处理函数中:

  • 阻止了表单的默认提交事件。
  • 调用 emit ,触发自定义的 submit 事件,这里它就会调用父组件传递进来的事件处理函数或代码。
  • 后面的 content,也就是用户输入的留言,可以在父组件捕获事件的时候获取到。因为 content 是 ref, 所以需要使用 value 属性访问它的值。
  • 最后清空输入框中的内容。
const handleSubmit = (e) => {
e.preventDefault();
emit("submit", content.value);
content.value = "";
};

处理发表留言事件

现在,在 CommentsApp 组件里,我们可以监听 CommentBox 的 submit 事件了,在处理监听之前,我们先定义一个工具函数,用于组装新的留言对象:

import face4 from "./assets/face4.webp";
import { ref } from "vue";

let rid = ref(4);

const constructNewComment = (content) => {
return {
id: rid.value++,
user: "当前用户",
avatar: face4,
content,
time: "1秒前",
};
};

constructNewComment 函数:

  • 接收留言内容作为参数。
  • 返回一个新的留言对象,对象的属性和我们之前定义的属性一致。
  • 这里的 id 是我们自动生成的,因为原来的留言列表中的 id 排到了 3,那么新添加的 id 就为 4,我们把它定义为 ref,然后每次创建留言的对象的时候,都对它进行自增,这里只是用来演示,实际开发需要后台去生成。
  • 发布留言默认都是当前用户,那么我们就假设当前用户的用户名为“当前用户”,头像用的 face4,这里记得导入进来。
  • 发布时间这里先写死了,1 秒前。

之后,我们把之前定义的 comments 定义为 ref,这样在新留言添加到列表时,组件能够自动刷新:

const comments = ref([
/* ... */
]);

现在,我们处理 CommentBox 的 submit 事件,给它传递一个 addNewComment 事件处理回调:

<CommentBox @submit="addNewComment" />

之后定义这个函数:

const addNewComment = (content) => {
const newComment = constructNewComment(content);
comments.value.push(newComment);
};

在这个函数里中:

  • 它的参数 content,就是之前在 CommentBox 中,调用 emit() 时,传递进来的留言内容
  • 在里边,调用 constructNewComment 创建一个新的留言对象。
  • 最后把它压入到 comments 留言列表中。

现在,可以运行 yarn dev 测试一下,编写一条示例的留言,点击提交按钮,就可以看到,它追加到留言列表中了。

修改回复留言列表的样式

我们可以看到,新添加的留言下边没有回复按钮,那之前我们设计的留言列表组件结构需要修改,现在的 ReplyBox 里,是只有当留言有回复的时候才显示,而我们把回复按钮定义到它里边了,这样如果留言里没有回复时,回复按钮就不会显示,这样的逻辑有问题:

<ReplyBox v-if="comment.replies">

那么我们可以把回复按钮和回复列表区分开。这里定义个新的 ReplyContainer 组件,专门存放留言列表,把 ReplyBox 最外层的 div 拿过来:

<template>
<div class="pl-8 border-l-2 border-gray-200">
<slot></slot>
</div>
</template>

<script setup></script>

<style></style>

之前的 ReplyBox 则只剩下留言按钮:

<template>
<button class="pt-4 pb-10 text-blue-600">回复</button>
</template>

<script setup></script>

<style></style>

之后在 CommentsApp 中,把以前的 ReplyBox 改成 ReplyContainer:

import ReplyContainer from "./components/ReplyContainer.vue";

<ReplyContainer v-if="comment.replies">

在 ReplyContainer 后面,使用 ReplyBox 加载回复按钮:

<ReplyBox />

ReplyBox 后面会显示回复留言的表单,我们稍后再看一下,现在看一下页面,可以看到,新添加的留言下边,也有回复按钮了。

处理回复留言

接下来我们来看一下回复留言的事件处理。我们在点回复的时候,首先要在它下面显示发表留言的表单,这个表单和上边发表留言的是同一个,可以复用。在 ReplyBox 组件里,我们先定义控制是否显示,发表回复表单的状态,showCommentBox:

import { ref } from "vue";

const showCommentBox = ref(false);

默认值为 false 不显示。然后引入 CommentBox 组件:

import CommentBox from "./CommentBox.vue";

在 template 中,回复按钮的下边,渲染 CommentBox 组件:

<button>回复</button>
<CommentBox
v-if="showCommentBox"
class="mb-4"
/>

这里使用了 v-if,根据 showCommentBox 状态的值,来决定是否显示回复框。 再给 button 回复按钮处理一下点击事件:

  <button
@click="showCommentBox = !showCommentBox"
>

让它点击的时候,切换 showCommentBox 的值,如果为 true 就改为 false,如果为 false 就改为 true。另外,在显示回复留言表单时,回复按钮离表单过远,那么我们定义一个动态的 :class,在里边判断,当显示回复留言表单时,回复按钮距离回复表单的间距为 pb-2,如果没显示,则为 pb-10:

  <button
class="pt-4 text-blue-600"
:class="[showCommentBox ? 'pb-2' : 'pb-10']"
@click="showCommentBox = !showCommentBox"
>

这样,可以看一下页面,点击回复按钮的时候,就能切换显示回复留言的表单了。 这个回复框的事件,和发表留言的一样,这里我们直接接收 CommentBox 的 submit 事件,并把它转发到父组件中,首先使用 defineEmits 定义它可以触发的事件,这里我们还叫它 submit:

const emit = defineEmits(["submit"]);

在 CommentBox 标签处,处理 CommentBox 的 submit 事件,调用 emit,触发 ReplyBox 这个组件自定义的 submit 事件,然后使用$event,获取 CommentBox 事件传递进来的 content 内容参数, 把它传递给父组件的回调函数中,最后把留言表单关闭:

  <CommentBox
...
@submit="
emit('submit', $event);
showCommentBox = false;
"
/>

回到 CommentsApp 这个整体的组件中,处理 ReplyBox 留言回复组件的事件,submit,调用一个 addReply() 函数:

<ReplyBox @submit="addReply($event, comment.id)" />

我们稍后再看它的定义,这里,它接收两个参数,$event 就是 CommentBox 里传递出来的 content 内容,第二个参数是上级评论的 id,这样就是针对这一条留言,发表的回复。 现在看一下 addReply 函数的定义:

const addReply = (content, id) => {
const reply = constructNewComment(content);
let comment = comments.value.find((comment) => comment.id === id);
if (comment.replies) {
comment.replies.push(reply);
} else {
comment.replies = [reply];
}
};

在这个函数中:

  • 我们像之前添加新留言时一样,先组装一个新的留言对象。
  • 然后根据 id,找到上级留言。
  • 如果上级留言之前已经有回复了,那么就再追加一条。
  • 如果没有,就创建一个新数组,把这条回复添加进去。

现在,留言回复功能就完成了,可以运行 yarn dev 来测试一下,在一条评论的下边点击回复按钮,如果能发表成功,并显示在该条留言的下边,那么就没问题了。

小结

这期视频给我们之前的 Vue 项目加了动态的交互,实现了发表留言和回复的功能,只是现在数据还都是只保存在了前端本地,后面的视频我们再定义后台 API。在 Vue 中,子组件跟父组件通信最主要的途径,就是通过 emit() 触发事件,给父组件传递参数,这样父组件可以通过回调函数的参数来获取子组件传递的数据。定义自定义事件使用 defineEmits,在里边用数组的形式定义这个组件会触发的事件,之后 defineEmits() 会返回 emit 函数,在合适的时机调用它就可以了。

好了,这个就是给 Vue 3.0 项目添加交互的过程,你学会了吗?如果有帮助请三连,想学更多有用的前端开发知识,请关注峰华前端工程师,感谢观看!

提示

一系列的课程让你成为高级前端工程师。课程覆盖工作中所有常用的知识点和背后的使用逻辑,示例全部都为工作项目简化而来,学完即可直接上手开发!

即使你已经是高级前端工程师,在课程里也可能会发现新的知识点和技巧,让你的工作更加轻松!

《React 完全指南》课程,连载中现只需 48 元(领取优惠券)点击查看详情。

《Vue 3.x 全家桶完全指南与实战》课程,包括 Vue 3.x、TypeScript、Vue Router 4.x、Vuex 4.x 所有初级到高级的语法特性详解,让你完全胜任 Vue 前端开发的工作。点击查看详情。

《React即时通信UI实战》课程,利用 Storybook、Styled-components、React-Spring 打造属于自己的组件库。

《JavaScript 基础语法详解》本人所著图书,包含 JavaScript 全面的语法知识和新特性, 可在京东、当当、淘宝等各大电商购买