Skip to content
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

Ractive @state RFC #2345

Closed
wants to merge 1 commit into from
Closed

Ractive @state RFC #2345

wants to merge 1 commit into from

Conversation

martypdx
Copy link
Contributor

With @evs-chris' work on #2250 which introduced the @ractive keyword, it did most of the heavy lifting to open the door to a state mechanism that works in along side with ractive data.

how it works

  • Because having to use the prefix {{@ractive.foo}} would litter the template with extraneous noise, at parse-time, refs that use the format {{@foo}} are treated as aliases for state on the ractive instance, except for the the reserved keywords keypath|rootpath|index|key|ractive|global:

    new Ractive({
        el: 'main',
        template: `{{@foo}} {{@upper(bar)}}`,
        data: { bar: 'bar' },
        foo: 'foo',
        upper ( str ) { return str.toUpperCase(); } 
    });
  • The double @@ is an alias for @ractive.root, with the intent being global "app" state. For example, if you add a user property to the root instance, you can access {{@@user}} anywhere.

    new Ractive({
        el: 'main',
        template: `<child/>`,
        data: { content },
        user,
        components: {
            child: Ractive.extend({ template: `{{@@user}}` })
        }
    });
  • The api also supports @foo, so you can use it in computations or anywhere else you would access keypaths.

    ractive.get( '@foo' );
    ractive.set( '@bar', 'barzy' );

State behaves differently than data in the following ways:

  1. State is always bound to the ractive instance and not the context stack, which makes sense as it refers to a property of the ractive instance.
  2. As a corollary to 1., there is no lookup of state up the context stack or up the component stack. It's always on the instance.

Given those rules, when passing state between components:

  1. State can be passed into components and become data:

    <child bar='{{@foo}}'/>

    In this case, it is mapped like normal component data and bar will update as @foo updates in the parent and vice-versa.

  2. State can be set in components (from either parent state or parent data):

    <child @bar='{{@foo}}' @qux='{{bizz}}'/>

    However, when setting state on a component it is copied and not mapped. Object references would of course point to the same object, but would not be updated if changed to another instance in one and not the other. So passing state works as a starting point for the state on the component much more like a traditional js function parameter.

concerns

  1. @state exists on the instance, so it is possible to munge the ractive api, so perhaps these should be disallowed at parse time.
  2. Also, there are internally used property names like ractive.viewmodel that probably need to get changed to ractive._viewmodel or added to the disallow list.
  3. While this is all optional, and based on use cases people have shared, perhaps it is more confusion than help.

misc

An additional level of state that could be added would be using a property prefixed with @ on an existing data model. This would allow it to be associated with that data model keypath without actually writing it to that data. So it would look like: {{ someObject.@foo }}, {{ this.@selected }}, etc.

@evs-chris
Copy link
Contributor

Interesting! Here are my random thoughts thus far:

I like the shortcut syntax, and as far as comprehension goes, there is pretty good precedent from handlebars and ruby. My only immediate concern is that computations are referenced as @whatever.foo(), and I've been looking at extending a few outstanding computation issues by allowing set and update with those keypaths. Of course, there's no reason we couldn't also swap the computation prefix to # or some other illegal reference sigil. I'm thinking that setting/updating derived computation keys should probably be explicit too, though, e.g. ractive.set('@my.expr().foo', 'yep', { computed: true }) if that PR makes it. I'm not sure though.

I think this could also open an avenue to resolve some of the confusion around method events as a breaking change. The existing method events could be swapped to the @ state syntax e.g. <button on-click="@someMethod(event, etc)">Yep</button>. That would allow events to use regular refs to look up the event handler fn from data. That would be consistent with what I've seen about what most seem to infer about how method events work. It still doesn't handle the proxy event shortcut of on-click="foo:{{bar}}" vs on-click="fire('foo', bar)", but I think another sigil shortcut would help e.g. on-click="#foo(bar)" where an expression is parsed between the # and the first un-matched ( to replace the on-click="{{ foo ? 'bar' : 'baz' }}:{{bippy}},{{boppy}}" syntax with on-click="#{{ foo ? 'bar' : 'baz'}}(bippy, boppy)" or perhaps on-click="#(foo ? 'bar' : 'baz')(bippy, boppy)". Anyways, if you made it this far, that segue was mostly looking toward syntactical and conceptual unification and consistency.

