博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Traps of Computed in Vue
阅读量:7005 次
发布时间:2019-06-27

本文共 7944 字,大约阅读时间需要 26 分钟。

If you have used I guess you probably know or have used computed. It seems pretty convenient and easy to use. However, it may bring you some problems if you didn't understand it totally.

Trap in Computed

Let's see the demo below:

      
Document

And here is the result:

clipboard.png

Until now, nothing weird happens. However, if I changed the mounted hook to below:

let selectIndex = this.products.findIndex(obj => obj.id === this.selectedId)this.products[selectIndex] = Object.assign({}, this.products[selectIndex], {  name: '1-1'})

Any difference with the ui ? Yes, it has. The result would be:

clipboard.png

As you might thought, currentProduct is not right. Is that a bug?

No! Let's look at the doc:

However, the difference is that computed properties
are cached based on their dependencies. A computed property will
only re-evaluate when some of its dependencies have changed.

That is how computed works.

In case above, currentProduct only has two dependencies which are products and selectedId. When we use code below:

this.products[selectIndex] = Object.assign({}, this.products[selectIndex], {  name: '1-1'})

We didn't actually change products or selectedId. We just change the attributes of products not products itself. Neither did code below:

this.products[selectIndex].name = '1-1'

Code above works because we are changing the existing attributes of currentProduct. So, it works as expected.

Computed or Methods

If that bothers you, doc also gives you another choice:

Instead of a computed property, we can define the same function as a method instead. For the end result, the two approaches are indeed exactly the same.

But in this case, it needs to be a little complicated. The code would be:

const product = Vue.component('product', {  template: `      
I am {
{item.name}}
selected: {
{getCurProduct().name}}
`, props: ['products', 'selectedId'], methods: { getCurProduct() { return this.products.find(({ id }) => id === this.selectedId) } }})const app = new Vue({ el: '#app', components: { product }, data: { products: [ { id: 0, name: '0' }, { id: 1, name: '1' }, { id: 2, name: '2' } ], selectedId: 1 }, mounted() { let selectIndex = this.products.findIndex(obj => obj.id === this.selectedId) this.products[selectIndex] = Object.assign({}, this.products[selectIndex], { name: '1-1' }) this.$children[0].$forceUpdate() }})

The point which needs to take care is

this.$children[0].$forceUpdate()

In this case, simply change currentProduct to getCurProduct() doesn't work because we have to let product invoke getCurProduct() to get the latest data.

Also, you can use getCurProduct in the root:

const product = Vue.component('product', {  template: `      
I am {
{item.name}}
selected: {
{currentProduct.name}}
`, props: ['products', 'selectedId', 'currentProduct']})const app = new Vue({ el: '#app', components: { product }, data: { products: [ { id: 0, name: '0' }, { id: 1, name: '1' }, { id: 2, name: '2' } ], selectedId: 1 }, mounted() { let selectIndex = this.products.findIndex(obj => obj.id === this.selectedId) this.products[selectIndex] = Object.assign({}, this.products[selectIndex], { name: '1-1' }) // this.$children[0].$forceUpdate() this.$forceUpdate() }, methods: { getCurProduct() { return this.products.find(({ id }) => id === this.selectedId) } }})

Insist on Computed for Cache Control ?

Sometimes methods isn't be a better choice because we really need the cache control like the doc said:

Why do we need caching? Imagine we have an expensive computed property
A, which requires looping through a huge Array and doing a lot of computations. Then we may have other computed properties that in turn depend on
A. Without caching, we would be executing
A’s getter many more times than necessary!

So, somebody proposed a feature request for $recompute in . Also, some guys figured out some solutions.

Take a look at the code below:

const product = Vue.component('product', {  template: `      
I am {
{item.name}}
selected: {
{currentProduct.name}}
`, props: ['products', 'selectedId', 'currentProduct']})const app = new Vue({ el: '#app', components: { product }, data: { products: [ { id: 0, name: '0' }, { id: 1, name: '1' }, { id: 2, name: '2' } ], selectedId: 1, currentProductSwitch: false }, computed: { currentProduct() { // just let currentProductSwitch become a new dependency of currentProduct this.currentProductSwitch return this.products.find(({ id }) => id === this.selectedId) } }, mounted() { let selectIndex = this.products.findIndex(obj => obj.id === this.selectedId) this.products[selectIndex] = Object.assign({}, this.products[selectIndex], { name: '1-1' }) // update currentProductSwitch to recompute currentProduct this.currentProductSwitch = !this.currentProductSwitch }})

Do you understand the principle?

I add currentProductSwitch in currentProduct getter to make currentProductSwitch become one dependency of currentProduct. So, we can make currentProduct recompute when we change the value of currentProductSwitch.

Now, let's take a look at the solution gives:

const product = Vue.component('product', {  template: `      
I am {
{item.name}}
selected: {
{currentProduct.name}}
`, props: ['products', 'selectedId', 'currentProduct']})const recompute = Vue.mixin({ data() { return { __recomputed: Object.create(null) } }, created() { const watchers = this._computedWatchers if (!watchers) { return } if (typeof this.$recompute === 'function') { return } this.$recompute = key => { const { __recomputed } = this.$data this.$set(__recomputed, key, !__recomputed[key]) } Reflect.ownKeys(watchers).forEach(key => { const watcher = watchers[key] watcher.getter = (function(getter) { return vm => { vm.$data.__recomputed[key] return getter.call(vm, vm) } })(watcher.getter) }) }})const app = new Vue({ el: '#app', components: { product }, mixins: [recompute], data: { products: [ { id: 0, name: '0' }, { id: 1, name: '1' }, { id: 2, name: '2' } ], selectedId: 1 }, computed: { currentProduct() { return this.products.find(({ id }) => id === this.selectedId) } }, mounted() { let selectIndex = this.products.findIndex(obj => obj.id === this.selectedId) this.products[selectIndex] = Object.assign({}, this.products[selectIndex], { name: '1-1' }) this.$recompute('currentProduct') }})

The core code is:

// ....//* each time called $recompute, reverse the new dependency to let getter recomputethis.$recompute = key => {  const { __recomputed } = this.$data  this.$set(__recomputed, key, !__recomputed[key])}//* let __recomputed[key] become a new dependency of key's getterReflect.ownKeys(watchers).forEach(key => {  const watcher = watchers[key]  watcher.getter = (function(getter) {    return vm => {      vm.$data.__recomputed[key]      return getter.call(vm, vm)    }  })(watcher.getter)})// ....this.$recompute('currentProduct')

转载地址:http://ssytl.baihongyu.com/

你可能感兴趣的文章
UITabBarController
查看>>
[Aaronyang] 写给自己的WPF4.5 笔记16[多线程]
查看>>
如果将一维数组编程一个字符串
查看>>
codeforces B. Ohana Cleans Up
查看>>
PHP 对象 “==” 与 “===”
查看>>
Atitit.播放系统规划新版本 and 最近版本回顾 v3 pbf.doc 1 版本11 (ing)41.1 规划h5本地缓存系列 41.2 Android版本app41.3 双类别系统,...
查看>>
jenkins2 hello pipeline
查看>>
10个调试技巧
查看>>
Atitit.常用的gc算法
查看>>
Cesium原理篇:4Web Workers剖析
查看>>
python 设计模式
查看>>
javaweb学习总结二十一(servlet开发入门、servlet生命周期以及调用过程)
查看>>
html传参数
查看>>
nodemanager启动失败
查看>>
spring整合mybatis是如何配置事务的?
查看>>
打印报表
查看>>
POJ 2756 Autumn is a Genius 大数加减法
查看>>
redis+mysql
查看>>
Redis(二):Redis的九大应用场景
查看>>
sonarqube 指定jdk
查看>>