我的编程空间,编程开发者的网络收藏夹
学习永远不晚

vue定义在computed的变量无法更新问题及解决

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

vue定义在computed的变量无法更新问题及解决

vue定义在computed的变量无法更新

情境是这是线上商城的详情页面,商品详情是items数组,点击分类页面的商品,路由跳转到详情页面,路由参数是商品在items中的序号。

但是问题是只有第一次点击商品i的时候可以正常加载items[i]的数据到html中,退出后点击商品j,发现加载的还是商品i的信息,只有刷新后才会更新成商品j的信息。

部分代码:

        <shopPanel
            :key = "nums"
            :goodname="item.text"
            :tag="item.tag"
            :sale="item.sale"
            :price="item.price"
            :ori="item.ori_price"
        >
        </shopPanel>

和computed定义的,注意这里定义的数据item,是不能够修改的,想修改只能用set来修改。

我这里测试时发现明明item关联的两个属性this.focus和this.idx改变了,那为什么item没有改变呢?

而且是刷新后就能改变。

        computed: mapState({
            items:'items', //商品详情信息
            item(){
                console.log(this.focus+" "+this.idx);
                return this.items[this.focus].children[this.idx]
            }
        }),

最后的原因竟然是定义路由的时候选择了keepalive,页面不会销毁,改成false就行。

        path: '/item/item01',
        name:'itemdetail',
        meta: {
            title: '商品1',
            keepAlive: false,
            showTab: false
        },
        component: (resolve) => require(['../page/itemdetail/item01'], resolve),

vue computed依赖收集与更新原理

今天面了家小公司,上来直接问 computed 底层原理,面试官是这样问的,data 中定义了 a 和 b 变量。computed 里面定义了 c 属性,c 的结果依赖与 a 和 b,模板中使用了变量 c。假设改变了 a,请问底层是如何收集依赖,如何触发更新的?

<div>{{ c }}</div>
data(){
    return {
        a: 'foo',
        b: 'bar'
    }
},
computed: {
    c() { 
        return this.a + ' - ' + this.b;
     }
},
mounted(){
    setTimeout(() => { this.a = 'FOO' }, 1000)
}

页面效果:先显示了 foo - bar,一秒之后显示 FOO - bar

这里查源码后,对computed原理简述如下

1.initState 函数中初始化了 initComputed

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true )
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

2.initComputed 函数中遍历 computed 中每一个属性,并且 new Watcher(computed watcher),可以看到传入 Watcher 构造函数的 cb 是 noop,它是一个空函数。并且 defineComputed

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}

3.defineComputed 中使用了 Object.defineProperty 对属性进行劫持,获取是返回对应的 getter 即 createComputedGetter

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

看到这里,我们大致可以理解,Vue 组件在初始化时,initState -> initComputed -> new Watcher() 计算watcher,传入的 callback 是 computer 属性的 getter,由于 computed 是 lazy: true watcher,所以 new Watcher 时并不会立即执行 watcher 实例上的 get(), 而是在 defineComputed 函数里面调用 createComputedGetter 函数,在 createComputedGetter 函数执行了 watcher.evaluate() 进而执行了 watcher.get().

执行 watcher.get() 首先 pushTarget(this),将 Dep.target 赋值为当前的 computed watcher,然后执行 this.getter也就是执行 computer 属性配置的 getter,执行getter 就会访问所依赖的每一个值,就会被 Object.defineProperty 劫持到进入 get ,执行 dep.depend() , 会为每一个属性对应的 dep 实例添加一个 computed watcher,同时这个 computed watcher 也会保存对应的 dep。

说了这么多都在讲 computed watcher,那修改 this.a 页面为什么会发生更新呢?

答案:因为 this.a 的依赖中不仅有 computed watcher 还有一个 render watcher

原因:

$mount 是会执行 mountComponent,会创建一个 render watcher,它会立即执行 cb(目前 Dep.target 是 render watcher),调用 render 函数在底层编译模板,最后访问属性计算属性 c,访问计算属性 c 就必定会访问 a,当访问 a 时会触发 defineComputed 中的 Object.defineProperty 进而劫持调用 createComputedGetter,进而调用 watcher.depend(),这个 watcher 是 computed watcher

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}
// Watcher.js
depend () {
  let i = this.deps.length
  while (i--) {
    this.deps[i].depend()
  }
}

调用 watcher.depend() , this 指的是 computed watcher,会将 computed watcher 里面的 deps 保存在所有依赖调用 deps[i].depend(),进而调用 Dep 类中的 Dep.target.addDep(this),使得 render watcher 中保存了当前的 dep,dep 中同时保存了 render watcher。

dep 中同时保存了 render watcher。就可以看出,示例中的属性 a 的 dep 中也会保存 render watcher,所以 a 属性的 dep 中有两个 watcher: [computedWatcher, renderWatcher]

所以,修改 a 属性的值,最后 notify 会清空这个 保存 watcher 的队列,进行页面更新!Okay! 

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

vue定义在computed的变量无法更新问题及解决

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

vue定义在computed的变量无法更新问题及解决

这篇文章主要介绍了vue定义在computed的变量无法更新问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-01-10

vue定义在computed的变量无法更新如何解决

今天小编给大家分享一下vue定义在computed的变量无法更新如何解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。vue
2023-07-04

Vue无法访问.env.development定义的变量值问题及解决

这篇文章主要介绍了Vue无法访问.env.development定义的变量值问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-01-10

keras.layers.Layer中无法定义name的问题及解决

这篇文章主要介绍了keras.layers.Layer中无法定义name的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-02-21

Linuxservice无法使用系统环境变量的问题及解决

这篇文章主要介绍了Linuxservice无法使用系统环境变量的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-05-14

Linux service无法使用系统环境变量的问题及解决

目录linux service无法使用系统环境变量详细描述问题探索解决办法service文件中环境变量文件的使用测试准备测试流程总结Linux service无法使用系统环境变量 详细描述 之前在做一个mysql多实例安装时,安装成功,li
2023-04-07

spring注解在自定义jar包中无法被扫描的问题如何解决

这篇文章主要介绍“spring注解在自定义jar包中无法被扫描的问题如何解决”,在日常操作中,相信很多人在spring注解在自定义jar包中无法被扫描的问题如何解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答
2023-06-20

vue router嵌套路由在history模式下刷新无法渲染页面问题的解决方法

VueRouter嵌套路由在History模式下刷新无法渲染页面的解决方法:使用Vuex管理路由状态:在store中保存路由路径,刷新后恢复它。使用HTML5HistoryAPI:用pushState()更新URL哈希,刷新后使用window.location.hash获取更新后的哈希。使用VueRouter的keepAlive:缓存嵌套组件实例,防止刷新后销毁。示例:使用Vuex管理路由状态的方法。
vue router嵌套路由在history模式下刷新无法渲染页面问题的解决方法
2024-04-25

vue自定义密码输入框解决浏览器自动填充密码的问题(最新方法)

这篇文章主要介绍了vue自定义密码输入框解决浏览器自动填充密码的问题,通过将密码输入框的type设置为text,修改样式上的显示,来实现既可以让浏览器不自动填充密码,又可以隐藏密码的效果,需要的朋友可以参考下
2023-05-16

编程热搜

目录