那些Vue开发遇到的坑---响应式系统

6个月前

一定是Vue有问题,破框架!!!!
Vue是目前使用较为广泛的前端框架之一。相比React,Vue更容易学习上手。毕竟在React中万物皆JavaScript。这让一些习惯于编写HTML+JavaScript的程序员不太乐于接受。相比之下,Vue的模板语法它不香么。
当然,Vue同样支持类似于JSX的语法的渲染函数,但是相信我,只要你学了模板语法,你就会放弃渲染函数。
有的同学可能会提到AngularJS,这里就要说道,Vue的一些语法设计的确参考了AngularJS,但是Vue的API设计相对AngularJS要简单的多,学习成本更低。
而且,Vue在设计过程中解决了很多AngularJS存在的问题,包括Vue对数据流的控制都会让你的代码更加清晰易懂,让你可以在使用框架或者阅读别人代码的时候少说几句F**k(这个不完全保证)。



虽然Vue上手容易,但这并不代表你可以轻而易举的完全掌握它,要想真正了解并熟练这个框架,它的一些底层原理还是要了解一二的,这同样有助于开阔你的编程思路。今天我们就先介绍一下Vue最独特的特性之一--------响应式系统(这句话是抄官方的)。
Vue的响应式指的是你在一个页面中展示了一个变量的值,当这个变量的值由于一些操作发生改变时,Vue会自动在无需刷新界面的前提下帮你把新的值展示到相应的位置,当然这个过程不需要你自己写任何的dom刷新渲染的代码(我觉得我说的够通俗易懂了,再看不懂,请你去看尤雨溪的官方解说,保证你更看不懂)。
为了实现这一效果,Vue做了很多你不知道的事(不然怎么会不用你写一行代码,因为他们替你写了)。接下来的解说涉及一些Vue和JavaScript的基础知识,比如Object.defineProperty等,不太了解的同学请复制Object.defineProperty并打开浏览器粘贴到检索栏按下回车看看这到底是啥。
虽然Vue3版本弃用defineProperty改用proxy,但是响应式系统的主要思路还是没有变的,所以此处提到的defineProperty是Vue2的实现方法,这点请小伙伴们注意哦。



 VUE·响应式原理

一个Vue实例具备一个名为data的数据对象,对象中包含了当前Vue实例所需要的数据,当一个Vue实例生成时,Vue的响应式系统会递归的将data的property通过Object.defineProperty转换为getter/setter。
你可以理解为响应式系统对每一个实例数据绑定了getter/setter函数,要获取数据需要通过调用getter函数,为数据写入新值则需要调用setter函数。
每一个Vue实例还对应一个watcher实例(看名字就是知道这是拿来监听的)。这个watcher实例会记录与它对应的Vue实例的所接触过的所有数据。在此之后如果某条数据发生改变,那么必将通过setter函数去设置新值,这时watcher会监听到这一变化,然后通知用到这个数据的Vue实例进行重新渲染,更新新值到页面上,整个流程如下图:

引自:https://cn.vuejs.org/v2/guide/reactivity.html

上面那段话可能会比较晦涩难懂,因此我准备了下面这段话:我们以一个按钮为例,按钮上显示了一个由变量定义的字,当点击按钮时按钮上的文字会发生改变,代码如下:
<template>
    <div>
        <button type="button" @click="message='Do not click me!'">{{message}}</button>
    </div>
</template>

<script>
  export default {
    name: "demo",
    data() {
      return {
        message: 'Click Me!'
      }
    }
  }
</script>
从代码中我们可以看到,这个Vue实例包含一个按钮和一个名为message的数据,在按钮上的字通过调用message来展示。
当这个Vue实例被注册时,我们的响应式系统会为message设置一对getter/setter函数,然后这个Vue实例会去一个叫做watcher的地方登记他用到的变量,这里它登记的就是message,它告诉watcher,我用到了message,当他改变的时候请及时告诉我。Watcher就在小本本上记下来了,并且和message的getter/setter函数保持联系,当我们点击按钮,按钮的click事件改变了message的值,这时会先调用setter函数,setter函数在改变message的值的同时会通知watcher,watcher收到这一消息之后就会通知Vue实例,告诉他,你用到的message变了,Vue实例收到这一消息就会重新渲染按钮,把新的message值显示在按钮上,至此,一次响应式更新完成了。

