competed中访问ref为undefined

1. 问题描述

级联选择器中的数据通过接口回传,当选中某项数据,获取value的同时须拿到uuid,用于页面跳转。uuid的获取在两个地方:一是级联选择器初始化时,若已有选中项,则需取出uuid赋值;二是用户改变选项时,需拿到新的uuid赋值。两个地方都是通过$refs访问级联选择器topicSelect实例,通过调用getCheckedNodes方法获取所选项的uuid。因此考虑在computed中调用ref获取uuid。但在computed中访问ref为undefined。

 <el-cascader
          filterable
          v-model="uuidArray"
          ref="topicSelect"
          placeholder="请选择源表。支持按名称搜索"
          @change="handleValue"
          :disabled="onlyRead"
          :props="{ expandTrigger: 'hover', lazy: true, lazyLoad: cascaderLazyLoad }"
          :show-all-levels="false"
          size="small"
        >
        </el-cascader>
tableUuid(){
    return  this.$refs.topicSelect.getCheckedNodes()[0] ? this.$refs.topicSelect.getCheckedNodes()[0].data.uuid: '';//TypeError: Cannot read properties of undefined (reading 'getCheckedNodes')
},

2. 分析过程

  1. 根据vue的生命周期,观测computed属性调用this.$refs的时期,以及ref在refs中挂载的时期。如图所示,computed中tableUuid是在beforeMount之后,mounted之前。而topicSelect是在 mounted之后才出现在this.$refs上的,因此在计算属性中通过$refs.tableUuid调用所需属性或方法时为undefined。

  2. 进一步观测发现,所要调用的topicSelect已经存在了,但调用的this.$refs为{},没有与topSelect建立联系,无法通过this.$refs.topicSelect的方式获取。

  3. ref本身作为渲染结果被创建,在初始渲染的时候访问是不存在的,因此ref需要在DOM渲染完成后应用,使用时需确保DOM已经渲染完成。因此,有两种解决措施:1.使用this.$nextTick(),2.在mounted中调用。

3. 解决过程

解决措施1

  1. 首先尝试在computed中使用this.$nextTick(),发现可以获取ref。

  2. 继续朝着目标出发,尝试访问目标ref及其相关属性。再次遇到问题。可以访问到ref,也能看见相关属性是有数据的,但是单独获取,却是没有值的。

  3. 由于ref是页面完成渲染后才访问得到的,因此观测mounted之后的生命周期,调用this.topicSelect.checkedValue发生了什么变化。如图所示,数据是在更新完成后才出现的。

  4. 通过调试语句,期待数据发生变化时,再次调用computed中的属性,执行相关的方法。但是事与愿违,更新数据时,computed中的tableUuid并没有再次调用。

tableUuid(){
      let tableuuid = 'default';
      this.$nextTick(()=>{
        tableuuid = this.$refs.topicSelect.checkedValue.length !== 0 ? this.$refs.topicSelect.getCheckedNodes()[0] ? this.$refs.topicSelect.getCheckedNodes()[0].data.uuid: 'getCheckedNodes()无数据' : 'checkedValue无数据';
 console.log('tableUuid',tableuuid);
    });
      return tableuuid;
},
  1. 尝试在各生命周期中访问tableUuid,发现在生命周期中访问tableUuid得到的结果都是默认值,在nextTick中操作并未生效。

复习computed
● computed本质是一个惰性求值的观察者,具有缓存性,只有当依赖发生变化后,访问computed中的属性,才会重新计算。
● 初始化computed调用initComputed函数: ○ 注册一个watcher实例,并在其中实例化一个Dep消息订阅器用于收集依赖;

  1. 调用计算属性时会触发其Object.defineproperty的get访问函数;
  2. 调用watcher.depend()方法向自身的消息订阅器dep的subs中添加其他属性的watcher;
  3. 调用watcher.evaluate,进而调用get方法,让自身成为其他watcher的消息订阅器的订阅者,首先将watcher赋给Dep.target,执行getter,当访问getter中的属性(可能来自于data、props或其他computed)时,会同样触发它们的get访问器函数,从而将该计算属性的watcher添加到求值函数中属性的watcher的消息订阅器dep中,当这些操作完成,最后关闭Dep.target赋为null并返回求值函数结果
  4. 当某个属性发生变化,触发set拦截函数,然后调用自身dep的notify方法,遍历当前dep中保存着所有订阅者的subs数组,并逐个调用watcher的update方法,完成响应更新

● computed定义的属性接收return的结果,return是同步执行的,没办法拿到异步操作的结果。

  1. 在computed使用this.$nextTick失败
    解决措施2
  2. 采用第二种方式,由解决措施1可知,在mounted中可以成功调用ref。为了动态获取数据,需要对ref进行监听,将监听到的数据赋给tableUuid。该方法验证可行。
mounted(){
    let refTmp = this.$refs.topicSelect;
    this.$watch(
      function(){
        return refTmp.getCheckedNodes()[0] ? refTmp.getCheckedNodes()[0].data.uuid: '';
      },
      (val, oval)=>{
        this.tableUuid = val;
        console.log('watch',val,oval);
      }
    )
  },

4. 总结

● 对于ref的注册时间需注意⚠️ 引用官网

因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 -
它们还不存在!$refs也不是响应式的,因此你不应该试图用它在模板中做数据绑定。

● computed的结果会被缓存,只有当依赖的响应式property变化时才会重新计算,但若某个依赖是非响应式的(例如:常量、Date.Now()),则计算属性是不会被更新的。computed是不支持异步操作的,因此,在computed中执行异步操作是无效的,异步操作的结果不会决定计算属性的返回结果。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值