关于 Vue 响应式原理的困惑

需求描述

需要将用户信息的 UI(下文用 UserInfo 来代替) 写成一个 Vue 组件,达到重用的目的。

UserInfo 组成

  • 用户信息的模板
  • 获取用户信息的逻辑
  • 样式

大概长这样

请先忽略丑陋的UI,显示的元素主要就俩:

  • 头像
  • 昵称

服务端返回的数据格式

{
    "data":{
        "id":"2",
        "type":"user",
        "attributes":{
            "nick_name":"HelloVass",
            "avatar":"https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epZOhVL6QcUqJjEo7mqSpiamWRAaX1lB9dV79PzfOA5CMzBlBmCUfKibb2JyMQ0Rubic9OLMzjBRS9Gw/132",
            "score":0,
            "data":{
                "gender":1,
                "city":"Hangzhou",
                "province":"Zhejiang",
                "country":"China"
            }
        }
    }
}

UserInfo 的代码非常简单

<template>

  <div class="user-container">
    <!--背景-->
    <image
      class="user-bg"
      mode="aspectFill"
      :src="userInfo.avatar"
    >
    </image>
     <!--用户信息-->
    <div class="user-info">
      <!--头像-->
      <image
        class="user-avatar"
        :src="userInfo.avatar"
      ></image>
      <!--昵称-->
      <div
        class="user-nickname"
      >{{userInfo.nick_name}}
      </div>
    </div>
  </div>
</template>

<script>
  // 用户Api
  import UsersApi from "@/network/users-api";

  export default {
      
    name: "UserHeader",

    data() {
      return {
        result: Object
      };
    },

    computed: {
      // 用户是否登录
      isLogin() {
        return this.$store.getters.isLogin;
      },
	  // 用户信息
      userInfo() {
        return this.result.data.attributes;
      }
    },

    created() {
        
      // 未登录,return
      if (!this.isLogin) {
        return;
      }
        
      // 已经登录,直接从服务器获取用户数据
      UsersApi.getUserInfo()
        .then(res => {
          this.result = res;
        });
    }
  };
</script>

// 省略样式
<style scoped lang="scss">
</style>

看起来似乎没什么问题,实验一下

哦豁,凉凉

