Skip to content

Commit 5d0f430

Browse files
committed
mark reactive declarations dependencies as mutated if the declaration is mutated
1 parent 1644f20 commit 5d0f430

File tree

3 files changed

+171
-1
lines changed

3 files changed

+171
-1
lines changed

src/compiler/compile/Component.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1166,13 +1166,28 @@ export default class Component {
11661166
if (node.type === 'LabeledStatement' && node.label.name === '$') {
11671167
this.reactive_declaration_nodes.add(node);
11681168

1169-
const assignees = new Set();
1169+
const assignees = new Set<string>();
11701170
const assignee_nodes = new Set();
11711171
const dependencies = new Set();
11721172

11731173
let scope = this.instance_scope;
11741174
const map = this.instance_scope_map;
11751175

1176+
const assignee_stack: Array<{
1177+
has_mutated_assignee: boolean,
1178+
}> = [];
1179+
1180+
const new_assignee_stack = () => assignee_stack.push({
1181+
has_mutated_assignee: false,
1182+
});
1183+
1184+
const update_assignee_stack = (name) => {
1185+
const variable = component.var_lookup.get(name);
1186+
if (variable) {
1187+
if (variable.mutated) assignee_stack[assignee_stack.length - 1].has_mutated_assignee = true;
1188+
}
1189+
};
1190+
11761191
walk(node.body, {
11771192
enter(node: Node, parent) {
11781193
if (map.has(node)) {
@@ -1182,9 +1197,13 @@ export default class Component {
11821197
if (node.type === 'AssignmentExpression') {
11831198
const left = get_object(node.left);
11841199

1200+
new_assignee_stack();
1201+
11851202
extract_identifiers(left).forEach(node => {
11861203
assignee_nodes.add(node);
11871204
assignees.add(node.name);
1205+
1206+
update_assignee_stack(node.name);
11881207
});
11891208

11901209
if (node.operator !== '=') {
@@ -1193,13 +1212,21 @@ export default class Component {
11931212
} else if (node.type === 'UpdateExpression') {
11941213
const identifier = get_object(node.argument);
11951214
assignees.add(identifier.name);
1215+
1216+
new_assignee_stack();
1217+
update_assignee_stack(identifier.name);
11961218
} else if (is_reference(node as Node, parent as Node)) {
11971219
const identifier = get_object(node);
11981220
if (!assignee_nodes.has(identifier)) {
11991221
const { name } = identifier;
12001222
const owner = scope.find_owner(name);
12011223
const variable = component.var_lookup.get(name);
12021224
if (variable) variable.is_reactive_dependency = true;
1225+
1226+
if (variable && owner === component.instance_scope && assignee_stack.length) {
1227+
if (assignee_stack[assignee_stack.length - 1].has_mutated_assignee) variable.mutated = true;
1228+
}
1229+
12031230
const is_writable_or_mutated =
12041231
variable && (variable.writable || variable.mutated);
12051232
if (
@@ -1218,6 +1245,9 @@ export default class Component {
12181245
if (map.has(node)) {
12191246
scope = scope.parent;
12201247
}
1248+
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
1249+
assignee_stack.pop();
1250+
}
12211251
},
12221252
});
12231253

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
export default {
2+
html: `
3+
<div>
4+
<input type="checkbox">
5+
<input type="text">
6+
<p>one</p>
7+
</div>
8+
<div>
9+
<input type="checkbox">
10+
<input type="text">
11+
<p>two</p>
12+
</div>
13+
<div>
14+
<input type="checkbox">
15+
<input type="text">
16+
<p>three</p>
17+
</div>
18+
<p>completed 1, remaining 2, total 3</p>
19+
`,
20+
21+
ssrHtml: `
22+
<div>
23+
<input type="checkbox">
24+
<input type="text" value="one">
25+
<p>one</p>
26+
</div>
27+
<div>
28+
<input checked="" type="checkbox">
29+
<input type="text" value="two">
30+
<p>two</p>
31+
</div>
32+
<div>
33+
<input type="checkbox">
34+
<input type="text" value="three">
35+
<p>three</p>
36+
</div>
37+
<p>completed 1, remaining 2, total 3</p>
38+
`,
39+
40+
async test({ assert, component, target, window }) {
41+
function set_text(i, text) {
42+
const input = target.querySelectorAll('input[type="text"]')[i];
43+
input.value = text;
44+
input.dispatchEvent(new window.Event('input'));
45+
}
46+
47+
function set_done(i, done) {
48+
const input = target.querySelectorAll('input[type="checkbox"]')[i];
49+
input.checked = done;
50+
input.dispatchEvent(new window.Event('change'));
51+
}
52+
53+
component.filter = 'remaining';
54+
55+
assert.htmlEqual(target.innerHTML, `
56+
<div>
57+
<input type="checkbox">
58+
<input type="text">
59+
<p>one</p>
60+
</div>
61+
<div>
62+
<input type="checkbox">
63+
<input type="text">
64+
<p>three</p>
65+
</div>
66+
<p>completed 1, remaining 2, total 3</p>
67+
`);
68+
69+
await set_text(1, 'four');
70+
71+
assert.htmlEqual(target.innerHTML, `
72+
<div>
73+
<input type="checkbox">
74+
<input type="text">
75+
<p>one</p>
76+
</div>
77+
<div>
78+
<input type="checkbox">
79+
<input type="text">
80+
<p>four</p>
81+
</div>
82+
<p>completed 1, remaining 2, total 3</p>
83+
`);
84+
85+
await set_done(0, true);
86+
87+
assert.htmlEqual(target.innerHTML, `
88+
<div>
89+
<input type="checkbox">
90+
<input type="text">
91+
<p>four</p>
92+
</div>
93+
<p>completed 2, remaining 1, total 3</p>
94+
`);
95+
96+
component.filter = 'done';
97+
98+
assert.htmlEqual(target.innerHTML, `
99+
<div>
100+
<input type="checkbox">
101+
<input type="text">
102+
<p>one</p>
103+
</div>
104+
<div>
105+
<input type="checkbox">
106+
<input type="text">
107+
<p>two</p>
108+
</div>
109+
<p>completed 2, remaining 1, total 3</p>
110+
`);
111+
},
112+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script>
2+
let items = [
3+
{ done: false, text: 'one' },
4+
{ done: true, text: 'two' },
5+
{ done: false, text: 'three' }
6+
];
7+
export let filter = 'all';
8+
9+
$: done = items.filter(item => item.done);
10+
$: remaining = items.filter(item => !item.done);
11+
12+
$: filtered = (
13+
filter === 'all' ? items :
14+
filter === 'done' ? items.filter(item => item.done) :
15+
items.filter(item => !item.done)
16+
);
17+
18+
</script>
19+
20+
{#each filtered as item}
21+
<div>
22+
<input type="checkbox" bind:checked={item.done}>
23+
<input type="text" bind:value={item.text}>
24+
<p>{item.text}</p>
25+
</div>
26+
{/each}
27+
28+
<p>completed {done.length}, remaining {remaining.length}, total {items.length}</p>

0 commit comments

Comments
 (0)