Vue3高阶
文章目录
前言
啦啦啦,新的篇章,本期也是Vue3工程化的最后一篇了,一个人的旅程好难,什么都要靠自己去摸索,遇到问题需要好久好久才搞懂,苦逼~~~~
Mixin
在Vue3x版本中用的不多了
mixin指Vue中可重复使用的部分,比如说,在一个实例中的data,methods等另外一个实例也要用,那么就可将要复用的东西拎出来,单独放在一个对象中,在实例中用mixins接住即可,mixins是一个数组,可以放多个mixin对象
<body>
<div id="app">
<div>
<h1>{{msg}}</h1>
<button @click="hello">打个招呼</button>
</div>
</div>
<script>
// 创建一个mixin对象
const myMixin = {
data() {
return {
msg: "你好"
}
},
methods: {
hello() {
alert("你好")
}
}
}
const app = Vue.createApp({
mixins:[myMixin]
}).mount('#app')
</script>
mixin细节点
- 实例中的data,methods之类的选项,优先级高于mixin中定义的
eg:如果mixin对象的数据中有msg变量,当前实例中也有msg变量,那么会优先采用实例中的msg - mixin对象中的数据会和实例中的数据混用,
eg:假设mixin对象data中有msg变量,实例中有一个site变量,那么实例在调用这两个变量的时候都会显示 - 如果mixin对象和实例中都有生命周期钩子,两者都会执行,但是会先执行mixin对象中的钩子
mixin-自定义属性
有的时候我们会在mixin对象中传入自定义的属性,这些属性不会直接存在data中,所以不能直接使用,而是要在$options中调用
$options:是一个对象,当一个实例加载完毕后,里面的所有内容都会挂载到$options对象中
注意:如果实例中也有一个同名的属性,首先会用实例中的
此外还可以通过配置,优先使用mixin中的属性,具体见一下代码
注意:mixin还可以全局配置,然后当前实例的组件就不需要用mixins接收了,
具体见下面代码
<div id="app">
<div>
<h1>{{msg}}</h1>
<h1>{{$options.age}}</h1>
</div>
</div>
<script>
// 创建一个mixin对象,里面是自定义属性
const myMixin = {
age:30
}
const app = Vue.createApp({
mixins: [myMixin],
data() {
return {
msg: "你好",
}
},
age:89,
})
// 如果说mixin和实例中有同名的属性,默认是先使用实例中的,如果想改变的话,使用一下配置
app.config.optionMergeStrategies.age = (mixinVal, appVal) => {
return mixinVal || appVal
}
// mixin还可以全局配置,在整个实例中的所有组件都可以用
// app.mixin({
// data(){
// return {
// age:100
// }
// }
// })
app.mount('#app')
</script>
自定义指令
可以在全局创建,也可以在局部创建
指令里面的钩子,具体可以去官网手册查询
EG:mounted - 元素插入父 DOM 后发生。
每个钩子里面的参数含义:
mounted(el,binding,vnode){…}
el:代表当前使用该指令的元素
binding:指令传来的值
vnode:当前元素节点相关
在调用的时候可以:eg:v-fiexd=”xxx”传递值
全局定义
<script>
app.directive("focus",{
mounted(el) {
el.focus()
},
})
</script>
还可以这样定义,不在钩子函数中使用,直接使用
<script>
app.directive("focus",(el,binding,vnode)=>{
el.focus()
}
})
</script>
局部定义
只能被挂载的组件使用
需要在使用的实例中挂载
<script>
// 创建一个局部的自定义指令
const myDirective = {
focus:{
mounted(el) {
el.focus()
},
}
}
</script>
<script>
const app = Vue.createApp({
directives:myDirective,
})
</script>
调用的时候:
v-指令名即可,可传参可不传递(传参的话需要创建的时候接收一下)
<div id="app">
<h1>自定义指令</h1>
<input type="text" placeholder="请输入内容" v-focus="[值]">
<!--[值]:表示可选-->
</div>
teleport 传送门
比如说,父组件中包裹一个子组件,不想让这个子组件显示到父组件上,想让子组件显示到其他地方,这时候就可以用teleport了
将要传送的标签或者组件,用包裹
to—属性取值为选择器,
eg:to=”body” to=”#box” to=”.box”
<div id="app">
<div class="box1"></div>
<div class="box">
<!-- 让蒙版展示到id为box1的元素中 -->
<button @click="btn">蒙版</button>
<teleport to=".box1">
<div v-if="isShow" class="mask">hahah</div>
</teleport>
</div>
</div>
组合式API (composition API)
在vue2x的版本中,是是由分散式的API(options API),就是用到哪个就写哪个,比如用到data,将data写进实例,用到methods,将其写入,一旦代码量多了不容易维护
vue3x中采用了组合式的API,所有相关的数据、方法等等都放在一个函数中,用到哪个函数,通过setup钩子调用即可,增加了代码的可维护性
setup
从生命周期的角度来看,steup是取代了2x版本的BeforeCreate(创建前)和create函数
注意点:setup函数里面是不能使用this相关的东西,和实例中的选项因为setup没被挂载,也不能使用生命周期函数
ref
让基础类的数据具备响应式,
在composition API中的数据是不具备相应式的,修改数据,视图层不会改变。
在创建数据的时候用ref包裹一下即可
<script>
const app = Vue.createApp({
setup(props,context) {
// 在composition API 中更改数据,页面中不会变化,不具备响应式
// ref :让基础类的数据具备响应式
// 被包裹的数据 底层通过proxy代理,让其具备响应式-->proxy({value:"你好呀!"})
// 1.引入ref
const {ref} = Vue
// 用ref将数据包裹
let msg = ref("你好呀!")
// 因为被包裹的数据,被proxy代理的成了一个对象,value指向这个对象的值,所以要修改value
// 两秒后修改
setTimeout(()=>{
msg.value="hahah"
},2000)
return{
msg
}
}
}).mount('#app')
</script>
reactive
让引用类数据具备响应式
用法同ref
<script>
const app = Vue.createApp({
setup(props, context) {
// reactive:让引用类的数据具备响应式
// 被包裹的数据 底层通过proxy代理,让其具备响应式-->proxy({name:"张三",gender:"男"})
// 1.引入reactive
const { reactive } = Vue
// 用reactive将数据包裹
let pObj = reactive({ name: "张三", gender: "男" })
let pArr = reactive(["Vue", "React"])
// 这里直接修改即可,因为地址一样
setTimeout(() => {
pObj.name = "张某某"
pArr[0] = "javaScript"
}, 2000)
return {
pObj,
pArr
}
}
}).mount('#app')
</script>
readonly
让数据只读,不能修改
在composition API 中数据是可以修改的,这违背了Vue单项数据流的初心,为了防止子组件中修改父组件的数据,用readonly将数据包裹即可,
eg:
let pObj = readOnly({name:”张三”,age:12})
toRefs
让解构的变量也具备响应式,
我们正常解构出来的变量是不具备响应式的,修改数据视图层不会改变,通过toRefs包裹要解构的数据,即可将解构出来的变量具备响应式
<script>
setup(props, context) {
const { reactive, toRefs } = Vue
let pObj = reactive({ name: "张三", gender: "男" })
setTimeout(() => {
pObj.name = "张某某"
}, 2000)
// 将数据从变量结构出来
// 将数据从变量结构出来,用toRefs将原对象包裹
// 原理,会将对象中的每个属性拿出来在代理包装一下,此时变量就具备响应式了
/*
proxy({ name: "张三", gender: "男" })---->
name:proxy({value:"张三"})
gender:proxy({value:"男"})
*/
const {name, gender} = toRefs(pObj)
return {
name,
gender
}
}
</script>
toRef
有时候不确传来的数据对象中是否包含某个属性,
eg:针对此类情况如果pObj对象中有age就拿出来赋值给age变量,如果没有就强行添加一个值为undefined的属性age
let age = toRef(pObj, "age")
context
在setup中有两个参数,其中一个参数是context,代表上下文对象里面有attrs,slots,emit
attrs:里面存放的是no-props相关的东西
slots:里面存放的是插槽相关的东西
emit:里面存放的是子组件发射的自定义事件
context-attrs
这个里面是no-props相关的东西,比如父组件在调用子组件的时候传入了一个属性,那么通过context.attrs.属性名,即可获取到传入的属性
父组件给子组件传参:
<box s1=”666”></box>
子组件中:
setup(props,context){
console.log(context.attrs.s1)
//结果:666
}
context-slots
这个里面存放的是插槽相关的东西,比如子组件中有一个具名插槽box和一个匿名插槽,父组件在具名插槽box使用button按钮占位,匿名插槽使用输入框占位,在子组件的setup函数中,如果想获得匿名插槽相关的东西,就调用context.slots.default(),如果是具名插槽,context.slots.具名插槽名()
获得数据是数组,访问里面内容的时候记得加下标
<script>
const box = {
template: "#box",
setup(props, context) {
const { slots } = context
console.log(slots.default());//获取的是匿名插槽的内容
console.log(slots.box())//获取的是具名插槽box的内容
},
}
</script>
context-emit
主要解决在composition API 中,不能使用this。$emit发射事件也不能用。
只需要将emit 从context中解构出来,用emit代替this.$emit发射事件即可
<script>
function handel() {
alert("子组件点击")
// 在composition API中,因为不能用this,
//所以直接用context中的emit代替this.$emit发射事件
emit("my-son", "我是信息")
}
</script>
计算属性(computed)的新用法:
在composition API 中computed用法也发生了改变,仅在创建的时候发生了改变,其他的都没变,创建前需要从Vue中引入
<script>
const app = Vue.createApp({
setup(props, context) {
// 引入
const { ref, computed } = Vue;
let num1 = ref(10);
// 计算属性
let num2 = computed(() => {
return num1.value * 10
})
// add
let add = () => {
num1.value += 10;
}
// 将数据抛出
return {
num1, num2, add
}
}
}).mount('#app')
</script>
此外计算属性的参数也可以是set和get,而且也可以对引用数据类型的值经行修改
<script>
const app = Vue.createApp({
setup(props, context) {
// 引入
const { reactive, computed } = Vue;
// 值为引用类
let num1 = reactive({ count: 100 });
// 计算属性
let num2 = computed({
get() {
return num1.count * 10
},
set(res) {
console.log(res);
num1.count = res / 10
}
})
// 定时器
setTimeout(() => {
num2.value = 2000
}, 2000)
// add
let add = () => {
num1.count += 10;
}
// 将数据抛出
return {
num1,
num2,
add
}
}
}).mount('#app')
</script>
侦听器的新用法watch
在composition API 中,侦听器也需要改变一下用法,
以下代码中包含监听一个属性或者监听多个属性的用法
既可以侦听基础类型的数据也可以侦听引用类型的数据
侦听器的特性:
- 具有惰性(页面加载的时候不触发,必须在数据变动的情况下才会触发)
- 更加具体,
- 可以访问属性改变之前的值
- 可配置(非惰性、深度监视…)
深度监视:加入监听的是一个对象,对象中有数组,浅度监听,数组变化不会触发,深度监听则会触发监听函数
侦听器回调函数的options参数:
immediate:true 是否非惰性,默认false
deep:true 是否深度监听:默认false
})
侦听基础类型数据:
<script>
const app = Vue.createApp({
setup(props, context) {
const { ref, watch } = Vue
let brand = ref("")
let site = ref("")
// 侦听器
// watch(source,cb,option)
// 第一个参数表示侦听谁 第二个是回调函数 第三个是用来配置深度监视还是浅度监视
// 监听一个属性
/* watch(brand, (currentVal, preVal) => {
// 相应业务
console.log("现在的值:" + currentVal);
console.log("之前的值:" + preVal);
}) */
// 侦听多个属性
watch([brand, site], ([currentBrand, currentSite], [preBrand, preSite]) => {
console.log("现在的值:" + currentBrand, currentSite);
console.log("之前的值:" + preBrand, preSite);
})
return {
brand,
site
}
},
}).mount('#app')
</script>
侦听引用类型数据:
如果要侦听对象中的某个属性,只需要将对象的属性包裹在函数中return出去即可
<script>
setup(props, context) {
const { reactive, watch, toRefs } = Vue
let brandObj = reactive({ brand: "" })
// 侦听器
// watch(source,cb,option)s
// 第一个参数表示侦听谁 第二个是回调函数 第三个是用来配置深度监视还是浅度监视
// 监听引用数据类型属性,
// ()=>{return brandObj.brand }:简写
watch(() => brandObj.brand, (currentVal, preVal) => {
// 相应业务
console.log("现在的值:" + currentVal);
console.log("之前的值:" + preVal);
})
const { brand } = toRefs(brandObj)
return {
brand,
}
},
</script>
watchEffect
副作用函数,监听整个实例中的数据,只要当前实例中任何数据发生了改变就会触发。
特点:
- 非惰性
- 更加抽象
- 不可以访问属性之前的值
<script>
const app = Vue.createApp({
setup(props, context) {
const { ref, watch, watchEffect } = Vue
let brand = ref("")
let site = ref("")
// watchEffect
watchEffect(()=>{
console.log("开始侦听了");
console.log(brand.value);
console.log(site.value);
})
return {
brand,
site
}
},
</script>
provide和inject
发射和接收数据,当然也可以发射函数
有的时候组件的层级太深了,正常的组件传参会很麻烦,或者兄弟组件没法传参,这时候就用用到provide和inject。
用法:在要发射数据的组件中使用provide发射数据,在要接收数据的组件中使用inject接收数据。
子组件中:
<script>
const myson = {
template: `
<div style="width:150px;height:150px;background-color:#f00">
{{sMsg}}-------{{sCollege}}
<button @click="changeMsg">修改</button>
</div>
`,
setup(props, context) {
const { inject } = Vue;
// 通过inject接收别的组件发射的值,参数一是发射过来的值,参数二是默认值,可选参数
let sMsg = inject("msg", "默认值")
let sCollege = inject("college", "默认值")
let changeName = inject("changeName")
// 创建修改数据的方法
let changeMsg = () => {
changeName("我不是一般的好!!")
}
return {
sMsg,
sCollege,
changeMsg
}
}
}
</script>
父组件中:
<script>
const app = Vue.createApp({
setup(props, context) {
const { ref, reactive, provide, readonly } = Vue
let msg = ref("你好");
let college = reactive(["数学", "语文", "英语"])
let changeName = (val) => {
msg.value = val
}
// 发射
// 通过provide发射数据,参数一是发射的数据的名称(自定义),参数二是值
// 在composition API中数据没有数据单向流,所以要在传递数据的时候设置一下
provide("msg", readonly(msg))
provide("college", readonly(college))
// 如果想要在子组件中修改父组件的数据,应当遵循单项数据流的原则,在源头修改,
// 发射一个数据的方法
provide("changeName",changeName)
return {
}
},
components: {
"my-son": myson
}
}).mount('#app')
</script>
生命周期钩子的新写法
在composition API 中,因为setup代替了beforeCreate和created,所以这两个钩子失效,其他的只需要在Vue中引入,然后前面加一个on即可。
<script>
const app = Vue.createApp({
setup(props, context) {
const { onBeforeMount, onRenderTracked, onRenderTriggered, ref } = Vue
// 生命周期钩子的使用
onBeforeMount(() => {
console.log("onBeforeMount");
})
// 每次渲染后重新收集响应式依赖,当视图层更新就会触发调用
onRenderTracked(() => {
console.log("onRenderTracked()");
})
// 每次触发页面重新渲染自动执行,当控制层的数据发生了变化调用
onRenderTriggered(() => {
console.log("onRenderTriggered()");
})
let msg = ref("魑魅魍魉")
let handel = ()=>{
msg.value = "哈哈"
}
return {
msg,
handel
}
}
}).mount('#app')
</script>
ref(获取真实DOM)
不推荐使用,除非真的需要
<div id="app">
<div ref="box">哈哈</div>
<div ref="aaa">嘿嘿</div>
</div>
<script>
const app = Vue.createApp({
setup(props, context) {
const { onMounted, ref } = Vue
// ref 这里ref指的是获取真实dom
// 创建一个和试图层标签同名的ref
const box = ref(null)
const aaa = ref(null)
//
onMounted(()=>{
console.log(box.value);
console.log(aaa.value);
})
return{
box,
aaa
}
}
}).mount('#app')
</script>