一、Vue2处理响应式的源码方式:
html
const initData = {
value: 1
}
const data = {};
Object.keys(initData).forEach(key => {
Object.defineProperty(data, key, {
get() {
return initData[key]
},
set(value) {
initData[key] = value
}
})
})
console.log(data.value) // 1
initData.value2 = 4;
console.log(initData.value2) // 4
console.log(data.value2) // undefined
修改initData.value2的值,data值没有修改,这是因为初始化劫持时已经拿到了initData所有的key,然后使用Object.defineProperty来修改getter和setter方法,如果再用initData.value2 = 4方法修改,data是拿不到更新的值的
因此,在Vue2中有几种修改方式是无法更新data值的:
- 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:vm.items.length = newLength
为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:
java
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)
为了解决第二类问题,你可以使用 splice:
java
example1.items.splice(newLength)
为了解决Vue2中的问题,Vue3进行了修改
二、Vue3处理响应式的方式
javascript
const initData = {
value: 1
}
const proxy = new Proxy(initData, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
}
})
console.log(proxy.value)
proxy.value = 2
console.log(proxy.value)
Reflect与Proxy的handler结合,修改getter和setter方法,实现数据劫持响应
三、Vue3响应式的使用方式
在 Vue 3 中,ref
、reactive
、shallowReactive
、shallowRef
、toRefs
和toRef
都用于处理响应式数据,但它们之间存在一些区别。
一、ref
ref
用于创建一个响应式的数据对象,内部包含一个值,访问时需要通过 .value
属性。
示例代码:
vue
<template>
<div>
<p>{{ counter }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const counter = ref(0);
const increment = () => {
counter.value++;
};
return {
counter,
increment
};
}
};
</script>
在这个例子中,counter
是一个使用ref
创建的响应式变量,通过修改counter.value
来更新其值,视图也会随之更新。
注意:
ref只追踪对 .value 的直接修改
。如果 .value 是一个对象,内部属性的修改不会自动触发响应
。但可以通过 reactive 包裹 .value 内部的对象来实现深度响应性。
例如:const objRef = ref({ prop: 1 }); objRef.value.prop = 2;
(这不会触发响应,除非使用特殊方法如 Vue.set(objRef.value, 'prop', 2)
)。
二、reactive
reactive
用于创建一个响应式的对象。它会深度响应式地追踪对象内所有属性的变化。
示例代码:
vue
<template>
<div>
<p>{{ person.name }}</p>
<p>{{ person.age }}</p>
<button @click="updatePerson">Update Person</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
setup() {
const person = reactive({
name: 'John',
age: 30
});
const updatePerson = () => {
person.name = 'Jane';
person.age++;
};
return {
person,
updatePerson
};
}
};
</script>
这里,person
对象是使用reactive
创建的响应式对象,修改其属性会触发视图更新。
reactive深度响应式地追踪对象内所有属性的变化
。当对象的属性被修改时,会自动触发响应。
例如:const obj = reactive({ prop: 1 }); obj.prop = 2;
(这会触发响应)。
因此:
- 如果只是对值的响应式请用ref
- 如果是对复杂对象的响应式请用reactive
三、shallowReactive
shallowReactive
创建一个响应式对象,但只对对象的第一层属性进行追踪,不会深度追踪嵌套对象的属性变化。
示例代码:
vue
<template>
<div>
<p>{{ shallowPerson.name }}</p>
<p>{{ shallowPerson.age }}</p>
<p>{{ shallowPerson.nestedObj.prop }}</p>
<button @click="updateShallowPerson">Update Shallow Person</button>
</div>
</template>
<script>
import { shallowReactive } from 'vue';
export default {
setup() {
const shallowPerson = shallowReactive({
name: 'John',
age: 30,
nestedObj: {
prop: 'initial'
}
});
const updateShallowPerson = () => {
shallowPerson.name = 'Jane';
shallowPerson.age++;
// 修改嵌套对象的属性,不会触发视图更新
shallowPerson.nestedObj.prop = 'updated';
};
return {
shallowPerson,
updateShallowPerson
};
}
};
</script>
四、shallowRef
shallowRef
创建一个响应式的引用,但只追踪对其 .value
的直接修改,不会深度追踪其 .value
内部属性的变化。
示例代码:
vue
<template>
<div>
<p>{{ shallowRefValue.num }}</p>
<button @click="updateShallowRefValue">Update Shallow Ref Value</button>
</div>
</template>
<script>
import { shallowRef } from 'vue';
export default {
setup() {
const shallowRefValue = shallowRef({ num: 1 });
const updateShallowRefValue = () => {
// 直接修改 shallowRefValue.value 会触发更新
shallowRefValue.value = { num: 2 };
// 修改 shallowRefValue.value 内部属性不会触发更新
shallowRefValue.value.num = 3;
};
return {
shallowRefValue,
updateShallowRefValue
};
}
};
</script>
五、toRefs
toRefs
用于将一个响应式对象转换为一组属性的引用,这样可以在解构响应式对象时保持属性的响应性。
示例代码:
vue
<template>
<div>
<p>{{ name }}</p>
<p>{{ age }}</p>
<button @click="updatePerson">Update Person</button>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
setup() {
const person = reactive({
name: 'John',
age: 30
});
// 使用 toRefs 将 person 转换为一组引用
const { name, age } = toRefs(person);
const updatePerson = () => {
person.name = 'Jane';
person.age++;
};
return {
name,
age,
updatePerson
};
}
};
</script>
六、toRef
toRef
创建一个对响应式对象中某个特定属性的引用。
示例代码:
vue
<template>
<div>
<p>{{ personName }}</p>
<button @click="updatePersonName">Update Person Name</button>
</div>
</template>
<script>
import { reactive, toRef } from 'vue';
export default {
setup() {
const person = reactive({
name: 'John',
age: 30
});
const personName = toRef(person, 'name');
const updatePersonName = () => {
personName.value = 'Jane';
};
return {
personName,
updatePersonName
};
}
};
</script>
综上所述,ref
和reactive
分别用于创建单个响应式值和对象,shallowReactive
和shallowRef
提供了更浅层的响应性追踪,toRefs
用于在解构响应式对象时保持属性的响应性,toRef
用于创建对特定属性的引用。在实际应用中,可以根据具体需求选择合适的方法来处理响应式数据。
四、watch
javascript
watch(()=> state.count, (val, oldVal)=>{
console.log('watch', val, oldVal)
})
watch([()=> state.count, ()=> state.name], ([val1, val2], [oldVal1, oldVal2])=>{
这里有两种,一种是监听单个,一种是监听数组
五、在 Vue 3 中可以继续使用 Vue 2 的一些生命周期方法
但 Vue 3 也引入了新的组合式 API 的生命周期钩子,并且推荐优先使用新的生命周期钩子。
1、Vue 2 生命周期在 Vue 3 中的兼容性
-
beforeCreate
和created
:-
在 Vue 3 的选项式 API 中仍然可以使用这两个生命周期钩子,其行为与 Vue 2 基本一致。
-
示例:
vueexport default { beforeCreate() { console.log('beforeCreate called'); }, created() { console.log('created called'); }, //... };
-
-
beforeMount
、mounted
、beforeUpdate
、updated
、beforeDestroy
和destroyed
:-
同样可以在 Vue 3 的选项式 API 中使用,功能也基本保持不变。
-
例如:
vueexport default { mounted() { console.log('mounted called'); }, updated() { console.log('updated called'); }, //... };
-
2、推荐使用 Vue 3 组合式 API 的生命周期钩子
Vue 3 引入了新的组合式 API,其中提供了与选项式 API 生命周期钩子相对应的函数形式的钩子:
onBeforeMount
:在组件挂载之前调用。onMounted
:在组件挂载完成后调用。onBeforeUpdate
:在组件即将更新之前调用。onUpdated
:在组件更新完成后调用。onBeforeUnmount
:在组件卸载之前调用。onUnmounted
:在组件卸载完成后调用。
示例:
vue
import { onMounted, onUpdated } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('onMounted called');
});
onUpdated(() => {
console.log('onUpdated called');
});
//...
return {};
}
};
使用组合式 API 的生命周期钩子具有以下优点:
- 可以更好地组织和复用逻辑,因为可以将相关的逻辑放在一起,而不是分散在不同的生命周期钩子中。
- 与 Vue 3 的新特性更好地集成,如响应式系统和组合式函数。
综上所述,虽然 Vue 3 可以继续使用 Vue 2 的生命周期方法,但推荐使用 Vue 3 组合式 API 的生命周期钩子以充分利用新的特性和更好的开发体验。
六、 Vue 3 相比 Vue 2 有很多新特性
以下是一些主要的方面:
一、性能提升
-
编译优化:
- 静态提升(Static Hoisting):Vue 3 在编译阶段会分析模板,将静态的节点提升到渲染函数之外,避免在每次渲染时重复创建。这可以提高运行时的性能,特别是在大型应用中。
- 补丁算法优化:Vue 3 的虚拟 DOM 补丁算法更加高效,能够快速识别和更新变化的部分,减少不必要的 DOM 操作。
- 事件监听缓存:对于频繁触发的事件监听器,Vue 3 会进行缓存,避免在每次更新时重新绑定事件监听器,提高性能。
-
体积更小:Vue 3 进行了优化,整体包体积更小,使得应用加载更快,占用的网络带宽更少。
二、组合式 API(Composition API)
- 更好的逻辑复用:组合式 API 允许开发者将相关的逻辑封装在函数中,然后在不同的组件中复用这些函数。这使得逻辑复用更加灵活和可维护,避免了在 Vue 2 中使用 mixins 可能带来的命名冲突和不清晰的问题。
- 更清晰的代码结构:通过组合式 API,可以将组件的逻辑按照功能进行分组,使得代码结构更加清晰,易于理解和维护。例如,可以将数据获取、状态管理、副作用处理等分别封装在不同的函数中。
- 响应式系统改进:Vue 3 的响应式系统基于 Proxy 对象实现,相比 Vue 2 的 Object.defineProperty 更加高效和强大。可以直接监听对象和数组的变化,而不需要进行额外的处理。
三、Teleport(传送门)
Vue 3 引入了 Teleport 组件,允许将一个组件的模板内容传送到指定的 DOM 节点中,而不是在组件的父级层次结构中渲染。这在处理模态框、弹出窗口等场景时非常有用,可以将这些元素渲染到页面的特定位置,而不受组件层次结构的限制。
四、Fragments(片段)
在 Vue 2 中,组件的模板必须有一个根元素。而在 Vue 3 中,可以使用 Fragments,即组件的模板可以没有根元素,多个元素可以直接作为组件的模板内容。这使得模板更加灵活,特别是在处理复杂的布局时。
五、Emits 选项的改进
Vue 3 对组件的 emits 选项进行了改进,使其更加严格和明确。可以在 emits 选项中定义组件触发的事件名称和参数类型,从而提高代码的可读性和可维护性。
六、更好的 TypeScript 支持
Vue 3 对 TypeScript 的支持更加友好,提供了更好的类型推断和类型定义。组合式 API 与 TypeScript 结合使用时,可以获得更好的类型安全和代码提示。
七、Suspense(异步组件加载)
Vue 3 改进了异步组件的加载方式,引入了 Suspense 组件,可以在异步组件加载过程中显示加载状态或错误信息。这使得异步组件的使用更加方便和用户友好。
这些只是 Vue 3 的一些主要新特性,还有其他一些小的改进和优化。总的来说,Vue 3 在性能、开发体验和功能方面都有了很大的提升,为开发者提供了更强大的工具来构建高效、可维护的前端应用。