methods: { updateMessage: async function () { this.message = '已更新' //在这里可以看出,message并没有立刻被执行 //要理解页面刷新和代码执行速度的差别 //通常我们在页面上立刻就能看到结果,那是因为一轮队列执行其实很快,感觉不出DOM刷新的过程和所耗费的时间 //但对于代码的执行,属于即刻级别,DOM没更新就是没更新,就是会有问题 console.log(this.$el.textContent) // => '未更新' await this.$nextTick() console.log(this.$el.textContent) // => '已更新' } }
通俗的解释:
1 Vue的DOM刷新机制是个异步队列,并不是你想象中的立刻、马上、即时更新!
2 这个异步队列是一轮一轮的执行并刷新
3 上面带来的问题是,一些依赖DOM更新完毕才能进行的操作(比如对新增加的DOM元素进行事件绑定),无法立刻执行,必须等待一轮队列执行完毕
4 最容易碰到上面问题的地方:created生命周期钩子函数中对DOM进行操作
5 解决办法:使用this.nextTick(回调函数)方法,将对DOM的操作作为它的回调函数使用。
四、函数式组件
因为传统编写模板的能力不足,我们引入了渲染函数createElement。我们又希望获得更多的灵活度,于是引入了JSX。最后,我们发现有些简单的模板可以更简单更小巧的实现,于是引入了函数式组件。Vue总是试图为每一种场景提供不同的能力。
有这么一类组件,它的特点是:
1 比较简单
2 没有管理任何状态,也就是说无状态,没有响应式数据
3 没有监听任何传递给它的状态
4 没有写生命周期方法
5 本质上只是一个接收一些prop的函数
6 没有实例,没有this上下文
那么这个组件可以定义为函数式组件。与普通组件相比,函数式组件是无状态的,无法实例化,没有任何的生命周期和方法,适合只依赖于外部数据的变化而变化的组件,因其轻量,渲染性能会有所提高。
创建函数式组件
以定义全局组件的方式
Vue.component('my-component', { functional: true, // Props 是可选的 props: { // ... }, // 为了弥补缺少的实例 // 提供第二个参数作为上下文 render: function (createElement, context) { // ... } })
注意其中的functional: true,
在 Vue 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。
当使用函数式组件时,该引用将会是 HTMLElement,因为他们是无状态的也是无实例的。
对于单文件组件,创建函数式组件的方式是在模板标签内,添加functional属性
<template functional> ... </template> <script> ... </script> <style> ... </style>
最重要的context参数
因为无状态,没有this上下文,所以函数式组件需要的一切都是通过 context 参数来传递,它是一个包括如下字段的对象:
props:提供所有 prop 的对象
children:VNode 子节点的数组
slots:一个函数,返回了包含所有插槽的对象
scopedSlots:(2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
parent:对父组件的引用
listeners:(2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
injections:(2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的 property。
应用场景
函数式组件的一个典型应用场景是作为包装组件,比如当你碰到下面需求时:
程序化地在多个组件中选择一个来代为渲染;
在将 children、props、data 传递给子组件之前操作它们。
下面是一个 smart-list 组件的例子,它能根据传入 prop 的值来代为渲染更具体的组件:
var EmptyList = { /* ... */ } var TableList = { /* ... */ } var OrderedList = { /* ... */ } var UnorderedList = { /* ... */ } Vue.component('smart-list', { functional: true, props: { items: { type: Array, required: true }, isOrdered: Boolean }, render: function (createElement, context) { function appropriateListComponent () { var items = context.props.items if (items.length === 0) return EmptyList if (typeof items[0] === 'object') return TableList if (context.props.isOrdered) return OrderedList return UnorderedList } return createElement( appropriateListComponent(), context.data, context.children ) } })
五、监听子组件的生命周期
假如我们有父组件Parent和子组件Child,如果在父组件中需要监听子组件的mounted这个生命周期函数,并做一些逻辑处理,常规写法可能如下:
// Parent.vue <Child @mounted="doSth" /> //Child.vue mounted(){ this.$emit('mounted'); }
但是,Vue给我们提供了一种更简便的方法,子组件无需做任何处理,只需要在父组件引用子组件时使用@hook事件来监听即可,代码如下:
// Parent.vue <Child @hook:mounted="doSth" /> methods:{ doSth(){ //some codes here } }
核心是@hook:mounted="doSth"的写法!
当然这里不仅仅可以监听mounted,其他生命周期都可以监听,例如created、updated等。
六、样式穿透
我们知道,在单文件组件的style中使用 scoped 属性后,父组件的样式将不会渗透到子组件中。