跳到主要内容位置

Vite 2.0 + Tailwind CSS 留言板项目改装为 Vue 3.0

Vue 和 React 的出现,让前端开发的效率有了质的飞跃,我们不用再手动用 JavaScript 去操作 DOM 了,而是借助这些框架帮助我们管理状态和管理 DOM 结构。这期视频我们就把上期使用纯 HTML 编写的页面,改装为 Vue 3.0 的项目,借此也演示一下如何把一个 Vite 2.0 的 html 项目,扩展为 Vue 3.0 的项目。

安装 Vue3.0 依赖

Vite 2.0 创建的 HTML 项目默认是不支持 Vue 的,我们需要安装 Vue 3.0 依赖和插件,使用 yarn add -D @vitejs/plugin-vue @vue/compiler-sfc vue@next  命令:

yarn add -D @vitejs/plugin-vue @vue/compiler-sfc vue@next

这里使用 -D,把这些依赖安装为开发依赖,分别安装了 Vite 的 vue 插件、它依赖的 compiler-sfc,还有 vue 3.0 库。

配置 vite.config.js

在安装完依赖后,我们需要配置 vite,让它使用 vue 插件。首先创建一个 vite.config.js 文件,它是 vite 可以识别的配置文件:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
plugins: [vue()],
});

在里面:

  • 从 vite 库里导入 defineConfig 函数,用于添加插件。
  • 之后从 "@vitejs/plugin-vue" 中导入 vue 插件。
  • 默认导出 defineConfig() 的返回值。
  • 在函数参数中,给它传递一个配置对象,设置 plugins 属性为一个数组,把调用 vue() 函数的结果放到数组中。
  • 这样就配置好了 vue 3.0 的环境。

安装 ESLint

我们还可以安装 ESLint 来帮我们规范化代码,这一步是可选的,如果你对代码的风格要求比较高,可以参考这个步骤进行安装。 使用 yarn 安装 eslint 和 eslint-plugin-vue 这两个插件,同样是使用 -D,添加到开发依赖中:

yarn add -D eslint eslint-plugin-vue

接着在项目的根目录下,新建一个.eslintrc.js,这是 eslint 的配置文件,在里边导出一个对象,注意,这里需要使用 node.js 的模块导出方式:

module.exports = {
extends: [
// add more generic rulesets here, such as:
// 'eslint:recommended',
"plugin:vue/vue3-recommended",
// 'plugin:vue/recommended' // Use this if you are using Vue.js 2.x.
],
rules: {
// override/add rules settings here, such as:
// "vue/no-unused-vars": "error",
"vue/singleline-html-element-content-newline": "off",
},
};

对象中:

  • 使用 extends 属性来继承一个 eslint 的配置,这里传递一个数组,数组里使用 eslint-plugin-vue 提供的 vue3-recommended 这个配置。
  • rules 里定义要覆盖的规则,这里我把 "vue/singleline-html-element-content-newline" 设置成了 off,这样,当 html 元素的标签和内容在一行的时候,就不提示错误了,这里主要是为了避免和 prettier 这个代码格式化插件起冲突,因为如果 html 元素代码短的话,prettier 是不会把它里边的内容单独放一行的。

到这里 eslint 就配置完了。

挂载 Vue 组件

现在,我们需要在 HTML 中设置一个 div 元素,把 vue 挂载到那上边。打开 index.html,在我们的静态代码上方,body 元素的第一行,定义一个 id 为 "app" 的 div:

<body>
<div id="app"></div>
</body>

新建一个 src 文件夹,把 main.js 移动到里边去并打开,在里边挂载 Vue:

import { createApp } from "vue";
import "../style.css";
import App from "./App.vue";

createApp(App).mount("#app");

之前,我们导入了 tailwind css,在这里我们:

  • 从 vue 中导入 createApp。
  • 修改一下 style.css 的相对路径。
  • 导入 App.vue 组件,这个我们稍后再创建。
  • 使用 createApp() 把 App.vue 这个组件传递进去,再调用 mount 挂载到 id 为 app 的这个 div 上。

修改一下 Index.html 中,对于 main.js 的引入路径,加上 src:

<script type="module" src="/src/main.js"></script>

接下来,在 src 目录下创建一个 App.vue 组件,在 template 里写上示例的代码:

<template><h1>Hello</h1></template>

<script setup></script>

<style></style>

这里我们使用了 script setup 语法,不熟悉的话可以参考我之前的视频。好,现在这个 App.vue 入口组件就挂载好了,我们运行 yarn dev 来看一下效果。 可以看到,App.vue 的内容成功的显示到了页面的上方。

拆分组件

根据我们的页面视图结构,我们大致可以把各个部分分解成这样几个组件:

  • CommentsApp.vue - 整体的留言系统组件,它会直接在 App.vue 中引用,之所以我们不在 App.vue 中直接编写代码,是因为我想把 App.vue 作为全局配置的入口,例如加上路由或状态之类的,所以让它保留最少的代码。
  • CommentBox.vue - 留言表单容器组件。
  • DividerHorizontal.vue - 分割线组件。
  • CommentItem.vue - 单个留言组件。
  • ReplyBox.vue - 回复列表容器组件。

我们分别创建这几个文件:

  • 其中 CommentsApp.vue 放到和 App.vue 同级的 src 目录下。
  • 之后在 src 下新建一个 components 文件夹,存放其它组件。
  • 新建 CommentBox.vue、DividerHorizontal.vue、CommentItem.vue、ReplyBox.vue 这几个组件文件。
  • 在所有组件里都写上 Vue 的模板代码,template、script、和 style:
<template></template>

<script setup></script>

<style></style>

编写组件代码

接下来编写组件的代码,这里几乎就是剪切粘贴,把 HTML 划分成组件,把它们放到各自的组件文件中,我们可以先把所有的 HTML 代码放到 CommentsApp.vue 这个组件中(记得删除 index.html 中的代码):

<main class="p-4 bg-gray-50 min-h-screen">
<div class="max-w-screen-xl mx-auto bg-white p-8 rounded-lg shadow-2xl">
<h2 class="text-3xl my-6">评论</h2>
<form action="" class="grid">
<textarea
name="comment"
id=""
placeholder="请输入你的评论"
class="bg-gray-50 p-2 rounded"
></textarea>
<fieldset class="py-4">
<input
type="submit"
value="评论"
class="px-4 py-1 bg-blue-600 rounded text-white"
/>
<input
type="reset"
value="取消"
class="px-4 py-1 bg-white rounded border ml-3"
/>
</fieldset>
</form>
<div class="border-b border-gray-300 my-2 mb-4"></div>
<div>
<div class="flex">
<!-- 即使在 public 文件夹下,也需要使用 / 绝对路径引用它下面的文件,省略 Public -->
<img
src="/images/face1.webp"
alt=""
class="w-12 h-12 mr-4 rounded-full"
/>
<div>
<p>梦落轻寻</p>
<p class="text-gray-600 text-sm">2 小时之前</p>
</div>

<!-- 菜单考右对齐 -->
<span class="ml-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-500"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</span>
</div>
<p class="text-gray-600 py-4">
哇!这篇文章真是写的太好啦!收到很大的启发,希望博主能够再接再厉,产出越来越多,越来越好的文章!凑字数,字数,字数...
</p>
<div class="pl-8 border-l-2 border-gray-200">
<!-- 回复 -->
<div class="flex">
<!-- 即使在 public 文件夹下,也需要使用 / 绝对路径引用它下面的文件,省略 Public -->
<img
src="/images/face2.webp"
alt=""
class="w-12 h-12 mr-4 rounded-full"
/>
<div>
<p>陌上花开</p>
<p class="text-gray-600 text-sm">2 小时之前</p>
</div>

<span class="ml-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-500"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</span>
</div>
<p class="text-gray-600 py-4">赞!</p>
<div class="flex">
<!-- 即使在 public 文件夹下,也需要使用 / 绝对路径引用它下面的文件,省略 Public -->
<img
src="/images/face3.webp"
alt=""
class="w-12 h-12 mr-4 rounded-full"
/>
<div>
<p>半梦半醒半浮生√</p>
<p class="text-gray-600 text-sm">2 小时之前</p>
</div>

<span class="ml-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-500"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</span>
</div>
<p class="text-gray-600 py-4">
这是一篇非常长的长篇大论,这篇文章写的非常好,无论是技术点还是理论点,都非常的好。而且主题分明,每一个点都有自己的解释,这篇文章的主题是:CSS3的新特性,如何使用CSS3的新特性,以及如何使用CSS3的新特性。真的是非常好的文章。
</p>
</div>
<button class="pt-4 pb-10 text-blue-600">回复</button>
</div>
</div>
</main>

之后,在 App.vue 中,导入 CommentsApp 组件并在 template 中加载:

<template><CommentsApp /></template>

import CommentsApp from "./CommentsApp.vue";

现在,我们按顺序来拆分代码,首先看留言表单。

留言表单

留言表单就是整个的 form 元素,我们把它剪切到 CommentBox.vue 的 template 中:

<form action="" class="grid">
<textarea
name="comment"
id=""
placeholder="请输入你的评论"
class="bg-gray-50 p-2 rounded"
></textarea>
<fieldset class="py-4">
<input
type="submit"
value="评论"
class="px-4 py-1 bg-blue-600 rounded text-white"
/>
<input
type="reset"
value="取消"
class="px-4 py-1 bg-white rounded border ml-3"
/>
</fieldset>
</form>

之后回到 CommentsApp.vue 中,在 script 中引入 CommentBox 组件:

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

然后把 template 中的 form,替换成 CommentBox 组件:

<h2 class="text-3xl my-6">评论</h2>
<!-- 评论框 -->
<CommentBox />

这时可以运行 yarn dev,看一下效果,如果没有变化,就说明没有问题。其它的组件也是如此拆分, 我们简单过一下。

分隔线

把分割线的 HTML 代码,放到 DividerHorizontal.vue 组件的 template 中:

<div class="border-b border-gray-300 my-2 mb-4"></div>

单个留言

对于每个留言,把相关的 html 代码放到 CommentItem.vue 组件的 template 中:

<div class="flex">
<img src="/images/face1.webp" alt="" class="w-12 h-12 mr-4 rounded-full" />
<div>
<p>梦落轻寻</p>
<p class="text-gray-600 text-sm">2 小时之前</p>
</div>

<!-- 菜单考右对齐 -->
<span class="ml-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-500"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</span>
</div>
<p class="text-gray-600 py-4">
哇!这篇文章真是写的太好啦!收到很大的启发,希望博主能够再接再厉,产出越来越多,越来越好的文章!凑字数,字数,字数...
</p>

它是由 class 为 flex 的 div,以及紧邻的 p 元素构成的,剪切的时候别漏了 p 元素。

回复列表容器

对于留言回复列表容器,它里边也是使用同样的 CommentItem 组件,而它需要显示左边的竖线,以及回复按钮,我这里就把它单独抽离成了组件,对于这个小型的项目,也可以不抽离,我们首先把回复列表的 HTML 代码全部剪切过来,包括回复按钮,放到 ReplyBox.vue 的 template 中:

<div class="pl-8 border-l-2 border-gray-200">
<!-- 回复 -->
<div class="flex">
<!-- 即使在 public 文件夹下,也需要使用 / 绝对路径引用它下面的文件,省略 Public -->
<img src="/images/face2.webp" alt="" class="w-12 h-12 mr-4 rounded-full" />
<div>
<p>陌上花开</p>
<p class="text-gray-600 text-sm">2 小时之前</p>
</div>

<span class="ml-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-500"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</span>
</div>
<p class="text-gray-600 py-4">赞!</p>
<div class="flex">
<!-- 即使在 public 文件夹下,也需要使用 / 绝对路径引用它下面的文件,省略 Public -->
<img src="/images/face3.webp" alt="" class="w-12 h-12 mr-4 rounded-full" />
<div>
<p>半梦半醒半浮生√</p>
<p class="text-gray-600 text-sm">2 小时之前</p>
</div>

<span class="ml-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-500"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</span>
</div>
<p class="text-gray-600 py-4">
这是一篇非常长的长篇大论,这篇文章写的非常好,无论是技术点还是理论点,都非常的好。而且主题分明,每一个点都有自己的解释,这篇文章的主题是:CSS3的新特性,如何使用CSS3的新特性,以及如何使用CSS3的新特性。真的是非常好的文章。
</p>
</div>
<button class="pt-4 pb-10 text-blue-600">回复</button>

这里,我想让使用它的组件自行把回复列表项传递进来,所以,可以在这里使用一个 slot,就不用自己维护回复列表了,这样也可以少传递一层属性:

<div class="pl-8 border-l-2 border-gray-200">
<slot />
</div>
<button class="pt-4 pb-10 text-blue-600">回复</button>

留言系统主组件

现在,各个组件的代码都拆分完了,我们需要在 CommentsApp.vue 中,把代码替换成使用组件的形式,之前我们导入了 CommentBox,现在我们导入剩下的组件,DividerHorizontal、CommentItem 和 ReplyBox:

import DividerHorizontal from "./components/DividerHorizontal.vue"; import
CommentItem from "./components/CommentItem.vue"; import ReplyBox from
"./components/ReplyBox.vue";

把 HTML 代码替换成这些组件:

<CommentBox />
<!-- 分隔线 -->
<DividerHorizontal />
<div>
<!-- 评论 -->
<CommentItem />
<!-- 回复容器 -->
<ReplyBox>
<!-- 回复 -->
<CommentItem />
</ReplyBox>
</div>