VM116:1 thirdScriptError
Cannot read property 'attributes' of undefined;at pages/users/main page lifeCycleMethod onReady function
TypeError: Cannot read property 'attributes' of undefined
    at VueComponent.userInfo (http://127.0.0.1:46848/appservice/static/js/pages/users/main.js:367:30)
    at Watcher.get (http://127.0.0.1:46848/appservice/static/js/vendor.js:2635:25)
    at Watcher.evaluate (http://127.0.0.1:46848/appservice/static/js/vendor.js:2742:21)
    at VueComponent.computedGetter [as userInfo] (http://127.0.0.1:46848/appservice/static/js/vendor.js:2971:17)
    at VueComponent.render (http://127.0.0.1:46848/appservice/static/js/pages/users/main.js:399:17)
    at VueComponent.Vue._render (http://127.0.0.1:46848/appservice/static/js/vendor.js:3785:22)
    at VueComponent.updateComponent (http://127.0.0.1:46848/appservice/static/js/vendor.js:2305:21)
    at Watcher.get (http://127.0.0.1:46848/appservice/static/js/vendor.js:2635:25)
    at new Watcher (http://127.0.0.1:46848/appservice/static/js/vendor.js:2624:12)
    at mountComponent (http://127.0.0.1:46848/appservice/static/js/vendor.js:2309:17)

console 里直接报错了,而 UserInfo 也没有正常渲染出来,why?

冷静分析

data() {
     return {
       result: Object
     };
   },

因为服务端返回的数据遵循标准 JSONApi 格式(有时候嵌套层级会比较深),而我这里想偷懒,就定义了一个 result,并没有定义 result 里的具体字段,并给他们赋值。

按照我的思路

目前只需要 nick_name 和 avatar 两个字段的值,而这两个字段嵌套的比较深,我不希望在 template 里写这样的绑定代码:

<template>

  <div class="user-container">
    <!--背景-->
    <image
      class="user-bg"
      mode="aspectFill"
      :src="result.data.attributes.avatar"
    >
    </image>
     <!--用户信息-->
    <div class="user-info">
      <!--头像-->
      <image
        class="user-avatar"
        :src="result.data.attributes.avatar"
      ></image>
      <!--昵称-->
      <div
        class="user-nickname"
      >{{result.data.attributes.nick_name}}
      </div>
    </div>
  </div>
</template>

太丑陋了!!!

于是,我在计算属性中定义了一个 userInfo() 方法,将 result.data.attributes 作为它的返回值,当 getUserInfo 方法获取到服务器上的数据后,进行一个this.result = res 操作,这样,计算属性 userInfo 依赖的 result 更新了,userInfo 也会更新,也就完成了UI的渲染。这一切是多么美好啊!

但是为什么没有按照我的剧本演呢?

这就涉及我的知识盲区了,Vue 是如何追踪数据变化,实现响应式编程的?

遇事不顺找 Google,这里我找到三篇比较有参考价值的文章:

第一篇文章提到了变化检测的问题,

受限于JS及废弃的Object.observe,Vue不能检测到对象属性的添加或删除。由于Vue会在初始化实例时对属性执行getter/setter转化的过程,所以属性必须在data对象上保存才能被转换,如此,才可以让它是响应的。例如: > new Vue({ > data:{ > a:1 > } > }) > /* < !-- vm.a 是响应的 --> */ > > vm.b = 2 > /* < !-- vm.b 是非响应的 --> */ >

Vue不允许在已创建的实例上动态添加新的根级响应式属性。但是可以使用Vue.set(object,key,value)方法将响应属性添加到嵌套的对象上: > /*< !-- 一定要在实例化之前添加! -- > */ > Vue.set(vm.someObject, 'b', 2) >

第二篇,也就是vue官方的说明:

还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除: > var vm = new Vue({ > data: { > a: 1 > } > }) > // `vm.a` 现在是响应式的 > > vm.b = 2 > // `vm.b` 不是响应式的 >

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于: > var vm = new Vue({ > data: { > userProfile: { > name: 'Anika' > } > } > }) >

你可以添加一个新的 age 属性到嵌套的 userProfile 对象: > Vue.set(vm.userProfile, 'age', 27) >

最后捋一捋思路,为什么会发生错误呢?

当页面中的 image、div 渲染是,userInfo 数据肯定还没获取到,但是这时候 userInfo() 方法里 result.data.attrbutes 的 result.data 还没有定义,所以就会报错 Cannot read property 'attributes'

解决方案

别偷懒,按照后端返回的 JSON 的格式初始化 data 里的字段,如下:

data() {
      return {
        result: {
          data: {
            id: Number,
            type: String,
            attributes: Object
          }
        }
      };

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏GreenLeaves

Jquer学习之jQuery(function(){})与(function(){})(jQuery)之间的区别

Jquery是优秀的Javascrīpt框架。我们现在来讨论下在 Jquery 中两个页面载入后执行的函数。 $(document).ready(functio...

1K50
来自专栏Golang语言社区

Golang URL解析

Golang URL解析 今天利用课余时间看了一下Go语言的URL解析,于是就实践了一下,为了大家一起学习交流,故贴出来和大家一起分享,如有什么错误,请各位大神...

670120
来自专栏开源项目

Vue 2.0 学习总结,精华全在这里了

摘要:年后公司项目开始上vue2.0,自己对学习进行了总结,希望对大家有帮助! 1Vue 介绍 Vue 是什么? https://vuefe.cn/guide ...

320110
来自专栏IMWeb前端团队

Zepto源码分析之form模块

前言 JavaScript最初的一个应用场景就是分担服务器处理表单的责任,打破处处依赖服务器的局面,这篇文章主要介绍zepto中form模块关于表单处理的几个...

212100
来自专栏逸鹏说道

01.Web大前端时代之:HTML5+CSS3入门系列~初识HTML5

Web大前端时代之:HTML5+CSS3入门系列:http://www.cnblogs.com/dunitian/p/5121725.html 文档申明 <!-...

30740
来自专栏较真的前端

Chrome开发者工具还有这些功能,你知道吗?

31080
来自专栏搞前端的李蚊子

Vue中的$set的使用

在我们使用vue进行开发的过程中,可能会遇到一种情况:当生成vue实例后,当再次给数据赋值时,有时候并不会自动更新到视图上去; 当我们去看vue文档的时候,会发...

427100
来自专栏雪胖纸的玩蛇日常

vue学习(3)

webpack:打包机,它能将我们的html、css、js,font,png进行打包,交给服务器。

23120
来自专栏前端说吧

JS-DOM2级封装练习题--点击登录弹出登录对话框

34670
来自专栏MasiMaro 的技术博文

MFC中属性表单和向导对话框的使用

每次在使用MFC创建一个框架时,需要一步步选择自己的程序的外观,基本功能等选项,最后MFC会生成一个基本的程序框架,这个就是向导对话框;而属性表单则是另外一种对...

12810

扫码关注云+社区

领取腾讯云代金券