As far as the bits of state that a Ractive instance holds, perhaps it would be best to move them into a _private object that swings off the instance. Object.keys is showing 27 properties on the first instance that was handy, all but about 5 of which should probably not be exposed.

I also like the idea of state paths external to the data that live in a sort of shadow data tree. I don't have much use for them personally, but it seems like it would solve some issues for a fair number of others.

@fskreuz
Copy link
Contributor

fskreuz commented Jan 14, 2016

From a zero-internal-knowledge-of-internals point of view:

  • So it's somewhat like this "Strict mode" brainstorm #2183
  • Can they be set from the parent (i.e.: <foo @somewhere="here">)?
  • Multiple instances? Are they shared or are they cloned for each instance?
    • Nested data?
  • Does that mean I can pass anything other than keypath|rootpath|index|key|ractive|global? Can I reference configuration (i.e.: {{@data}}, {{@isolated}})?
    • If so, can they be modified on runtime? Read only?
  • Is there some weird side effect when I deal with nested data?
  • It may confuse the method syntax (<foo on-click="someMethod()" /> vs <foo on-click="{{ @someMethod() }}" />) like this The many places for functions #2176.
    • They look almost the same until one realizes one needs {{ }} and it's actually a value.
    • It's like a getter-only computed prop, I like it.
    • Can I return a function from it? What happens to the function? Does it get assigned to the event?
  • In what context are the methods executed in?
  • At what phase in the lifecycle are the methods executed and in what order?

I'm 👍 , just a few clarifications that may need documentation.

@heavyk
Copy link
Contributor

heavyk commented Feb 19, 2016

holy shit, how did I miss this one?! @martypdx I really like this proposal a lot. also, you totally blew my mind with the @ractive.root thing. appears I'm gonna need to go back and change some ugly code now that I used to hack around that, thanks :)

some thoughts:

  1. I really appreciate the way that this PR changes the way I fundamentally see the interactions between components. mentally, it clears a lot of things up for me. I did not realise this was even possible.
  2. I dislike the inconsistency of @ being a shortcut for @ractive except for the cases where it's keypath|rootpath|index|key|ractive|global - that's annoying, but I totally can live with it ;/
  3. holding state like this would make it easy to turn the root component into a state machine which simplifies things like logged in/out. (eg. add a function to allow for transition between defined states - logged('in'))
  4. assuming this gets in, I think explaining this differentiation between state and data in the docs will be a clear guideline for new users to correctly build ractive apps and not mix state and data in the data (like I did), which would naturally clear up the docs quite a bit. 👍

and, a question: if I understand you correctly, we can read/modify the state of the root component, but not its data, right?

@heavyk
Copy link
Contributor

heavyk commented Feb 27, 2016

can we get others to weigh in on this PR? personally, I'm quite fond of it. I REALLY like the idea of separating data and state like this

@waynebaylor
Copy link

i guess i'll chime in with a description of the 3 phases i go through when using state in ractive.

the beginning: man it would be awesome if i could store state outside of data, that way i can do xhr stuff with data and not worry about it being littered with extraneous state properties.

the middle: i can't do the above so i guess i'll just segregate data into something like
data: { state: { ... }, realData: { ... } }

the end: crap, i actually need state information on nested objects within data, e.g. while each-ing over an array of items. well i could try to store the state in an equivalent structure but outside of data... nah too much of a hassle, i'll just jam the state into data.

so, the misc part at the end of the original comment is actually what interests me the most. and if you stick with the @ symbol, then it kind of makes sense because they're not "real" properties, they're something managed by the ractive instance like @index, @keypath, etc just with the bonus ability to write to them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants