Vue3 学习笔记
有 Vue2 的基础,笔记只记载之前不熟悉的知识
一、Vue 基本知识
1. Vue3 基本指令
1.1 v-pre
v-pre
用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:
跳过不需要编译的节点,加快编译的速度;
<template>
<div>
<div v-pre>{{message}}</div>
<div>{{message}}</div>
</div>
</template>
- 使用
v-pre
,将不会渲染message
对应的内容,直接显示文本{{message}}
1.2 v-bind 用法
1.2.1 动态绑定属性名称
<template>
<div>
<div :[name]="value"></div>
<!-- 上述渲染后相当于下方 -->
<div abc="cba"></div>
</div>
</template>
<script>
export default {
data(){
return {
name: 'abc',
value: 'cba'
}
}
}
</script>
1.2.2 v-bind绑定一个对象
绑定对象后,会将对象解构,赋值到相应的标签上。
作用于组件时,可一次性将子组件需要的props进行传递
<template>
<div v-bind="info">哈哈哈哈</div>
<div :="info">哈哈哈哈</div>
<!-- 上述渲染后相当于下方 -->
<div name="fct" age="18" height="1.88">哈哈哈哈</div>
</template>
<script>
export default {
data() {
return {
info: {
name: "fct",
age: 18,
height: 1.88
}
}
}
}
</script>
1.3 v-for 中 key 的作用
主要是Diff算法运用
-
key
这个特殊的 attribute 主要作为 Vue 的 虚拟 DOM 算法 提示,在比较新旧节点列表时用于识别 vnode。 -
在没有
key
的情况下,Vue 将使用一种最小化元素移动的算法,并尽可能地就地更新/复用相同类型的元素。源码如图:
-
如果传了
key
,则将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。源码如图:
-
同一个父元素下的子元素必须具有唯一的 key。重复的 key 将会导致渲染异常。
2. 侦听器 watch
2.1 函数形式
ownName
为需要侦听的变量的名称,如:要侦听 data 中 userName 的改变,对应的 侦听器为 userName(new, old){}
。
但是当需要侦听的为对象或其他引用类型的值时,使用函数形式的侦听器只能侦听引用类型数据的地址改变,而不能侦听内部值的改变。
{
data() {
return {
userInfo: { userName: 'fct', age: "18" },
ownName: 'changqing'
}
},
watch: {
// 侦听 ownName
ownName(newValue, oldValue) {
// 处理逻辑
},
// 侦听 userInfo 对象
userInfo(newValue, oldValue) {
// 处理逻辑
},
// 侦听 userInfo 对象中的 userName的改变
"userInfo.userName": function(newValue, oldValue) {
// 处理逻辑
}
}
}
2.2 对象形式
- 相当于函数形式:
watch: {
userInfo: {
handler(newValue, oldValue){
// 处理逻辑
}
}
}
- 深度侦听
watch: {
userInfo: {
handler(newValue, oldValue){
// 处理逻辑
},
deep: true, // 深度侦听,能侦听到对象内值的改变
}
}
- 立即执行(第一次渲染后就执行一次侦听器,而不是等到侦听的值改变再执行第一次)
watch: {
userInfo: {
handler(newValue, oldValue){
// 处理逻辑
},
deep: true, // 深度侦听,能侦听到对象内值的改变
immediate: true // 立即执行
}
}
2.3 $watch API
使用$watch
API来侦听:
- 第一个参数:侦听的源
- 第二个参数:侦听的回调函数callback
- 第三个参数配置选项对象:如deep、immediate
created(){
this.$watch('userInfo', (newValue, oldValue) => {
// 处理逻辑
},{
deep: true,
immediate: true
})
}
3. 组件
3.1 全局注册组件
// app 是全局根实例,APPObj是配置对象
const app = Vue.createApp(appObj);
// 全局注册组件
app.component('my-component', {
// 配置对象
})
二、Vue 组件化
1. $attrs
前提:当父组件传递参数给子组件时:
<son-component class="title" title="标题1" content="内容"></son-component>
对应的子组件有相应的props来接受title
和content
属性,而class属性属于非props属性,Vue默认会将非props属性传递给子组件的根元素来继承。而Vue3中组件可以不用设置根元素来包裹,所以当在子组件不设置根元素的话,vue控制台输出警告。
那如何消除警告?
- 给子组件设置根节点元素。
- 使用
$attrs
取捕获非props属性,并加以使用
<template>
<h2 :class="$attrs.class">陈平安</h2>
<p>{{ title }}</p>
<p>{{ content }}</p>
</template>
<script>
export default {
props: ['title', 'content'],
mounted() {
console.log(this.$attrs);
// { class: 'title' }
}
};
</script>
2.子组件与父组件通信
Vue3 和 Vue2有区别:
Vue3 需要提前注册声明自定义事件。可以使用数组或对象形式
export default { emits: ['自定义事件名称'], // 对象语法允许我们对触发事件的参数进行验证: emits: { '自定义事件名称1':null, '自定义事件名称2': (plaload1,....) => { // 通过返回值为 `true` 还是为 `false` 来判断 // 验证是否通过 } } }
父组件:
<template>
<div class="hello">
<h2>{{ count }}</h2>
<!-- 在子组件上注册事件接收 -->
<count-operator @add="add" @reduce="reduce"></count-operator>
</div>
</template>
<script>
import CountOperator from './CountOperator.vue';
export default {
components: { CountOperator },
data() {
return {
count: 0
};
},
methods: {
add() {
this.count++;
},
reduce() {
this.count--;
}
}
};
</script>
子组件
<template>
<div>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script>
export default {
// 声明自定义事件
emits: ['add', 'reduce'],
methods: {
increment() {
// 触发自定义add
this.$emit('add');
},
decrement() {
// 触发自定义reduce
this.$emit('reduce');
}
}
};
</script>
3. 非父子组件通信
3.1 provide、inject
用于一些深度嵌套的组件,孙组件想要获取父组件的部分内容;
父组件有一个 provide
选项来提供数据;
孙组件有一个 inject
选项来开始使用这些数据;
3.1.1 组件结构
App.vue----->Home.vue----->HomeNavBar.vue
App.vue
export default {
provide: {
fct: '我是app组件'
}
}
HomeNavBar.vue
export default {
inject: ['fct']
}
3.1.2 provide 的函数形式
如果Provide
中提供的一些数据是来自data,那么我们可能会想要通过this来获取。
直接在provide对象中使用 this 会报错,是因为 provide 中的 this 指向的不是vue实例,而是当前vue组件的script标签的根this,而根 this 为 undefined。
要正确使用this,则需要更改provide为函数形式:
<script>
export default {
data() {
return {
names: ['a', 'b']
}
},
provide() {
return {
fct: '我是app组件',
length: this.names.length
}
}
}
</script>
3.1.3 处理响应式数据
在上例的基础上,当我们在names数组中新增数组项,而provide提供给孙组件的数据没有变化,不是响应式数据。
这时我们需要Vue提供的响应式函数来解决:computed
函数
<script>
import { computed } from 'vue'
export default {
data() {
return {
names: ['a', 'b']
}
},
provide() {
return {
fct: '我是app组件',
length: computed(() => this.names.length)
}
}
}
</script>
在孙组件中,取值需要通过.value
取值。因为computed
函数返回的是一个ref对象
。
3.2 全局事件总线 mitt库
Vue3从实例中移除了 $on、$off 和 $once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库:
-
Vue3官方有推荐一些库,例如
mitt
或tiny-emitter
; -
这里我们主要讲解一下
mitt
库的使用;
3.2.1 安装:
npm i mitt
3.2.2 使用:
- 封装工具
eventbus.js
import mitt from 'mitt';
const emitter = mitt();
export default emitter;
- 触发事件
App.vue
<script>
import emitter from './utils/eventBus';
export default {
methods: {
clickEvent() {
emitter.emit('fct', { name: 'fct' });
}
}
};
</script>
- 监听事件
其他组件
<script>
import emitter from './utils/eventBus';
export default {
created() {
// 监听单一事件
emitter.on('fct', (data) => {
console.log('fct-', data);
});
// 监听所有事件
emitter.on('*', (type, data) => {
console.log('* event', type, data);
});
},
};
</script>
- Mitt事件的取消
// 取消mitter中所有的监听
emitter.all.clear()
// 定义一个函数
function onFoo() {}
emitter.on('foo', onFoo); // 监听
emitter.off('foo', onFoo); // 取消监听
4. 插槽
5.动态组件
动态组件有时会配合
keep-alive
使用,因为当使用<component :is="...">
来在多个组件间作切换时,被切换掉的组件会被卸载。通过 KeepAlive组件 强制被切换掉的组件仍然保持“存活”的状态。
6. 异步组件
常用:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
搭配 Suspense 使用
异步组件可以搭配内置的 <Suspense>
组件一起使用, Suspense 中查看。
可进行设定异步组件加载失败后展示的组件
7. $refs、$parent、$root
$refs
获取在template模板中设置有ref属性的标签或组件。
<!-- 模板引用 -->
<input ref="input">
<script>
this.$refs.input.focus()
</script>
$parent
获得当前组件的父组件的引用。
$root
获得当前组件的所在的根组件。
Vue3已经移除
$children
属性
8.组件的v-model
💥💥💥
环境为:
Vue3
1. 原生元素上的 v-model
<p>{{ msg }}</p>
<input v-model="msg" />
<!-- 上面的代码其实等价于下面这段 (编译器会对 v-model 进行展开): -->
<input :value="msg" @input="msg = $event.target.value" />
2. 组件上的 v-model
2.1 组件v-model的展开
<template>
<p>{{ msg }}</p>
<!-- 组件上的v-model -->
<CustomeInput v-model="msg"></CustomeInput>
</template>
<script>
import CustomeInput from './components/CustomeInput.vue';
export default {
name: 'App',
components: {
CustomeInput
},
data() {
return {
msg: '我是付常涛'
};
}
};
</script>
v-model
展开如下:
<CustomInput
:modelValue="msg"
@update:modelValue="newValue => msg = newValue"
/>
✨✨✨
v-model="msg"
,默认将属性modelValue
当做 props 传进组件,并接收组件内名为update:modelValue
的自定义事件。
2.2 组件内部
通过点击按钮,可以发现App组件和CustomeInput组件
中的msg/modelValue
发生了改变。
<template>
<p>
<strong>{{ modelValue }}</strong>
</p>
<p><button @click="update">更改msg</button></p>
</template>
<script>
export default {
// props 接收
props: ['modelValue'],
// 注册自定义事件
emits: ['update:modelValue'],
methods: {
update() {
this.$emit('update:modelValue', 'fct');
}
}
};
</script>
2.3 在自定义组件内与父组件的 msg 保持同步
在自定义组件中使用
input
,实现组件内输入的内容动态绑定App组件的msg
2.3.1 v-model 手动展开
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
2.3.2 v-model + 计算属性
<template>
<input v-model="modelValueComputed" />
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
computed: {
modelValueComputed: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
}
}
}
}
</script>
3. 组件上多个 v-model
当组件上有一个v-model
时,都是使用 modelValue
作为 prop,并以 update:modelValue
作为对应的事件。
当一个组件上有多个
v-model
时如何区分传入组件的prop?
v-model
其实有参数:
v-model='msg'
其实等于v-model:modelValue='msg'
组件上的多个v-model:
// App.vue
<h2>{{ msg }}</h2>
<h2>{{ title }}</h2>
<MyComponent v-model='msg' v-model:title="title" />
子组件中不仅需要声明modelValue
prop 还得声明title
prop。并通过触发 update:title
事件更新父组件值。
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
<input
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
<script>
export default {
props: ['title', 'modelValue'],
emits: ['update:title', 'update:modelValue']
}
</script>