Mixin
目前我们是使用组件化的方式在开发整个Vue的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑,我 们希望对相同的代码逻辑进行抽取。
在Vue2和Vue3中都支持的一种方式就是使用Mixin来完成:
Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能;
一个Mixin对象可以包含任何组件选项;
当组件使用Mixin对象时,所有Mixin对象的选项将被 混合 进入该组件本身的选项中;
合并规则
-
情况一:如果是data函数的返回值对象,默认情况下会进行合并;如果data返回值对象的属性发生了冲突,那么会保留组件自身的数据;
-
情况二:如果生命周期钩子函数,则会被合并到数组中,都会被调用;
-
情况三:值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。
比如都有methods选项,并且都定义了方法,那么它们都会生效;
但是如果对象的key相同,那么会取组件对象的键值对;
全局混入
如果组件中的某些选项,是所有的组件都需要拥有的,那么这个时候我们可以使用全局的mixin:
全局的Mixin可以使用 应用app的方法 mixin 来完成注册;
一旦注册,那么全局混入的选项将会影响每一个组件;
Options API的弊端
在Vue2中,我们编写组件的方式是Options API:Options API的一大特点就是在对应的属性中编写对应的功能模块;
比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子;
但是这种代码有一个很大的弊端:当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);这种碎片化的代码使用理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题; 并且当我们处理单个逻辑关注点时,需要不断的跳到相应的代码块中;
setup参数
setup 我们可以用它来替代之前所编写的大部分其他选项;比如methods、computed、watch、data、生命周期等等;
们先来研究一个setup函数的参数,它主要有两个参数:
-
第一个参数:props
-
第二个参数:context
props非常好理解,它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取:
对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;并且在template中依然是可以正常去使用props中的属性,比如message;
如果我们在setup函数中想要使用props,那么不可以通过 this 去获取;
因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;
另外一个参数是context,我们也称之为是一个SetupContext,它里面包含三个属性:
-
attrs:所有的非prop的attribute; -
slots: 组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到); -
emit: 当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);
setup函数的返回值
setup既然是一个函数,那么它也可以有返回值,它的返回值用来做什么呢?
setup的返回值可以在模板template中被使用;
也就是说我们可以通过setup的返回值来替代data选项;
甚至是我们可以返回一个执行函数来代替在methods中定义的方法:
setup 不可以使用this
在setup被调用之前,data、computed、methods等都没有被解析;
所以无法在setup中获取this;
Reactive()
在setup中定义的数据提供响应式的特征,
当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;
当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
<template>
<div>
<p>{{ message }}</p>
<h2>当前计数:{{ state.counter }}</h2>
<button @click="add">点我加加</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
//响应式API
const state = reactive({
counter: 100
})
const add = () => {
state.counter++
}
//将所有的数据返回出去,才可进行使用,或呈现在页面中
return {
message: 'Hello world',
state,
add
}
}
}
</script>
<style>
</style>
reactive() 的局限性
reactive() API 有两条限制:
-
仅对对象类型有效(对象、数组和
Map、Set这样的集合类型),而对string、number和boolean这样的 原始类型 无效。 -
因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({ count: 1 })
同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性:
const state = reactive({ count: 0 })
// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++
// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count)
Reactive判断的API
- isProxy
检查对象是否是由 reactive 或 readonly创建的 proxy。
- isReactive
检查对象是否是由 reactive创建的响应式代理:
如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
- isReadonly
检查对象是否是由 readonly 创建的只读代理。
- toRaw
返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
- shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
- shallowReadonly
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
Ref()
reactiveAPI对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:
如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;
这个时候Vue3给我们提供了另外一个API:ref API 创建可以使用任何值类型的响应式
ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值.
它内部的值是在ref的 value 属性中被维护的;
这里有两个注意事项:
在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用;
<h2>当前计数:{{ counter }}</h2>
但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;
<script>
import { ref } from 'vue'
export default {
setup() {
let counter = ref(100)
const add = () => {
counter.value++
}
//将所有的数据返回出去,才可进行使用,或呈现在页面中
return {
message: 'Hello world',
counter,
add
}
}
}
</script>
ref其他的API
- unref
如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
如果参数是一个 ref,则返回内部值,否则返回参数本身;
这是val = isRef(val) ? val.value : val 语法糖函数;
- isRef:
判断值是否是一个ref对象。
- shallowRef
创建一个浅层的ref对象;
- triggerRef
手动触发和 shallowRef 相关联的副作用:
readonly()
接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);
在开发中常见的readonly方法会传入三个类型的参数:
类型一:普通对象;
类型二:reactive返回的对象;
类型三:ref的对象;
在readonly的使用过程中,有如下规则:
readonly返回的对象都是不允许修改的;
但是经过readonly处理的原来的对象是允许被修改的;
- 比如 const info = readonly(obj),info对象是不允许被修改的;
- 当obj被修改时,readonly返回的info对象也会被修改;
- 但是我们不能去修改readonly返回的对象info;
其实本质上就是readonly返回的对象的setter方法被劫持了而已;
应用:在我们传递给其他组件数据时,往往希望其他组件使用我们传递的内容,但是不允许它们修改时,就可以使用readonly
toRefs()
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
import { reactive, toRefs } from 'vue'
export default {
setup() {
const info = reactive({ name: 'why', age: 18 })
//解构赋值 拿到name和age,不是响应式的
// toRefs :使解构赋值之后拿到的数值为响应式
// 将reactive对象中的所有属性都转成ref,建立连接
const { name, age } = toRefs(info)
const changeAge = () => {
// age.value++
info.age++
console.log(age)
}
return {
name,
age,
changeAge
}
}
}
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive返回的state对象,数据都不再是响应式的:那么有没有办法让我们解构出来的属性是响应式的呢?
Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;
那么我们再次进行结构出来的 name 和 age 本身都是 ref的; 这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化;
toRef()
对其中的一个属性进行转化,建立连接
基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
import { reactive, toRef } from 'vue'
export default {
setup() {
const info = reactive({ name: 'why', age: 18 })
const { name } = info
// 单个不要使用{}
const age = toRef(info, 'age')
const changeAge = () => {
// age.value++
info.age++
console.log(age)
}
return {
name,
age,
changeAge
}
}
}
</script>