Web学习笔记-Vue3(环境配置、概念、整体布局设计)

  1. 1. 环境配置
  2. 2. 使用 Vue 开发项目的基本概念
  3. 3. 导航栏
  4. 4. 页面创建

本文记录 Vue3 的学习过程,内容为环境配置、概念、整体布局设计。
Vue 中文官网:Vue.js

Vue.js 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助开发者高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。Vue 的设计非常注重灵活性和“可以被逐步集成”这个特点。根据你的需求场景,你可以用不同的方式使用Vue。

Vue 与 React 的区别主要在于:

  • React 的思路是 HTML in JavaScript,也可以说是 All in JavaScript,通过 JavaScript 来生成 HTML,所以设计了 JSX 语法,还有通过 JS 来操作 CSS。
  • Vue 是把 HTML、CSS、JS 组合到一起,用各自的处理方式,Vue 有单文件组件,可以把 HTML、CSS、JS 写到一个文件中,HTML 提供了模板引擎来处理。
  • React 整体是函数式的思想,在 React 中是单向数据流,推崇结合 immutable 来实现数据不可变。
  • Vue 的思想是响应式的,也就是基于数据可变的,通过对每一个属性建立 Watcher 来监听,当属性变化的时候,响应式地更新对应的虚拟 DOM。

总的来说,React 的性能优化需要手动去做,而 Vue 的性能优化是自动的,但是 Vue 的响应式机制也有问题,就是当 state 特别多的时候,Watcher 会很多,会导致卡顿。所以大型应用(状态特别多的)一般用 React,更加可控。而考虑易用性方面,Vue 是更容易上手的,对于项目来说新人更容易接手。

1. 环境配置

首先需要安装 Node.js:NodeJS 的安装及配置

打开 PowerShell,安装 @vue/cli

1
npm i -g @vue/cli

Vue 相比于 React 的其中一个好处是默认提供了一个图形化的项目管理界面,在想要创建项目的文件夹下以管理员身份打开终端输入以下命令:

1
vue ui

然后我们创建一个名为 my_space 的项目,包管理器选择 npm,然后选上无新手指引的脚手架项目(Scaffold project without beginner instructions),预设选择 Vue3,然后创建项目。

项目创建好后在左侧能够看到导航栏,进入插件页面(Plugins)安装以下插件,其中 @vue/cli-plugin-router 用于多页面路由,@vue/cli-plugin-vuex 类似于 React 中的 Redux,可以让我们在多个组件之间维护同一个数据:

1
2
@vue/cli-plugin-router
@vue/cli-plugin-vuex

然后我们在依赖页面(Dependencies)安装 Bootstrap。

最后在任务页面(Tasks)的 serve 选项卡中可以运行项目,运行后在输出(Output)中可以看到网站链接:http://localhost:8080/,访问该链接即可看到 Vue 的初始化页面。

我们用 VS Code 打开项目的根目录,源代码位于 src 目录下,其中的 views 目录类似于 Django 中的 Views,每一个页面是一个 View;router 目录是路由,打开可以看到默认有两个路由分别是 //aboutcomponents 目录用于存放各种组件(views 也能存放组件,根据个人习惯决定);App.vue 为根组件,整个项目的入口在 main.js 文件中。

仔细看一下页面的链接会发现其中有一个 #,如果想去掉可以将 router 目录下 index.js 文件中的 createWebHashHistory 修改为 createWebHistory(有两处需要修改)。

main.js 文件中可以看到以下代码:

1
createApp(App).use(router).use(store).mount('#app')

其中创建了我们的根组件 Approuter 就表示路由,storevuex,然后将其挂载到 #app 标签上,该标签可以在 public/index.html 中看到。

2. 使用 Vue 开发项目的基本概念

每一个 .vue 文件都会由三个部分组成:HTML、CSS、JS。其中 CSS 的标签可以加一个属性 scoped,这样不同组件之间的 CSS 选择器就不会相互影响到了:

1
2
3
<style scoped>
...
</style>

我们的每个页面中都可能由多个组件组成,每个组件是一个 .vue 文件,可以用以下的方式引入 HelloWorld 组件,并传递 msg 属性给该组件:

1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
</script>

HelloWorld 组件需要 export 出去:

1
2
3
4
5
6
7
8
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>

其中 props 中的属性可以在创建该组件的父组件中传递进来。

现在我们来具体了解一下 <script> 中的 export default 的参数:

  • name:组件的名称。
  • components:存储 <template> 中用到的所有组件。
  • props:存储父组件传递给子组件的数据。
  • watch():当某个数据发生变化时触发。
  • computed:动态计算某个数据。
  • setup(props, context):初始化变量、函数。
    • ref:定义变量,可以用 .value 属性取值或者重新赋值,效率稍微比 reactive 低一些。
    • reactive:定义对象,不可重新赋值。
    • props:存储父组件传递过来的数据。
    • context.emit():触发父组件绑定的函数。

然后是 <template> 中的内容:

  • <slot></slot>:存放父组件传过来的 children
  • v-on:click@click 属性:绑定事件。
  • v-ifv-elsev-else-if 属性:判断。
  • v-for 属性:循环,注意需要用 :key 给循环的每个元素绑定唯一的 key
  • v-bind:::绑定属性。

