比较Vue2与Vue3的响应式
vue2的响应式
- 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
- 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
问题
对象直接新添加的属性或删除已有属性, 界面不会自动更新
直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
Vue3的响应式
原理实现
<script>
//目标对象
const user = {
name: "tome",
age: 18,
wife: {
name: "lisa",
age: 18,
},
};
const proxyUser = new Proxy(user, {
get(target, props) {
console.log("get调用");
return Reflect.get(target, props);
},
set(target, props,value) {
console.log("set调用");
return Reflect.set(target, props,value);
},
deleteProperty(target, props) {
console.log("deleteProperty调用");
return Reflect.deleteProperty(target, props);
},
});
console.log(proxyUser.name);
proxyUser.name = "jack";
delete proxyUser.name;
</script>
认识Vue3
1. Vue3支持vue2的大多数特性
2. 更好的支持Typescript
3. 打包速度加快,渲染速度加快
4. 使用Proxy代替vue2中defineProperty实现数据响应式
1.ref(用于定义响应式数据)
作用: 定义一个数据的响应式
语法: const xxx = ref(initValue):
- 创建一个包含响应式数据的引用(reference)对象
- js中操作数据: xxx.value
- 模板中操作数据: 不需要.value
<template>
<h2>{{ count }}</h2>
<button @click="update">更新</button>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const count = ref(1);
console.log(count);
function update() {
count.value = count.value + 1;
}
return {
count,
update,
};
},
};
</script>
注意:如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象”
2.reactive(用于定义响应式数据)
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
<template>
<div>
<h2>reactive的基本使用</h2>
<h5>名字:{{ user.name }}</h5>
<h5>年龄:{{ user.age }}</h5>
<h5>年龄:{{ user.gender }}</h5>
<h5>媳妇:{{ user.wife }}</h5>
<br />
<button @click="updateUser">更新数据</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "App",
setup() {
const obj: any = {
name: "小明",
age: 20,
wife: {
name: "小甜甜",
age: 20,
cars: ["奔驰", "宝马", "奥迪"],
},
};
//user现在是代理对象,obj是目标对象
const user = reactive<any>(obj);
//方法
function updateUser() {
//直接使用目标对象的方式更新目标对象中的成员值是不可能的,只能使用代理对象的方式来更新数据(响应式数据)
// obj.name = '小红'
// user.name = '小红'
// user.gender = '男'
// delete user.age
user.wife.cars[1] = "布加迪";
user.wife.cars[3] = "布加迪2";
}
return {
user,
updateUser,
};
},
});
</script>
3.setup
- setup是组合api的入口函数,在这可以定义响应式数据, 所有的组合API函数都在此使用
setup执行的时机
- setup在beforeCreate前执行,而且就执行一次。由此可以推断出:set执行的时候组件还没有创建出来,意味着组件this根本不能用,所以所有的composition API相关回调函数中也都不可用
setup的返回值
- 一般返回一个对象, 对象中的属性或方法, 可以直接在html(template)直接使用
- 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
- 返回对象中的方法会与methods中的方法合并成功组件对象的方法
- 如果有重名, setup优先
注意:
- 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
- setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
setup执行的参数
setup(props, context) / setup(props, {attrs, slots, emit})
- props: 包含props配置声明且传入了的所有属性的对象
- attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
4.计算属性与监视
computed函数:
-
与computed配置功能一致
-
有getter和setter
watch函数
- 与watch配置功能一致
- 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
- 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
- 通过配置deep为true, 来指定深度监视
watchEffect函数
-
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
-
默认初始时就会执行第一次, 从而可以收集需要监视的数据
-
监视数据发生变化时回调
-
如果是ref对象, 直接指定 如果是reactive对象中的属性, 必须通过函数来指定
const c= ref(""); watch([() => user.a, () => user.b, c], (values) => { console.log("监听多个数据", values); });
5. 生命周期
与 2.x 版本生命周期相对应的组合式 API
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
<template>
<div>
<h2>Child子组件 {{a}}</h2>
<button @click="updateData">updateData</button>
</div>
</template>
<script lang="ts">
import { defineComponent, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref } from "vue";
export default defineComponent({
setup() {
onBeforeMount(() => {
console.log("v3--onBeforeMount");
});
onMounted(()=>{
console.log("v3--onMounted");
})
onBeforeUpdate(() => {
console.log("v3--onBeforeUpdate");
});
onUpdated(() => {
console.log("v3--onUpdated");
});
onBeforeUnmount(() => {
console.log("v3--onBeforeUnmount");
});
onUnmounted(() => {
console.log("v3--onUnmounted");
});
const a = ref(1)
function updateData(){
a.value=2
}
return{
a,
updateData
}
},
});
</script>
<style scoped></style>
6.自定义hook函数
-
使用Vue3的组合API封装的可复用的功能函数
-
自定义hook的作用类似于vue2中的mixin技术
-
自定义Hook的优势: 很清楚复用功能代码的来源, 更易懂
7.toRefs
- 把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
- 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
<template>
<div>
<h2>toRefs的使用</h2>
<!-- <h2>{{state.name}}</h2>
<h2>{{state.age}}</h2> -->
<h2>{{ name2 }}</h2>
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from "vue";
function useToRefs() {
let obj = reactive({
name2: "wjy",
});
return toRefs(obj);
}
export default defineComponent({
name: "App",
setup() {
const state = reactive({
name: "tom",
age: 45,
});
const state2 = toRefs(state);
setInterval(() => {
state2.name.value += "=";
}, 1000);
const {name2} = useToRefs()
return {
// ...state, //不是响应式数据
...state2,
name2
};
},
});
</script>
8.ref获取元素
<template>
<h2>App</h2>
<input type="text">---
<input type="text" ref="inputRef">
</template>
<script lang="ts">
import { onMounted, ref } from 'vue'
/*
ref获取元素: 利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
*/
export default {
setup() {
const inputRef = ref<HTMLElement|null>(null)
onMounted(() => {
inputRef.value && inputRef.value.focus()
})
return {
inputRef
}
},
}
</script>
9. shallowReactive 与 shallowRef
-
hallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
-
shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
-
什么时候用浅响应式呢?
一般情况下使用ref和reactive即可
- 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
- 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
10.readonly 与 shallowReadonly
readonly
- 深度只读数据
- 只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly - 浅只读数据
- 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
11.toRaw 与 markRaw
toRaw
- 返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。
- 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
markRaw - 标记一个对象,使其永远不会转换为代理。返回对象本身
12.toRef
- 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
- 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
13.customRef
- 创建一个自定义的 ref,对其依赖项跟踪和更新触发进行显式控制
- 需求: 使用 customRef 实现 debounce 的示例
<template>
<div>
<h2>App</h2>
<input v-model="keyword" placeholder="搜索关键字" />
<p>{{ keyword }}</p>
</div>
</template>
<script lang="ts">
import { customRef } from "vue";
export default {
setup() {
const keyword = useDebouncedRef("", 500);
return {
keyword,
};
},
};
/*
实现函数防抖的自定义ref
*/
function useDebouncedRef<T>(value: T, delay = 200) {
let timeout: number;
return customRef((track, trigger) => {
return {
get() {
// 告诉Vue追踪数据
track();
return value;
},
set(newValue: T) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
// 告诉Vue去触发界面更新
trigger();
}, delay);
},
};
});
}
</script>
14.provide 与 inject
- provide和inject提供依赖注入,功能类似 2.x 的provide/inject
- 实现跨层级组件(祖孙)间通信
父:
provide("name", color);
孙:
const color = inject('name')
15. 响应式数据的判断
isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
16.新组件
1) Fragment(片断)
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment-虚拟元素中
- 好处: 减少标签层级, 减小内存占用
2) Teleport(瞬移)
- Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示
<template>
<teleport to="body">
------
</teleport>
</template>
3) Suspense(不确定的)
- 它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
<Suspense>
<template v-slot:default>
<div>
<AsyncComp />
<AsyncAddress />
</div>
</template>
<template v-slot:fallback>
<h1>LOADING...</h1>
</template>
</Suspense>
17.其他新的API
1)全新的全局API
- createApp()
- defineProperty()
- defineAsyncComponent()
- nextTick()
2)将原来的全局API转移到应用对象
- app.component()
- app.config()
- app.directive()
- app.mount()
- app.unmount()
- app.use()
3)模板语法变化
v-model的本质变化
- prop:value -> modelValue;
- event:input -> update:modelValue;
- .sync修改符已移除, 由v-model代替
- v-if优先v-for解析
[GuiGu]笔记(https://24kcs.github.io/vue3_study/)