-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support to computed and store for immutable structures #1164
Conversation
Adds optional performance support for apps using an immutable data structure such as redux. Adds the `immutable` boolean option for compile and an `immutable` option to store as well. When these options are used, computed will not recompute if the object has not changed. If your data structure is not immutable you should not use this as svelte cannot know if a mutation was made on objects. This PR also adds support for Dates and NaN values so computed properties will not recompute if a date has not changed or a value did not change from NaN. This closes out these issues: * sveltejs#1146 * sveltejs#1161 This is my first PR for Svelte. Any feedback would be appreciated!
Codecov Report
@@ Coverage Diff @@
## master #1164 +/- ##
==========================================
+ Coverage 91.51% 91.72% +0.21%
==========================================
Files 126 127 +1
Lines 4526 4569 +43
Branches 1462 1491 +29
==========================================
+ Hits 4142 4191 +49
+ Misses 163 159 -4
+ Partials 221 219 -2
Continue to review full report at Codecov.
|
Thank you! This is going to be great. I'm not sure we can give dates special treatment, since they're mutable objects: methods: {
forward() {
const { date } = this.get();
date.setHours(date.getHours() + 1);
this.set({ date });
}
} In that situation we can't rely on We could accommodate function differs(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} (i.e. the current function differsImmutable(a, b) {
return a != a ? b == b : a !== b;
} Am wondering if we should use the same |
Yes, the dates cannot be checked this way, you are right. I've made the changes and also added some tests. |
This supports this sveltejs/svelte#1164 PR for the command line.
I'm reworking this some more in order to support customizing on a component-by-component basis. I believe this would remove the need for a compiler option entirely. Right now I have it to the point where you can pass in immutable to the constructor options like this: var app = new App({
target: document.body,
immutable: true
}); But I feel it would be useful to also add it as a component property like: export default {
immutable: false, // or true
data: () => {
return {}
}
}; Thoughts on adding it to the definition like this? Should it be a method like with |
This allows components to opt in (or out) of using immutable data checking for greater flexibility in app design. It also removes the compiler option.
The API in the previous comment has now been implemented. There is no compiler option (which means we can close sveltejs/svelte-cli#31 assuming this is an accepted solution). I use the same option flag ( This allows individual components to use immutable when the rest of the app is not, useful for expensive computed property calculations. It also allows individual components to NOT use immutable when the rest of the app does, useful for interacting with 3rd-party libraries that might have mutable data. Future improvements might include setting |
I'm unsure what the failing check is about. Does it just need to be rerun? |
The failing test is just because codecov has detected that too few of the new lines of code are covered by tests — maybe we need to add another one or two? Making Also: not something that this PR needs to consider, but something that occurred to me: deep two-way bindings on components with <input bind:value=foo.bar> ...is basically equivalent to this: var state = this.get();
state.foo.bar = input.value;
component.set({ foo: state.foo }); // won't trigger an update, because it's the same object I think we'll probably have to just disallow those sorts of bindings if |
This will keep existing code smaller and _mostly_ only add size when using the `immutable` compiler option.
Thank you! Merged with a couple of small changes — I put the |
If we can fix the 2-way binding with the immutable flag that would be amazing. |
Also, I have a CLI PR to add the immutable option sveltejs/svelte-cli#31 |
Quick docsJotting down some documentation on how this works because I already forgot and had to look through the code to remind myself. 😊 Should probably add this to the website docs I suppose. Svelte works with mutable data and immutable data by default. In addition to the default behavior Svelte can provide performance gains if you are working with immutable data by more easily knowing when values changed and computed properties need to be recalculated. To take advantage of these performance gains, add the compiler option const app = new App({
target: document.body,
immutable: true
}); To illustrate the different between immutable comparison and mutable comparison consider the following: <!-- App.html -->
<div>{{ name }}</div>
<script>
export default {
computed: {
name: person => {
console.log('ran computed name');
return person.firstName + ' ' + person.lastName;
}
}
}
</script> // index.js
const person = { firstName: 'John', lastName: 'Smith' };
const immutableApp = new App({
target: document.body,
immutable: true,
data: { person }
});
immutableApp.set({ person });
immutableApp.set({ person });
immutableApp.set({ person });
// Result's in console:
// > ran computed name
const mutableApp = new App({
target: document.body,
immutable: false,
data: { person }
});
mutableApp.set({ person });
mutableApp.set({ person });
mutableApp.set({ person });
// Result's in console:
// > ran computed name
// > ran computed name
// > ran computed name
// > ran computed name With immutable turned on, if the object does not change, Svelte assumes the properties did not change either. With immutable turned off, Svelte must always rerun computations in case the object properties changed. If you have some data that is not immutable, you can instruct a particular component to not use the immutable comparison by defining it with the immutable flag set to false. <div>{{ name }}</div>
<script>
export default {
immutable: false,
computed: {
name(mutableInput) {
return mutableInput.firstName + ' ' + mutableInput.lastName;
}
}
};
</script> You may also only want to make a single component compare data with the immutable comparison if most of your data is mutable but you have an expensive computation that you can use immutable data with. In this case, do not pass <div>{{ name }}</div>
<script>
export default {
immutable: true,
computed: {
name(immutableInput) {
return expensiveCalculation(immutableInput);
}
}
};
</script> You can also tell the Svelte Store whether the data stored there is immutable or not. import { Store } from 'svelte/store.js';
const store = new Store({
name: 'world'
}, { immutable: true });
const app = new App({
target: document.body,
immutable: true,
store
}); |
Adds optional performance support for apps using an immutable data structure such as redux. Adds the
immutable
boolean option for compile and animmutable
option to store as well. When these options are used, computed will not recompute if the object has not changed. If your data structure is not immutable you should not use this as svelte cannot know if a mutation was made on objects.This PR also adds support for Dates and NaN values so computed properties will not recompute if a date has not changed or a value did not change from NaN.
This closes out these issues:
This is my first PR for Svelte. Any feedback would be appreciated!