Last updated: 7/19/2020, 1:51:27 AM(官方文档更新时间)
在JS中如何实现数据响应式?
当您将纯JavaScript对象作为data选项传递给Vue实例时,Vue将遍历其所有属性,并使用带有getter和setter的处理程序将其转换为Proxy。 这是ES6支持的功能,但在我们提供Vue 3.0中,使用较旧的Object.defineProperty
支持IE浏览器。两者具有相同的Surface API,但是Proxy版本更轻量,性能更佳。
打个广告,如果对Proxy不太熟悉,可以看看我之前的博客,proxy学习笔记,故这里跳过vue3文档关于Proxy用法的讲解。
之前我们提到过,为了使API能够在发生变化时更新最终值,我们将必须在发生变化时set
新值。 我们在handler中通过一个名为track
的函数进行此操作,在该函数中传递target
和key
。
最后,当变化发生时,我们会set
新值。 为此,我们通过触发这些更改来set到新Proxy上:
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, prop, receiver) {
track(target, prop)
return Reflect.get(...arguments)
},
set(target, key, value, receiver) {
trigger(target, key)
return Reflect.set(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
还记得文章开始提到的如何实现数据响应式吗?现在我们来解答以下Vue是如何处理这些变化的:
Proxy对象对用户是透明的,但是在后台,它们使Vue在访问或修改属性时,能够执行依赖跟踪和更改通知。 从Vue 3开始,我们的数据响应式可以在单独的软件包中使用。 需要注意的是,记录转换后的数据对象时,浏览器控制台的格式会有所不同,因此您可能需要安装vue-devtools以获得更友好的检查界面。
Vue在内部跟踪所有reactive对象,因此它始终为同一对象返回相同的代理。
当从reactive代理那访问嵌套对象时,该对象在返回之前也将转换为代理:
const handler = {
get(target, prop, receiver) {
track(target, prop)
const value = Reflect.get(...arguments)
if (isObject(value)) {
return reactive(value)
} else {
return value
}
}
// ...
}
使用Proxy确实会引入新的警告,需要注意的是:代理对象不等于(===)target对象本身。如:
const obj = {}
const wrapped = new Proxy(obj, handlers)
console.log(obj === wrapped) // false
原对象和代理对象在大多数情况下将具有相同的行为,但是请注意,它会使依赖于强比较的操作(如:.filter()
或 .map()
)失败。 使用选项API时,这种警告不太可能出现,因为所有响应状态都可以从this中访问,并保证已经是代理。
然而,使用Composition API来生成响应式对象时,最佳做法是永远不要保留对原始对象的引用,而是只使用reactive版本:
const obj = reactive({
count: 0
}) // no reference to original
每个组件实例都有一个相应的watcher实例,将组件渲染期间“涉及”到的所有属性记录为依赖项。稍后,当触发依赖项的setter时,它会通知watcher,从而导致组件重新渲染。
当您将对象作为data传递给Vue实例时,Vue会将其转换为Proxy。 当访问或修改属性时,该代理使Vue能够执行依赖项跟踪(dependency-tracking)和更改通知(change-notification)。 每个属性均被视为依赖项。
在第一次渲染之后,一个组件将跟踪一个依赖项列表——它在渲染过程中访问的属性。 相反,该组件成为每个依赖属性的订阅者。 当代理拦截set操作时,该属性将通知其所有订阅的组件重新渲染。
Last updated: 7/18/2020, 5:08:24 PM(官方文档更新时间)
把一个JS对象变成一个响应式的状态要用到reactive
方法:
import { reactive } from 'vue'
// reactive state
const state = reactive({
count: 0
})
reacttive
等效于Vue 2.x中的Vue.observable()
API,改了个名称以防与RxJS observables混淆。 在这里,返回的状态是一个响应式的对象。 响应式转换是“深度”的——它影响传递对象的所有嵌套属性。
Vue中响应式状态的基本用例是我们可以在渲染期间使用它。 由于依赖关系跟踪,当响应式状态更改时,视图会自动更新。
这是Vue响应式系统的本质。 当您在组件的data()
中返回一个对象时,该对象会在data内部被reactive()
变为可响应的。 模板被编译为依赖这些reactive属性的渲染函数。
你可以在Basic Reactivity API’s中了解更多关于reactive的特性。
想象一下,我们有一个独立的原始值(例如,一个string),并且希望使其具有响应性。 当然,我们可以使一个对象具有与字符串相等的单个属性,并将其传递给reactive。 Vue提供一种方法可以由相同的效果-ref
:
import { ref } from 'vue'
const count = ref(0)
ref
将返回一个响应的、可变的对象,该对象充当对其内部属性值的响应式引用——这就是名称的来源。 该对象仅包含一个名为value的属性:
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
当ref作为渲染上下文(从setup()
返回的对象)的属性返回并在模板中访问时,它会自动展开为内部值。 无需在模板中附加.value
:
<template>
<div>
<span>{{ count }}</span>
<button @click="count ++">Increment count</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
</script>
当ref
作为reactive对象的属性进行访问或改变时,它会自动展开为内部值,因此其表现为普通属性:
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
如果一个新ref
被分配给已有ref
关联的属性,它将替换旧的ref
:
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1
ref展开
仅在reactive对象内部存在嵌套时发生。 从数组或原生集合类型(如Map)访问ref
时,不会执行ref展开
:
const books = reactive([ref('Vue 3 Guide')])
// need .value here
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// need .value here
console.log(map.get('count').value)
当我们想使用一个属性很多的reactive对象中的小部分属性时,可以使用ES6的解构来得到我们想要的属性:
import { reactive } from 'vue'
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: 'You are reading this book right now ;)',
price: 'free'
})
let { author, title } = book
不幸的是,解构后这些属性的响应式特性会消失。这种情况下,我们需要将reactive对象转换为refs
。 refs
将保留与源对象的响应式连接:
import { reactive, toRefs } from 'vue'
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: 'You are reading this book right now ;)',
price: 'free'
})
let { author, title } = toRefs(book)
title.value = 'Vue 3 Detailed Guide' // we need to use .value as title is a ref now
console.log(book.title) // 'Vue 3 Detailed Guide'
你可以在Refs API中了解更多。
有些时侯,我们想跟踪reactive对象(ref或reactive)的变化,但是,我们也想防止从应用的某个位置更改它。 例如,当我们有一个reactive对象时,我们想防止在注入它的地方对其进行更改。 为此,我们可以为原始对象创建一个只读代理:
const original = reactive({ count: 0 })
const copy = readonly(original)
// mutating original will trigger watchers relying on the copy
original.count++
// mutating the copy will fail and result in a warning
copy.count++ // warning: "Set operation on key 'count' failed: target is readonly."
不好意思,翻不动了,等我哪天无聊再搞吧~