前端必读:Vue响应式系统大PK

开发 前端
响应系统是一种使自动使数据源(模型)与数据表示(视图)层自动保持同步的机制。每次模型更改时,都会重新渲染视图。

 [[400474]]

响应式系统(Reactivity systems)是现代前端框架的关键部分之一。应用系统的的高度交互性、动态性和响应能力全靠它支持。每个Web开发人员而言都应该了解这一系统的功能和实践操作。

原理

响应系统是一种使自动使数据源(模型)与数据表示(视图)层自动保持同步的机制。每次模型更改时,都会重新渲染视图。

以一个简单的Markdown编辑器为例。通常编辑器有两个窗格:一个窗格用于编写Markdown代码(用于修改基础模型),另一个窗格用于预览已编译的HTML(显示已更新的视图)。当我们在书写窗格中写东西时,它会立即在预览窗格中自动预览。这个例子比较简单,在实际情况中会复杂很多。

在许多情况下,我们要显示的数据取决于其他数据。在这种情况下,需要跟踪相关数据,并根据跟踪情况来更新数据。例如,我们有一个fullName,该属性由firstName和lastName属性组成。修改其任何依赖项后,fullName将自动重新评估,并在视图中显示结果。

了解什么是响应式系统后,在了解Vue 3中的响应系统如何工作以及如何在实践中使用之前,让我们一起来快速回顾一下Vue 2中的响应系统内容及其注意事项。

Vue 2的响应式系统简介

Vue 2中的响应或多或少会被“隐藏”。无论我们放置在data对象中的是什么,Vue都会使其隐式反应(reactive implicitly)。这样虽然可以使开发人员的工作更加轻松,但灵活度却会不可避免的降低。

在幕后,Vue 2使用ES5 Object.defineProperty将data对象的所有属性转换为getter和setter。对于每个组件实例,Vue创建一个依赖关系观察程序实例,观察者会记录组件渲染期间依赖收集/跟踪的任何属性。当属性触发依赖项的设置器时,将通知观察者,并将组件重新渲染并更新视图。但是却也会有一些问题存在。

变更检测警告

由于Object.defineProperty方法的限制,Vue无法检测到某些数据更改。包括:

  • 给对象添加属性或把对象移除属性(例如obj.newKey = value)
  • 按索引设置数组项(例如arr[index] = newValue)
  • 修改数组的长度(例如arr.length = newLength)
    不过为了解决这些问题, Vue为提供了Vue.set API方法,该方法向响应对象添加了一个属性,确保新属性也是响应性的,从而触发了视图更新。

用下述实例讨论该情况:

  1.   <h1>Hello! My name is {{ person.name }}. I'm {{ person.age }} years old.</h1> 
  2.   <button @click="addAgeProperty">Add "age" property</button> 
  3.   <p>Here are my favorite activities:</p> 
  4.   <ul> 
  5.     <li v-for="item, index in activities" :key="index"
  6.       {{ item }} 
  7.       <button @click="editActivity(index)">Edit</button> 
  8.     </li> 
  9.   </ul> 
  10.   <button @click="clearActivities">Clear the activities list</button> 
  11. </div> 

 

  1. el: '#app'
  2.   data: { 
  3.     person: { 
  4.       name: "David" 
  5.     }, 
  6.     activities: [ 
  7.       "Reading books"
  8.       "Listening music"
  9.       "Watching TV" 
  10.     ] 
  11.   }, 
  12.   methods: {  
  13.     // 1. Add a new property to an object 
  14.     addAgeProperty() { 
  15.       this.person.age = 30 
  16.     }, 
  17.     // 2. Setting an array item by index 
  18.     editActivity(index) { 
  19.       const newValue = prompt('Input a new value'
  20.       if (newValue) { 
  21.         this.activities[index] = newValue 
  22.       } 
  23.     }, 
  24.     // 3. Modifying the length of the array 
  25.     clearActivities() {  
  26.       this.activities.length = 0  
  27.     } 
  28.   } 
  29. }); 

在上面的示例中,我们会发现这三种方法都不起作用。我们不能向该person对象添加新属性,无法使用activities的索引来编辑数组中的项目,也不能修改activities数组的长度。