那些VUE开发遇到的坑——响应式系统


Vue的响应式系统非常好用,开发者甚至可以不懂得DOM的渲染相关知识就能完成一个响应式页面的开发,但是,我们日常开发总不可能是都像教程里的demo一样简简单单清清楚楚,一个庞大的web系统会有复杂的组件嵌套引用,组件之间有着复杂的数据交互,偶尔经常就会出现bug,而且有时候你在你的代码中找不到任何问题(那是你以为),然后就会百思不得其解为什么我的数据没有及时更新到页面上!!!一定是Vue有问题,破框架!!!!
好了,吐槽完之后我们还是老老实实看看,到底那里出了问题,为什么你的代码没有按照预期的运行。
今天我就为大家分析一下,在利用Vue进行开发的时候,为什么有些数据的变化不会被及时监听到并触发相关组件从新渲染。
对象类型在JavaScript中是一个引用类型,与基本类型不同,对象是按照引用访问的。因此,如果你想在Vue中监听到一下对象类型变量的变化时,你需要一些额外的操作,就比如下面这几行代码:
<template>
    <div>
        {{message.content}}
        <button type="button" @click="message.content='clicked'">click message</button>
    </div>
</template>

<script>
  export default {
    name: "demo",
    data() {
      return {
        message: {}
      }
    }
  }
</script>
以上代码渲染了一个按钮,并且声明了一个名为message的空的对象变量,意图是想要在点击按钮时,为message对象设置contact属性的值为‘clicked’。当我们开始运行我们的代码并在页面上点击按钮时,页面上并没有按照我们预期的展示出message的content属性值。然后作为一个程序员,你可能就要开始打debugger一步一步的调试,然后你会发现,你的代码并没有写错,在调试器中,message的属性确实改变了,并且按照预期被设置为‘clicked’,但是,为什么页面毫无反应,预期的‘clicked’字符串去哪里了?
其实,这是由于Vue虽然在初始化的时候向watcher注册了message, watcher中并没有记录一个后续添加的content属性,除非你重新为message赋值否则Vue是无法监听到message的content属性的。
Vue的开发者当然不可能这么无情的让你换个写法,所以他们提供了一个set函数,这个函数可以保证你为message添加的属性也是响应式的,那么就可以让代码按照你的要求执行了,具体实现如下:
<template>
    <div>
        {{message.content}}
        <button type="button" @click="click">click message</button>
    </div>
</template>

<script>
  export default {
    name: "demo",
    data() {
      return {
        message: {}
      }
    },
    methods:{
      click(){
        this.$set(this.message,'content','clicked')
      }
    }
  }
</script>
除了以上的情况,还有另外一种常见情形,就是当一个组件接收一些来自父组件的变量时,如果这个变量是一个对象,你同样无法使用Vue实例的watch去监听到他,在此情况下,可以通过设置deep为true来实现对象的深度监听,如下:
<template>
    <div>
        {{message.content}}
    </div>
</template>

<script>
  export default {
    name: "demo",
    props:{
      message:{
        default:null,

      }
    },
    watch:{
      message:{
        deep:true,
        handler(val){
          //message发生改变后的一些逻辑处理
        }
      }
    }
  }
</script>
值得提醒的是,数组类型在JavaScript中也是一个比较特殊的数据类型,与对象类型相似,数组也是引用类型,因此在开发中也会遇到和对象类型相似的问题,解决方法也是大同小异,此处就不多加说明。


 END



关于作者:夏夏,前端工程师,参与普元DevOps产品开发,以及微服务、容器云等产品开发,负责前端页面设计、架构搭建等工作。善于架构搭建、组件封装及相关算法设计。

关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享。长按二维码关注!

COMMENTS

需要 后方可回复
如果没有账号可以 一个帐号。