Vue学习笔记 — Vue的响应式原理
Vue3.0学习笔记
Vue3.0的学习正在一点点地推进,我会在这里记录一些学习的过程与重要的知识点
该笔记仅记录componsitionApi的方式
基础
~~
组件
组件是vue中最重要的内容之一,组件内部也有很多区别于基础的方法与属性
props
当我们需要将一个对象的所有属性全部当作prop传入的时候,可以使用v-bind的方式直接传入整个对象
1
2
3
4
5// post对象中所有的属性都要单独传入
const post = {
id: 1,
title: 'Hello Word'
}该情况下下面的两种使用方式完全相同
1
2<BlogPost v-bind="post" />
<BlogPost :id="post.id" :title="post.title" />⚠️ 通过prop传入的对象/数组都是通过引用的形式,因此在自组件中修改对应的对象/数组内部的值并不会报错且会修改原值
所有的prop参数默认为可选的,如果需要必选则要声明
require: true
由于对ts的支持,prop的type也可以是自定义的类或构造函数,可以通过
instanceof
来检查、断言1
2
3
4
5
6
7
8
9
10class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
defineProps({
author: Persion
})如上会校验
author
的值是否是通过new Person
的方式创建的
事件
组件内事件的声明可以通过
const emit = defineEmits(['inFocus', 'submit'])
的方式进行声明emits
方法支持对象语法,可以用来对触发事件的参数进行验证1
2
3
4
5
6
7
8
9
10
11
12<script setup>
const emit = defineEmits({
submit: ({account, password}) => {
if (account && password) {
return true;
} else {
console.warn('账号/密码未通过校验');
return false;
}
}
})
</script>虽是可选的但是官方推荐将所有要出发的emit事件都定义到emits中
属性透传
当我们在父元素中对子组件挂在了一个属性或者v-on监听事件,那么当dom渲染时会将这些属性透传到子组件的根元素中,并与根元素已有的属性活事件进行合并,最常见的就是class
和style
的设定
如果我们不希望组件自动继承透传属性则需要在组件选项中设置inheritAttrs: false
如果使用的是<script setup>
方式则需要增加一个<script>
来书写这个声明
1 | <script> |
如此设定之后可以通过v-bind=“$attrs”
来决定透传进来的属性如何应用
在js中可以通过const attrs = useAttrs()
的方式访问一个组件的所有透传attribute(非响应式)
插槽
插槽是组件只是中最重要的一个属性,也是自定义组件中最重要的一部分
⚠️插槽内容是无法访问子组件数据的
- 任何父组件模板中的东西都只被编译到父组件的作用域中;而任何子组件模板中的东西都只被编译到子组件的作用域中。
- 子组件的模板中的内容只能通过插槽来访问,不能通过子组件的data来访问
当我们在父组件中使用子组件中定义的具名插槽时,可以使用v-slot:name
的指令,也可简写为#name
1 | <BaseLayout> |
作用域插槽:在某些场景下我们需要我们可能需要同时使用父组件域内和子组件域内的数据(例如
<el-table>
组件中的自定义列),这样的情况就需要使用作用域插槽来实现1
2
3
4
5
6
7
8
9
10
11
12
13
14<!-- 子组件 -->
<div>
<slot :text="message" :count="1"></slot>
</div>
<!-- 父组件1 -->
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
<!-- 父组件2 -->
<MyComponent v-slot="{text, count}">
{{ text }} {{ count }}
</MyComponent>
依赖注入
背景:当我们需要从父组件向子组件传递数据时我们会使用props进行传递,但是在真实开发中存在组件层层嵌套,层级很深的情况,在层层嵌套传递数据的过程中,会有很多组件其实并不关心这个props但是迫于子组件需要又必须沿着链路传递下去,这一过程被称为“prop drilling”,这时候我们就需要使用依赖注入的方式来解决这个问题
解决这个问题我们可以使用provide
和inject
来实现,provide
用来提供依赖,inject
用来注入依赖,无论层级多深都可以注入由父组件提供给这条链路的依赖
Provide:用来提供依赖,可以提供多个依赖,也可以提供一个依赖
- 如果我们不使用
<script setup>
的方式,那么必须保证provide
与setup()
可以同步调用
1
2
3
4
5<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>- 如果我们不使用
Inject:用来注入依赖,可以注入多个依赖,也可以注入一个依赖
- 如果注入的值是一个ref,则不会进行解包,保持响应性链接
- 如果我们不使用
<script setup>
的方式,那么必须保证inject
与setup()
可以同步调用 inject(key, value)
如果该key值没有被任何处于组件链中的组件提供,则value为其默认值(形似props)
在我们注入一个响应式的值的时候,我们应该尽量保证对响应式状态的修改都保持在
provide
内部,这样可以确保provide的状态和变更操作都位于同一个组件内部如果某些情况下我们需要在injector中更改数据,那么可以在provide中提供一个函数,这个函数会在injector中被调用,这样就可以在injector中修改数据
如果想要保证通过provide传的数据无法被injector修改,那么可以使用
readonly
1
2
3
4
5<script setup>
import { provide, inject, readonly } from 'vue'
const count = ref(0);
provide('count', readonly(count));
</script>协同开发中推荐使用Symbol来作为注入名来避免命名冲突
异步组件
Vue中提供了一个defineAsyncComponent
方法用于在需要时再从服务器加载相关组件
1 | import { defineAsyncComponent } from 'vue'; |
defineAsyncComponent
方法接收的是一个返回Promise的函数,这个函数会在组件被调用时执行,这个函数返回的Promise会在组件被渲染时被解析,模块动态导入也会返回一个Promise,所以可以和defineAsyncComponent
配合使用
1 | import { defineAsyncComponent } from 'vue'; |
上述程序的执行可以得到一个AsyncComp
的包装器组件,这个包装器组件会在组件被渲染时被解析,并且会在组件被渲染时被解析,这样就可以在组件被渲染时加载相关的组件
当然异步操作一定会涉及到加载和错误状态的处理,因此defineAsyncComponent()
也支持处理这些状态
1 | const AsyncComp = defineAsyncComponent({ |
自定义指令
先看一个例子
1 | <script setup> |
上述代码执行的效果就是会在页面加载时自动聚焦到input,而focue
就是我们自定义的指令,这会比autofocus
更有用,因为它可以在任何你需要的时候运行
所谓自定义指令的目的就是为了重用涉及到元素底层DOM访问的逻辑。
自定义指令在模版中使用前必须提前进行注册,即使用directives
选项完成注册,在<script setup>
中任何以v
开头的驼峰式命名的变量都可以被用作自定义指令,即vFocus
在模版中可以v-focus
的方式使用
1 | export default { |
也可以通过以下方法将自定义指令注册在全局
1 | const app = createApp({}) |
指令钩子
一个指令的定义对象可以有如下钩子可供选择:
1 | const myDirective = { |
钩子参数如下:
el
:指令绑定到的元素。这可以用于直接操作 DOMbinding
:一个对象,包含以下 property。value
:传递给指令的值oldValue
:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用arg
:传递给指令的参数modifiers
:一个包含修饰符的对象instance
:使用该指令的组件实例dir
:指令的定义对象vnode
:代表绑定元素的底层VNodeprevNode
:之前的渲染中代表指令所绑定元素的 VNode。仅在beforeUpdate
和updated
钩子中可用。
简化形式
如果在自定义指令中需要在mounted
和updated
上实现相同的行为,且无需定义在其他钩子中的事件时,可以进行如下操作
1 | <template> |
内置组件
Teleport·传送门
<Teleport>
是一个内置组件,使我们可以将一个组件的一部分模板“传送”到该组件的 DOM 层次结构之外的 DOM 节点中。
<Teleport>
最好的实例就是模态框的实现,<Teleport :to="body">
可以将标签下的元素传送到body上,它只会改变渲染的DOM结构,并不会改变