前言

由于hexo不单独支持vue的语法高亮,因此部分vue的高亮使用的是html。

什么是Vite?

vite是一个现在化的前端开发构建工具,能够帮助开发者更加便捷管理安装的npm包,更快的启动和更新修改后的前端页面,是Vue官方主推的构建工具。

Vite | 下一代的前端工具链 (vitejs.dev)

创建一个Vite项目

在项目文件夹中使用命令

1
npm create vite@latest

输入后会让我们输入一些指令,按照提示输入即可:

1
2
3
4
5
6
7
8
9
10
11
12
D:\Web\vite_test>npm create vite@latest
√ Project name: ... demo
√ Select a framework: » Vue
√ Select a variant: » JavaScript

Scaffolding project in D:\Web\vite_test\demo...

Done. Now run:

cd demo
npm install
npm run dev

之后就会看见一个名为demo的文件夹,其中的文件为

1
2
3
4
5
6
─demo
├─.vscode
├─public
└─src
├─assets
└─components

按照提示继续输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
D:\Web\vite_test>cd demo

D:\Web\vite_test\demo>npm install

added 27 packages in 6s

D:\Web\vite_test\demo>npm run dev

> demo@0.0.0 dev
> vite


VITE v5.2.11 ready in 467 ms

➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help

点击链接打开本地服务器即可看到vite的原始界面。

之后可以将默认的HelloWorld.vue和style.css删除,并在main.js和App.vue中删除导入即可。

之后你就会得到一个空的vue文件

1
2
3
4
5
6
7
8
9
10
11
<script setup>

</script>

<template>

</template>

<style scoped>

</style>

VSCode插件

VSCode本身其实就是一个文本编辑器,如果想要让他支持vue的开发,那么必须下载一些其他的插件。

  1. Vue-Official - 支持Vue语法高亮
  2. Vue VSCode Snippets - 快速生成vue3模版
  3. 别名路径跳转 - 替换@和/

安装了2之后可以通过以下命令创建初始模版:

1
vbase-3-setup

创建一个如下的空模版

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>

</div>
</template>

<script setup>

</script>

<style lang="scss" scoped>

</style>

但是现在vue默认的模版中template已经不需要div包裹了,因此我们需要修改一下插件的格式,进入C:\Users\用户名\.vscode\extensions\sdras.vue-vscode-snippets-3.1.1\snippets,找到vue.json,搜索vbase-3-setupvbase-3-ts-setup,将divlang="scss"删除,将template和script调换位置。

