vuejs / Discussion

Vue.js discussion
166 stars 17 forks source link

state management, integrating redux with vue #362

Open queckezz opened 8 years ago

queckezz commented 8 years ago

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:

store.subscribe(() => App.$set('data', store.getState()));

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.

yyx990803 commented 8 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

jordangarcia commented 8 years ago

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.

LegalRadius commented 7 years ago

@jordangarcia Have you seen Vuex? Is there a reason you chose not to use it?

EvanBurbidge commented 6 years ago

@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.