Open queckezz opened 9 years ago
This is as "inefficient" as dirty-checking or virtual DOM diffing. In most cases it's fast enough. See this dbmon benchmark which is replacing the entire array with brand new data: http://vuejs.github.io/js-repaint-perfs/vue/
But this is just to make the point that if you want to adopt an "replacing entire state with immutable copies" approach, it's feasible. Obviously it's more efficient and straightforward to manage state in a mutable fashion in Vue, and here's an example: https://github.com/vuejs/vuex/tree/master/example
Hi @queckezz
At Optimizely our entire app is written in VueJS. With an app our size (hundreds of components) state management was a tough problem from the very start. Our solution was to build a flux like solution, NuclearJS. In fact one of the inspirations of Redux is NuclearJS.
Much like Redux NuclearJS keeps all app state in a single centralized "store", however there is large difference in how app state changes are propagated to the UI layer. Having written NuclearJS to work and be performant with VueJS we choose not to lean on virtual dom diffing and instead use the dispatch cycle + immutability to determine what changed and the bind component directly to pieces of state through Getters
Here is some example code of how we use NuclearJS with Vue
First off we extend the standard reactor
with the method bindVueValues
var Nuclear = require('nuclear-js');
class VueReactor extends Nuclear.Reactor {
constructor(config) {
super(config)
}
/**
* Binds a VM property to some value computed from one or more flux
* stores. This value will always stay in sync
*
* @param {Vue} vm the Vue view model
* @param {Array.<string, Getter>} bindings
*/
bindVueValues(vm, dataBindings) {
if (!vm.__fluxUnwatchFns__) {
vm.__fluxUnwatchFns__ = [];
vm.$on('hook:beforeDestroy', function() {
while (vm.__fluxUnwatchFns__.length > 0) {
vm.__fluxUnwatchFns__.shift()();
}
});
}
_.each(dataBindings, function(getter, prop) {
// get the initial value
vm.$set(prop, this.evaluateToJS(getter));
var unwatchFn = this.observe(getter, function(value) {
vm.$set(prop, Nuclear.toJS(value));
});
vm.__fluxUnwatchFns__.push(unwatchFn);
}.bind(this));
}
}
In a Vue component we use the bindVueValues
method in either created
or ready
hook, which will always keep some Vue VM property in sync with some part of the app state. NuclearJS is intelligent enough to efficiently to know whenever any piece (or subsection) of App State changes and only notify the concerned parties (and it does this incredibly efficiently).
With an application our size there is no way we could pass a single app state map down through the component hierarchy, and not just because of performance.
Passing a mutable value throughout the component tree doesn't protect you against accidental mutations that any child view model may want to do. And to be honest we've found the behavior of v-with
to be a bit unpredictable, as its a little less straightforward than props
in react due to being in the template layer.
Here is an example of one of our components
var flux = require('optly/flux2');
var Router = require('optly/sandbox/router');
var Nav = require('optly/modules/p13n/nav');
var UrlHelper = require('optly/services/router');
var CurrentProject = require('optly/modules/current_project');
var Permissions = require('optly/modules/permissions');
module.exports = {
componentId: 'p13n-audiences-home',
replace: true,
template: require('./audiences.html'),
components: {
'audience-actions': require('./audience_dashboard/audience_actions'),
'audience-table': require('./audience_dashboard/audience_table'),
'audience-explorer': require('bundles/audience_explorer/components/audience_explorer'),
'interesting-audiences': require('./interesting_audiences/interesting_audiences'),
},
data: {
AudienceTabs: Nav.enums.AudienceTabs,
},
computed: {
canSeeTabs: function() {
return (
this.canUseAudienceExplorer ||
this.canUseInterestingAudiences
);
},
interestingAudiencesUrl: function() {
return UrlHelper.p13nInterestingAudiences(this.currentProjectId);
},
audienceDashboardUrl: function() {
return UrlHelper.p13nAudiencesHome(this.currentProjectId);
},
audienceExplorerUrl: function() {
return UrlHelper.audienceExplorer(this.currentProjectId);
},
},
created: function() {
flux.bindVueValues(this, {
currentProjectId: CurrentProject.getters.id,
activeTab: Nav.getters.activeTab(Nav.enums.NavItems.AUDIENCES),
canUseAudienceExplorer: [
CurrentProject.getters.project,
Permissions.fns.canUseAudienceExplorer,
],
canUseInterestingAudiences: [
CurrentProject.getters.project,
Permissions.fns.canUseInterestingAudiences,
],
});
},
};
I hope this helps. If there is more interest in others using NuclearJS with VueJS i'd be happy to provide more documentation / tooling. We currently have https://github.com/jordangarcia/nuclear-vue-mixin/ but its a bit out of date.
@jordangarcia Have you seen Vuex? Is there a reason you chose not to use it?
@LegalRadius two words .. mutable state, the whole idea behind redux is to have an immutable state that allows you to quickly move through the time travel tools and ensure that you're never corrupting your state.
In https://github.com/yyx990803/vue/issues/1175 they were some brief mentionings about using redux as a possible state management container.
Redux is all about immutability so on every change it emits a completely new state snapshot. Doesn't this go against the reactive approach of vue? The only way i thought about using something like redux with vue is updating the entire data top to bottom with:
But this is extremely inefficient since vue would reavaluate all bindings in the app right? Are there any recommendations how to use them together or maybe even alternatives?
There are also angular bindings for redux called ng-redux. They are manually updating the angular state and force a digest cycle through
$root.$apply()
.I think, state management is not something that gets a lot of attention in the vue.js community right now. I would love to hear some examples of how you guys manage application state. Maybe we can even scratch up some examples and add them to the official docs.