前言
vue3相对于vue2做了不少的改变,个人对其进行了一些整理。并结合在日常中的使用,对这些新特性在开发时的影响度进行了分级。
T0档次
setup
介绍:setup(props,content) objet
官方文档给出的解释是,在目前vue在编写复杂组件的时候(组件内代码量的庞大),会导致本来是一个逻辑块的代码,分散开来。这样会增加后面接手的人阅读上的难度。所以提供了一个组合api,可以把相同逻辑的代码块放在一个地方,这样便于阅读。不过这个东西仁者见仁智者见智,本人就觉得把一个个逻辑块分块放好也挺好的,重点开发人员编码习惯,一个邋遢的开发人员,就算你给他提供再好的api,照样会给你写出奇形怪状的代码来。但话说回来,你可以不用,但我不能没有。vue3充分尊重了开发人员的选择,setup你可以用,也可以不用。就像vue2一样,在钩子函数和methods中编写逻辑块,也是一种正确的选择
使用细节:
相对于vue2的执行周期
setup(beforeCreate,created)凡是在原来beforeCreate和create中执行的代码,你都可以放在setup中,而且setup执行的时机还在brforeCreate之前
一些限制
1.不能访问$data,this
2.必须返回一个{},
返回值
如果存在返回值,那么它将会合并到$data中,如果$data已经存在相同的key,那么将会覆盖原有key值。值得一提的是,如果你返回的是一个普通对象,那么即便合并到$data之后,改变对应的值,也不会具有响应性。你需要使用vue3提供的响应式方法,将普通对象转化成响应式对象,这样合并在$data上的属性才会是响应式的
响应式原理
变化:
vue3将原来vue2中对数据响应式处理方法,由es5的Object.defineProperty改为es6的Proxy
不同的点:
defineProperty:是对对象的属性定义拦截,如果被定义的obj对象存在多个属性或者是多嵌套结构,那么就要便利递归它所有的属性,挨个去使用Object.defineProperty定义属性拦截
let value = "obj1 - prop1"; let obj = {}; Object.defineProperty(obj, "prop1", { get() { return value + " suffix"; }, }); console.log(obj); //obj1-name suffix
Proxy:是对obj整个对象进行代理,它返回一个新的代理对象,这意味着:
1.当我们改变数组中某一项的值的时候再也不需要使用$set了,直接this.arr[1]=new value即可触发视图更新。
2.我们在响应式对象上新增的属性时,依然会触发也会触发视图更新
let obj = { prop1: "obj1 - prop1" }; let objProxy = new Proxy(obj, { get(obj, prop) { return obj[prop] + " suffix"; }, }); console.log(obj.prop1); //obj1-prop1 console.log(objProxy.prop1); //obj1-prop1 suffix return reactive({ data: "9999" });
生命周期函数钩子
介绍:vue3新增一批生命周期钩子函数,其目的是为了解决在使用setup的时候,因为setup的执行周期是在beaforeCreate和ceated之间,一些需要在其他周期时机内执行的一些逻辑,比如获取dom。无法setup中执行。所以为了使在setup中的代码,能够在指定的声明周期内执行,vue3补充了一些周期钩子函数,它们的作用和vue的声明周期钩子完全一样。
举例使用
import { onMounted } from "vue"; //引入钩子函数,其他的声明周期钩子函数也是如此引入 export default { setup(props, content) { //因为在setup中无法访问this,所以也无法访问到$data,代码中需要依赖到$data中的字段的话,可以在setup中定义data,setup return之后,会合并至$data const data = reactive({ data: 1, }); //所有的钩子函数都是接收一个方法作为参数 onMounted(function() { console.log("参数方法,在对应的生命周期内执行 mounted", data.data); }); console.log("在setup中的代码,在beforeCreate至created 生命周期内执行"); return data; },
响应式api
简介:vue3提供的响应式api可以使普通对象转化为响应式对象,这也是为了配合setup的使用提供的一个方法,在setup的执行时机是beforeCreate和created,并且无法访问到this和$data,而使用setup可能会使用到一些响应式的变量(比如说,请求一个接口,在接口响应后将数据渲染到页面上,因为接口响应是异步的,所以就必须要一个响应式的对象接收数据发生改变,从而触发视图更新),而setup中不能读取this,自然就无法获取到data(){}项中定义的响应式属性,所以只能通过return的方式,把一个响应式的对象合并到data中。
<template> <div class="hello"> {{ prop1 }} </div> <button class="but" @click="changeVal">改变值</button> </template> <script> export default { setup(props, content) { //setup中将一个普通对象return合并到$data上之只会在初次渲染的时候作用在视图上,后续再对prop1进行更改this.prop1=new value,是不会触发视图更新的.因为data中的prop1只是一个普通的对象,而非响应式对象 const data = { prop1: "old value", }; return data; //return对象中所有属性都会合并到$data上 }, methods: { changeVal() { this.data = "new value"; //不会触发视图更新 }, }, }; </script>
//使用响应式api //引入响应式api import { ref } from "@vue/reactivity"; export default { //要想使setup中return的对象具有响应式那么只需将return的对象使用响应式钩子处理即可 setup(props, content) { const data = { prop1: ref("old value"),//使prop1属性变成响应式对象 }; return data; }, methods: { changeVal() { this.prop1 = "new value";//视图发生改变 }, }, };
响应式api分类:
1.reactive 将引用类型对象转化成响应式对象,(这样的好处是,遇到复杂的数据类型,不需递归去使用ref,不然又回到了vue2)
reactive({} || []); //针对引用类型,将引用类型转化为响应式对象
2.ref 适合将基本类型转化成响应式对象,但ref同样也能对引用类型进行转化,与reactive不同的是,你需要通过ref.value来使用属性。对于基本类型来说这是实现响应式的基本条件,但是对于引用类型来说已经可以通过Proxy来实现代理,没有必要在进行过渡封装本来可以prop直接使用,没有必要value.prop,所以为什么说,ref适合基本类型。值得一提的是ref在对引用类型处理的时候,也是将其转化成Proxy
const obj = ref(1 || "1"); //针对基本类型,接受一个基本类型返回响应式对象 console.log(obj.value); //返回的响应式对象,通过value获取值
响应式api拓展
为了让开发者在使用响应式api时,能够便捷,全面的适用场景,vue3,对响应式api进行了一些拓展,围绕reactive和ref提供了一些功能性的api
1.reactive
readonly:创建一个只读的响应式对象。ps:(都只读了还需要响应式吗?)
import {readonly} from "vue";//引入方法
const copy = readonly({ prop: "测试属性", level: { prop: "level 测试属性" }, }); //将普通对象的副本转化成一个只读的响应式对象 copy.prop = "1234"; //warning;在这里改变只读的copy响应式对象时,会出现警告,不允许改变响应式对象copy的prop属性; copy.level.prop = "9999"; //warning 只读属性是深层次的,不管嵌套了多少层,只要是属于copy内部的属性,都无发更改 },
isProxy:判断响应式对象是否是由reactive或者readonly创建的Proxy
import {readonly} from "vue" //引入方法
const copy = reactive({ prop: "测试属性", }); const copyReadonly = readonly({ prop: "only 属性" }); console.log(isProxy(copy)); //true console.log(isProxy(copyReadonly)); //true
console.log(isProxy({copyReadonly,copy})); //false
isReactive:判断响应式对象是否是由reactive创建响应式对象
import {isReactive} from "vue"
const copy = reactive({ prop: "测试属性", }); const copyReadonly = readonly({ prop: "only 属性" }); console.log(isReactive(copy)); //true console.log(isReactive(copyReadonly)); //true console.log(isReactive(readonly(copy))); //true 如果readonly依赖与reactive创建,那么也返回true console.log(isReactive(readonly({ copy }))); //false
isReadonly:判断是否是由readonly创建的响应式对象
import {isReadonly} from "vue";//引入方法
const copy = reactive({ prop: "测试属性", }); const copyReadonly = readonly({ prop: "only 属性" }); console.log(isReadonly(copy)); //false console.log(isReadonly(copyReadonly)); //true console.log(isReadonly(readonly(copy))); //true 如果readonly依赖于reactive创建,同样返回ture
toRaw:返回 reactive
或 readonly
代理的原始对象。这是一个转义口,可用于临时读取而不会引起代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用。请谨慎使用。
const copy = reactive({ prop: "测试属性", }); const copyReadonly = readonly({ prop: "only 属性" }); console.log(toRaw(copy)); //{prop:"测试属性"} console.log(toRaw(copyReadonly)); //{prop:"only 属性"} console.log(toRaw(readonly(copy))); //{prop:"测试属性"} 多重嵌套同样可以获取到原始值
markRaw:标记一个对象,使其永远不会被转还成代理
const base = markRaw({ prop: "base prop属性" }); const reactiveBase = reactive(base); const readonlyeBase = readonly(base); const proxyBase = reactive({ reactiveBase }); const proxyBase2 = reactive({ reactiveBase, prop2: "1245" }); //这里多出一个普通属性prop2 console.log(isReactive(reactiveBase)); //false console.log(isReadonly(readonlyeBase)); //false console.log(isReactive(proxyBase)); //true 但是改变属性reactiveBase中的prop,仍不会触发视图更新 console.log(isReactive(proxyBase2)); //true 只改动属性reactiveBase中的prop不会触发视图更新,但同时改变prop2属性,因为prop2属性正常的属性,所以会触发视图更新,视图更新的时候发现reactiveBase中的prop也发生了改变,
//所以即便它被设置成了markRaw,也一样会在视图上呈现改动后的内容,
shallowReactive:只对自身的属性进行响应式的追踪,对于嵌套的属性,不做响应式处理
const obj = { level1: "测试属性level1", sub: { subProp1: "测试属性subProp1", }, }; const shallowReactiveObj = shallowReactive(obj);
const reactiveObj = reactive(obj);//reactive console.log(isReactive(shallowReactiveObj)); //true console.log(isReactive(shallowReactiveObj.sub)); //false console.log(isReactive(reactiveObj.sub)); //true // ... //改动自身属性,触发视图更新 shallowReactiveObj.level1 = "new level1"; // ... //改动嵌套属性,不触发视图更新 shallowReactiveObj.sub.subProp1 = "new sub subProp1"; // ... //同时改变嵌套属性和自身属性 shallowReactiveObj.level1 = "new level1"; shallowReactiveObj.sub.subProp1 = "new sub subProp1"; //和markRaw一样,因为改变自身属性时会造成视图更新,而更新的时候发现嵌套熟悉sub.subProp1也改变了,所以改变后的sub.subProp1也会渲染至最新视图 return shallowReactiveObj;
shallowReadonly:使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。
const state = shallowReadonly({ foo: 1, nested: { bar: 2 } }) // 改变状态本身的property将失败 state.foo++ // ...但适用于嵌套对象 isReadonly(state.nested) // false state.nested.bar++ // 适用
2.ref
unref:如果参数为 ref,则返回内部值,否则返回参数本身
let baseVal = 1; console.log(unref(ref(1))); //返回内部置 1 console.log(unref(baseVal)); //number 1
toRef:可以为响应式对象的其中property创建一个ref,然后将其独立使用
let baseVal = { prop1: "baseVal.prop1", }; let prop1 = toRef(reactive(baseVal), "prop1"); ... prop1 = "new prop1"; //触发视图更新
toRefs:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的
let state = { prop1: "baseVal.prop1", prop2: "baseVal.prop2", sub: { prop1: "axsxsxs", }, }; let stateRef = toRefs(reactive(state)); //将一个rective转化成普通对象,并将reactive的property转化成ref // ... stateRef.prop1 = "new value"; //触发试图更新 console.log(stateRef.sub); //Proxy 如果reactive的peoperty中存在{}||[],那么则不做处理,只是简单的保留,换句话说,就是toRefs只对基本类型property处理,非基本类型的proper,就地保留
isRef:检查变量是否是一个ref,使用方法和isReactive一致
const state = "test"; const refState = ref(state); console.log(isRef(refState)); //true; console.log(isRef(state)); //false;
cutomRef:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track
和 trigger
函数作为参数,并应返回一个带有 get
和 set
的对象。
let state = "test"; const refState = customRef((track, trigger) => { let timeout; return { get() { track(); //track方法在get必须要执行,它是用来搜集依赖元素(订阅者), return state; }, set(newValue) { clearTimeout(timeout); //这里表示延迟两秒通知订阅者更新,使用延迟函数能够更好的体现变化的异步效果,在学习过程中更便于理解 timeout = setTimeout(() => { state = newValue; trigger(); //trigger是必须要执行的,他表示通知vue,refState已经发生改变,通过track() 收集到的订阅者需要更新了 }, 1000); }, }; });
shallowRef:创建一个 ref,它跟踪自己的 .value
更改,但不会使其值成为响应式的。在改变ref.value的时候会发生试图更新,在改变ref.value.prop的时候则不会触发试图更新
setup() { let stateRef = shallowRef({ prop: "old val" }); //这里之所以使用引用类型,是因为对于基本类型ref.value直接可以访问值,无法演示ref.value.prop setTimeout(() => { stateRef.value = { prop: "new val" }; //触发视图更新(副作用之一) }, 2000); setTimeout(() => { stateRef.value.prop = "new val2"; //无法触发视图更新 }, 3000); return { stateRef, }; }
triggerRef:手动触发与shallowRef的关联效果
setup() { const shallow = shallowRef({ greet: "Hello, world", }); // 第一次运行时记录一次 "Hello, world" watchEffect(() => { console.log(shallow.value.greet); }); // 这不会触发作用,因为 ref 很浅层 shallow.value.greet = "Hello, universe"; // 记录 "Hello, universe" triggerRef(shallow); return { shallow }; }