<style> 部分需要注意的就是添加 scope 属性后,不同组件间的 CSS 不会相互影响。

最后是第三方组件:

  • vue-router:实现路由功能。
  • vuex:存储全局状态,全局唯一。
    • state:存储所有数据,可以用 modules 属性划分成若干模块。
    • getters:根据 state 中的值计算新的值。
    • mutations:所有对 state 的修改操作都需要定义在这里,不支持异步,可以通过 $store.commit() 触发。
    • actions:定义对 state 的复杂修改操作,例如使用 Ajax 向后端请求数据后再做修改操作,支持异步,可以通过 $store.dispatch() 触发。注意不能直接修改 state,只能通过 mutations 修改 stateactions 中函数的第一个参数为 context,通过 context.commit() 可以调用 mutations 中的操作。
    • modules:定义 state 的子模块。

这些概念先不用背,之后用到时再回来理解什么意思即可。

3. 导航栏

我们将整个页面分为导航栏(NavBar)和内容部分(Content),导航栏在每个页面都是固定不变的,变化的只有内容部分。我们将要实现首页、用户列表、用户动态、登录、注册以及404页面,每个页面我们都可以实现为一个组件。

在根 JS 文件 main.js 中引入 Bootstrap:

1
2
3
4
5
6
7
8
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap/dist/js/bootstrap'

createApp(App).use(router).use(store).mount('#app')

这时候会报错,提示我们没有找到模块 @popperjs/core,这时我们需要去项目管理页面的依赖页面中安装 @popperjs/core 依赖。

现在先把 HelloWorld.vue 删掉,然后把 HomeView.vue 中关于 HelloWorld 组件的内容删去,在 components 目录下创建 NavBar.vue,我们现在先不实现路由功能,直接去 Bootstrap 官网找一个导航栏复制过来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<a class="navbar-brand" href="#">My Space</a>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="#">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">用户列表</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">用户动态</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">关于</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="#">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">注册</a>
</li>
</ul>
</div>
</div>
</nav>
</template>

<script>
export default {
name: "NavBar",
};
</script>

<style></style>

然后我们在 App.vue 中把导航栏组件添加进来,并删去原本的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<NavBar />
<router-view />
</template>

<script>
import NavBar from "@/components/NavBar.vue";

export default {
name: "App",
components: {
NavBar: NavBar,
},
};
</script>

<style></style>

4. 页面创建

首先我们用 Bootstrap 的 Card 先实现一个卡片组件 Card.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="container">
<div class="card">
<div class="card-header">
<h3>{{ title }}</h3>
</div>
<div class="card-body">
<slot></slot>
</div>
</div>
</div>
</template>

<script>
export default {
name: "Card",
props: {
title: String,
},
};
</script>

<style scoped>
.card {
margin-top: 20px;
}
</style>

注意,如果发现报错:Component name "Card" should always be multi-word vue/multi-word-component-names,说明文件名没有驼峰命名,可以在 package.json 文件的 rules 中添加一行代码,然后停止项目重新启动即可:

1
"vue/multi-word-component-names": "off"

然后我们在 HomeView 中即可使用这个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<Card title="首页">
这是首页内容
</Card>
</template>

<script>
// @ is an alias to /src
import Card from "@/components/Card.vue"

export default {
name: "HomeView",
components: {
Card: Card,
},
};
</script>

可以看到 Card 中使用 {{ msg }} 在 HTML 标签中获取 proptitle 的值,该值由父组件 HomeView 传入。<slot></slot> 类似 React 中的 this.props.children

现在我们可以根据 HomeView 创建其他页面:UserListViewUserNewsViewLoginViewRegisterViewNotFoundView

创建好页面后我们需要实现路由,即根据地址来显示对应的组件。在 src/router/index.js 文件中修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView";
import UserListView from "../views/UserListView";
import UserNewsView from "../views/UserNewsView";
import LoginView from "../views/LoginView";
import RegisterView from "../views/RegisterView";
import NotFoundView from "../views/NotFoundView";

const routes = [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/userlist",
name: "userlist",
component: UserListView,
},
{
path: "/usernews",
name: "usernews",
component: UserNewsView,
},
{
path: "/login",
name: "login",
component: LoginView,
},
{
path: "/register",
name: "register",
component: RegisterView,
},
{
path: "/404",
name: "404",
component: NotFoundView,
},
{
path: "/about",
name: "about",
component: () => import("../views/AboutView.vue"),
},
];

const router = createRouter({
history: createWebHistory(),
routes,
});

export default router;

最后我们在导航栏 NavBar 中实现地址的跳转,如果直接在 <a> 标签上写地址那么为后端渲染,即每次切换页面都需要访问一遍后端请求数据,我们可以用 <router-link> 实现前端渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<router-link class="navbar-brand" :to="{ name: 'home' }">My Space</router-link>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'home' }">首页</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'userlist' }">用户列表</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'usernews' }">用户动态</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'about' }">关于</router-link>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'login' }">登录</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'register' }">注册</router-link>
</li>
</ul>
</div>
</div>
</nav>
</template>

<script>
export default {
name: "NavBar",
};
</script>

<style></style>

其中 :to 表示绑定 to 属性,参数是一个对象,其中的 name 属性表示名称,即之前在 router 中定义的 name