Vue3组件化开发
# Vue打包后不同版本解析
vue(.runtime).global(.prod).js:
通过浏览器中的
<script src="...">
直接使用;我们之前通过CDN引入和下载的Vue版本就是这个版本;
会暴露一个全局的Vue来使用;
vue(.runtime).esm-browser(.prod).js:
- 用于通过原生 ES 模块导入使用 (在浏览器中通过
<script type="module">
来使用)。
vue(.runtime).esm-bundler.js:
用于 webpack,rollup 和 parcel 等构建工具;
构建工具中默认是
vue.runtime.esm-bundler.js
;如果我们需要解析模板template,那么需要手动指定
vue.esm-bundler.js
;
vue.cjs(.prod).js:
服务器端渲染使用;
通过require()在Node.js中使用;
# 全局组件和局部组件
全局组件所有的组件里面都可以使用
app.component("component-b",{}); // 注册全局组件
const App = {
template: '#my-app',
components: {
// key: 组件名称
// value: 组件对象
ComponentA: ComponentA
}, ...
}
2
3
4
5
6
7
8
# 父子组件之间通信
父子组件之间如何进行通信呢?
父组件传递给子组件:通过
props
属性;子组件传递给父组件:通过
$emit
触发事件;
# 1、子组件props属性
什么是Props呢?
Props是你可以在组件上注册一些自定义的attribute;
父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值;
# Props有两种常见的用法
方式一:字符串数组,数组中的字符串就是attribute的名称;
方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等
数组用法中我们只能说明传入的attribute的名称,并不能对其进行任何形式的限制,接下来我们来看一下对象的写法是如何让我们的props变得更加完善的。
当使用对象语法的时候,我们可以对传入的内容限制更多:
比如指定传入的attribute的类型;
比如指定传入的attribute是否是必传的;
比如指定没有传入时,attribute的默认值;
type的类型都可以是哪些
String、Number、Boolean、Array、Object、Date、Function、Symbol
Prop 的大小写命名(camelCase vs kebab-case)
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符;
这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
export default {
// props: ['title', 'content']
inheritAttrs: false, // 不继承父元素传来的非prop属性,可以使用$attrs获得
props: {
title: String,
content: {
type: String,
required: true,
default: "123"
},
counter: {
type: Number
},
info: {
type: Object, // 引用类型,需要传值
default() {
return {name: "why"}
}
},
messageInfo: {
type: String
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 非Prop的Attribute
当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的Attribute**;
常见的包括class、style、id属性等;
Attribute继承
当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中:
如果我们不希望组件的根元素继承attribute,可以在组件中设置 inheritAttrs: false:
- 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素;
我们可以通过 $attrs
来访问所有的 非props的attribute;
多个根节点的attribute
多个根节点的attribute如果没有显示的绑定,那么会报警告,我们必须手动的指定要绑定到哪一个属性上:
# 2、子组件传递给父组件
# 开发流程
首先,我们需要在子组件中定义好在某些情况下触发的事件名称;
其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中;
最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件;
# 自定义事件的参数和验证
export default {
// emits: ["add", "sub", "addN"],
// 对象写法的目的是为了进行参数的验证
emits: {
add: null,
sub: null,
addN: (num, name, age) => {
console.log(num, name, age);
if (num > 10) {
return true
}
return false;
}
},
data() {
return {
num: 0
}
},
methods: {
increment() {
console.log("+1");
this.$emit("add");
},
decrement() {
console.log("-1");
this.$emit("sub");
},
incrementN() {
this.$emit('addN', this.num, "why", 18);
}
}
}
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
# 非父子组件间通住
# Provide/Inject
无论层级结构有多深,父组件都可以作为其所有子组件的依赖提供者;
父组件有一个
provide
选项来提供数据,建议写成函数,可以通过this拿data里面的数据;子组件有一个
inject
选项来开始使用这些数据;
处理响应式数据
computed返回的是一个ref对象,需要取出其中的value来使用
import { computed } from 'vue';
export default {
components: {
Home
},
provide() { // 建议写成函数,通过this拿到data里面的数据
return {
name: "why",
age: 18,
length: computed(() => this.names.length) // ref对象 .value
}
},
2
3
4
5
6
7
8
9
10
11
12
13
Provide / Inject | Vue.js (vuejs.org) (opens new window)
# Mitt全局事件总线
Vue3从实例中移除了 $on、$off 和 $once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库:
- Vue3官方有推荐一些库,例如 mitt 或 tiny-emitter
npm install mitt
以封装一个工具eventbus.js
import mitt from 'mitt';
const emitter = mitt(); // 可以生成多个
export default emitter;
2
3
# 插槽slot
# 具名插槽
具名插槽顾名思义就是给插槽起一个名字,<slot>
元素有一个特殊的 attribute:name;
一个不带 name 的slot,会带有隐含的名字 default
我们可以通过
v-slot:[dynamicSlotName]
方式动态绑定一个名称v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #;
<nav-bar :name="name">
<template v-slot:left>
<button>左边的按钮</button>
</template>
<template #center>
<h2>我是标题</h2>
</template>
<template #right>
<i>右边的i元素</i>
</template>
<template v-slot:[name]>
<i>why内容</i>
</template>
</nav-bar>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 渲染作用域
父级模板里的所有内容都是在父级作用域中编译的;
子模板里的所有内容都是在子作用域中编译的;
# 作用域插槽
子组件通过绑定slot的属性将值传给父组件,父组件通过v-slot:default="slotProps"
的方式获取到slot的props
# 独占默认插槽的缩写
如果我们的插槽是默认插槽default,那么在使用的时候 v-slot:default="slotProps"可以简写为v
slot="slotProps":
<!-- 独占默认插槽 -->
<show-names :names="names" v-slot="coderwhy">
<button>{{coderwhy.item}}-{{coderwhy.index}}</button>
</show-names>
<!-- 注意: 如果还有其他的具名插槽, 那么默认插槽也必须使用template来编写 -->
<show-names :names="names">
<template v-slot="coderwhy">
<button>{{coderwhy.item}}-{{coderwhy.index}}</button>
</template>
<template v-slot:why>
<h2>我是why的插入内容</h2>
</template>
</show-names>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template v-for="(item, index) in names" :key="item">
<slot :item="item" :index="index"></slot>
<slot name="why"></slot>
</template>
2
3
4
# 动态组件
动态组件是使用 component 组件,通过一个特殊的attributeis
来实现
<component :is="currentTab"
name="coderwhy"
:age="18"
@pageClick="pageClick">
</component>
<!-- 1.v-if的判断实现 -->
<!-- <template v-if="currentTab === 'home'">
<home></home>
</template>
<template v-else-if="currentTab === 'about'">
<about></about>
</template>
<template v-else>
<category></category>
</template> -->
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# keep-alive
默认情况下,我们在切换组件后,about组件会被销毁掉,再次回来时会重新创建组件; 但是,在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉,这个时候我们就可以使用一个内置组件:keep-alive
keep-alive有一些属性:
include - string | RegExp | Array。只有名称匹配的组件会被缓存;
exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存;
max - number | string。最多可以缓存多少组件实例,一旦达到这个数字,那么缓存组件中最近没有被访问的实例会被销毁;
include 和 exclude prop 允许组件有条件地缓存:
二者都可以用逗号分隔字符串、正则表达式或一个数组来表示;
匹配首先检查组件自身的 name 选项
# 缓存组件的生命周期
对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数的,但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件;这个时候我们可以使用activated
和 deactivated
这两个生命周期钩子函数来监听
# v-model
前面我们在input中可以使用v-model来完成双向绑定:
p这个时候往往会非常方便,因为v-model默认帮助我们完成了两件事;
v-bind:value
的数据绑定和@input
的事件监听;
如果我们现在封装了一个组件,其他地方在使用这个组件时,是否也可以使用v-model来同时完成这两个功能呢?
也是可以的,vue也支持在组件上使用v-model;
当我们在组件上使用的时候,等价于如下的操作:
- 我们会发现和input元素不同的只是属性的名称
modelValue
和事件触发的名称update:modelValue
而已;
<!-- <input v-model="message">
<input :value="message" @input="message = $event.target.value"> -->
<!-- 组件上使用v-model -->
<!-- <hy-input v-model="message"></hy-input> -->
<!-- <hy-input :modelValue="message" @update:model-value="message = $event"></hy-input> -->
<!-- 绑定两个v-model v-model:XXX -->
<hy-input v-model="message" v-model:title="title"></hy-input>
2
3
4
5
6
7
8
9
<!-- 1.默认绑定和事件处理 -->
<!-- <button @click="btnClick">hyinput按钮</button>
<h2>HyInput的message: {{modelValue}}</h2> -->
<!-- 2.通过input -->
<!-- <input :value="modelValue" @input="btnClick"> -->
<!-- 3.绑定到props中是不对的 -->
<!-- <input v-model="modelValue"> -->
<!-- 4. -->
<input v-model="value">
export default {
props: {
modelValue: String,
title: String
},
emits: ["update:modelValue", "update:title"],
computed: {
value: {
set(value) {
this.$emit("update:modelValue", value);
},
get() {
return this.modelValue;
}
},
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
自定义事件 | Vue.js (vuejs.org) (opens new window)
# 异步组件
# defineAsyncComponent
如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理,在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了实现这个效果,Vue 有一个 defineAsyncComponent
方法
defineAsyncComponent接受两种类型的参数:
类型一:工厂函数,该工厂函数需要返回一个Promise对象;
类型二:接受一个对象类型,对异步函数进行配置;
<suspense>
<template #default>
<async-category></async-category>
</template>
<template #fallback>
<loading></loading>
</template>
</suspense>
import { defineAsyncComponent } from 'vue';
import Home from './Home.vue';
import Loading from './Loading.vue';
// import AsyncCategory from './AsyncCategory.vue';
const AsyncCategory = defineAsyncComponent(() => import("./AsyncCategory.vue"))
// const AsyncCategory = defineAsyncComponent({
// loader: () => import("./AsyncCategory.vue"),
// loadingComponent: Loading,
// // errorComponent,
// // 在显示loadingComponent组件之前, 等待多长时间
// delay: 2000,
// /**
// * err: 错误信息,
// * retry: 函数, 调用retry尝试重新加载
// * attempts: 记录尝试的次数
// */
// onError: function(err, retry, attempts) {
// }
// })
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
# 与 Suspense 一起使用
异步组件在默认情况下是可挂起的。这意味着如果它在父链中有一个 <Suspense>
,它将被视为该 <Suspense>
的异步依赖。在这种情况下,加载状态将由 <Suspense>
控制,组件自身的加载、错误、延迟和超时选项都将被忽略。
通过在其选项中指定 suspensible: false
,异步组件可以退出 Suspense
控制,并始终控制自己的加载状态。
你可以在 API 参考 (opens new window)查看更多可用的选项
Suspense是一个内置的全局组件,该组件有两个插槽:
default:如果default可以显示,那么显示default的内容;
fallback:如果default无法显示,那么会显示fallback插槽的内容;
参考:
动态组件 & 异步组件 | Vue.js (vuejs.org) (opens new window)
# 组件生命周期
什么是生命周期呢?
每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程;
在这个过程中的某一个阶段,用于可能会想要添加一些属于自己的代码逻辑(比如组件创建完后就请求一些服务器数据);
但是我们如何可以知道目前组件正在哪一个过程呢?Vue给我们提供了组件的生命周期函数;
生命周期函数:
生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调;
通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段;
那么我们就可以在该生命周期中编写属于自己的逻辑代码了;
# 引用元素和组件
# $refs
某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例:
在Vue开发中我们是不推荐进行DOM操作的;
这个时候,我们可以给元素或者组件绑定一个ref的attribute属性;
组件实例有一个$refs
属性:
p它一个对象Object,持有注册过 ref attribute 的所有 DOM 元素和组件实例
<!-- 绑定到一个元素上 -->
<h2 ref="title">哈哈哈</h2>
<!-- 绑定到一个组件实例上 -->
<nav-bar ref="navBar"></nav-bar>
2
3
4
5
this.$ref.navBar可以拿到组件的data数据和methods方法
# $parent和$root
在Vue3中已经移除了$children的属性,所以不可以使用了。