为学习Vue基础知识,我动手操作通关了Vue演练场,该演练场教程的目标是快速体验使用 Vue 是什么感受,设置偏好时我选的是选项式 + 单文件组件。以下是我结合深入指南写的总结笔记,希望对Vue初学者有所帮助。
文章目录
- 十一. 侦听器
- 深层侦听器
- 即时回调的侦听器
- 一次性侦听器(仅支持 3.4 及以上版本)
- 副作用清理
- 回调的触发时机
- Post Watchers
- 同步侦听器
- this.$watch()
- 十二. 注册组件
- 全局注册
- 局部注册
- 组件名格式
十一. 侦听器
计算属性 computed
可用于计算衍生值,但不能承担有副作用的操作,如更改 DOM,或是根据异步操作的结果去修改另一处的状态。
在选项式 API 中,我们可以使用 watch
选项在每次响应式属性发生变化时触发一个函数。
export default {data() {return {id: 0, data: null};},watch: {id() {this.getDataById();}},methods: {async getDataById() {const res = await fetch('https://xxx?id=' + this.id);this.data = await (res.json()).data;}}
}
watch: {id() {...}}
也可以写作:
watch: {id(newId, oldId, onCleanup) {...}}
// onCleanup用于清除副作用watch: {'obj.data.id'() {...}}
// 侦听简单路径的深层属性,不支持表达式。
深层侦听器
watch
默认是浅层的:被侦听的属性,仅在被赋新值时,才会触发回调函数——这对于String、Number等简单属性没什么限制,但会导致嵌套属性的变化不会触发回调函数。
因为在嵌套属性的变更中,只要没有替换对象本身,那么JS就会认为修改前的对象和修改后的对象相等。
watch: {// 不会被this.obj.id = 1触发obj(newObj, oldObj) {xxx}
},
如果想侦听所有嵌套的变更,你需要深层侦听器:
watch: {obj: {handler() {xxx},deep: true,}
}
deep: true
还可以写作:
deep: n
// n是一个数字,表示最大遍历深度——即 Vue 应该遍历对象嵌套属性的级数。
ps:深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。
即时回调的侦听器
watch
默认是懒执行的:仅当数据源变化时,才会执行回调。也可以强制立即执行回调:
watch: {obj: {handler() {xxx},immediate: true,}
}
这样就能使回调函数的初次执行发生在在 created
钩子之前,beforeCreated
之后,Vue 此时已经处理了 data
、computed
和 methods
选项,所以这些属性在第一次调用时就是可用的,但是还没挂载,所以this.$emit()
是不能用的。
一次性侦听器(仅支持 3.4 及以上版本)
可以设置侦听器仅在响应式属性第一次变化时被触发:
watch: {obj: {handler() {xxx},once: true,}
}
副作用清理
有时我们可能会在侦听器中执行副作用,比如上文中我们在id变化时通过异步请求获取数据来更新data
,那如果在请求的过程中id
再次变化了,那上一次请求完成时就会使用过时的数据来触发异步操作的回调函数。
理想情况下,我们希望能够在 id 变为新值时取消过时的请求。我们可以使用watch
中函数的第三个参数onCleanup
注册一个清理函数,当侦听器失效并准备重新运行时会被调用:
export default {data() {return {id: 0, data: null};},watch: {id(oldId, newId, onCleanup) {const controller = new AbortController();this.getDataById(controller);onCleanup(() => {controller.abort();});}},methods: {async getDataById(controller) {const res = await fetch('https://xxx?id=' + this.id,{signal: controller.signal});this.data = await (res.json()).data;}}
}
onCleanup
在 3.5 之前的版本有效。 Vue 3.5+ 中可以使用onWatcherCleanup
:
import { onWatcherCleanup } from 'vue'
…
onWatcherCleanup(() => {controller.abort();
});
…
请注意,onWatcherCleanup
仅在 Vue 3.5+ 中支持,并且必须在 watchEffect 效果函数或 watch 回调函数的同步执行期间调用:你不能在异步函数的 await 语句之后调用它。而onCleanup
不受同步限制。
回调的触发时机
Post Watchers
默认情况下,侦听器回调会在父组件更新 (如有) 之后、所属组件的 DOM 更新之前被调用。这意味着如果你尝试在侦听器回调中访问所属组件的 DOM,那么 DOM 将处于更新前的状态。
如果想在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM,你需要指明 flush: ‘post’ 选项:
watch: {key: {handler() {},flush: 'post'}}
同步侦听器
当你更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调。
类似于组件更新,用户创建的侦听器回调函数也会被批量处理以避免重复调用。例如,如果我们同步将一千个项目推入被侦听的数组中,我们可能不希望侦听器触发一千次。
批量执行:系统会将一定时间内收集到的所有事件或回调函数请求进行批量处理,然后统一执行一次回调函数,而不是每次事件触发都立即执行一次回调函数。这样可以减少回调函数的调用次数,提高程序的性能和效率.
export default {data() {return {arr:[]}},mounted() {for(let i=0; i<1000; i++){this.arr.push(i);}},watch: {arr: {handler(newVal,oldVal) {console.log('newVal:',newVal,'和oldVal:',oldVal)// 只打印了一次,内容是"newVal:[0,...,999]和oldVal:[0,...,999]"},deep: true}}
}
如果不希望watch
自动批量处理回调函数,你也可以创建一个同步触发的侦听器,它会在 Vue 进行任何更新之前触发:
watch: {key: {handler() {},flush: 'async'}}
同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发。可以使用它来监视简单的布尔值,但应避免在可能多次同步修改的数据源 (如数组) 上使用。
this.$watch()
我们也可以使用组件实例的 $watch() 方法来命令式地创建一个侦听器:
data() {return {unwatch: null};
}
created() {if (在特定条件下设置一个侦听器,或者只侦听响应用户交互的内容) {this.unwatch = this.$watch('question', (newQuestion) => { ... })}// this.$watch允许提前停止侦听,否则会在宿主组件卸载时自动停止this.unwatch();
}
十二. 注册组件
一个 Vue 组件在使用前需要先被“注册”,这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册。
全局注册
Vue2的全局注册:
// 定义一个全局组件
const MyComponent = {template: '<div>这是全局注册的组件</div>'
};// 全局注册组件
Vue.component('my-component', MyComponent);// 创建 Vue 实例
new Vue({el: '#app'
});
Vue3的全局注册:
import { createApp } from 'vue';// 定义一个全局组件
const MyComponent = {template: '<div>这是全局注册的组件</div>'
};// 创建 Vue 应用实例
const app = createApp({});// 全局注册组件
app.component('my-component', MyComponent);// 挂载应用
app.mount('#app');// app.component还可链式调用
app.component('my-component1', MyComponent1).component('my-component2', MyComponent2).component('my-component3', MyComponent3);
总结
• Vue 2使用Vue.component
方法进行全局注册,注册后可以在任何 Vue 实例中使用。
• Vue 3使用app.component
方法进行全局注册,并且需要先创建一个 Vue 应用实例,再注册在该实例上,注册后只有在这个 Vue 实例中使用。
局部注册
父组件可以在模板中渲染另一个组件作为子组件。
<script>
import ChildComp from './ChildComp.vue'; // 导入子组件
export default {components: {ChildComp} // 注册子组件
}
</script><template><ChildComp />
</template>
局部注册的子组件只能在当前父组件上使用,在父组件的后代组件中不可用。
组件名格式
一个以 MyComponent 为名注册的组件,在模板中可以通过 或 引用。
在单文件组件和内联字符串模板中,用;在DOM 内模板中,用 。
内联字符串模板:
const BlogPost = {props: ['postTitle'],template: `<h3>{{ postTitle }}</h3>`
}
DOM模板(大小写不敏感):
<!DOCTYPE <html><head><meta charset="utf-8"><title>Vue Component</title></head><body><div id="app"> Hello Vue<my-component></my-component></div><script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script><script >//全局注册Vue.component('my-component', {template: '<div>组件内容</div>'});new Vue ({el: '#app'});</script></body>
</html>