Vue | 32 内部 - 深入响应式原理

news/2024/7/10 1:28:10 标签: vue, frontend technology, framework

主要内容:

  1. 改变是如何被追踪到的
  2. 改变检测的一些说明及声明属性的方式
  3. 声明响应式属性的形式及原因
  4. 异步更新队列的方式及如何在nextTick下手动更新数据

现在是时候深入研究一下了!Vue最独特的功能之一就是非侵入式的响应系统。模型仅仅是纯JavaScript对象。当你修改他们,视图更新。它使状态管理更加简单直观,然而理解他们是如何工作的以避免一些常见的陷阱。在这章,我们将深入研究Vue的响应系统的一些细节。

如何追踪变化

当你把普通的JavaScript对象传递给一个Vue实例作为data选项,Vue将遍历所有的属性,并使用Object.defineProperty将这些属性转为getter/setters。这是ES5中不可模拟的特性,这就是为什么Vue不支持IE8及以下的版本。

getter/setter是对用户不可见的,当属性被访问或修改的时候,钩子会被启用,使得Vue执行依赖追踪和改变通知。需要注意的是,在打印转换数据对象时,浏览器控制台打印的getters/setters格式并不相同。所以为了更友好的界面检查你可能需要安装vue-devtools。

每一个组件实例有一个一致的watcher实例,它会把组件渲染的过程中把任何属性记录为依赖。而后当一个依赖的setter被触发的时候,它会通知watcher,反过来导致组件被重新渲染。
reactivity in depth

变化检测说明

由于现代Javascript的限制(而且Object.observe已经被终止),Vue不能检测到属性的添加或删除。由于Vue在实例初始化期间执行了getter/setter转换过程,为了Vue转换换它和响应它,属性必须在data对象中存在。例如:

var vm = new Vue({
	data: {
		a: 1
	}
})
// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 不是响应式的

Vue不允许动态的添加一个新的属性到一个已经被创建好的实例中。然而,使用Vue.set(object, key, value)方法向一个被嵌套的对象中添加响应式属性是可以的:

Vue.set(vm.someObject, 'b', 2)

你也能够使用vm.$set实例方法,它是Vue.set的一个别名:

this.$set(this.someObject, 'b', 2)

有时你可能分配许多属性给一个已经存在的对象,例如使用Object.assign()_.extend()。然而,新的属性被添加到对象里将不会响应变化,在这种情况下,用原始对象和新加入的对象作为属性包装成一个新的对象:

// 代替`Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Objet.assign({}, this.someObject, {a: 1, b: 2})

这里有一些和属性相关的注意事项,在之前的列表渲染章节已经讲过。

声明响应式属性

由于Vue不允许动态添加根级响应属性,你必须通过声明所有根级响应式属性初始化Vue实例,即使值为空:

	var vm = new Vue({
		data: {
			// 用一个空值声明message
			message: ''
		},
		template: '<div>{{ message }}/div>'
	})
	// 稍后设置'message'
	vm.message = 'Hello!'

如果你没有声明message在data操作项,渲染函数试着访问一个不存在的属性,Vue将警告你。

这样的限制在背后是有技术上的原因的-它消除了依赖追踪中的边缘情况,也使得Vue实例在类型检查系统的帮助下发挥更好的作用。而且在代码可维护性方法也有重要的考虑:data对象就像组件状态的概要。当一会再看或者另一个开发者读代码的时候,预先声明所有响应式属性使得组件代码更容易理解。

异步更新队列

可能你没有注意到,Vue执行DOM更新是异步的。无论何时当一个数据改变被观察到,它会开一个队列,缓存发生在同一个消息循环内的所有data改变。如果相同的watcher被触发多次,它仅仅被pushed到队列一次。这种缓存重复数据删除对于避免不必要的计算和DOM维护是重要的。然后,在下一次消息循环"tick"中,Vue刷新队列并执行实际的(已经去重的)工作。内部的Vue尝试对异步队列使用原生的Promise.thenMessageChannel,如果环境不支持,会采用setTimeout(fn, 0)代替。

