Vue中修改了数组数值,视图没有更新?

开篇闲扯

在人类求知的路上,会形成对求知的不同的态度,比如好读书,不求甚解;又例如要知其然知其所以然,追本溯源等等,人类在满足自己好奇心的同时,掺杂着复杂的人性。前者“懒惰”,后者“贪婪”。人类总能逻辑自洽的自圆其说。技术的变化很快,新东西更替不断,让人眼花缭乱。一个新的框架出来,大家都会在各种社区或者谈论组表示 学不动.gif 。了解背后的原理就显得特别重要。最近在看网络协议和浏览器方面的东西。浅尝辄止的了解编译原理方面的知识,所以今天就分享下vue中视图跟数据的关系的一个知识点(皮.png)。

进入正题

在使用 Vue 过程中,你可能会发现使用索引修改一个数组的值,是不能触发视图刷新的。

用代码展示下

<div id="app">
   <span v-for="item in list" :key="item">
     {{item}}
   </span>
 </div>

new Vue({
    el: '#app',
  data:{
        list: ['a ','b ','c']
  }
})

当然渲染出来就是a b c了。

然后在页面中加入一个点击事件,把中间的b 换成d

<button @click="change">点击</button>
change(){
  this.list[1] = 'd'
}

结果就是控制台可以看到数据变了,但是页面没有变化;数据变化视图没有变化,当然这个在vue官方文档也有说明

在这里插入图片描述

文档中只说了不能这么做,没有说不能这么做的原因,你说气人不。我们试着分析下不能这么做的原因

vue的渲染机制

vue渲染中的数据变化检测和依赖收集

vue是如何将一个组件转化DOM渲染在页面中,首先找到组件中的template标签包裹的部分,会经过编译,得到Render函数,在渲染时执行Render函数,Render函数执行过程中,读取data里边的数据,生成一个虚拟DOM(

Virtual DOM),包含了页面的结构和数据信息,在通过Patch函数修改实际浏览器的DOM,最终完成渲染。

页面中的事件逻辑修改了data,会造成数据的变化,vue会重新执行Render函数,得到一个新的Virtual DOM,传个patch函数,patch函数将两次的虚拟DOM进行对比,根据相应的diff算法更新视图。

Data(changed&事件触发)-- Reander – Virtual DOM – Patch – 视图变化 – Data(changed&事件触发)

此处可以脑补一张循环的图

这是vue完整的渲染流程,但是疑问还是没有解开,按照正常流程就像上边的change方法把b修改为d,视图就没有更新。

vue中的数据不只在一个组件渲染,例如在vuex中,同一个变量可能会影响很多组件,如何知道某个数据变化后修改哪个组件呢?如何检测一个数据发生了变化呢?

数据变化视图

思路是一个数据变化时,就要对应通知这个组件更新,在vue中通过依赖收集实现的。vue源码中组件初始化会有new 一个watcher,用来作为依赖收集对象,触发组建更新。官网给出了一个详细的图例

在这里插入图片描述

vue是通过Data中的getter(监听数据读取)和setter(监听数据修改),getter在读取数据的时候收集watcher作为依赖,setter在数据发生变化时通知watcher触发视图更新。众所周知,在vue2.x中是通过

Object.defineProperty(obj, key, {
  enumberable:true,
  configurable:true,
  get() {},
  set(value) {}
  });

允许添加或修改对象的属性,第一个参数,是一个对象,在这个对象上定义属性;第二个参数是要定义的属性的名称;最后一个参数是属性描述符,它是一个对象,它又分为数据描述符和存取描述符,而vue用的就是存取描述符,详解点击这里。给对象的属性设置getter和setter方法,组件的render函数执行,读取data中的属性时,触发getter方法,收集依赖;代码逻辑修改data中的值时,触发setter方法,执行render,完成视图更新。在初始化的时候,vue会给data里的每一个值设置getter和setter。

vue源码中对数组的处理

在vue源码中可以看到

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys) // 数组时augment是protoAugment
      this.observeArray(value) //数组时执行
    } else {
      this.walk(value)
    }
  }

解惑

回到开篇的代码,添加change事件修改b为d。为什么没有修改成功呢?是因为没有触发setter方法。为什么触发不了setter呢?因为代码中this.observeArray(value) 如果时数组的执行检测数组方法,数组中的项没有设置getter和setter,直接赋值的方式不能被检测,所以视图不会更新。

同时在调用this.observeArray(value)之前如果是数组,调用protoAugment方法,该方法会将value的原型设置为arrayMethods,它对常见的数组操作,例如push,pop,shift,unshift,splice等方法都进行了修改,类似与setter函数,通过调用这些方法时,会通知数组的依赖进行更新。

开篇的change事件修改为this.list.splice(1,1,‘d’)视图就更新了。看完是不是对背后的原理更加清晰了呢。

vue3.0的Proxy

MDN中的用法

var p = new Proxy(target,{
get(target,property,receiver){
},
set(target,property,value,receiver){
}
})

Proxy和Object.defineProperty的区别是:后者定义的是一个对象上某个值得表现,前者是一个对象行为得表现,不局限某个属性,能观察到对象中属性的增加和删除,可以弥补后者的不足。

参考文章:
1.https://ustbhuangyi.github.io/vue-analysis/v2/reactive/

2.https://time.geekbang.org/dailylesson/detail/100028432
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值