前言
继前面讲了
Vue3搭建环境之后今天就来学习一下它的新特性以及新的API,赶紧背起来 ~😆
Componsition API
前情提要:
Vue3没有放弃Options API两者可以用在同一个文件夹中,但是不建议,不提倡;
Vue3相当重大的更新,负责任的讲学完这个Vue3就学完了😎,怎么解释?:一套允许灵活组合组件逻辑的附加的 API 函数;
Setup
想要学习
Componsition Api先从这里看起,setup是Vue3中新增的一个选项也是Componsition的入口函数;
- 接收两个参数
- 返回值可以是对象,函数,
JSX
参数 props, context
export default {
setup(props, context) {
// props: 就是 Vue2.x 中的接收父组件数据的 props, 切记不要使用结构语法对 props 进行结构,否则会失去 props 的响应式效果;
// context: 是一个 js 对象,暴露三个属性;slots emit attrs;
// 在 setup 中应该避免对 this 的使用,因为 setup 的执行时机比较原来的生命周期钩子 beforeCreate 之前,此时的 this 是 undefined
return {}
}
}
context
setup选项的第二个参数,是一个普通的对象,可以对其进行结构,包含三个参数slots, emit, attrs;以下是摘抄自官网对于
attrs, slots的描述;
-
emit:触发自定义事件使用,后面组件传值时,会有讲到; -
slots:访问slots时,如果是具名插槽可以使用slots.name()如果是默认插槽则使用slots.default()进行访问,该方法返回一个对象; -
attrs:值得注意的是,在Vue3中attrs是可以获取到class和style属性的;
export default {
props: {
name: {
type: String,
default: ''
}
},
emits: ['custom-event'], // 此选项可以是最简的数组也可以是对象;
setup(props, { slots, attrs, emit }) {
console.log(props.name);
console.log(slots.default());
console.log(attrs.style)
// 注意触发事件于之前有些许不同,需要新增一个 emits 的选项
emit('custom-event', 'params');
}
}
对象返回
import { ref } from 'vue'; // 先不要管
export default {
setup() {
const msg = ref('hello vue3');
return { msg } // setup return 出来的值可以直接再模板中使用
}
}
JSX 或者函数 返回
import { h } from 'vue';
export default {
setup() {
return () => {
// 函数其实就是手写 VNode 也可以使用 jsx 语法,如果是自己搭建的项目需要安装插件
// @vue/babel-plugin-jsx
return h('div', {}, 'hello vue3');
}
}
}
地址点这里 👉🏿 @vue/babel-plugin-jsx
关于 h 函数和 jsx 可以后面再单独开一篇,这里就不赘述了;
生命周期钩子
本来想把这个往后放一放,毕竟简单,相信大多数用过
Vue2的人,看一遍就知道了,但是为了照顾大多数人,还是放到这里吧;什么是生命周期?你能看这个就肯定知道,不解释了;直接看钩子;
强烈建议不要读一遍就完事了,钩子手敲之后感悟很深;
准备前提:
parent.vuechild.vue,为了演示会在一个文件中写入options api和componsition api仅用于演示,不建议用于生产环境;
-
Vue3中去把原来的卸载阶段钩子beforeDestroydestroyed更改为beforeUnmountunmounted; -
Vue3中所有的同阶段的Componsition API执行时机早于Options API;
// child.vue
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from "vue";
export default {
setup() {
// setup 可以理解为是之前的 beforeCreate 和 created 钩子
console.log("childComponsition --- setup");
onBeforeMount(() => {
console.log("childComponsition --- onBeforeMount");
});
onMounted(() => {
console.log("childComponsition --- onMounted");
});
onBeforeUpdate(() => {
console.log("childComponsition --- onBeforeUpdate");
});
onUpdated(() => {
console.log("childComponsition --- onUpdated");
});
onBeforeUnmount(() => {
console.log("childComponsition --- onBeforeUnmount");
});
onUnmounted(() => {
console.log("childComponsition --- onUnmounted");
});
return () => {
return <div>this is child</div>;
};
},
beforeCreate() {
console.log("childOptions --- beforeCreate");
},
created() {
console.log("childOptions --- created");
},
beforeMount() {
console.log("childOptions --- beforeMount");
},
mounted() {
console.log("childOptions --- mounted");
},
beforeUpdate() {
console.log("childOptions --- beforeUpdate");
},
updated() {
console.log("childOptions --- updated");
},
beforeUnmount() {
console.log("childOptions --- beforeUnmount");
},
unmounted() {
console.log("childOptions --- unmounted");
}
};
<template>
<Child v-if="isShow" />
<button @click="isShow = !isShow">toggleChild</button>
</template>
<script>
// parent.vue
import Child from "@/components/child.vue";
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref } from "vue";
export default {
components: {
Child
},
setup() {
const isShow = ref(true);
// setup 可以理解为是之前的 beforeCreate 和 created 钩子
console.log("parentComponsition --- setup");
onBeforeMount(() => {
console.log("parentComponsition --- onBeforeMount");
});
onMounted(() => {
console.log("parentComponsition --- onMounted");
});
onBeforeUpdate(() => {
console.log("parentComponsition --- onBeforeUpdate");
});
onUpdated(() => {
console.log("parentComponsition --- onUpdated");
});
onBeforeUnmount(() => {
console.log("parentComponsition --- onBeforeUnmount");
});
onUnmounted(() => {
console.log("parentComponsition --- onUnmounted");
});
return {
isShow
};
},
beforeCreate() {
console.log("parentOptions --- beforeCreate");
},
created() {
console.log("parentOptions --- created");
},
beforeMount() {
console.log("parentOptions --- beforeMount");
},
mounted() {
console.log("parentOptions --- mounted");
},
beforeUpdate() {
console.log("parentOptions --- beforeUpdate");
},
updated() {
console.log("parentOptions --- updated");
},
beforeUnmount() {
console.log("parentOptions --- beforeUnmount");
},
unmounted() {
console.log("parentOptions --- unmounted");
}
};
</script>
准备工作完成,首先看创建阶段;
挂载阶段演示
-
全程所有钩子,同级别
Componsition API早于Options API执行; -
父组件优先进入
beforeMount阶段;然后渲染子组件,子组件渲染完成进入父组件mounted阶段;
卸载更新阶段演示
- 父组件进入
beforeUpdate阶段,直到子组件卸载完成,进入父组件updated阶段;
常用响应式 API
ref
接收一个值并返回一个
ref对象;使其成为响应式,
- 访问时通过
.value进行访问;如果是在模板中访问不需要.value;
<template>
{{ msg }}
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const msg = ref('hello vue3');
return {
msg
}
}
}
</script>
- 也可以通过给属性绑定
ref获取DOM元素
<template>
<div ref="rootRef">hello Vue3</div>
</template>
<script>
import { ref, onMounted } from 'ref';
export default {
setup() {
const rootRef = ref(null);
// onMounted Vue3 中的生命周期
// 注意 setup 执行时机比较早,如果想要访问 DOM 最好等待组件挂载完成之后;
onMounted(() => {
// 访问时 .value 后就是原生的 DOM 对象可以使用里面的 属性以及方法
console.log(rootRef.value.innerText);
});
return {
rootRef
}
}
}
</script>
当然 ref 可以获取某一个元素,也可以获取多个元素,具体使用方法可以演示
<template>
<!-- ref 接收一个函数,参数就是这个元素,并且把他存储起来 -->
<div v-for="item in lists" :key="item" :ref="el => eles.push(el)"></div>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
const lists = [1, 2, 3, 4, 5];
const eles = ref([]); // 存储元素的 ref
onMounted(() => {
console.log(eles);
});
}
}
</script>
- 了解了
ref的使用,也应该了解一些它的特性;看如下代码的打印信息
import { ref } from 'vue';
export default {
setup() {
const msg = ref('hello Vue3');
const msg1 = ref({
name: 'zhangsan',
age: 24
});
console.log(msg);
console.log(msg1);
return {}
}
}
上面内容可以得出,会返回一个 ref 类型的对象,如果是一个简单数据类型那么就直接存放在 value 中,如果是复杂数据类型的对象,就是一个 proxy 代理对象;
reactive
接收一个对象,返回这个对象的代理对象 基于
ES2015中的Proxy实现,注意代理对象并不等同于对象本身,所以后续操作应当操作这个代理对象,才能保证对象的响应式
- 注意使用时,只能接收对象,切勿传递普通值;造成错误;
import { reactive } from 'vue';
export default {
setup() {
// 正常使用
const msg = reactive({
name: 'zangsan',
age: 24
});
// 错误使用。可以正常打印,但是会有 ⚠️
const msg1 = reactive('111');
console.log(msg1)
return {}
}
}
- 关于类型,它其实就是返回的代理对象
Proxy;看到这里回过头来看我们给ref传递对象时里面的值是一样的;也间接证明了,ref中的对象其实是由reactive进行包装;
readonly
接收一个对象,或者
ref对象、reactive代理对象,返回这个对象的只读代理;看下面代码打印结果,如果修改被
readonly包裹的对象,会出现警告,并且无法修改;
import { ref, reactive, readonly } from 'vue';
export default {
setup() {
const msg = ref("111");
const msg1 = reactive({ name: "zhangsan" });
const msg2 = { name: "lisi" };
const readMsg = readonly(msg);
const readMsg1 = readonly(msg1);
const readMsg2 = readonly(msg2);
readMsg.value = 1;
console.log(readMsg);
readMsg1.name = 1;
console.log(readMsg1);
readMsg2.name = 1;
console.log(readMsg2);
}
}
computed 计算属性
传入一个
getter或者getter和setter函数,返回一个ref对象;注意:
有
setter的前提是一定有getter;返回的是
ref对象,意味着后续的访问要有.value;
只有 getter 函数时,传递一个回调函数即可;
import { computed, ref } from 'vue';
export default {
setup() {
const count = ref(1);
const counter = computed(() => {
return ++count.value;
})
return {
counter
}
}
}
当 getter 和 setter 同时存在时,需要传入一个对象包含 get 和 set 方法;
<template>
<button @click="add">add</button>
{{ counter }}
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const count = ref(0);
// 计算属性
const counter = computed({
get: () => {
return count.value;
},
set: (val) => {
count.value = val;
}
});
// 事件函数
const add = () => {
count.value++;
};
return {
add,
counter
}
}
}
</script>
侦听属性
watch
- 单个监听源:简单理解就是监听一个值(可以是复杂数据类型,也可以是简单数据类型);
watch的第一个参数只会接收一个getter函数或者一个ref对象;
import { watch, ref, reactive } from 'vue';
export default {
setup() {
const count = ref(0);
const counter = reactive({ val: 0 });
watch(count, () => console.log('触发了 watch')); // 侦听 ref
watch(() => counter.val, () => console.log('触发了 watch')); // 侦听 getter
// 注意:如果不是 ref 对象或者 getter 函数会有警告,但不会报错;
watch(counter.val, () => console.log('触发了 watch'));
const add = () => {
count.value++;
counter.val++;
};
return {
count,
counter,
add
}
}
}
- 监听多个源,如果我们把值换成数组可以实现监听多条数据,这个功能记得
vue2.x是不具备的;虽然是多个监听源但是上面的规则同样适用,只要监听对象里面有一个不是ref对象就要使用getter方法;
import { watch, ref, reactive } from 'vue';
export default {
setup() {
const count = ref(0);
const count1 = ref(1);
// 注意这个不是 ref 对象
const count2 = reactive({ val: 2 });
// 正确的侦听方式
watch(() => [count, count1, count2.val], () => console.log('触发了 watch1 '));
watch([count, count1], () => console.log('触发了 watch2'));
// 错误的侦听方式:存在非 ref 对象时,必须使用 getter 函数;
// 不会报错,但是会触发警告 ⚠️ ;
watch([count, count1, count2.val], () => console.log('触发了 watch3'));
const add = () => {
count.value++;
count1.value++;
count2.val++;
}
return { count, count1, count2, add };
}
}
- 这个版本的
watch同样支持深度监听或者立即触发机制;只需要在第三个参数增加配置即可;
import { watch, reactive } from 'vue';
export default {
setup() {
const counter = reactive({
count: 1
});
watch(
() => counter,
() => {'触发了深度监听和立即执行'},
{ immediate: true, deep: true }
);
const add = () => {
counter.count++;
}
return { add, count: counter.count}
}
}
watch新版本增加了暂停侦听操作;侦听函数会返回一个stop函数;
import { watch, ref } from 'vue';
export default {
setup() {
const count = ref(0);
const stop = watch(count, () => console.log('update'));
const add = () => {
count.value++;
if (count.value === 5) {
stop();
}
}
}
}
watchEffect
新增的侦听函数,根据其内部依赖值得变化调用,初始的时候会先调用一次;
-
非响应式的值是不可以触发二次更新的;默认是深度监听;
-
和
watch一样返回一个stop可以停止侦听; -
如果使用在
setup或者 生命周期钩子中时,在unmounted钩子中会自动停止侦听;
import { watchEffect, ref } from 'vue';
export default {
setup() {
const count = ref(0);
watchEffect(() => {
console.log(count.value);
});
setTimeout(() => {
count.value++;
}, 1000)
return {}
}
}
- **注意:**如果你想在
watchEffect中去获取DOM或者模板中refs的话,一定要放到onMounted钩子中去进行;
export default {
setup() {
const rootRef = ref(null);
onMounted(() => {
watchEffect(() => {
console.log(rootRef.value.innerText);
});
});
}
}
watchEffect 的第二个参数
watchEffect 中的内容,首次调用的时候,是同步进行调用的,当内容触发更改再次调用时默认回调函数是在组件更新之前调用的;我们可以做一个小小的测试进行校验;以下是完整的测试代码,建议自行复制下来跑一下进行测试;
<template>
{{ count }}
</template>
<script>
import { ref, watchEffect, onBeforeUpdate } from 'vue';
export default {
setup() {
const count = ref(0);
setTimeout(() => {
++count.value;
}, 2000);
watchEffect(() => {
console.log('count', count.value);
});
onUpdated(() => {
console.log('onBeforeUpdate');
});
return { count }
}
}
</script>
那么问题来了,顺序有没有办法改变呢?答案是肯定有的,官网也已经说明 这里 watchEffect 提供了第二个参数;
它接收的第二个参数是一个对象,接收一个键值对;
{ flush: 'pre'}默认就是pre即我们上面演示的结果(在副作用更新之后执行页面更新操作onBeforeUpdate);{ flush: 'post' }就是在副作用更新之前执行;{ flush: 'sync' }这会将副作用强制进行同步执行,效率比较低下不推荐;
debugger watchEffect
上面说到我们会在第二个参数中新增加一个选项用来控制副作用执行的时机,此外我们还可以使用两个方法来进行调试;
onTrack:会在侦听值被追踪时调用;换句话说,就是第一次执行的时候就会被调用;onTrigger:会在侦听值发生改变导致副作用产生时进行调用;是早于onInvalidate- 注意: 这两个方法仅适用于开发环境调试适用;
<template>
{{ counter }}
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const counter = ref(0);
setTimeout(() => {
++counter.value;
}, 2000);
watchEffect(
() => {
console.log('counter', counter.value);
},
{
// 首次就会执行,此后每次依赖变更也会执行
onTrack(e) {
console.log('onTrack', e);
},
// 只有当依赖变更时才会执行,执行时要早于 onTrack
onTrigger(e) {
console.log('onTrigger', e);
}
}
);
return { counter };
}
}
</script>
清除副作用 onInvalidate
onInvalidate是watchEffect中回调函数的参数,它本身也是一个函数,接收一个回调函数;它的调用时机是变化之后调用
watchEffect调用之前调用,用来清除一些副作用(关于副作用]);它也是早于stop函数的;
export default {
setup() {
const count = ref(0);
// 首次执行的时候并不会触发 onInvalidate ;
// 当 count 的值改变时再次触发 watchEffect 的时候,会优先调用 onInvalidate;
watchEffect(onInvalidate => {
console.log('count', count.value);
onInvalidate(() => {
console.log('onInvalidate');
});
});
}
}
以官网为示例,假设我们现在需要请求一个异步函数,下次调用之前,我们肯定要清楚这个副作用,此时我们就会用到
onInvalidate这个回调
export default {
setup() {
watchEffect(async onInvalidate => {
const data = await getData();
onInvalidate(() => {
data.cancel();
});
});
}
}
这里可以脱离业务做一个小的案例,比如我们现在有一个定时器,一直在给某一个数自增,如果这个这个数字大于n的时候,我们去停止这个定时器,当然啦实现的方法有很多种,但为了演示,还是使用
onInvalidate去尝试一下;
export default {
setup() {
const count = ref(0);
const timerId = setInterval(() => {
++count.value;
}, 2000);
watchEffect(onInvalidate => {
console.log('count', count.value);
onInvalidate(() => {
if (count.valut >= 5) {
clearInterval(timerId);
}
});
});
}
}
响应式工具函数
isRef
检测一个值是否为
ref对象;返回布尔值;需要注意的后面我会提到的shallowwRef包裹的ref对象也是会返回为true的;以下所有拥有shallow系列api的函数,享有相同的特性,具体差距会在后面讲到;
import { isRef, ref } from 'vue'
export default {
setup() {
const count = ref(0);
// 注意不要加上.value value访问的是值;
// 一句话概括就是 vue3.x 中只要用.value访问的对象都是 ref 对象;
console.log(isRef(count)); // true
}
}
unref
引用官网的原文:如果传入参数是 ref 这个函数会返回内部值, 否则就返回传入值.
这是一个
val = isRef(val) ? val.value : val的语法糖.我个人认为也可以理解为可以将一个
ref对象变成非ref的值或者对象,里面也做了一些容错,比如返回的不是ref那么就原样返回
import { ref, unref } from 'vue';
export default {
setup() {
const count = ref(0);
const counter = ref({count: 1});
console.log(unref(count));
// 注意非 ref 对象不代表不是响应式对象;返回 Proxy{count:1}
console.log(unref(counter));
}
}
toRef
引用官网的原文:可以为响应式对象的属性创建 ref, 并保留原属性的响应式.
这句话只是为了强调这个
api它不会丢失原有的响应式,实际上你也可以给一个普通对象使用;
import { toRef, reactive, isRef } from 'vue';
export default {
setup() {
// 按照上面api的解释我们可能写出下面的代码
const obj = reactive({ name: 'zangsan' });
const name = toRef(obj, 'name');
console.log(isRef(name)); // true
}
}
toRefs
接收一个响应式对象(非响应式也可以)使其内部所有的
property变成ref对象;这个
api可是有奇用;比如当你的想给模板一些见得值而非对象的时候可以借助这个函数进行结构;
import { reactive, toRefs } from 'vue';
export default {
setup() {
const obj = reactive({name: 'zhangsan', age: 24});
const {name, age} = toRefs(obj);
return { name, age }
}
}
isReactive
用来判断这个对象是否是
reactive创建的对象,如果是被readonly包裹的reactive对象也会返回true;需要注意的包括我们后面介绍的shallowReactive和shallowReadonly包裹的对象也会返回为true的;
import { reactive, readonly, isReactive } from 'vue'
export default {
setup() {
const obj = reactive({ name: 'zhangsan', age: 24 });
const readObj = readonly(obj);
console.log(isReactive(obj)); // true
console.log(isReactive(readObj)); // true
}
}
isReadonly
判断是否是一个
readonly或者shallowReadonly包裹的只读代理对象;
import { readonly, isReadonly } from 'vue'
export default {
setup() {
const obj = readonly({name: 'zhangsan', age: 24});
console.log(isReadonly(obj)); // true
}
}
isProxy
判断是否为
readonly或者reactive创建的代理对象;
import { reactive, readonly, isProxy } from 'vue'
export default {
setup() {
const obj = { name: "zhangsan", age: 24 };
const reactiveObj = reactive(obj);
const readonlyObj = readonly(obj);
console.log(isProxy(reactiveObj)); // true
console.log(isProxy(readonlyObj)); // true
}
}
依赖注入(provide, inject)
在
vue3中provide, inject同样提供了依赖注入的能力,可以在祖孙(父子)间调用,官网对于这也有示例以及描述,provide, inject在这咱们也做个假设,加入你有两个组件(父子),但是父组件是用
slot来封装的,比如一下这样
<!-- /compoennts/map.vue -->
<template>
<slot></slot>
</template>
<!-- /components/marker.vue -->
<template>
this is marker
</template>
以上这种情况子组件其实并不一定是marker,虽然例子有些牵强,但相信实际工作中都有碰到过类似情况,如果我们想把 map 中的内容传递下去,此时就可以考虑依赖注入
// /compoennts/map.vue
import { provide } from 'vue'
export default {
setup() {
// provide 接收两个值,name: String, value: any
provide('position', {lg: 100, lt: 105})
}
}
// /compoennts/marker.vue
import { inject } from 'vue'
export default {
setup() {
// inject 接收两个值,name: String, default: 默认值
const position = inject('position')
return { position }
}
}
当然也可以注入响应式数据,就是把 provide 的数据,用 ref 或者 reactive 进行包装,但是如果涉及到修改数据,vue 强烈建议你再父组件中进行修改,为了防止用户修改,我们可以在 ref 或 reactive 之外再加一个 readonly 进行包裹;
// /compoennts/map.vue
import { reactive, readonly, provide } from 'vue'
export default {
setup() {
const pos = reactive({ lg: 100, lt: 105 });
const updatePos = () => {
pos.lg = 101
};
provide('pos', readonly(pos));
provide('updatePos', updatePos);
}
}
高级响应式API
markRaw
标记一个对象,使其不能再转换为代理对象,返回值是它本身
export default {
setup() {
const testData = { name: "zhangsan", info: { sex: "boy", hoppy: "football" } };
console.log(isReactive(markRaw(readonly(testData)))); // false
console.log(isReactive(markRaw(shallowReadonly(testData)))); // false
console.log(isReactive(markRaw(reactive(testData)))); // false
console.log(isReactive(markRaw(shallowReactive(testData)))); // false
}
};
toRaw
返回被
reactivereadonly代理对象的原始对象,包含shallow系列的 API,可以在操作数据时,不触发响应式数据的跟踪;
export default {
setup() {
const testData = { name: "zhangsan", info: { sex: "boy", hoppy: "football" } };
const reactiveData = reactive(testData);
const shallowReactiveData = shallowReactive(testData);
const readonlyData = readonly(testData);
const shallowReadonlyData = shallowReadonly(testData);
// 修改数据
toRaw(reactiveData).name = 'lisi'
console.log(reactiveData)
const refData = ref(testData);
const shallowRefData = shallowRef(testData);
console.log(toRaw(reactiveData) === testData); // true
console.log(toRaw(shallowReactiveData) === testData); // true
console.log(toRaw(readonlyData) === testData); // true
console.log(toRaw(shallowReadonlyData) === testData); // true
console.log(toRaw(refData) === testData); // false
console.log(toRaw(shallowRefData) === testData); // false
}
};
customRef
顾名思义,创建一个自定义的 ref ,可以自行跟踪其中的依赖变化,
它需要一个工厂函数,这个函数要接收两个函数
tracktrigger作为参数,并且这个函数要返回一个带有getset的对象;
function useDebounceRef(value) {
return (track, trigger) => {
return {
get() {
track(); // 告诉Vue这里要追踪依赖变化
return value;
},
set(newVal) {
newVal = value;
trigger(); // 通知Vue更新视图
}
}
}
}
export default {
setup() {
const data = useDebounceRef("")
// 如果在script标签中访问这个 data 就需要使用 data.value
}
}
shallowRef
创建一个跟踪自身
.value变化的ref,但是不会使值也变成响应式的;这句话太晦涩了,我个人的理解就是,浅包装,只会对第一层数据有响应式
import { ref, shallowRef, isReactive } from 'vue'
export default {
setup() {
// 如果是简单数据类型,响应式仍然存在还没有丢失;
const data = shallowRef('aaa');
// 如果是复杂数据类型,内部是使用reactive来进行包装的,所以我们可以来使用 isReactive 来测试内部数据是否还存在有响应式
const data1 = shallowRef({a: 'aaa'});
const data2 = ref({a: 'aaa'});
console.log(isReactive(data1)); // false
console.log(isReactive(data2)); // true
}
}
shallowReactive
创建一个只能跟踪自身属性的响应式数据代理,但是不进行深层跟踪;
简单理解就是只有对象的第一层属性是响应式的,内嵌的所有属性,都没有响应式
import { shallowReactive, reactive, isReactive } from 'vue'
export default {
setup() {
const data = shallowReactive({ a: "aaa", info: { b: "bbb" } });
const data1 = reactive({ a: "aaa", info: { b: "bbb" } });
const changeValue = () => {
console.log(isReactive(data.info)); // false
console.log(isReactive(data1.info)); // true
};
}
}
shallowReadonly
创建一个只能跟踪自身属性的响应式只读数据代理,但是不进行深层跟踪;
import { readonly, shallowReadonly, isProxy } from 'vue'
export default {
setup() {
const data = shallowReadonly({ a: "aaa", info: { b: "bbb" } });
const data1 = readonly({ a: "aaa", info: { b: "bbb" } });
const changeValue = () => {
console.log(isProxy(data.info)); // false
console.log(isProxy(data1.info)); // true
};
}
}
getCurrentInstance
获取当前组件实例,不要把他当成
this慎用,后面有使用场景在进行举例说明,只能在setup和 生命周期中使用;
import { getCurrentInstance } from 'vue'
export default {
setup() {
const instance = getCurrentInstance();
}
}
内置组件
teleport
在
vue中我们虽然可以随意的创建组件,并且挂载到任意位置,但是我们的组件,始终都是在一个 app 容器中,某些时候特定的组件,放到外部分离会显得更加合适,比如dialog我们想让他挂载到和app作为兄弟节点;此时我们就可以使用vue3新增的组件teleport
比如在 vue2 中我们想封装一个 dialog 组件可能需要这样,样式什么的我这里就不补充了;
<!-- /components/Dialog.vue -->
<template>
<div class="dialog" v-if="isShow">
<div class="dialog-hander">
</div>
<div class="dialog-content">
<slot></slot>
</div>
<div class="dialog-footer">
<div v-if="isFooter">
<button size="mini" type="primary" @click="confirmHandler">确定</button>
<button size="mini" @click="cancelHanlder">取消</button>
</div>
<div v-else>
<slot name="footer"></slot>
</div>
</div>
</div>
</template>
封装完成之后,我们如果需要把他放到某个页面中去使用他,就肯定需要导入他,比如
<!-- /views/home.vue -->
<teamplte>
<div class="home-page">
<button @click="isShow = !isShow">点击</button>
<Dialog :isShow="isShow"></Dialog>
</div>
</teamplte>
<script>
export default {
name: 'Home',
data() {
return {
isShow: false
}
}
}
</script>
但是问题回来了,渲染到这里真的很合适吗?如果我们想把这个Dialog 和 APP 同级放到 body 下有什么方案吗,Vue3 的 teleport 内置组件就是为了解决这个问题出现的;它接收一个属性 to
- 这个属性必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用);
此时只需要在我们封装的Dilaog 标签的外部用 teleport 包裹一下,在增加一个 to 属性即可
<template>
<teleport to="body">
... dialog
</teleport>
</template>
应用配置
这个其实还是有挺多的,这里我就说一个我认为日常开发很重的的方法吧,其他的大家可以自行查阅
gloalProperties
Vue2中Vue.prototype.xxx的替代者;
const app = createApp({})
app.config.globalProperties.$http = () => {}
// 此时刚好可以用咱们之前讲到的 currentinstance 来获取这个 $http 方法
// /page/xxx.vue
import { getCurrentInstance } from 'vue'
export default {
setup() {
setup() {
const instance = getCurrentInstance()
// $http 方法可以通过以下获得
console.log(instance.appContext.config.globalProperties)
}
}
}
指令
个人认为比较人性化的修改,更改的指令的生命周期钩子,与组件的钩子基本相同,这里就不详细说明了,有兴趣的可以去官网看下;
import { createApp } from 'vue'
const app = createApp({})
app.directive('my-directive', {
// 在绑定元素的 attribute 或事件监听器被应用之前调用
created(el, binding, vnode) {},
// 在绑定元素的父组件挂载之前调用
beforeMount(el, binding, vnode) {},
// 绑定元素的父组件被挂载时调用
mounted(el, binding, vnode) {},
// 在包含组件的 VNode 更新之前调用
beforeUpdate(el, binding, vnode, preVNode) {},
// 在包含组件的 VNode 及其子组件的 VNode 更新之后调用
updated(el, binding, vnode, preVNode) {},
// 在绑定元素的父组件卸载之前调用
beforeUnmount(el, binding, vnode) {},
// 卸载绑定元素的父组件时调用
unmounted(el, binding, vnode) {}
})
-
el:指令绑定到的元素。这可用于直接操作 DOM。 -
binding:是一个对象arg:指令的参数 例如:v-my-directive:param="11"其中 param 就是arg对应的值dir:一个对象,在注册指令时作为参数传递。
-
instance:使用这个指令的组件的实例对象; -
modifiers:指令的修饰符(修饰符可以写在参数的前后任意位置)例如:v-my-directive.sync1:params.sync="11" -
oldValue:仅在beforeUpdateupdate中可用 -
value:即给指令传递的值 例如:v-my-directive="11"其中 11 就是value -
vnode:虚拟节点 -
preVNode:仅仅在beforeUpdateupdated钩子中可以,上一次 的虚拟节点;
除了
el官方建议我们其他的参数尽量不要修改;应该时刻保持只读的状态;
其他
Vue3的更新当然不止上面的这些,只是个人认为想要入门Vue3这些是必须要掌握的,当然还有很多地方,比如像 组件上的v-model修改,$attrs的修改,去除了$listener等等,还有Vue3中一些实验性的功能Suspense, 这些大家可以去查阅一下官网,毕竟都是面向百度编程的 😂;参考内容: