本文是阅读1所做的笔记。
vue 3组件的生命周期写法称为Composition API(组合式API)。
vue 3组件的生命周期的函数统一放在setup里运行:onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted、onErrorCaptued。
被包含在
defineComponent是vue3推出的一个API,用来自动对TypeScript进行类型推导(省去手动类型推导的过程)。
steup是vue3推出的compostion API之一,是vue3组件生命周期的启用入口。
steup(props,context)的入参:props是传承自父组件的数据,context是本组件的执行上下文;如果一个定义不在props中定义,那么就会存在于context.attrs中;context.slots是插槽,context.emit是触发事件(可以用emit('xxx')
代替context.emit('xxx')
)。
setup()模板如下:
import { defineComponent } from 'vue'
export default defineComponent({
setup(props, context) {
// 业务代码写这里...
return {
// 需要给 template 用的数据、函数放这里 return 出去...
}
},
})
vue3官方推荐defineComponent+Composition API的写法。
所谓响应性,官方文档是这样解释的:响应性是一种允许我们以声明式的方式去适应变化的编程范例。简单地可以理解为,具有响应性的数据,如果在某处地方被使用,这个数据如果发生了变化,那么使用的地方也会相应更新。
例如,有一个数据a,然后b使用了数据a,如果a在某处发生变化了,会触发b使用a的地方的更新。如果还不理解,再具体一点,vue data 选项里定义的就是响应性的数据,你在模板中使用了这些数据,然后这些数据在某处发生了变化,你模板里面的数据是不是也会相应更新?这就是响应性。vue3 通过 Proxy 和依赖&副作用收集 实现这种响应性,相比起vue3的Proxy,vue2是使用defineProperty实现的。
关于副作用,官方文档是这么描述的:Vue 通过一个副作用 (effect) 来跟踪当前正在运行的函数。副作用是一个函数的包裹器,在函数被调用之前就启动跟踪。Vue 知道哪个副作用在何时运行,并能在需要时再次执行它。
文档说得有点抽象,可以这么简单地理解,副作用就是记录[使用了响应式数据的地方]且可能需要被重新执行的一种机制,在vue3里面体现为一个函数包裹器,里面便包裹了使用了响应式数据的地方。也就是说,如果我们跟踪了副作用依赖于哪些响应式数据,在这些数据做出了变化之后,我们便可以触发副作用的执行,达到更新的目的。
你可以发现,其实computed,watch,甚至使用了数据的模板,都可以理解为跟副作用相关,因为他们都依赖于某些响应式的数据。
ref,reactive可以创建响应式的数据,可以理解为setup里面与原来data选项相对应的功能,一旦有地方使用到了这些数据,就会被包裹进副作用里,并在这些数据修改的时候运行副作用更新。
vue3使用Proxy的getter/setter实现数据的响应性。
ref用来定义响应式数据(随状态变化而变化的数据),定义方式是用<>包裹类型定义,紧跟ref API之后:
// 单类型
const msg = ref('Hello World!');
// 多类型
const phoneNumber = ref(13800138000);
不同类型的ref定义有些差异:基本类型、对象、普通数组、对象数组。
ref也可以挂在html语言的DOM元素、子组件上,获取它们响应式变化的值。获取值时注意几点:
reactive也是用于定义响应式数据,但只适用于对象、数组。
reactive对象读取字段的值或修改值的时候,与普通对象是一样的(不像ref对象那样需要xx.value取值)。
但是一些不良操作可能会使reactive数组失去响应性:重置数组;对reactive定义的对象进行解构(解构(解构赋值)是一种表达式,将数组或者对象中的数据赋予另一个变量[10])。
toRef、toRefs都是用来将reactive对象转为ref对象的,前者转换某个字段,后者转换所有字段,当修改转换后ref对象的字段时,转换前的reactive对象相应字段会同步更新。
注意使用toRef最好不要转换原reactive不存在的字段。
注意toRef与toRefs在业务场景中的运用:
vue3的函数都是放在setup()中定义的,注意执行时放在对应的生命周期中,比如OnMounted。
如果是暴露给模板通过click、change等行为除法的函数,需要把函数名在setup里进行return才可以在模板里使用。
数据的监听即是对组件的监听,比如监听它的路由变化、参数变化等。
watch API分基础用法和批量监听,该API接受3个入参:source(必传) 数据源,callback (必传)监听到变化后执行的回调函数,options(可选) 一些监听选项。watch返回一个可以用来停止监听的函数。
watch API 代码:
import { watch } from 'vue'
// 基本用法
watch(
source, // 必传,要监听的数据源
callback, // 必传,监听到变化后要执行的回调函数
// options // 可选,一些监听选项
)
watch的监听源必须是:1、响应式变量(Ref);2、计算数据(ComputedRef);3、getter函数(()=>T);
注意如果监听响应性对象中的某个字段(这种情况下对象本身是响应式,但它的字段不是),就需要在return中写成getter函数返回需要监听的字段,比如()=>foo.bar。
// watch 第 2 个入参的 TS 类型
// ...
export declare type WatchCallback = (
value: V,
oldValue: OV,
onCleanup: OnCleanup
) => any
// ...
注意回调函数在watch批量监听时的简化写法。
如果有多个数据源监听,并且监听到变化后执行的行为一样,可以用批量监听。
注意回调函数只有在数据源发生变化时,才会被执行。
deep控制是否开启深度监听,默认不开启,且默认对ref对象不起作用(可以手动开启监听)。
immediate让回调函数不等数据源的变化就立马执行,即watch所监听的ref对象一旦初始化,就触发回调函数(immediate不能在第一次会调时就取消该数据源的监听)。
flush控制是在渲染的什么时候进行回调函数的执行。
watch函数会返回一个停止监听的函数(一般来说不太关心这个)。
有时 watch 的回调会执行异步操作,当 watch 到数据变更的时候,需要取消这些操作,这个函数的作用就用于此,会在以下情况调用这个清理函数:
这里注意watch只有在数据变化时才会执行监听,而watchEffect会默认执行一次,然后在数据变化时再监听。
vue数据的计算采用computed API,通过现有的响应式数据,去计算得到新的响应式变量。
定义出的computed变量,也是和ref变量一样,需要xx.value才能获取到它的值,但区别在于,computed变量的value是只读的。
一般不需要显示定义computed出来的变量类型,defineComponent会自动推导。
computed变量优势:
computed变量劣势:
如果一定要对computed变量手动复制,可以使用其setter属性更新数据:
// 注意这里computed接收的入参已经不再是函数
const foo = computed({
// 这里需要明确的返回一个值
get() {
// ...
},
// 这里接收一个参数,代表修改 foo 时,赋值下来的新值
set(newValue) {
// ...
},
})
此时使用xx.value=所赋的值,computed变量就会用其set方法更新数据。
computed变量的应用场景:
指令是vue模板语法里的特殊标记,统一以v-开头(比如v-html),它以简单的方式实现了常用的JavaScript表达式功能,当表达式的值改变的时候,响应式地作用到DOM上。
有两个指令有别名:
自定义指令分局部注册和全局注册两种方式:
注意:自定义指令还有一个deep选项,如果自定义指令用于一个有嵌套属性的对象,并且需要在嵌套属性更新的时候触发beforeUpdate和updated钩子,那么需要将这个选项设置为true才能够生效:
参考2,Vue实现了一套内容分发的API,将元素作为承载分发内容的出口,这是vue文档上的说明。具体来说,slot就是可以让你在组件内添加内容的空间。举个例子:
//子组件 : (假设名为:ebutton)
//父组件:(引用子组件 ebutton)
如果直接想要在父组件中的
//子组件 : (假设名为:ebutton)
子组件可以在任意位置添加slot , 这个位置就是父组件添加内容的显示位置。
简言之,插槽就是父组件内部定义的内容通过slot替换子组件默认的内容。
上面我们了解了,slot 其实就是能够让我们在父组件中添加内容到子组件的‘空间’。我们可以添加父组件内任意的data值,比如这样:
//父组件:(引用子组件 ebutton)
{{ parent }}
new Vue({
el:'.app',
data:{
parent:'父组件'
}
})
使用数据的语法完全没有变,但是,我们能否直接使用子组件内的数据呢?显然不行。
// 子组件 : (假设名为:ebutton)
{{ child }}
new Vue({
el:'.button',
data:{
child:'子组件'
}
})
// 父组件:(引用子组件 ebutton)
所谓的后背内容,其实就是slot的默认值,有时我没有在父组件内添加内容,那么 slot就会显示默认值,如:
//子组件 : (假设名为:ebutton)
有时候,也许子组件内的slot不止一个,那么我们如何在父组件中,精确的在想要的位置,插入对应的内容呢? 给插槽命一个名即可,即添加name属性。
//子组件 : (假设名为:ebutton)
父组件通过v-slot : name 的方式添加内容:
//父组件:(引用子组件 ebutton)
这是插入到one插槽的内容
这是插入到two插槽的内容
这是插入到three插槽的内容
当然 vue 为了方便,书写 v-slot:one 的形式时,可以简写为 #one 。
通过slot 我们可以在父组件为子组件添加内容,通过给slot命名的方式,我们可以添加不止一个位置的内容。但是我们添加的数据都是父组件内的。上面我们说过不能直接使用子组件内的数据,但是我们是否有其他的方法,让我们能够使用子组件的数据呢? 其实我们也可以使用v-slot的方式:
//子组件 : (假设名为:ebutton)
// 通过v-slot的语法 将插槽 one 的值赋值给slotonevalue
{{ slotonevalue.value1 }}
// 同上,由于子组件没有给slot命名,默认值就为default
{{ slottwovalue.value2 }}
new Vue({
el:'.button',
data:{
child1:'数据1',
child2:'数据2'
}
})
//父组件:(引用子组件 ebutton)
总结来说就是:
vue组件样式的基础写法,即在vue文件中创建一个style标签,就可在里面写CSS代码了。
可以在DOM元素的标签中使用:class属性,动态绑定一到多个样式(预先在script文件中定义好的)。
最常见的场景,应该就是导航、选项卡了,比如你要给一个当前选中的选项卡做一个突出高亮的状态,那么就可以使用:class来动态绑定一个样式。
如果你觉得使用class需要提前先写样式,再去绑定样式名有点繁琐,有时候只想简简单单的修改几个样式,那么你可以通过:style来处理。
由于CSS没有作用域的概念,有可能一旦写了某个样式,造成全局污染。vue组件有两种方案可以避免这种污染问题,一个是vue2的