Mixin
组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取。
在Vue2和Vue3中都支持的一种方式就是使用Mixin来完成:
- Mixin提供了一种非常灵活的方式,来
分发Vue组件中的可复用功能 - 一个Mixin对象可以包含
任何组件选项---其本质就是一个对象 - 当组件使用Mixin对象时,
所有Mixin对象的选项将被 混合 进入该组件本身的选项中
mixin定义者 --- /mixins/fooMixin.js
export default {
data() {
return {
message: 'something in fooMixn'
}
},
methods: {
foo() {
console.log('foo')
}
}
}
mixin使用者 --- App.vue
<template>
<div>
<h2>{{ message }}</h2>
<button @click="foo">click me</button>
</div>
</template>
<script>
import fooMixin from './mixins/fooMixin'
export default {
name: 'App',
// mixin本质上是一个对象
// 一个组件可以混入多个,所以mixins所对应的值是一个对象
mixins: [fooMixin]
}
</script>
合并规则
-
如果是data函数的返回值对象
- 返回值对象默认情况下会进行合并
- 如果data返回值对象的属性发生了冲突,那么会
保留组件自身的数据
-
如何生命周期钩子函数
-
生命周期的钩子函数会被
合并到数组中,都会被调用 -
先执行Mixin中对应的逻辑,在执行组件中对应生命周期钩子的逻辑
-
-
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象
- 比如都有methods选项,并且都定义了方法,那么它们都会生效
- 但是如果对象的
key相同,那么会取组件对象的键值对
全局混入
如果组件中的某些选项,是所有的组件都需要拥有的,那么这个时候我们可以使用全局的mixin:
- 全局的Mixin可以使用 应用app的方法 mixin 来完成注册
- 一旦注册,那么全局混入的选项将会影响每一个组件
- 全局混入的生命周期会优先执行 --- 因为最早被插入到数组中
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mixin({
created() {
console.log('全局混入')
}
})
app.mount('#app')
extends
另一个和Mixins十分相似的属性,就是extends
vue组件只能实现单继承
基础组件
<template>
<div>
<!--
如果使用继承,只能继承script中的内容
无法继承模板中的内容
-->
<h1>Base</h1>
</div>
</template>
<script>
export default {
name: 'BaseComponent',
data() {
return {
msg: 'base msg'
}
},
methods: {
foo() {
console.log('foo')
}
}
}
</script>
使用继承组件
<template>
<div>
<h2>{{ msg }}</h2>
<button @click="foo">click ms</button>
</div>
</template>
<script>
import BaseComponent from './BaseComponent.vue'
export default {
name: 'App',
// 继承自BaseComponent, 对其进行扩展
// 组件只能进行单继承
extends: BaseComponent
}
</script>
Composition API
在Vue2中,我们编写组件的方式是Options API:
Options API的一大特点就是在对应的属性中编写对应的功能模块
比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命 周期钩子
但是这么做有一个很大的弊端:
-
当我们实现某一个功能时,这个功能对应的
代码逻辑会被拆分到各个属性中 -
当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么
同一个功能的逻辑就会被拆分的很分散 -
尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人)
Composition API(Vue Composition API ---> VCA)就是为了解决上述问题而存在的
Composition API能将同一个逻辑关注点相关的代码收集在一起
总结: option api 编写的代码相对比较分散,不利于后期的维护和抽取(代码复用)
composition api 可以将option api的代码增合到一起,增加了代码的可读性和可复用性
同时 方便代码的抽离,可以将代码抽离成为一个个的hook函数,以提高代码的可复用性
Setup
为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地
在Vue组件中,这个位置就是 setup 函数
参数
setup函数,主要有两个参数:
第一个参数:props:
-
父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可 以直接通过props参数获取
-
对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义
-
并且在template中依然是可以正常去使用props中的属性,比如message
-
如果我们在setup函数中想要使用props,那么不可以通过 this 去获取
-
setup函数中,是没有this关键字的
-
因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可
第二个参数:context
另外一个参数是context,我们也称之为是一个SetupContext,它里面包含三个属性:
- attrs:所有的非prop的attribute
- slots:父组件传递过来的插槽
- emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件)
<template>
<div>
<h2>{{ message }}</h2>
</div>
</template>
<script>
export default {
name: 'Home',
props: {
message: {
type: String,
required: true
}
},
setup(props, { emit }) {
// 获取父组件传递过来的数据
console.log(props)
// 类似于vue2中的this.$emit方法
emit('homeClick')
}
}
</script>
返回值
setup既然是一个函数,那么它也可以有返回值
setup的返回值可以在模板template中被使用, 也就是说我们可以通过setup的返回值来替代data选项
<template>
<div>
<h2>{{ msg }}</h2>
</div>
</template>
<script>
export default {
name: 'Home',
setup() {
return {
msg: 'message'
}
}
}
</script>
但是setup函数的返回值默认情况下,只是一个普通变量,Vue并不会跟踪它的变化,来引起界面的响应式操作
<template>
<div>
<button @click="increment">{{ count }}</button>
</div>
</template>
<script>
export default {
name: 'Home',
setup() {
let count = 0
// 此时点击了按钮后,setup中的count发生了改变,但是界面中的count并不会发现相应的改变
const increment = () => count++
return {
count,
increment
}
}
}
</script>
Reactive
当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集
当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面)
事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的
<template>
<div>
<button @click="increment">{{ state.count }}</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'Home',
setup() {
const state = reactive({
count: 0
})
const increment = () => state.count++
return {
state,
increment
}
}
}
</script>
Ref
reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型
如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告
这个时候Vue3给我们提供了另外一个API: ref API
- ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源
- 它内部的值是在ref的 value 属性中被维护的
<template>
<div>
<!--
在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用
-->
<button @click="increment">{{ count }}</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Home',
setup() {
const count = ref(0)
// 在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式
const increment = () => count.value++
return {
count,
increment
}
}
}
</script>
<template>
<div>
<!-- 这里的info实际上是解包后的info,即原本的info.value -->
{{ info.name }} --- {{ info.age }}
<button @click="changeAge">change age</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Home',
setup() {
// ref的参数也是可以是对象类型的 --- 不推荐 --- 后续无法使用toRefs或toRef进行解构操作
const info = ref({ name: 'Klaus', age: 23 })
// 取值的时候依旧是通过value属性来取
const changeAge = () => info.value.age++
return {
info,
changeAge
}
}
}
</script>
tips: 可以使用ref实现的代码,就不要使用reactive来进行实现
因为使用ref函数来实现的响应式数据是独立的,可以和业务逻辑进行整合
但是使用reactive声明的响应式数据,应该是那些相互之间联系比较强的数据
如果联系不强的数据使用reactive整合在一起的时候,会增加代码的耦合性,不利于代码的拆分和复用
// name和age是联系强的数据,但是其和count以及msg之间是没有任何联系的,所以不应该放在一个reactive对象中
const state = reactive({
name: 'Klaus',
age: 23,
count: 0,
msg: 'Hello World'
})
// 推荐修改为如下形式
// 这样,就可以方便将对应的数据整合到对应的hooks函数中
const info = reactive({
name: 'Klaus',
age: 23
})
const count = ref(0)
const msg = ref('Hello World')
readonly
我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个
响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?
Vue3为我们提供了readonly的方法
readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,只不过这个proxy的set方法被劫持,不能对进行值的修改操作)
// 普通对象
setup() {
const counter = {
count: 0
}
const readOnlyCounter = readonly(counter)
const change = () => readOnlyCounter.count++ // error
return {
counter,
readOnlyCounter,
change
}
}
// reactive对象
const counter = reactive({
count: 0
})
const readOnlyCounter = readonly(counter)
const change = () => readOnlyCounter.count++ // error
return {
counter,
readOnlyCounter,
change
}
// ref对象
const counter = ref(0)
const readOnlyCounter = readonly(counter)
const change = () => readOnlyCounter.value++ // error
return {
counter,
readOnlyCounter,
change
}
在readonly的使用过程中,有如下规则:
- readonly返回的对象都是不允许修改的
- 但是经过readonly处理的原来的对象是允许被修改的
- 其实本质上就是readonly返回的对象的setter方法被劫持了而已
此时,如果我们传递给其他组件数据时,希望其他组件使用我们传递的内容,但是不允许它们修改时,就可以使用 readonly了