文章目录
- 1. vite 的基本使用
- 2. 组件的基本使用
- 3. 自定义事件
- 4. 组件上的 v-model
- 5. 组件的生命周期
- 6. 组件之间的数据共享
- 7. vue 3.x 中全局配置 axios
- 8. 自定义指令
- 9. vue-router
- 10. 常用组合式API
- 11. 其他组合式API
- 12. 新的组件
- 13. vue响应式原理
- 14. <script setup\> 语法糖
- 15. Pinia
- 16. 其他改动
1. vite 的基本使用
vite官网:https://vitejs.cn
vue官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
- vite:新一代前端构建工具。
- 相比 webpack 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
1.1 创建vite的项目
按照顺序执行如下的命令,即可基于vite 创建 vue 3.x 的工程化项目:
npm init vite@latest
// 中间根据提示来构建vue3项目
cd deom
npm install
npm run dev
1.2 梳理项目的结构
使用 vite 创建的项目结构如下:
- node_modules 目录用来存放第三方依赖包
- public 是公共的静态资源目录
- src 是项目的源代码目录(程序员写的所有代码都要放在此目录下)
- .gitignore 是 Git 的忽略文件
- index.html 是SPA 单页面应用程序中唯一的HTML 页面
- package.json 是项目的包管理配置文件
- vite.config.js 是 vite 的配置文件
在 src 这个项目源代码目录之下,包含了如下的文件和文件夹:
- assets 目录用来存放项目中所有的静态资源文件(css、fonts等)
- components 目录用来存放项目中所有的自定义组件
- App.vue 是项目的根组件
- index.css 是项目的全局样式表文件(上图没有)
- main.js 是整个项目的打包入口文件
1.3 vite 项目的运行流程
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。其中:
① App.vue 用来编写待渲染的模板结构
② index.html 中需要预留一个el 区域
③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中
- 步骤一:在 App.vue 中编写模板结构
清空 App.vue 的默认内容,并书写如下的模板结构:
<template>
<h1>这是App.vue根组件</h1>
</template>
- 步骤二:打开 index.html 页面,确认预留了el 区域:
<body>
<!--id 为app的div元素,就是将来vue要控制的区域 -->
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
- 步骤三:在main.js 中进行渲染
按照 vue 3.x 的标准用法,把 App.vue 中的模板内容渲染到index.html 页面的el 区域中:
// 1.从vue中按需引入一个名为createApp的工厂函数:作用创建vue的“单页面应用程序实例”
import {
createApp } from "vue";
// 2. 导入待渲染的App组件
import App from "./App.vue";
// 3.调用createApp()函数,返回值是“单页面应用程序实例对象”,用常量spa_app接收
// 同时把App组件作为参数传给createApp函数,表示要把App渲染到index.html页面上
const spa_app = createApp(App);
// 4. 挂载:调用spa_app实例的mount方法,用来指定vue实际要控制的区域
spa_app.mount("#app");
// createApp(App).mount("#app"); 可以简写
建议安装vue3开发者工具插件
2. 组件的基本使用
2.1 注册组件的两种方式
vue 中注册组件的方式分为“全局注册”和“局部注册”两种,其中:
- 被全局注册的组件,可以在全局任何一个组件内使用
- 被局部注册的组件,只能在当前注册的范围内使用
2.2 全局注册组件
使用 app.component()
方法注册的全局组件,直接以标签的形式进行使用即可
import {
createApp } from "vue";
import App from "./App.vue";
// 1.在main.js文件中, 导入需要被全局注册的组件
import Left from "./components/Left.vue";
import Right from "./components/Right.vue";
const spa_app = createApp(App);
// 2. 调用spa_app实例的component()方法全局注册组件
spa_app.component("my-left", Left);
spa_app.component("my-right", Right);
spa_app.mount("#app");
<template>
<h1>这是App.vue根组件</h1>
<!-- 在其他组件中,直接以标签的形式,使用全局注册过的组件 -->
<my-left></my-left>
<my-right></my-right>
</template>
2.3 局部祖册
<template>
<h1>这是App.vue根组件</h1>
<my-left></my-left>
</template>
<script>
// 导入需要的组件
import Left from "./components/Left.vue";
export default {
// 2. 通过 components节点,为当前组件注册私有子组件
components: {
"my-left": Left,
},
};
</script>
2.4 全局注册和局部注册的区别
- 被全局注册的组件,可以在全局任何一个组件内使用
- 被局部注册的组件,只能在当前注册的范围内使用
应用场景:
- 如果某些组件在开发期间的使用频率很高,推荐进行全局注册;
- 如果某些组件只在特定的情况下会被用到,推荐进行局部注册。
2.5 组件注册时名称的大小写
在进行组件的注册时,定义组件注册名称的方式有两种:
- ① 使用 kebab-case 命名法(俗称短横线命名法,例如 my-swiper 和 my-search)
短横线命名法的特点:必须严格按照短横线名称进行使用 - ② 使用 PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper 和 MySearch)
帕斯卡命名法的特点:既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用
2.6 通过 name 属性注册组件
在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称,示例代码如下:
3. 自定义事件
3.1 什么是自定义事件
在封装组件时,为了让组件的使用者可以监听到组件内状态的变化,此时需要用到组件的自定义事件。
3.2 自定义事件的 3 个使用步骤
- 在封装组件时:
① 声明自定义事件
② 触发自定义事件 - 在使用组件时:
③ 监听自定义事件
3.2.1 声明自定义事件
开发者为自定义组件封装的自定义事件,必须事先在 emits 节点中声明,示例代码如下:
<template>
<div>
<h2>Counter 组件</h2>
<p>count 的值为 {
{
count }}</p>
<button @click="add">点击加1</button>
</div>
</template>
<script>
export default {
// 1.申明自定义事件
emits: ["countChange"],// 为当前Counter组件在emits节点下申明自定义事件
</script>
3.2.2 触发自定义事件
在 emits 节点下声明的自定义事件,可以通过 this.$emit('自定义事件的名称')
方法进行触发,示例代码如下:
<template>
<div>
<h2>Counter 组件</h2>
<p>count 的值为 {
{ count }}</p>
<button @click="add">点击加1</button>
</div>
</template>
<script>
export default {
// 1.申明自定义事件
emits: ["countChange"],
data() {
return {
count: 0,
};
},
methods: {
add() {
this.count++;
// 2. this.$emit()触发自定义事件,
// 参数为在emits节点下申明过的自定义事件名
this.$emit("countChange");
// 当点击加1按钮,调用了add()函数,自动触发了countChange事件
},
},
};
</script>
3.2.3 监听自定义事件
在使用自定义的组件时,可以通过 v-on (简写@)的形式监听自定义事件。示例代码如下:
<template>
<div class="app-container">
<h1>这是App.vue根组件</h1>
<!-- 当前在app组件中,使用了Counter组件,并监听了countChange事件-->
<!--一旦监听到了Counter组件的countChange事件,就调用getCount函数-->
<Counter @countChange="getCount"></Counter>
</div>
</template>
<script>
import Counter from "./components/Counter.vue";
export default {
components: {
Counter,
},
methods: {
getCount() {
console.log("触发了countChange自定义事件");
},
},
};
</script>
3.3 自定义事件传参
在调用 this.$emit()
方法触发自定义事件时,可以通过第 2 个参数为自定义事件传参,示例代码如下:
// ======事件触发方
methods: {
add() {
this.count++;
// 2. 触发自定义事件时,通过第二个参数传参
this.$emit("countChange", this.count);
},
},
//===========事件监听方
methods: {
getCount(val) {
console.log("触发了countChange自定义事件", val);
},
},
4. 组件上的 v-model
4.1 为什么需要在组件上使用 v-model
v-model 是双向数据绑定指令,当需要维护组件内外数据的同步时,可以在组件上使用 v-model 指令。示意图如下:
counter 组件中数据的变化,也会自动同步到外界
外界数据的变化会自动同步到 counter 组件中
4.2 在组件上使用 v-model 的步骤
4.2.1 父向子
① 父组件通过 v-bind:
属性绑定的形式,把数据传递给子组件
② 子组件中,通过 props
接收父组件传递过来的数据
实现点击父组件app中加1按钮,把数据同步到子组件中
<!-- ===================App父组件 -->
<template>
<div class="app-container">
<h1>这是App.vue根组件---{
{ count }}</h1>
<!-- 通过number自定义属性,给 Counter 组件传一个 count 的值 -->
<Counter v-model="count" :number="count"></Counter>
<button @click="count++">点击加1</button>
</div>
</template>
<script>
import Counter from "./components/Counter.vue";
export default {
data() {
return {
count: 0,
};
},
components: {
Counter,
},
};
</script>
<!-- =========================Counter子组件 -->
<template>
<div>
<h2>Counter 组件</h2>
<p>count 的值为 {
{ number }}</p>
</div>
</template>
<script>
export default {
// 接收从外部传来的数据
props: ["number"],
};
</script>
4.2.2 子向父
① 在 v-bind:
指令之前添加 v-model
指令
② 在子组件中声明 emits 自定义事件,格式为 update:xxx
③ 调用 $emit()
触发自定义事件,更新父组件中的数据
点击子组件的加1按钮,把数据同步给父组件
//==================app父组件
<template>
<div class="app-container">
<h1>这是App.vue根组件---{
{ count }}</h1>
<Counter v-model:number="count"></Counter>
</div>
</template>
<script>
import Counter from "./components/Counter.vue";
export default {
data() {
return {
count: 0,
};
},
components: {
Counter,
},
};
</script>
// =================== Counter子组件
<template>
<div>
<h2>Counter 组件</h2>
<p>count 的值为 {
{ number }}</p>
<button @click="add">点击加1</button>
</div>
</template>
<script>
export default {
props: ["number"],
emits: ["update:number"],
methods: {
add() {
this.$emit("update:number", this.number + 1);
},
},
};
</script>
5. 组件的生命周期
注意:选项式 API 和组合式 API 的生命周期有差异,具体参照vue3官方;下面以选项式为例
5.1 组件运行的过程
组件的生命周期指的是:组件从创建 -> 运行(渲染) -> 销毁的整个过程,强调的是一个时间段。
5.2 如何监听组件的不同时刻
vue 框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。例如:
① 当组件在内存中被创建完毕之后,会自动调用 created 函数
② 当组件被成功的渲染到页面上之后,会自动调用 mounted
函数
③ 当组件被销毁完毕之后,会自动调用 unmounted
函数
5.3 如何监听组件的更新
当组件的 data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证 View 视图展示的数据和 Model 数据源保持一致。
当组件被重新渲染完毕之后,会自动调用 updated
生命周期函数。
5.4 组件中主要的生命周期函数
生命周期函数 | 执行时机 | 所属阶段 | 执行次数 | 应用场景 |
---|---|---|---|---|
created | 组件在内存中创建完毕后 | 创建阶段 | 唯一1次 | 发 ajax 请求初始数据 |
mounted | 组件初次在页面中渲染完毕后 | 创建阶段 | 唯一1次 | 操作 DOM 元素 |
updated | 组件在页面中被重新渲染完毕后 | 运行阶段 | 0或以上 | - |
unmounted | 组件被销毁后(页面和内存) | 销毁阶段 | 唯一1次 | - |
5.5 组件中全部的生命周期函数
生命周期函数 | 执行时机 | 所属阶段 | 执行次数 | 应用场景 |
---|---|---|---|---|
beforeCreate | 在内存中开始创建组件之前 | 创建阶段 | 唯一1次 | - |
created | 组件在内存中创建完毕后 | 创建阶段 | 唯一1次 | 发 ajax 请求初始数据 |
beforeMount | 在把组件初次渲染到页面之前 | 创建阶段 | 唯一1次 | - |
mounted | 组件初次在页面中渲染完毕后 | 创建阶段 | 唯一1次 | 操作 DOM 元素 |
beforeUpdate | 在组件被重新渲染之前 | 运行阶段 | 唯一1次 | 操作 DOM 元素 |
updated | 组件在页面中被重新渲染完毕后 | 运行阶段 | 0或 以上 | - |
beforeUnmount | 在组件被销毁之前 | 销毁阶段 | 唯一1次 | - |
unmounted | 组件被销毁后(页面和内存) | 销毁阶段 | 唯一1次 | - |
疑问:为什么不在 beforeCreate 中发 ajax 请求初始数据?
答:在 beforeCreate生命周期函数中,data还没初始化,不能访问data里面的数据。 所以ajax 请求回来的数据没地方存,在 beforeCreate 中发 ajax 请求初始数据没有意义。
5.6 完整的生命周期图示
可以参考 vue 官方文档给出的 “生命周期图示 ”,进一步理解组件生命周期执行的过程:
6. 组件之间的数据共享
6.1 组件之间的关系
在项目开发中,组件之间的关系分为如下 3 种:
① 父子关系(AB,AC)
② 兄弟关系(BC,BE)
③ 后代关系(AD,AJ)
6.2 父子组件之间的数据共享
父子组件之间的数据共享又分为:
① 父 -> 子共享数据
② 子 -> 父共享数据
③ 父 <-> 子双向数据同步
6.2.1 父组件向子组件共享数据
父组件通过 v-bind
(简写:
)属性绑定向子组件共享数据。同时,子组件需要使用 props 接收数据。示例代码如下:
子组件:
<template>
<div>
<h1>Myleft组件</h1>
<p>{
{
msg }}</p>
<p>名字:{
{
user.name }}年龄:{
{
user.age }}</p>
</div>
</template>
<script>
export default {
props: ["msg", "user"],
};
</script>
父组件:
<template>
<div>
<h1>App根组件</h1>
<Myleft :msg="message" :user="userinfo"></Myleft>
</div>
</template>
<script>
import Myleft from "./components/Myleft.vue";
export default {
data() {
return {
message: "hello vue",
userinfo: {
name: "zs", age: 20 },
};
},
components: {
Myleft,
},
};
</script>
6.2.2 子组件向父组件共享数据
子组件通过自定义事件的方式向父组件共享数据。示例代码如下:
父组件:
<template>
<div>
<h1>App根组件</h1>
<!-- 1. 监听子组件的自定时事件n1change -->
<Myleft @n1change="getn1"></Myleft>
<p>{
{
n1FromSon }}</p>
</div>
</template>
<script>
import Myleft from "./components/Myleft.vue";
export default {
data() {
return {
n1FromSon: 0,
};
},
methods: {
// 2. 通过形参,接收子组件传递过来的参数
getn1(n1) {
this.n1FromSon = n1;
},
},
components: {
Myleft,
},
};
</script>
子组件:
<template>
<div>
<h1>Myleft组件</h1>
<p>{
{
n1 }}</p>
<button @click="addN1">点我加1</button>
</div>
</template>
<script>
export default {
emits: ["n1change"], // 声明自定义事件
data() {
return {
n1: 0,
};
},
methods: {
addN1() {
this.n1++;
// 数据变化时,触发自定义事件,并传了参数
this.$emit("n1change", this.n1);
},
},
};
</script>
6.2.3 父子组件之间数据的双向同步
父组件在使用子组件期间,可以使用 v-model
指令维护组件内外数据的双向同步:
父组件:
<template>
<div>
<h1>App根组件的 count 为{
{
count }}</h1>
<button @click="count++">App的按钮加1</button>
<Counter v-model:number="count"></Counter>
<p></p>
</div>
</template>
<script>
import Counter from "./components/Counter.vue";
export default {
data() {
return {
count: 0,
};
},
components: {
Counter,
},
};
</script>
子组件:
<template>
<div>
<h1>Counter组件的 number 为 {
{
number }}