mark reactive declarations dependencies as mutated if the declaration is mutated

pull/5045/head
Tan Li Hau 5 years ago
parent 1644f207b1
commit 5d0f430fd4

@ -1166,13 +1166,28 @@ export default class Component {
if (node.type === 'LabeledStatement' && node.label.name === '$') { if (node.type === 'LabeledStatement' && node.label.name === '$') {
this.reactive_declaration_nodes.add(node); this.reactive_declaration_nodes.add(node);
const assignees = new Set(); const assignees = new Set<string>();
const assignee_nodes = new Set(); const assignee_nodes = new Set();
const dependencies = new Set(); const dependencies = new Set();
let scope = this.instance_scope; let scope = this.instance_scope;
const map = this.instance_scope_map; const map = this.instance_scope_map;
const assignee_stack: Array<{
has_mutated_assignee: boolean,
}> = [];
const new_assignee_stack = () => assignee_stack.push({
has_mutated_assignee: false,
});
const update_assignee_stack = (name) => {
const variable = component.var_lookup.get(name);
if (variable) {
if (variable.mutated) assignee_stack[assignee_stack.length - 1].has_mutated_assignee = true;
}
};
walk(node.body, { walk(node.body, {
enter(node: Node, parent) { enter(node: Node, parent) {
if (map.has(node)) { if (map.has(node)) {
@ -1182,9 +1197,13 @@ export default class Component {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const left = get_object(node.left); const left = get_object(node.left);
new_assignee_stack();
extract_identifiers(left).forEach(node => { extract_identifiers(left).forEach(node => {
assignee_nodes.add(node); assignee_nodes.add(node);
assignees.add(node.name); assignees.add(node.name);
update_assignee_stack(node.name);
}); });
if (node.operator !== '=') { if (node.operator !== '=') {
@ -1193,6 +1212,9 @@ export default class Component {
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const identifier = get_object(node.argument); const identifier = get_object(node.argument);
assignees.add(identifier.name); assignees.add(identifier.name);
new_assignee_stack();
update_assignee_stack(identifier.name);
} else if (is_reference(node as Node, parent as Node)) { } else if (is_reference(node as Node, parent as Node)) {
const identifier = get_object(node); const identifier = get_object(node);
if (!assignee_nodes.has(identifier)) { if (!assignee_nodes.has(identifier)) {
@ -1200,6 +1222,11 @@ export default class Component {
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable.is_reactive_dependency = true; if (variable) variable.is_reactive_dependency = true;
if (variable && owner === component.instance_scope && assignee_stack.length) {
if (assignee_stack[assignee_stack.length - 1].has_mutated_assignee) variable.mutated = true;
}
const is_writable_or_mutated = const is_writable_or_mutated =
variable && (variable.writable || variable.mutated); variable && (variable.writable || variable.mutated);
if ( if (
@ -1218,6 +1245,9 @@ export default class Component {
if (map.has(node)) { if (map.has(node)) {
scope = scope.parent; scope = scope.parent;
} }
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
assignee_stack.pop();
}
}, },
}); });

@ -0,0 +1,112 @@
export default {
html: `
<div>
<input type="checkbox">
<input type="text">
<p>one</p>
</div>
<div>
<input type="checkbox">
<input type="text">
<p>two</p>
</div>
<div>
<input type="checkbox">
<input type="text">
<p>three</p>
</div>
<p>completed 1, remaining 2, total 3</p>
`,
ssrHtml: `
<div>
<input type="checkbox">
<input type="text" value="one">
<p>one</p>
</div>
<div>
<input checked="" type="checkbox">
<input type="text" value="two">
<p>two</p>
</div>
<div>
<input type="checkbox">
<input type="text" value="three">
<p>three</p>
</div>
<p>completed 1, remaining 2, total 3</p>
`,
async test({ assert, component, target, window }) {
function set_text(i, text) {
const input = target.querySelectorAll('input[type="text"]')[i];
input.value = text;
input.dispatchEvent(new window.Event('input'));
}
function set_done(i, done) {
const input = target.querySelectorAll('input[type="checkbox"]')[i];
input.checked = done;
input.dispatchEvent(new window.Event('change'));
}
component.filter = 'remaining';
assert.htmlEqual(target.innerHTML, `
<div>
<input type="checkbox">
<input type="text">
<p>one</p>
</div>
<div>
<input type="checkbox">
<input type="text">
<p>three</p>
</div>
<p>completed 1, remaining 2, total 3</p>
`);
await set_text(1, 'four');
assert.htmlEqual(target.innerHTML, `
<div>
<input type="checkbox">
<input type="text">
<p>one</p>
</div>
<div>
<input type="checkbox">
<input type="text">
<p>four</p>
</div>
<p>completed 1, remaining 2, total 3</p>
`);
await set_done(0, true);
assert.htmlEqual(target.innerHTML, `
<div>
<input type="checkbox">
<input type="text">
<p>four</p>
</div>
<p>completed 2, remaining 1, total 3</p>
`);
component.filter = 'done';
assert.htmlEqual(target.innerHTML, `
<div>
<input type="checkbox">
<input type="text">
<p>one</p>
</div>
<div>
<input type="checkbox">
<input type="text">
<p>two</p>
</div>
<p>completed 2, remaining 1, total 3</p>
`);
},
};

@ -0,0 +1,28 @@
<script>
let items = [
{ done: false, text: 'one' },
{ done: true, text: 'two' },
{ done: false, text: 'three' }
];
export let filter = 'all';
$: done = items.filter(item => item.done);
$: remaining = items.filter(item => !item.done);
$: filtered = (
filter === 'all' ? items :
filter === 'done' ? items.filter(item => item.done) :
items.filter(item => !item.done)
);
</script>
{#each filtered as item}
<div>
<input type="checkbox" bind:checked={item.done}>
<input type="text" bind:value={item.text}>
<p>{item.text}</p>
</div>
{/each}
<p>completed {done.length}, remaining {remaining.length}, total {items.length}</p>
Loading…
Cancel
Save