例如,当你设置vm.someData = 'new value',组件将不会立即重新渲染。它将更新在下一个’tick’ , 当一个队列被刷新后。多数情况下你不需要关心这个,但是当你想在DOM状态更新之后做点什么。这就可能有些棘手。虽然Vue一般鼓励开发者沿着数据驱动的方式思考,避免直接接触DOM,有时可能确实需要这么做。为了等待数据更新之后Vue.js已经完成了DOM更新,在数据改变之后你能够立刻使用Vue.nextTick(callback)。当DOM已经被更新之后这个回调将被调用。例如:

<div id="example">{{ message }}</div>
var vm = new Vue({
	el: '#example',
	data: {
		message: '123'
	}
})
vm.message = 'new message' // 改变数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
	vm.$el.textContent === 'new message' // true
})

还有vm.$nextTick()实例方法,在组件内特别好用,因为它不需要全局的Vue,它回调this上下文将自动绑定到当前的Vue实例:

Vue.component('example', {
	template: '<span>{{ message }}</span>',
	data: function () {
		return {
			message: 'not updated'
		}
	},
	methods: {
		updateMessage: function () {
			this.message = 'updated'
			console.log(this.$el.textContent)  // => 'not updated'
			this.$nextTick(function () {
				console.log(this.$el.textContent)  // => 'updated'
			})
		}
	}
})

由于$nextTick()返回一个promise对象,所以你可以使用新的ES2016 async/wait语法完成相同的事情:

methods: {
	async updateMessage: function () {
		this.message = 'updated'
		console.log(this.$el.textContent) // => 'not updated'
		await this.$nextTick()
		console.log(this.$el.textContent)  // => 'updated'
	}
}

http://www.niftyadmin.cn/n/1035737.html

相关文章

build.gradle常用总结1

1.设置编译版本&#xff0c;名字 android {compileSdkVersion 28buildToolsVersion "29.0.3"defaultConfig {versionCode 1versionName "name" buildDate()} }2.设置指定platform.keystore signingConfigs {mykey {storeFile file(../platform.keystore)…

Vue 系列 | Vue-Router

注意&#xff1a; 本文所有示例代码详见&#xff1a;vue-rouer-demo 1.What | 什么是Vue Router Vue Router是Vue.js提供的官方路由管理器&#xff0c;它和Vue.js深度集成,使构建单页面应用非常方便。 2.Why | 为什么要使用Vue Router 大家打开LorneNote个网站&#xff0c;这…

C++引用讲解

知识点整理引用是C的概念引用的使用1.普通引用&#xff08;必须要初始化&#xff09;2.引用做函数参数&#xff08;不用进行初始化&#xff09;3.复杂数据类型做函数引用4.引用本质5.函数的返回值是引用&#xff08;引用当左值&#xff09;6指针引用7常引用引用是C的概念 引用…

nvm npm 使用教程

nvm 这里推荐使用nvm进行node的版本管理&#xff0c;进行多版本切换比较方便。 What | 是什么 node version manager – node版本管理工具,可以同时切换node的多个版本在本地运行。 How | 如何使用 1.安装nvm 以curl为例,打开终端&#xff0c;复制粘贴如下命令后&#xff…

8.【Windows】安装ubuntu子系统

目录1windows打开开发者模式2.打开liux子系统3.microsoft store 下载 ubuntu,并安装4.安装完成&#xff0c;重启机器后&#xff0c;打开使用5.常见问题5.1 “指定的网络名不再可用”解决办法采用windows开发者模式中&#xff0c;打开linux子系统方式步骤&#xff1a;1windows打…

极简Webpack | 手写打包器

极简Webpack | 手写打包器 Webpack是现代JavaScript应用的静态模打包器。它能够内建一个被称为dependency graph的依赖关系图并生成一个或多个包。作为前端开发者&#xff0c;我们经常和它打交道&#xff0c;理解它如何工作可以使我们更好的处理我们的代码。今天我们通过一个简…

9.【Linux】ubuntu分辨率调节

输入&#xff1a; xrandrxrandr -- size 1360x768

JavaScript 原语

在JavaScript&#xff0c;一个原语&#xff08;primitive–原语值&#xff0c;原语数据类型&#xff09;是一个数据&#xff0c;不是一个对象并且没有方法。有七种原语数据类型&#xff1a; stringnumberbigintbooleannullundefinedsymbol 一般&#xff0c;原语表现为语言实现…