配置 CommentItem.vue 组件属性

这里,可以看到,对于每条留言,都显示了同样的内容,我们需要让 CommentItem 接收一些属性,来让它显示不同的内容。打开 CommentItem.vue 组件,在 script 中定义几个属性:

defineProps(["user", "avatar", "time", "content"]);

它接收:

  • user 用户名
  • avatar 头像
  • time 发布时间
  • content 留言内容

之后,把写死的内容替换成属性值:

<div class="flex">
<!-- 即使在 public 文件夹下,也需要使用 / 绝对路径引用它下面的文件,省略 Public -->
<img :src="avatar" alt="" class="w-12 h-12 mr-4 rounded-full" />
<div>
<p>{{ user }}</p>
<p class="text-gray-600 text-sm">{{ time }}</p>
</div>

<!-- 菜单考右对齐 -->
<span class="ml-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-500"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</span>
</div>
<p class="text-gray-600 py-4">{{ content }}</p>

编写示例数据

再回到 CommentsApp.vue 中,编写一些示例的数据。这里,因为 vite 可以打包 vue 项目中的图片,我们可以在 src 下新建一个 assets 目录,把图片从 public/images 里边复制过来,这样可以方便的在 CommentsApp.vue 中引入。

src/assets
├── face1.webp
├── face2.webpa
├── face3.webp
├── face4.webp
└── face5.webp

在 CommentsApp.vue 中导入几张图片,然后定义示例数据数组:

import face1 from "./assets/face1.webp";
import face2 from "./assets/face2.webp";
import face3 from "./assets/face3.webp";

const comments = [
{
id: 1,
user: "梦落轻寻",
avatar: face1,
time: "2小时之前",
content:
"哇!这篇文章真是写的太好啦!收到很大的启发,希望博主能够再接再厉,产出越来越多,越来越好的文章!凑字数,字数,字数...",
replies: [
{
id: 2,
user: "陌上花开",
avatar: face2,
time: "2小时之前",
content: "赞!",
},
{
id: 3,
user: "半梦半醒半浮生√<",
avatar: face3,
time: "2小时之前",
content:
"这是一篇非常长的长篇大论,这篇文章写的非常好,无论是技术点还是理论点,都非常的好。而且主题分明,每一个点都有自己的解释,这篇文章的主题是:CSS3的新特性,如何使用CSS3的新特性,以及如何使用CSS3的新特性。真的是非常好的文章。",
},
],
},
];

我们会在后面添加交互的时候,把它定义为 ref,这里只是把它定义到了一个常量里,做个演示。示例数据是个数组对象结构,每个对象有留言的 id、用户名、头像、发布时间和内容,如果留言有回复,那么它会放到 replies 子数组中。 现在有了数据,我们先遍历顶级回复:

<div v-for="comment in comments" :key="comment.id">
<!-- 评论 -->
<CommentItem
:user="comment.user"
:avatar="comment.avatar"
:time="comment.time"
:content="comment.content"
/>
</div>

在分隔线下边的 div 中

  • 使用 v-for 遍历 comments 数组,记得把 :key 设置为留言对象的 id。
  • 之后在里边使用 CommentItem 组件,把相关的属性传递进去。

接着加载 ReplyBox 组件,不过,它只有在当前留言有回复时才加载,所以使用 v-if 判断当前留言对象中是否有 replies 属性,如果有的话再加载 ReplyBox:

<ReplyBox v-if="comment.replies">
<!-- 回复 -->
<CommentItem
v-for="reply in comment.replies"
:key="reply.id"
:user="reply.user"
:avatar="reply.avatar"
:time="reply.time"
:content="reply.content"
/>
</ReplyBox>

之后在 ReplyBox 中,使用 CommentItem 组件和 v-for,遍历 replies 数组,显示回复。这样,整个示例数据就显示到页面上了,可以运行一下 yarn dev 来看一下效果,如果和之前一样,那就说明没有问题。

小结

这期视频我们把纯 HTML 项目改装成了 Vue 3.0 的项目,添加了 vue 的 vite 插件、修改了 vite 的配置文件、修改了目录结构,并在 main.js 中挂载了 vue 节点。再然后把 HTML 按结构拆分成了 Vue 的组件,把相关的代码剪切到了对应的组件中,定义了示例数据,来展示和之前页面一样的效果。

好了,这个就是把 HTML 项目改装成 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 全面的语法知识和新特性, 可在京东、当当、淘宝等各大电商购买