一 前置相关知识
1.1 vue2 mixin是什么?
mixin,翻译过来就是混入,不仅仅在vue框架中存在mixin,确切的说mixin是一种思想,一种混入的思想。混入的内容就是可以在被混入的地方使用,他会自动的将混入的东西准确的分配到指定的组件中。在vue中,mixin相当于指定混入的变量&函数放入他不混入时候该放的地方。可以认为,vue中的mixin就是相当于组件中的组件。
举个例子:现在组件A中的watch中需要处理的逻辑是hanldleParams, 在组件B中的watch中同样需要这样的逻辑-hanldleParams。那么我们应该如何将这两块相同的逻辑抽象出来复用呢?
那么有两种方法:(这两种方法的区别就代表了mixin和utils的区别)
1.抽函数:第一种方法就是将hanldleParams以函数的形式抽出来,然后在watch中调用hanldleParams;
2.mixin:上一种抽函数方法虽然可以解决一定的复用问题,但是我们还是需要在组件中写watch,毕竟两个组件都是在watch这个钩子中调用。如果每个组件都写watch,那么watch也是重复的东西,因此mixin就是将watch钩子都可以抽出来的组件,也就是说,mixin抽出来不仅仅是纯函数逻辑,还可以将vue组件特有的钩子等逻辑也可以抽出来,达到进一步复用,这就是mixin的作用。那么组件A\B通过mixin共用一个watch,导入即可,不需要开发人将其放置在指定位置。
特点:Mixin中的数据和方法都是独立的,组件之间使用后是互相不影响的
1.2 mixin解决了什么问题?
mixin解决了两种复用:
- 逻辑函数的复用
- vue 组件配置复用
注意:组件配置复用是指,组件中的选项式API(例如:data,computed,watch)或者组件的生命周期钩子(created、mounted、destroyed)
1.3 使用&使用场景?
关键:在vue中,mixin定义的就是一个对象,对象中放置的vue组件相应的选项式API和对应的生命周期钩子
export const mixins = {
data() {
return {};
},
computed: {},
created() {},
mounted() {},
methods: {},
};
记住:mixin中一般都是存在vue组件中的选项API和组件生命周期钩子,因为函数的抽象也是要放在组件中特定的API或者钩子中,因此mixin考虑了这一点,直接配置好了所有的API,只要在函数中放置即可。mixin中除了不能把组件中的template模版抽出来,其他任何options-API都可以抽出来放在mixin中(vue2中的所有逻辑无非就是放在data、methods、computed、watch中,这些都可以原封不动放在mixin中)
例如:抽象一个hanldleParams函数,我们一般是在config文件中导出,然后在组件中引入使用,data中method中使用处理data,并且都要设置在data中变量b,那么这段逻辑就可以抽离出来在mixin中放置。而对于与组件业务有关的数据或者逻辑一般都是写在组件中的。
例如:
// 定义一个mixin
export const mixins = {
data() {
return {
msg: "我是小猪课堂",
};
},
computed: {},
created() {
console.log("我是mixin中的created生命周期函数");
},
mounted() {
console.log("我是mixin中的mounted生命周期函数");
},
methods: {
clickMe() {
console.log("我是mixin中的点击事件");
},
},
};
//
// src/App.vue中使用导出的mixin
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<button @click="clickMe">点击我</button>
</div>
</template>
<script>
import { mixins } from "./mixin/index";
export default {
name: "App",
mixins: [mixins], // 注册mixin,这样mixin中所有的钩子函数等同于组件中钩子
components: {},
created(){
console.log("组件调用minxi数据",this.msg);
},
mounted(){
console.log("我是组件的mounted生命周期函数")
}
};
</script>
以上这段代码等价于:
// src/App.vue中使用导出的mixin
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<button @click="clickMe">点击我</button>
</div>
</template>
<script>
import { mixins } from "./mixin/index";
export default {
name: "App",
mixins: [mixins], // 注册mixin,这样mixin中所有的钩子函数等同于组件中钩子
data() {
return {
msg: "我是小猪课堂",
};
},
// 原来的组件中没有methdos方法,mixin中的方法直接放入组件中
// 注意:vue中当发生methods函数名和mixin中的methods方法冲突时,解决方案是本组件中优先级高于mixin
methods: {
clickMe() {
console.log("我是mixin中的点击事件");
},
},
created(){
console.log("我是mixin中的created生命周期函数");
// mixin中的created钩子中的,生命周期中mixin的钩子优先级要高于组件中的优先级
console.log("组件调用minxi数据",this.msg);
},
mounted(){
// mixin中的 mounted 钩子中的,生命周期中mixin的钩子优先级要高于组件中的优先级
console.log("我是mixin中的mounted生命周期函数");
console.log("我是组件的mounted生命周期函数")
}
};
</script>
注意:mixin中和vue组件中相同的钩子的优先级:
- mixin中的生命周期函数会和组件的生命周期函数一起合并执行。
- mixin中的data数据在组件中也可以使用。
- mixin中的方法在组件内部可以直接调用。
- 生命周期函数合并后执行顺序:先执行mixin中的,后执行组件的。
此外,mixin对于不同组件的导入,相互之间数据是不会影响的,例如:
// mixin文件
export const mixins = {
data() {
return {
msg: "我是小猪课堂",
};
},
computed: {},
created() {
console.log("我是mixin中的created生命周期函数");
},
mounted() {
console.log("我是mixin中的mounted生命周期函数");
},
methods: {
clickMe() {
console.log("我是mixin中的点击事件");
},
}
在文件app中使用mixin,并通过changeMsg函数来更改mixin中data的变量msg,此时对于其他组件使用的这个msg变量是不变的。
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<button @click="clickMe">点击我</button>
<button @click="changeMsg">更改mixin数据</button>
<demo></demo>
</div>
</template>
<script>
import { mixins } from "./mixin/index";
import demo from "./components/demo.vue";
export default {
name: "App",
mixins: [mixins],
components: { demo },
created() {
console.log("组件调用minxi数据", this.msg);
},
mounted() {
console.log("我是组件的mounted生命周期函数");
},
methods: {
changeMsg() {
this.msg = "我是变异的小猪课堂";
console.log("更改后的msg:", this.msg);
},
},
};
</script>
1.4 存在的缺点是什么
mixin
优点:组件中钩子函数的注册复用
缺点:
相同钩子中注册的函数名相同会发生冲突(vue中冲突的解决方案是本组件中优先级高于mixin)
定位错误需要花费时间
滥用会造成维护问题
二 vue3中的hooks
2.1 什么是hooks
一般来说,我们开发中会自动抽象出逻辑函数放在utils中,utils中放的纯逻辑,不存在属于组件的东西,例如methods中定义的纯函数等。而hooks就是在utils的基础上再包一层组件级别的东西(钩子函数等)。例如:我们每次点击button都会弹出一个弹窗,自动显示当前日期。但是我将函数放在util中,每次复用都需要click=handleClick 函数放入日期函数,通过handleClick函数管理utils,那么我不如直接将handleClick也封装起来,下次直接调用,复用了methods注册的环节 hooks和utils的区别: hooks中如果涉及到ref,reactive,computed这些api的数据,那这些数据是具有响应式的,而utils只是单纯提取公共方法就不具备响应式,因此可以把hook理解为加入vue3 api的共通方法
2.2 为什么会出现hooks
那么hooks相当于组件级别的逻辑封装,这种逻辑封装在vue2中的mixin也可以实现,为什么还要使用hooks呢? hooks与mixin的区别
mixin是options API的体现,一个是composition API的体现
1.mixin
- 在vue2 中有一个东西:Mixins 可以实现这个功能
- mixins就是将这些多个相同的逻辑抽离出来,各个组件只需要引入mixins,就能实现代码复用
- 弊端一: 会涉及到覆盖的问题
- 组件的data、methods、filters会覆盖mixins里的同名data、methods、filters
- 弊端二:隐式传入,变量来源不明确,不利于阅读,使代码变得难以维护
- 弊端三:mixin无法传入灵活的传入参数,例如(弊端3的举例1):
我需要定一个一个变量name,但是name的初始值是随机的,那么name定义在mixin中的时候,他的初始化一定是固定的,我们如果要改只能再method中注册一个方法来修改name的值:
例1: 使用mixin的例子
// 混入文件:name-mixin.js
export default {
data() {
return {
name: 'zhng'
}
},
methods: {
setName(name) {
this.name = name
}
}
}
// 组件:my-component.vue
<template>
<div>{{ name }}</div>
<template>
<script>
import nameMixin from './name-mixin';
export default {
mixins: [nameMixin],
mounted() {
setTimeout(() => {
this.setName('Tom') // 通过在组件中调用setName传入参数值,无法传入参数,限制了Mixin的灵活性
}, 3000)
}
}
<script>
例2:使用hooks的例子
import { computed, ref, Ref } from "vue"
// 定义hook方法
type CountResultProps = {
count:Ref<number>;
multiple:Ref<number>;
increase:(delta?:number)=>void;
decrease:(delta?:number)=> void;
}
export default function useCount(initValue = 1):CountResultProps{
const count = ref(initValue)
const increase = (delta?:number):void =>{
if(typeof delta !== 'undefined'){
count.value += delta
}else{
count.value += 1
}
}
const multiple = computed(()=>count.value * 2)
const decrease = (delta?:number):void=>{
if(typeof delta !== "undefined"){
count.value -= delta
}else{
count.value -= 1
}
}
return {
count,
increase,
decrease,
multiple
}
}
// 在组件中的使用
<template>
<p>count:{{count}}</p>
<p>倍数:{{multiple}}</p>
<div>
<button @click="increase(10)">加一</button>
<button @click="decrease(10)">减一</button> // 在模版中直接使用hooks中的方法作为回调函数
</div>
</template>
<script setup lang="ts">
import useCount from "../views/Hook"
const {count,multiple,increase,decrease} = useCount(10)
</script>
<style>
</style>
例3: 使用hooks入门的重点例子
使用hooks入门的重点例子: hooks文件一般是都出一个函数,例如:我需要hooks导出一个公用的name变量和setName函数
import {ref} from 'vue'
// 导出1个 name_hooks.ts文件
// hooks中不用写在setup中
export const name_hooks = function(value: string) {
const name = ref('')
const setName = (value: string) => {
name.value = value
}
return {
name, setName
}
}
// 引入hooks文件
<template>
<div>{{ name }}</div>
<select @change="setName"></select> // 这里select组件的change事件会自动传value值
// 然后value值作为传参传递给setName
</template>
import { name_hooks } from './name_hooks'
export default defineComponent({
setup() {
const { name, setName } = name_hooks() // 注意: 通常需要通过解构赋值将需要的属性方法添加进组件中
return {
name, setName
}
}
})
以上hooks使用方法,常见的操作:
1.导出的hooks是一个函数,函数中可以使用ref,reactive,这样hooks定义的变量和方法如同在组件中一样
2.hooks函数通常返回一个对象,对象中是双向绑定的变量,在vue中间引用的时候第一件事就是解构(vue3中的解构需要注意坑最好复习一下)
export
2.hooks:
- Vue3中我们可以: 自定义Hook
- Vue3 的 hook函数 相当于 vue2 的 mixin, 但是: hooks 是函数
- Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数
2.3 hooks使用场景-自定义hook
2.3.1 hooks中常见的业务使用场景
重复造轮子的组件,除开一些毫无必要的重复以外,有一些功能组件确实需要封装一下,比如说,一些需要请求后端字典到前端展示的下来选择框,点击之后要展示loading状态的按钮,带有查询条件的表单,这些非常常用的业务场景,我们就可以封装成组件,但是封装成组件就会遇到前面说的问题,每个人的使用习惯和封装习惯不一样,很难让每个人都满意,这种场景,就可以让hook来解决。
(1) 封装一个弹窗,参考文献:juejin.cn/post/695509…
课外知识点1:watch和watchEffect的区别:
课外知识点2: 浅析使用provide和inject有什么好处:
1 provide & inject的使用场景
使用提示:vue官网中有这样的提示:provide提供和inject注入一般适用于高阶插件和组件库中使用,并不推荐用于一般程序代码中
使用场景:跨级组件通信
一般来说,我们父子组件通信通过prop和emit使用即可,如果通过跨层级或者多个组件节点使用的时候,我们可以通过vuex使用,但是如果不想引入vuex来实现跨层级的话,这个时候可以用provide和inject作为爷孙组件的通信替代方案2.provide &inject 在 vue2 中的使用方法和需要点
注意点1: provide使用语法
provide的使用语法有两种:一种是赋值对象,另一种是定义一个返回对象的函数
inject使用语法一般是字符串数字,也可以是对象
注意点2: 响应式问题:provide提供&inject注入的变量并不是响应式的,如果有响应式的需求需要通过引用对象实现 例如:下图中的provide向孙组件提供的变量是简单类型,那么在爷爷组件中进行响应式变换后,孙子组件接受的变量test是不会随之变换的,因为传入的是字符串简单类型。
而如果爷爷组件提供的provide是引用累心,并且变换的是引用类型内某个属性的值,那么因为孙子组件接受的是对象地址,所以会随之变化,进而达到响应式变换的效果
总结: vue2中的provide和inject主要就是两块内容:使用语法 和 inject接受的响应式数据问题。
课外知识点3: vue3中如何使用provide和inject:
在vue3中我们应该如何在setup中使用provide和inject呢?区别:vue3中的provide和inject和vue2的使用是不同的
vue2中provide相当于和data并列的属性,属性值可以是对象也可以是返回对象的函数。可以看出vue2中的provide和inject还是面向过程或者面向对象的,可能inject中不是很明显,因为inject: ['name']接受的是一个数组或者对象inject: {name},但是provide中是provide: {name: '123'} 或者 provide() {return {name: 123}} ,如下的provide和inject:
但是vue3中是使用函数式编程,所以provide&inject都是函数形式提供,我们只需要传入参数即可。
vue3中的使用
提供rovide('name', 'jack')函数:爷爷组件通过provide('name', 'jack'),其中,第一个参数是孙子组件要拿的key,第二个参数是值。
提供inject('name', '默认值:无名'): 孙子组件通过调用inject函数获取爷爷组件的provide的值,其中第一个参数是key,第二个参数式默认值(可选) 参考文献:blog.csdn.net/weixin_5739…
2.3.2 自定义hook需要满足的规范
2.3.3 自定义hooks使用举例
hooks-1自定义:useCut
import {ref,watch} from "vue"
export function useCut({num1,num2}){
const cutNum = ref(0);
watch([num1,num2],(num1,num2)=>{
cutFunc(num1,num2)
})
const cutFunc = (num1,num2)=>{
cutNum.value = num1+num2
}
return {
cutNum,
cutFunc
}
}
hooks-2自定义
import {ref,watch} from "vue"
const useAdd = ({num1,num2})=>{
const addNum = ref(0);
watch([num1,num2],(num1,num2)=>{
addFunc(num1,num2)
})
const addFunc = (num1,num2)=>{
addNum.value = num1+num2
}
return {
addNum,
addFunc
}
}
export default useAdd
组件中使用自定义hooks
<template>
<div>
num1:<input v-model.number="num1" style="width:100px" />
<br />
num2:<input v-model.number="num2" style="width:100px" />
</div>
<span>加法等于:{{ addNum }}</span>
<br />
<span>减法等于:{{ cutNum }}</span>
</template>
import { ref } from 'vue'
import useAdd from './addHook.js' //引入自动hook
import { useCut } from './cutHook.js' //引入自动hook
const num1 = ref(2)
const num2 = ref(1)
const { addNum, addFunc } = useAdd({ num1, num2 }) // 加法功能-自定义Hook(将响应式变量或者方法形式暴露出来)
// 因为hooks是函数,不像mixin是对象形式,所以更方便的传入组件中的data变量,交给抽象逻辑使用
addFn(num1.value, num2.value)
const { cutNum, cutFunc } = useCut({ num1, num2 }) // 减法功能-自定义Hook (将响应式变量或者方法形式暴露出来)
subFn(num1.value, num2.value)