fix reactivity with assigning item in each block (#4945)

pull/4994/head
Tan Li Hau 4 years ago committed by GitHub
parent bf6c74fb17
commit 0f43ad40ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,6 +3,7 @@
## Unreleased
* Fix `bind:this` to the value of an `{#each}` block ([#4517](https://github.com/sveltejs/svelte/issues/4517))
* Fix reactivity when assigning to contextual `{#each}` variable ([#4574](https://github.com/sveltejs/svelte/issues/4574), [#4744](https://github.com/sveltejs/svelte/issues/4744))
* Fix binding to contextual `{#each}` values that shadow outer names ([#4757](https://github.com/sveltejs/svelte/issues/4757))
## 3.23.0

@ -14,6 +14,8 @@ import { invalidate } from '../../render_dom/invalidate';
import { Node, FunctionExpression, Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces';
import { is_reserved_keyword } from '../../utils/reserved_keywords';
import replace_object from '../../utils/replace_object';
import EachBlock from '../EachBlock';
type Owner = Wrapper | TemplateNode;
@ -134,6 +136,8 @@ export default class Expression {
const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
});
const each_block = template_scope.get_owner(name);
(each_block as EachBlock).has_binding = true;
} else {
component.add_reference(name);
@ -311,6 +315,10 @@ export default class Expression {
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
const object_name = get_object(assignee).name;
if (scope.has(object_name)) return;
// normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones
@ -327,6 +335,23 @@ export default class Expression {
}
});
const context = block.bindings.get(object_name);
if (context) {
// for `{#each array as item}`
// replace `item = 1` to `each_array[each_index] = 1`, this allow us to mutate the array
// rather than mutating the local `item` variable
const { snippet, object, property } = context;
const replaced: any = replace_object(assignee, snippet);
if (node.type === 'AssignmentExpression') {
node.left = replaced;
} else {
node.argument = replaced;
}
contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
}
this.replace(invalidate(block.renderer, scope, node, traced));
}
}

@ -0,0 +1,20 @@
export default {
html: `
<span class="content">foo</span>
<button>Test</button>
`,
async test({ assert, component, target, window }) {
const button = target.querySelector("button");
const clickEvent = new window.MouseEvent("click");
await button.dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<span class="content">bar</span>
<button>Test</button>
`
);
},
};

@ -0,0 +1,12 @@
<script>
let obj = {
prop: "foo"
};
let arr = [obj]
</script>
{#each arr as o}
<span class="content">{o.prop}</span>
<button on:click={ () => o = { ...o, prop: "bar" } }>Test</button>
{/each}

@ -0,0 +1,97 @@
export default {
html: `
<button>Add</button>
<span class="content">1</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`,
async test({ assert, component, target, window }) {
let [incrementBtn, ...buttons] = target.querySelectorAll("button");
const clickEvent = new window.MouseEvent("click");
await buttons[0].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`
);
await buttons[0].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`
);
await buttons[2].dispatchEvent(clickEvent);
await buttons[2].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
`
);
await incrementBtn.dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
<span class="content">4</span>
<button>Test</button>
`
);
[incrementBtn, ...buttons] = target.querySelectorAll("button");
await buttons[3].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
<span class="content">8</span>
<button>Test</button>
`
);
},
};

@ -0,0 +1,13 @@
<script>
let obj = {
prop: "foo"
};
let arr = [1, 2, 3]
</script>
<button on:click={() => arr = [...arr, arr.length + 1]}>Add</button>
{#each arr as o}
<span class="content">{o}</span>
<button on:click={() => { o *= 2; }}>Test</button>
{/each}
Loading…
Cancel
Save