我们继续来对后续的文档进行探究。
响应式状态(Vue3)
响应式状态在vue3进行了修改。
在选项式API中仍然为在data中声明属性,并可以使用this调用此实例。
接下来我们举一个例子,以便于更好理解异同;
(reactive函数:创建一个代理对象)
这是一个点击增加内部数字的按钮。
下例,在vue3中正常更新视图。
const originalState = { count: 0 };
const state = reactive(originalState);
state.count++;
下例,在vue3中无法正常更新视图,在vue2中正常更新视图。
const originalState = { count: 0 };
const state = reactive(originalState);
originalState.count++;
原因:
Vue 3 使用 Proxy 来实现响应式,当我们使用 reactive
函数将一个对象转换为响应式时,实际上 Vue 在内部创建了一个代理对象来包装这个原始对象。虽然你仍然使用 state.count++
这样的语法来访问和修改数据,但你实际上操作的已经不是原始对象本身,而是 Vue 创建的代理对象。
Vue 2 使用了一种称为“脏检查”(dirty checking)的策略的变体,结合了属性的 getter 和 setter,来追踪数据变化。具体来说,Vue 2 使用了 Object.defineProperty
方法来遍历每个对象属性,为它们添加 getter 和 setter,以此来监控数据的变化。在 Vue 2 中,你可以直接操作原始对象,而 Vue 通常能够检测到这些变化并更新视图。
总结:或许看着很烦,你只需要记住在vue3中,请不要在你定义的data实例之外操作属性,如有必要,请使用reactive函数让其被代理,避免响应式失效。
methods
在选项式API中方法请不要使用箭头函数,因为Vue 自动为 methods
中的方法绑定了永远指向组件实例的 this
。
eg:
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
// 在其他方法或是生命周期中也可以调用方法
this.increment()
}
}
export default {
methods: {
increment: () => {
// 反例:无法访问此处的 `this`!
}
}
}
对于DOM的更新详解
在 Vue 中,当你的响应式数据发生变化时,Vue 并不会立即更新 DOM(文档对象模型),而是将这些变化放入一个队列中,并在下一个事件循环(也就是所谓的“next tick”)中进行批量处理。这样做的目的是提高效率,避免频繁的 DOM 操作导致性能下降。
(nextTick:这是 Vue 提供的一个全局函数,它会延迟执行你传入的回调函数,直到 DOM 更新完成。)
eg:
export default {
data() {
return {
count: 0
};
},
methods: {
increment: function() {
this.count++;
console.log('数据已更新');
// 这里尝试获取更新后的元素,但 DOM 可能还没有更新
const element = document.querySelector('#my-element');
console.log(element.textContent); // 这里的值可能是旧的
// 使用 $nextTick 来确保 DOM 更新完成后再执行代码
this.$nextTick(() => {
const updatedElement = document.querySelector('#my-element');
console.log(updatedElement.textContent); // 这里的值应该是更新后的
});
}
},
mounted() {
// 如果需要在组件挂载后执行某些操作,可以在这里写
}
};
防抖函数
防抖函数:减少函数在短时间内被连续调用的次数,从而优化性能。而是为了防止在高频触发事件(如窗口调整大小、滚动或快速连续点击)时,某个函数被不必要的频繁执行。例如,在用户输入搜索框时,你可能不希望每次键盘敲击都发送一次网络请求,这时就可以使用防抖来限制请求的频率。
少量,单组件
import { debounce } from 'lodash-es'
export default {
methods: {
// 使用 Lodash 的防抖函数
click: debounce(function () {
// ... 对点击的响应 ...
}, 500)
}
}
多组件
(如果多个组件实例都共享这同一个预置防抖的函数,那么它们之间将会互相影响):
export default {
created() {
// 每个实例都有了自己的预置防抖的处理函数
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// 最好是在组件卸载时
// 清除掉防抖计时器
this.debouncedClick.cancel()
},
methods: {
click() {
// ... 对点击的响应 ...
}
}
}
计算属性
下面的例子是根据 author
是否已有一些书籍来展示不同的信息。
此例,正常运行,但模板过于复杂。
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
export default {
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
}
}
使用计算属性:
export default {
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
computed: {
// 一个计算属性的 getter
publishedBooksMessage() {
// `this` 指向当前组件实例
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}
【笔记:(在computed中如果只需要getter,则不需要声明,自动为getter;如果需要同时定义getter
和setter
,那么计算属性就需要被定义为一个对象,包含get
和set
两个方法
computed: {
uppercaseInput: {
get() {
return this.inputValue.toUpperCase();
},
set(newValue) {
this.inputValue = newValue.toUpperCase();
}
}
)】
优势:更加简便可见,逻辑更加清晰。
同样,我们使用方法也可以实现对应的逻辑功能,计算属性对于方法来说的优势在于,计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books
不改变,无论多少次访问 publishedBooksMessage
都会立即返回先前的计算结果,而不用重复执行 getter 函数。如果是方法,且例子为更大的list集合,当数字无论是否发生变化都会一直执行getter函数。
可写计算属性
1:计算属性默认只读。
2:getter 的职责应该仅为计算和返回该值。不要改变其他状态、在 getter 中做异步请求或者更改 DOM。
3:避免直接修改计算属性值(只读)
4:如果需要可写计算数学,使用setget来更改。
eg:
this.fullName = 'Jane Smith';
fullName
的setter将被触发,firstName
将被设置为'Jane'
,lastName
将被设置为'Smith'
。因此,即使你修改的是fullName
,实际上你是在修改firstName
和lastName
。