修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"Vue Single File Component Setup Composition API": {
"prefix": "vbase-3-setup",
"body": [
"<script setup>",
"",
"</script>",
"",
"<template>",
"",
"</template>",
"",
"<style scoped>",
"",
"</style>"
],
"description": "Base for Vue File Setup Composition API with SCSS"
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"Vue Single File Component Setup Composition API with Typescript": {
"prefix": "vbase-3-ts-setup",
"body": [
"<script setup lang=\"ts\">",
"",
"</script>",
"",
"<template>",
"",
"</template>",
"",
"<style scoped>",
"",
"</style>"
],
"description": "Base for Vue File Setup Composition API - Typescript"
},

修改完记得重启VSCode

接下来在vue文件中编写html标签会出现没有提示的情况,这时候可以在设置中搜索includeLanguages,在Emmet中添加至如下参数,重启后编写就有提示。

image-20240510163949692

或者直接在settings.json中添加如下设置文件:

1
2
3
4
"emmet.includeLanguages": {
"vue-html": "html",
"vue": "html"
}

这时候如果返回去打开其他html文件会发现ts-plugin这个插件导致全部Code冒红,这时候在在设置中搜索Validate,然后关闭TyScript的语法检查。

这个插件开启后VSCode的所有html都会冒红

代码迁移到Vue

之前使用html编写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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<div id="app">
{{web.show}}
<!-- v-show本质是生成了css的display属性-->
<p v-show="web.show">gcnanmu学Vue</p>

<hr>

<button @click="toggle">切换显示状态</button>
</div>

<script type="module">
import {createApp, reactive} from "./js/vue.esm-browser";

createApp({
setup() {


const web = reactive({
show: true
})

const toggle = () => {
web.show = !web.show
}

return {
web,
toggle
}
}
}).mount("#app")
</script>
</body>
</html>

如果要将其迁移到vue文件中只需要这样编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script setup>
import { reactive } from "vue";
const web = reactive({
show: true,
});

const toggle = () => {
web.show = !web.show;
};
</script>

<template>
{{ web.show }}
<!-- v-show本质是生成了css的display属性-->
<p v-show="web.show">gcnanmu学Vue</p>

<hr />

<button @click="toggle">切换显示状态</button>
</template>

<style scoped>
</style>

可以看到

  1. 不再需要编写return 和 setup
  2. 导入的时候只需要指定vue,不再需要指定到特定的vue.js
  3. 想要预览需要在终端使用npm run dev命令

父组件与子组件

在Vite中,我们一般都将vue组件放在components文件夹中,组件其实就是vue文件,我们先在components中创建header.vuefooter.vue两个组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup>

</script>

<template>
<!-- header组件写header -->
<h3>header</h3>
<!-- footer组件写footer -->
<h3>footer</h3>
</template>

<style scoped>

</style>

之后在App.vue中导入两个组件

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
import Header from "./components/header";
import Footer from "./components/footer";
</script>

<template>
<Header />
hello world
<Footer />
</template>

<style scoped></style>

可以看到导入和在template中使用的时候组件的名字需要大写。在这个例子中,App.vue是父组件,header.vuefooter.vue是子组件。

使用npm run dev命令运行,可以在显示出的页面中看到效果。

父子组件传值

父传子 defineProps

使用语法为:

父组件

1
2
3
4
5
6
<Header propsName="百度" propWeb="baidu.com" />

<button @click="userAdd">添加用户</button>
<!-- 传递响应式数据 -->
<!-- <Footer v-bind="propWeb"/> -->
<Footer :="propWeb"/>

子组件

1
2
3
4
<script setup>
const props = defineProps(["propsName", "propWeb"]);
console.log(props);
</script>

或者以对象的方式接收

1
2
3
4
5
6
7
8
9
<script setup>
const props = defineProps({
users:Number,
url:String
})

console.log(props.users);
console.log(props.url);
</script>

也可以在组件中设定一些传递的规则:

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
const props = defineProps({
users: Number,
url: {
type: String,
// 设置为必传的属性
required: true,
// 设置初始值
default: "baidu.com",
},
})
</script>

完整代码:

App.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
<script setup>
import Header from "./components/header.vue";
import Footer from "./components/footer.vue";
import { reactive } from "vue";

const propWeb = reactive({
users: 10,
url: "www.baidu.com",
});

const userAdd = () => {
propWeb.users++;
console.log(propWeb.users);
};
</script>

<template>
<Header propsName="百度" propWeb="baidu.com" />
<button @click="userAdd">添加用户</button>
<!-- 传递响应式数据 -->
<!-- <Footer v-bind="propWeb"/> -->
<Footer :="propWeb" />
</template>

<style scoped></style>

Header.vue

1
2
3
4
5
6
7
8
9
10
<script setup>
const props = defineProps(["propsName", "propWeb"]);
console.log(props);
</script>

<template>
<h3>header</h3>
</template>

<style scoped></style>

Footer.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup>
const props = defineProps({
users: Number,
url: {
type: String,
// 设置为必传的属性
required: true,
// 设置初始值
default: "baidu.com",
},
})

console.log(props);
</script>

<template>
<!-- header组件写header -->
<h3>footer</h3> {{ props.users }}
</template>

<style scoped></style>

子传父 defineEmits

语法为:

子组件:

1
2
3
4
5
6
7
8
<script setup>
const emits = defineEmits(["getWeb","addUser"])
emits("getWeb","www.baidu.com")

const add = () =>{
emits("addUser",10)
}
</script>

父组件:

1
2
3
<template>
<Header @getWeb="emitsGetWeb" @addUser="emitsUserAdd"/>
</template>

完整代码:

App.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
28
29
30
<script setup>
import Header from "./components/header.vue";
import { reactive,ref } from "vue";

const web = reactive({
name:"百度",
url: "baidu.com",
})

let users = ref(0);

const emitsGetWeb = (data) =>{
web.url = data;
console.log(web.url);
}

const emitsUserAdd = (data) =>{
users.value += data;
console.log(users.value);
}
</script>

<template>
<Header @getWeb="emitsGetWeb" @addUser="emitsUserAdd"/>

{{ web.url }}
{{ users }}
</template>

<style scoped></style>

header.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
const emits = defineEmits(["getWeb","addUser"])
emits("getWeb","www.baidu.com")

const add = () =>{
emits("addUser",10)
}
</script>

<template>
<h3>header</h3>
<button @click="add">添加用户</button>
</template>

<style scoped></style>

跨组件通信 provide inject

这个方法只能实现父组件向子组件传递数据,父组件使用provide,子组件使用的是inject

语法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script setup>
import Header from "./components/header.vue";
import { reactive, ref, provide } from "vue";

const web = reactive({
name: "百度",
url: "baidu.com",
})

provide("provideWeb", web);

let users = ref(0);

const userAdd = () => {
users.value++;
}

provide("provideFuncUserAdd", userAdd);

</script>

子组件语法为:

1
2
3
4
5
6
7
<script setup>
import {inject} from "vue";

const web = inject("provideWeb");

const funcUserAdd = inject("provideFuncUserAdd");
</script>

完整代码为:

App.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
28
29
<script setup>
import Header from "./components/header.vue";
import { reactive, ref, provide } from "vue";

const web = reactive({
name: "百度",
url: "baidu.com",
})

provide("provideWeb", web);

let users = ref(0);

const userAdd = () => {
users.value++;
}

provide("provideFuncUserAdd", userAdd);

</script>

<template>
<h3>App top</h3>
users:{{ users }}

<Header/>
</template>

<style scoped></style>

header.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import Nav from "./nav.vue";
import { inject } from "vue";

const funcUserAdd = inject("provideFuncUserAdd");
console.log("funcUserAdd", funcUserAdd);
</script>

<template>
<h3>header middle</h3>
<Nav/>
<button @click="funcUserAdd">添加用户</button>
</template>

<style scoped></style>

nav.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>

import {inject} from "vue";

const web = inject("provideWeb");
console.log("provideWeb",web);

</script>

<template>
<h3>nav button</h3>
</template>

<style scoped>

</style>

匿名插槽 v-slot

我们一开始渲染子组件使用的都是自闭和的形式<Header /><Footer />,插槽即为<Header><Header/>

匿名插槽的语法为:

父组件:

1
2
3
<Header>
<a href="www.baidu.com">百度</a>
</Header>

子组件:

1
<slot />

如果想要使用name进行标签的定义,可以这样写:

父组件:

1
2
3
4
5
6
7
<Footer >
<!-- <template v-slot:url> -->
<!-- 可以简写为如下形式 -->
<template #url>
<a href="tencent.com">腾旭</a>
</template>
</Footer>

子组件:

1
<slot name="url" />

完整代码:

App.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
<script setup>
import Header from './components/header.vue';
import Footer from './components/footer.vue';

</script>

<template>
<h3>App vue</h3>

<Header>
<a href="www.baidu.com">百度</a>
</Header>


<Footer >
<!-- <template v-slot:url> -->
<!-- 可以简写为如下形式 -->
<template #url>
<a href="tencent.com">腾旭</a>
</template>
</Footer>
</template>

<style scoped>

</style>

Header.vue

1
2
3
4
5
6
7
8
9
10
<script setup>

</script>

<template>
<h3>header vue</h3>
<slot />
</template>

<style scoped></style>

Footer.vue

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>

</script>

<template>
<h3>footer vue</h3>
<slot name="url"/>
</template>

<style scoped>

</style>

作用域插槽 #name

他的作用是来让子组件向父组件传递数据,并在父组件的模版中渲染

语法为:

子组件:

1
<slot name="url" title="vue" url="vuejs.org" />

父组件:

1
2
3
4
5
6
7
8
<!-- <template #url="data"> -->
<!-- {{ data.title }}
{{ data.url }} -->
<template #url="{title, url}">
{{ title }}
{{ url }}
<a href="tencent.com">腾旭</a>
</template>

生命周期函数

  • 挂载阶段 - onMounted onBeforeMount
  • 更新阶段 - onBeforeUpdate onUpdated
  • 卸载阶段 - onBeforeUnmout onUnmounted
  • 错误处理 - onErrorCoptured

下面是一个挂载和更新的例子:

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
<script setup>
import { onMounted,onUpdated,ref } from 'vue';

onMounted(() => {
console.log('App.vue mounted');
});

onUpdated(() => {
console.log('App.vue updated');
});

const user =ref(0)

console.log("user:",user);

</script>

<template>
{{ user }}
<button @click="user++">添加用户</button>

</template>

<style scoped>

</style>

当网页刚加载时,会打印“App.vue mounted”,当按钮被点击的时候,控制台会打印出多次”App.vue updated”。

toRef和toRefs

这两个方式是为了将响应式对象的属性转化为Ref对象。

  • toRef - 将单个属性转化
  • toRefs - 将整个对象转化
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
<script setup>
import { reactive, toRefs, toRef } from 'vue';

let web = reactive({
name: "百度",
url: "www.baidu.com"
})

// 结构后打印得到的类型是字符串
console.log(typeof web.url)

// 将整个对象转化为toRefs
let { name, url } = toRefs(web)

// 结构后打印得到的类型是对象
console.log("name:", name, "url:", url)

// 将整个对象的某个属性
const changeUrl = toRef(web.name)
</script>

<template>
{{ web.url }}
{{ changeUrl }}
</template>

<style scoped></style>