优化如下:

  1. el: '#app'
  2.   data: { 
  3.     person: { 
  4.       name: "David" 
  5.     }, 
  6.     activities: [ 
  7.       "Reading books"
  8.       "Listening music"
  9.       "Watching TV" 
  10.     ] 
  11.   }, 
  12.   methods: {  
  13.     // 1. Adding a new property to the object 
  14.     addAgeProperty() { 
  15.       Vue.set(this.person, 'age'30
  16.     }, 
  17.     // 2. Setting an array item by index 
  18.     editActivity(index) { 
  19.       const newValue = prompt('Input a new value'
  20.       if (newValue) { 
  21.         Vue.set(this.activities, index, newValue) 
  22.       } 
  23.     }, 
  24.     // 3. Modifying the length of the array 
  25.     clearActivities() {  
  26.       this.activities.splice(0
  27.     } 
  28.   } 
  29. }); 

在此示例中,我们用Vue.setAPI方法将新age属性添加到person对象,并从活动数组中选择/修改特定项目。在最后一种情况下,使用JavaScript内置splice方法。

这个做法完全可行但却略显笨拙,而且会导致前后代码不一致。而Vue 3就解决了这个问题。

我们用下面示例继续看:

  1. data() { 
  2.     return { 
  3.       person: { 
  4.         name: "David" 
  5.       }, 
  6.       activities: [ 
  7.         "Reading books"
  8.         "Listening music"
  9.         "Watching TV" 
  10.       ] 
  11.     } 
  12.   }, 
  13.   methods: {  
  14.     // 1. Adding a new property to the object 
  15.     addAgeProperty() { 
  16.       this.person.age = 30 
  17.     }, 
  18.     // 2. Setting an array item by index 
  19.     editActivity(index) { 
  20.       const newValue = prompt('Input a new value'
  21.       if (newValue) { 
  22.         this.activities[index] = newValue 
  23.       } 
  24.     }, 
  25.     // 3. Modifying the length of the array 
  26.     clearActivities() {  
  27.       this.activities.length = 0  
  28.     } 
  29.   } 
  30.  
  31. Vue.createApp(App).mount('#app'

可以看到在Vue 3中,所有方法都可以正常工作。

在Vue 2.6中,引入的Vue.observable API方法,一定程度的公开了响应式系统,使开发人员可以体验到响应式系统的内容。实际上,这和Vue内部用来包装data对象是完全相同的方法,对于在简单场景创建小的跨组件状态存储很有用。但依旧没办法和Vue3的响应式系统相比,接下来就为大家详细介绍。

注意:由于Object.defineProperty方法是仅限ES5且不可调整的功能,因此Vue 2不支持IE8及以下版本。

Vue 3响应式系统如何工作

为了充分利用ES6 Proxy and Reflect API ,Vue 3中的响应式系统已被完全重写。新版本新增响应式API,该API使系统比以前更加灵活和强大。

Proxy API允许开发人员拦截和修改目标对象上的更低级对象操作。代理(proxy)是对象的克隆/包装(clone/wrapper),并提供特殊功能(称为target),这些功能响应特定的操作并覆盖JavaScript对象的内置行为(称为traps)。如果仍然需要使用默认行为,则可以使用相应的Reflection API,其名称顾名思义就是反映Proxy API的方法。这里有一个示例,用来了解如何在Vue 3中使用这些API:

  1. name: "David"
  2.   age: 27 
  3. }; 
  4.  
  5. const handler = { 
  6.   get(target, property, receiver) { 
  7.     // track(target, property) 
  8.     console.log(property) // output: name 
  9.     return Reflect.get(target, property, receiver) 
  10.   }, 
  11.   set(target, property, value, receiver) { 
  12.     // trigger(target, property) 
  13.     console.log(`${property}: ${value}`) // output: "age: 30" and "hobby: Programming" 
  14.     return Reflect.set(target, property, value, receiver) 
  15.   } 
  16.  
  17. let proxy = new Proxy(person, handler);    
  18.  
  19. console.log(person) 
  20.  
  21. // get (reading a property value) 
  22. console.log(proxy.name)  // output: David 
  23.  
  24. // set (writing to a property) 
  25. proxy.age = 30
  26.  
  27. // set (creating a new property) 
  28. proxy.hobby = "Programming"
  29.  
  30. console.log(person) 

要创建一个新的代理,使用new Proxy(target, handler)构造函数。它带有两个参数:目标对象(person对象)和处理程序对象,该对象定义将拦截哪些操作(get和set操作)。在handler对象中, get和set陷阱来跟踪何时读取属性以及何时修改/添加属性。设置控制台语句以确保运行正确。

在get和set陷阱采用下列参数:

  • target:代理包装的目标对象
  • property:属性名称
  • value:属性值(此参数仅用于设置操作)
  • receiver:进行操作的对象(通常是代理)
  • Reflect API方法与其相应的代理方法接受相同的参数

注释中track函数和trigger函数特定用于Vue,用于跟踪何时读取属性以及何时修改/添加属性。

在示例的最后一部分,用控制台语句输出原始person对象。然后用另一份声明中读取属性name的proxy对象。接下来,修改age属性并创建一个新hobby属性。最后,再次输出该对象以查看它是否正确更新。

以上就是Vue3响应式系统的完整工作流程,但在实际工作中会复杂得多。

使用Vue 3响应式系统,还有一些注意事项:

  • 仅适用于支持ES6 +的浏览器
  • 响应代理不等于原始对象

总结

以上我们将Vue2和Vue3中响应式系统部分进行了比较,并对响应式系统的工作原理进行了说明,在后面的文章中,我们会进一步为大家介绍Vue3中响应式系统的API,敬请期待。

责任编辑:张燕妮 来源: 葡萄城官网
相关推荐

2022-06-26 00:00:02

Vue3响应式系统

2022-03-29 09:59:58

响应式系统Vue2

2020-06-09 11:35:30

Vue 3响应式前端

2019-07-01 13:34:22

vue系统数据

2022-04-03 19:27:35

Vue2响应式系统

2021-01-22 11:47:27

Vue.js响应式代码

2019-12-06 10:44:53

Vue 3.0响应式系统前端

2022-04-14 08:46:46

响应式系统js

2022-04-02 09:56:41

Vue2响应式系统

2022-04-06 07:28:47

数组响应式系统

2017-08-30 17:10:43

前端JavascriptVue.js

2020-03-16 10:25:49

前端React Hooks响应式布局

2023-06-01 19:19:41

2022-06-13 10:42:21

分布式事务数据库

2021-10-18 09:01:01

前端赋值浅拷贝

2022-03-31 10:15:10

分支切换响应式系统

2022-08-22 09:01:24

Vue响应式原则双向数据绑定

2021-06-28 07:12:28

赋值浅拷贝深拷贝

2022-04-12 10:05:18

响应式系统异步队列

2024-01-09 09:40:23

点赞
收藏

51CTO技术栈公众号