Conversation
|
@egoist Very nice, and very simple, just two decorations One issue: I question the pattern (or anti-pattern) of adding the store to the root Vue instance. It causes of a couple of problems: a) using this.$store explicitly, or even implicitly, loses all type information. We're just 'complecting' the whole framework, to use Rich Hickey's phrase. b) I suspect that it's also a reason for the problems with Hot Reloading, just a hunch. If folks really want that pattern (or anti-pattern), then the In summary: very nice pattern, but make the Inject/Provide of the store explicit so that the store can be typed without changing the rest of Vue. David |
|
I just added |
|
Not bad, I like inspirational and simplistic tuex (https://github.com/Raiondesu/Tuex) way, are the decorators really needed? |
|
we don't need decorators at all if we don't use the class based API 😄 btw here's also a import { Store, decorate, computed } from 'zerotwo'
class CounterStore extends Store {
get doubleCount() {
return this.state.count * 2
}
}
decorate(CounterStore, {
doubleCount: computed
}) |
|
I have given this a bit more thought. Here is my proposal: a) Introduce a single new concept, a Store, using either b) Drop the c) Drop actions, mutations, getters, mapgetters, mapsetters, etc, and just use d) Don't inject store into a root Vue instance, rather use e) (best I can tell, at the end of the day, the most valuabe thing that Vuex adds to a regular Vue component is the ability to plug into devtools, capture state change events, time-travel etc. no?). f) Optionally you could decorate nested attributes and/or methods in the store so that they can be Injected/Provided individually. So, from a developer perspective, it would look like this:
// store.ts:
@store
@component
export class MyStore extends Vue {
a : number
get double() { return this.a * 2}
set mutate_a(n:number) {this.a = n}
}alternatively, if you don't like decorators, a VueStore that extends Vue could be used, export class MyStore extends VueStore{}
<template>
<div>App</div>
<template>
<script lang="ts">
import Vue from 'vue'
import {Component} from 'vue-property-decorator'
// root component:
@Component
export default class App extends Vue {
@Provide store:MyStore; // this store will be available in any children
}
</script>
<template>
<div>Child</div>
<template>
<script lang="ts">
import Vue from 'vue'
import {Component} from 'vue-property-decorator'
// child component:
@Component
export default class Child extends Vue {
@Inject store:MyStore; // the store provided by the root
}
</script>
// with modules:
@store
export class MyStore {
module_a: ModuleA
module_b: ModuleB
}
Benefits: a) no need to invent new concepts, such as actions, mutations, getters, state, namespaces, mapgetters, mapmutations, ActionContext, GetterTree, MutatorTree, etc, etc. just leverage the existing getters/setters/modules already provided by Typescript (and JS) ! b) you'd get full type information for free from the injected instance (!!) c) you'd be able to manage the lifecycle of the store explicitly. This would make it easier to work with 3rd party components like AgGrid, which manage their own internal state, using d) You'd be able to swap in/out other storage systems (RxJs, MobX) much more easily, using e) No need to use strings anywhere, no need for complicated mapping of mutators and getters. f) no need for any boilerplate, just one new decorator g) No need to create and manage a separate module namespace system (why do we need that again?) Why treat these as different from any other modules in the project? Regarding implementation:
Just some initial thoughts, I'll try some of this out when I have a few spare cycles. David |
|
Good points, way you really want to call mutations and actions is something like |
I gave a couple reasons above: a) Typing:
And given that we are talking about the state of the application, that's the one part where you really need typing information! Whereas: btw - I have not seen any downside to importing the store into each Vue child instance, even without Provide/Inject. b) putting the store instance in the root Vue instance complicates the lifecycle, especially with 3rd party components and hot-reloading. eg, if you use a component like AgGridVue (arguably the best datatable for HTML5), then you have to figure out how to deal with it's livecycle (it is maintaining it's own state, using beforeMount()), especially with hot-reloading otherwise you end up with beauties like this:
btw, 'getters` in that error could be 'actions', 'mapgetters', various hooks, etc. Additionally because these are lifecycle bugs in an asynchronous runtime, it's pretty hard to reproduce, so if you look through the vuex issue list you'll see that most of the time the team just closes these issues because they can't be reproduced. here is an example: In the case of the AgGridVue example, presumably the combination of: hot-reloading, vue instance lifecycle management, and 3rd party state management are out of sync. So, to debug, you have to understand Vue internals, Webpack HMR internals, and AgGridVue internals, not easy. By using @Provide/@Inject you have hooks to explicitly handle those cases (and to diagnose them, just using devtools). c) You'd be able to swap in and out other state management solutions (eg RxJS, MobX, etc). d) You'd have the option to separate your stores altogether. (BTW I like the idea of a single store, so I am NOT advocating multiple stores in a single application, and current devtools wouldn't be able to handle that anyway, BUT, there are probably edge cases where that's useful and preferable to a modular single store, again, I'm not advocating that, just pointing out the decoupling the model from the view, and adding a level of indirection, open up additional options to handle edge cases. e.g. you might want to add options on a per-component basis, eg:
So, now let me ask the inverse question: What is the benefit of coupling the store to the view with this.$store ? Dave |
Well thats quite straightforward, since you have some variables in store that are defining look and feel of your app, like date formats, culture etc, its quite clear that most of the components would need something from store. Therefore writing every time provide inject seems as lots of repetitive work and hassle (write less do more principle - i use it a lot), it makes sense to be some kind of lazy loaded variable.
Im using typescript (I was playing with babel and flow also, but ts seems to offer less configuring.) and there if you make a small trick like somewhere in your base class if you assigned typed store into $store, (alternatively you can create typed computed prop) like this b,c,d |
|
@jack85 I agree re need for examples, so I'll put up an example of using Vuex and Typescript in a way that gives great typing, without putting store in a Vue root, without using provide/inject, without using vuex-class, without using strings, without using mapgetters or mapsetters, and without using your trick of assigning $store to a variable (I'm not a fan of that trick, even if it worked in Webstorm). |
|
Ok, great. |
How you would use it now:
store.js:App.vue:decoratefunction that lets you use decorators without ES decorator.actiondecorator so that we can actually track actions.