Skip to content

Commit b2d288d

Browse files
Fix issues with computed refs recomputing every time
This is a Vue issue (vuejs/core#12337), where computed refs that have no reactive dependencies will be recomputed every time. The issue was masked by the access of `instance[vueStoreWatchScope]` in the watch code, but Reactive doesn't use watches, so it was still encountering the issue.
1 parent fc9da16 commit b2d288d

File tree

4 files changed

+40
-3
lines changed

4 files changed

+40
-3
lines changed

src/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function createStore<T extends object>(model: T): T {
1111
const reactiveInstance = reactive(model)
1212
addComputed(reactiveInstance, descriptors)
1313
createWatches(reactiveInstance, descriptors)
14+
fixRecomputeIfNoDeps()
1415
return reactiveInstance as T
1516
}
1617

@@ -32,6 +33,7 @@ export class Reactive {
3233
const reactiveThis = reactive(this)
3334
// watches require late initialization to work properly, so we only do computed properties
3435
addComputed(reactiveThis, descriptors)
36+
fixRecomputeIfNoDeps()
3537
return reactiveThis
3638
}
3739
}
@@ -81,6 +83,7 @@ const VueStore: VueStore = function VueStore(this: object, constructor?: { new(.
8183
if (wrapperClass.prototype === Object.getPrototypeOf(this)) {
8284
addComputed(reactiveThis, descriptors)
8385
createWatches(reactiveThis, descriptors)
86+
fixRecomputeIfNoDeps()
8487
}
8588
return reactiveThis
8689
}
@@ -98,3 +101,17 @@ const vueStoreDecorated = Symbol("vue-class-store__decorated")
98101
function isDecorated(prototype: object) {
99102
return Object.hasOwn(prototype, vueStoreDecorated)
100103
}
104+
105+
/**
106+
* Creates a reactive dependency on a static value, which alleviates the issue where computed properties with no
107+
* dependencies are recomputed every time (see issue: [vuejs/core#12337](https://github.com/vuejs/core/issues/12337))
108+
*
109+
* This can safely be removed when [vuejs/core#12341](https://github.com/vuejs/core/pull/12341) is merged and released
110+
*/
111+
function fixRecomputeIfNoDeps() {
112+
// we use a key into an object instead of a ref because it'll show up more clearly in the renderTriggered debug report
113+
noDepTarget[noDepKey]
114+
}
115+
116+
const noDepKey = Symbol("vue-class-store__noDepFix")
117+
const noDepTarget = reactive({})

tests/shared_common.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {assert, expect} from 'chai';
22
import {computed} from "vue";
3-
import {spy} from "./test_utils";
3+
import {bumpGlobalVersion, spy} from "./test_utils";
44

55
/**
66
* Shared between `createStore`, `@VueStore`, and `extends Reactive`
@@ -61,6 +61,7 @@ export function testCommon(
6161
})
6262

6363
cachedComputed.value // <- constructs first value
64+
bumpGlobalVersion()
6465
cachedComputed.value // <- should be cached
6566
expect(constructSpy).to.be.called.once
6667
});
@@ -83,6 +84,7 @@ export function testCommon(
8384
})
8485

8586
cachedComputed.value // <- constructs first value
87+
bumpGlobalVersion()
8688
cachedComputed.value // <- should be cached
8789
expect(constructSpy).to.be.called.once
8890
});
@@ -106,6 +108,7 @@ export function testCommon(
106108
})
107109

108110
cachedComputed.value // <- constructs first value
111+
bumpGlobalVersion()
109112
cachedComputed.value // <- should be cached
110113
expect(constructSpy).to.be.called.once
111114
});
@@ -128,6 +131,7 @@ export function testCommon(
128131
})
129132

130133
cachedComputed.value // <- constructs first value
134+
bumpGlobalVersion()
131135
cachedComputed.value // <- won't be cached
132136
expect(constructSpy).to.be.called.exactly(2)
133137
});
@@ -158,6 +162,7 @@ export function testCommon(
158162
})
159163

160164
cachedComputed.value // <- constructs first value
165+
bumpGlobalVersion()
161166
cachedComputed.value // <- should be cached
162167
expect(constructSpy).to.be.called.once
163168
});
@@ -183,6 +188,7 @@ export function testCommon(
183188
})
184189

185190
cachedComputed.value // <- constructs first value
191+
bumpGlobalVersion()
186192
cachedComputed.value // <- should be cached
187193
expect(constructSpy).to.be.called.once
188194
});
@@ -206,6 +212,7 @@ export function testCommon(
206212
})
207213

208214
cachedComputed.value // <- constructs first value
215+
bumpGlobalVersion()
209216
cachedComputed.value // <- won't be cached
210217
expect(constructSpy).to.be.called.exactly(2)
211218
});
@@ -236,6 +243,7 @@ export function testCommon(
236243
})
237244

238245
cachedComputed.value // <- constructs first value
246+
bumpGlobalVersion()
239247
cachedComputed.value // <- should be cached
240248
expect(constructSpy).to.be.called.once
241249
});
@@ -268,6 +276,7 @@ export function testCommon(
268276
})
269277

270278
cachedComputed.value // <- constructs first value
279+
bumpGlobalVersion()
271280
cachedComputed.value // <- should be cached
272281
expect(constructSpy).to.be.called.once
273282
});

tests/test_utils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import chai from 'chai';
22
import {default as spiesPlugin} from 'chai-spies';
3-
import {watch, WatchSource} from "vue";
3+
import {ref, watch, WatchSource} from "vue";
44

55
chai.use(spiesPlugin)
66

@@ -114,3 +114,11 @@ export async function testGC<V>(
114114
}
115115
return ratio > threshold
116116
}
117+
118+
/**
119+
* Computed values won't check if they're dirty when the global version is unchanged, so this will bump it to ensure the
120+
* dirty check actually runs.
121+
*/
122+
export function bumpGlobalVersion() {
123+
ref(0).value++
124+
}

tests/vuestore_extends.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {assert, expect} from 'chai';
22
import VueStore from '../src';
33
import Vue, {computed, nextTick, reactive, watch} from "vue";
4-
import {spy, SpySet} from "./test_utils";
4+
import {bumpGlobalVersion, spy, SpySet} from "./test_utils";
55
import {testWatches} from "./shared_watches";
66
import {testProperties} from "./shared_properties";
77
import {testPrivateMembers} from "./shared_private_members";
@@ -125,6 +125,7 @@ describe("@VueStore + extends VueStore", () => {
125125
})
126126

127127
cachedComputed.value // <- constructs first value
128+
bumpGlobalVersion()
128129
cachedComputed.value // <- should be cached
129130
expect(constructSpy).to.be.called.once
130131
});
@@ -147,6 +148,7 @@ describe("@VueStore + extends VueStore", () => {
147148
})
148149

149150
cachedComputed.value // <- constructs first value
151+
bumpGlobalVersion()
150152
cachedComputed.value // <- won't be cached
151153
expect(constructSpy).to.be.called.exactly(2)
152154
});
@@ -177,6 +179,7 @@ describe("@VueStore + extends VueStore", () => {
177179
})
178180

179181
cachedComputed.value // <- constructs first value
182+
bumpGlobalVersion()
180183
cachedComputed.value // <- should be cached
181184
expect(constructSpy).to.be.called.once
182185
});

0 commit comments

Comments
 (0)