Vue基础

Vue的生命周期

vue的生命周期是什么,简单地说句就是一个组件自创建到销毁经历的各种状态。vue提供了一些生命周期相关的钩子函数(命名固定),可以方便开发者在组件的不同时期定义不同的行为方法,vue在执行的时候会自动调用生命周期钩子函数。

vue文档上有一张图大致总结了vue的生命周期: https://cn.vuejs.org/v2/guide/instance.html

另外还有一张介绍各个钩子函数的使用场景: vue钩子函数使用场景

下面介绍vue组件一个生命周期内会涉及到的钩子函数:

1.beforeCreate

vue实例还没有创建,这个时期实例的data、methods都是读取不到的

2.create

vue实例已经创建,这个时期可以使用data和methods,也完成了watch/event事件回调,但是挂载还没有开始,$el属性不可见,data数据也没有在DOM上渲染出来

3.beforeMounted

在挂载前调用,开始调用render函数

4.mounted

vue实例已经挂载到页面中,el选项的DOM节点被新创建的vm.$el替换,可以获取到el中的DOM元素,并且操作DOM。
使用场景:如果需要在实例挂载后马上进行相关的DOM操作,可以将方法写在mounted钩子内

5.beforeUpdate

数据更新时使用,但是不会对DOM重新渲染。
使用场景:在数据更新时DOM没渲染前,可以在这个钩子内进行状态处理,这样不会触发附加的重新渲染

6.updated

数据已更新且组件DOM已经重新渲染,这时可以执行依赖于DOM的操作。当实例每次数据更新时,该钩子都会被调用

7.beforeDestory

实例销毁前的调用

8.destoryed

实例销毁后的调用,vue实例相关的内容都会解绑,所有事件监听器都会移除,所有子实例也会移除

全局API

extend

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
和Vue.component的区别:
Vue.extend返回的是一个扩展实例构造器(不是组件哦),预设了部分的Vue选项,需要传入component来进行注册。
Vue.component是用来去全局注册组件的,通过Vue.extend生成的扩展实例构造器来注册一个组件,它可以取得组件,也可以创建组件。

demo和更多介绍可以参考官方文档:https://cn.vuejs.org/v2/api/#Vue-extend

数据相关

注意:
在混入Vue实例的属性中如果使用了箭头函数,里面的this不会指向该组件的实例,不过,你可以将实例作为函数的第一个参数传递进去访问,如:

var vm = new Vue({
	data: vm => ({ a: vm.test })
})

data

一般来说,它可以写成对象或者函数的形式,但是在组件复用的时候,它必须是一个函数: 组件的复用
如果写成对象形式,那么组件在复用的时候,多个实例同时引用一个对象,当修改其中一个实例的属性时,其他的实例也会被修改。所以在组件复用的时候,我们要将data写成函数形式

有关data相关的内容参考官方文档

props

用于父子组件间的通信。props可以是数组或对象,如果使用对象,允许配置数据类型、自定义校验和设置默认值

computed

计算属性混入vue实例中,所有的getter和setter内的this都指向vue
其结果会被缓存,,除非依赖的响应式属性发生变化才会重新计算。 参考: 官方文档-计算属性

var vm = new Vue({
  data: { a: 1 },
  computed: {
    double: function () {
      return this.a * 2
    },
    plus: {
      get() {
        return this.a + 1
      },
      set(v) {
        this.a = v
      }
    }
  }
})
console.log(vm.plus)  // -> 2
vm.plus = 4
console.log(vm.a)       // -> 4
console.log(vm.double) // -> 8

computed适用场景:
如果模板上涉及到一些比较复杂的逻辑或运算,那么可以在computed中处理:

<p>{{ message.split('').reverse().join('') }}</p>

<!-- 改为 -->
<p>{{ reverseMessage }}</p>

<script>
	export default {
		data() {
			return {
				message: 'Hi'
			}
		},
		computed: {
			reverseMessage() {
				return this.message.split('').reverse().join('')
			}
		}
	}
</script>

注意,不要在computed内修改所依赖的数据,只能对依赖的数据做运算后返回其运算结果,否则computed将被一直触发

methods

混入vue实例中,方法内的this自动绑定为vue实例。
一般来说,混入vue实例的属性中不建议使用箭头函数,原因前面已经提到了,所以在methods内定义的函数也不要用箭头函数

computed和methods的区别:
一般的,将需要计算的属性放到computed和method中都是可以修改数据的。只不过,computed是响应式的,methods是非响应式的。computed计算属性是可以缓存的,只要依赖的数据(一般是监听data属性的内容)不发生变化,就不会调用。如果需要进行较大的操作,那么可以将这个计算放入computed中。如果是放在methods中,每次重绘时都会触发这个计算(尽管本意不是一直需要),这样就比较耗费性能了。另外,computed的成员可以只定义一个函数作为只读属性,也可以定义get/set编程读写属性,methods无法做到
再简单地说,computed是计算属性,可以实时响应,比如要监听data内的值的变化而做出一些动作,就用computed;methods是方法,比如要在某个事件触发时执行一个方法,就用methods

watch

它是一个对象,键名是需要观察的表达式,值是对应的回调函数或包含选项的对象。vue实例会在实例化时调用$watch()遍历watch对象的每个属性

所以在使用watch的过程是这样的:实例化vue、调用$watch方法、属性变化后触发回调

它和computed有什么区别?
二者都是有监听数据的能力,不同的是,如果需要监听多个数据并进行处理时,computed可以在一个表达式中同时引用多个监听的数据,而watch需要被每个监听的数据分配一个方法,vue官方有个demo可以说明这个问题:

<div id="demo">{{ fullName }}</div>

<!-- computed -->
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

<!-- watch -->
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

上面的例子中,我们本意中需要监听的数据是firstName和lastName,并在

nextTick

官方文档的说明:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

举个例子:

<div class="app">
	<div ref="demo">{{demo}}</div>
	<button @click="handler">Click me</button>
</div>

new Vue({
	el: '.app',
	data() {
			return {
				demo: 'before click'
			};
		},
	methods: {
		handler() {
			this.demo = 'after click';
			console.log('1.' + this.$refs.demo.innerText);  // 1.before click
			this.$nextTick(() => {
				console.log('2.' + this.$refs.demo.innerText); // 2.after click
			})
		}
	}
})

在上面的例子中,nextTick中才正确地实现了我们的目的,这是因为vue中的DOM更新是异步的。如果需要更新的内容渲染到DOM节点后,我们要根据DOM元素拿到更新后的数据,那么我们就需要在nextTick的回调内获取

在created钩子函数进行的DOM操作一定要放在nextTick的回调中。因为created执行时DOM并没有渲染,执行DOM操作是没有意义的,所以一定要将DOM操作的相关代码放在nextTick的回调中。如果是在mounted钩子中,因为此时的DOM挂载和渲染已经完成,所以在这里不用nextTick也可以。但是假如数据变化后要执行的某个操作,这个操作需要使用随数据变化而变化的DOM结构时,就应该放在nextTick回调中。

官方的说明:

Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。


### 参考 vue官方文档
[掘金-简单理解Vue中的nextTick](https://juejin.im/post/5a6fdb846fb9a01cc0268618)