Skip to content

Passing data to Components is overly complicated - in common, simple examples #1987

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

Closed
FEA5T opened this issue Dec 8, 2015 · 15 comments
Closed

Comments

@FEA5T
Copy link

FEA5T commented Dec 8, 2015

Setup

Vue.component('bars', {
    template: '#bars-template', // is this even necessary? just pickup all "x/template" 's automatically and infer the name from the id?
})
<script type="x/template" id="bars-template">
    <svg>
        <g v-for="pt in pts">
            <rect x="{{pt.x}}" y="{{pt.y}}" width="{{z}}" height="{{z}}"></rect>
        </g>
    </svg>
</script>

Instance

var d = {
    pts: [{x: 1, y:2}, {x: 3, y:4}],
    z: 2
}

var v = new Vue({
    el: "#graph", // this should pass the data into all child templates automatically
    data: d
})

Why do I have to pass data down in my html elem? Why doesn't it have access to the Vue instance that it is a child of like normal html would?

<div id="graph">
    <bars :pts="pts"></bars>
</div>

So that I can do this:

<div id="graph">
    <bars></bars>
</div>
@yyx990803
Copy link
Member

Let's say parent components pass all its data implicitly into all children. Now all your child components implicitly make use of these parent scope variables. Later on:

  1. You look at a deeply nested child component. It uses this mysterious property which you have no idea where it is inherited from in the parent chain. And because there are no hints in the template (where the actual composition happens), you have to check the implementation details of every component in the chain to figure out which one of them introduced this property into the scope.
  2. You want to extract out a child component out for reuse, but it makes use of inherited properties which makes it tightly coupled to its parent components. It's so much work to refactor everything to make it reusable so you just give up.

Because <bar> is a component, it may get used in different context, by different parents. The explicit data passing makes the individual component decoupled from its usage context, makes it easier to reason about and maintain, and makes the component reusable if necessary.

@FEA5T
Copy link
Author

FEA5T commented Dec 8, 2015

First of all - thank you for the response.

_Regarding #1 -_ closures could be applied here as well to grab the closest vue instance. _Yes,_ check the component chain. Brilliant.

Then you could reference the properties of the vue instance directly. In my example above: pts and z.

_Regarding #2 -_ Child component is expecting x, y, and z, it is tightly coupled to a source of this structure anyways, regardless of how the data got there or what it is named...therefore:

Explicit data passing is only required when part of parent collections are named differently but have the same internal structure (x, y, z) - an unusual design. And in this scenario, you could still pass data explicitly. Win-win.

I would also like to note that I was forced to abandon templates and inject templates with jquery because _I actually just could not get it to work due to syntax._

I wanted to pass the entire 'd' object into my template ...just couldn't figure it out from the documentation.

_Would be awesome if this worked:_

<bars :d="d"></bars>

_Or even better:_ http://es6-features.org/#PropertyShorthand

<bars :d></bars>

@nirazul
Copy link

nirazul commented Dec 9, 2015

Vue.js is a sophisticated framework that uses established principles from other established libraries and framework. It's unlikely (and undesirable!) that it's throwing away its core principles due to your argumentation, so I don't really know what you're aiming for.

Why don't you provide a jsfiddle, so we can try to help you in your specific use case that didn't work out for you? :)

@FEA5T
Copy link
Author

FEA5T commented Dec 9, 2015

Sure.

https://jsfiddle.net/2rv60f4q/

@yyx990803
Copy link
Member

Here are a few things that needed to be fixed:

  1. You are passing d directly to the instance as root data. Instead you should be passing it in as data: { d: d };
  2. Your bars component did not declare the props it expects. Add props: ['d'] to its definition;
  3. You cannot use {{}} syntax for SVG attributes (see errors in the console). Use v-bind:x or shorthand :x instead.

Working fiddle: https://jsfiddle.net/2rv60f4q/2/

@FEA5T
Copy link
Author

FEA5T commented Dec 10, 2015

Evan - thanks. #3 is probably not your fault and not a big deal...

Regarding #2:

Is there any way to make the template system transparent (optionally)? To just pass through all data as props automatically all the way down through the component when:

1? The data is declared data: d
2? As some Vue parameter option?

Compare this component-less example to your fiddle:

Component-less: https://jsfiddle.net/p98serxm/
Component: https://jsfiddle.net/2rv60f4q/2/

Look how simple it is! Notice how I don't need these redundancies:

  1. Redundant data syntax data: { d: d }
  2. Props props: ['d']
  3. Html parameters <bars :d="d">

Why I think this is important:

When I first discovered Vue I was in shock, because of the simplicity of its design. Everything is _meticulously_ simple (without sacrificing any functionality). It literally could not be any cleaner if you tried - and that's beautiful.

However:

Doing in a component what a standard Vue instance can do requires 2 extra declarations and 1 extra abstraction in the way I pass data. That's not simple. I know there is great abstraction here - and reason for it - but often we don't need it - and Vue usually isn't the right place to do these abstraction (my opinion). Many times I just wan't the template to function in the exact way that the Vue instance functions - as if there was no template at all...which brings me to #1.

Regarding #1:

I don't ever pass data like this:

new Vue({
    el: "#id",
    data: {
        a: a,
        b: b,
        c: c,
        d: d
    }
})

In my opinion it shouldn't even be Vue's job to worry about this. I let Vue work on complex objects and package, name, alias the object however I want first.

var j = {
    a: a,
    b: b,
    c: c,
    d: d
}

new Vue({
    el: "#id",
    data: j
})

Root access: Obviously I need to be able to access the root data in props and the html. Not letting me access the root object in props and subsequently the html is doing a major disservice to your component system - because I have to use an expanded syntax.

Why: This combined with your props system makes the component system significantly more complicated than it needs to be - which goes against the reasons I chose Vue in the first place.

Solution: I am going to continue to use Vue but to be honest it is cleaner for me to inject that html manually and not have to worry about passing data around in the component system.

Please consider if a feature like this is something you think would benefit your framework in the future.

Thanks for the help!

@yyx990803
Copy link
Member

Component is the boundary for simplicity. The whole point of components is so that you break your code into logically isolated units. If you feel it's redundant to pass data then you are probably using components prematurely when you don't actually need it. Remember Vue is not just for simple stuff. When you focus on "how simple it is", you probably don't realize the maintainability issues this will give rise to in large applications.

Re how to pass data: you are missing the point. The point is the data object you pass to a Vue instance is just a container - Vue needs to know the property name in order to reference to them in templates. In the case of

var d = {
  a: 123
}

new Vue({
  el: '#id',
  data: d
})

There's no way Vue can tell the original object has a variable name of d. You can only access a in your templates, because that is the top level property the Vue instance ever sees.

@FEA5T
Copy link
Author

FEA5T commented Dec 10, 2015

"Vue needs to know the property name in order to reference to them in templates."

Yes a. But it shouldn't need to know the parent object name! d

"There's no way Vue can tell the original object has a variable name of d. You can only access a in your templates, because that is the top level property the Vue instance ever sees."

But I don't need to know/care what the variable name is d. The only reason I have to put the name of the variable here is because passing data into components requires it (which I wouldn't have to do if there was an option to pass all data automatically). I only need access to a. If I could pass into components <bars :d="*"></bars> where * is the root then this issue would be resolved and it would be on to the next issue.

"Remember Vue is not just for simple stuff. "

Of course not, Vue is great at making complicated stuff simple - that's the best thing about Vue.

I am not saying that Vue should pass all data down automatically, I am just saying that I should have the option to do that.

@simplesmiler
Copy link
Member

@FEA5T

  1. You can pass the whole $data (but you can not assign the root of the component data).
  2. You can have a single root property in your root instance data, and have all components accept the root and pass the root to the subcomponents.
  3. You can make the data global, and observe it from every component.

All of the above options reduce reusability of your components to some extent.
Demo: http://jsfiddle.net/simplesmiler/8bynp8xn/

@FEA5T
Copy link
Author

FEA5T commented Dec 10, 2015

@simplesmiler
@yyx990803

Solutions:

abcd: http://jsfiddle.net/rfn3hxrc/3/
bar graph: https://jsfiddle.net/2rv60f4q/3/

Html

<div id="demo"></div>


<script type="x/template" id="demo-template">
    <span>{{a}} and {{b}}</span>
</script>

JS

var d = {
    a: 'Freindship',
    b: 'Magic'
};

new Vue({
    el: '#demo',
    data: d,
    template: '#demo-template',
    replace: false
});

By using the template system I can access all the data.

"you are missing the point"
"you are probably using components prematurely when you don't actually need it"

^ nail on the head

@jochantrelle
Copy link

Sorry late chimer... I see what FEA5T is saying... what none of you are saying though is... the loss of DRY by repeating data declarations props etc... is a small violation considering the benefits, modularity, re-usability etc. I think FEA5T gets it, however, this rant may just be so the rest of us can admit to our awe in FEA5T's brilliance. So I will say it, You FEA5T are indeed smarter than the teams of people developing frameworks, Vue included. May we get your resume?

@breezewish
Copy link

breezewish commented Feb 20, 2017

How to pass bulk of events elegantly? Let's say, to build a vue component called <v-button>: event listeners and properties need to be passed explicitly when using templates otherwise there is no easy way to bind event listeners:

<template>
    <button @mouseover="handleMouseOver" @mousemove="handleMouseMove" @mousedown="handleMouseDown" @mouseup="handleMouseUp" @keydown="handleKeyDown" .....></button>
</template>

Besides, deriving the <v-button> to a child component (e.g. <v-ghost-button>) is difficult. Repeating those properties and event handlers again is a complete violation of DRY:

<template>
    <v-button @mouseover="handleMouseOver" @mousemove="handleMouseMove" @mousedown="handleMouseDown" @mouseup="handleMouseUp" @keydown="handleKeyDown" .....></v-button>
</template>

Now, it seems that the only way to implement <v-ghost-button> is to merge its functionality into <v-button>.. Then it is a violation of KISS :(

@rpbarbati
Copy link

Yo FEA5T - try this solution. If I interpreted your desires correctly, you want a solution that has all the following traits...

Creates no dependencies on parent components.
Creates no dependencies on child components.
Does not require passing any data from component to component.
Does not require you to receive any data from any other component.
Does not even require any components at all.

Simple solution - does not even require vue. Here it is...

Create a data model in its own .js and export it. Import this data model in whatever components you want to use it. Use it.

The other answer is a question - why are you using vue? Just go back to regular javascript, declare a and b in page scope and access them exactly like you want.

@sirlancelot
Copy link

sirlancelot commented Dec 14, 2017

This issue is over two-year old, has been closed for quite some time, and doesn't even apply to the current state of Vue...

@FEA5T
Copy link
Author

FEA5T commented Jan 17, 2018

@simplesmiler Thanks for the excellent advice
@yyx990803 Please consider these points
@sirlancelot Passing data is more relevant than ever (props, provide/inject, this.$parent, Vuex)

In revisiting this issue 2 years wiser, I think that there was a legitimate use case I presented here: passing data from a parent component to any (possibly deep) child. The reason I complained about the prop syntax is because:

Passing 5 children deep results in a ridiculous amount of code repetition.

I believe I caught this a long time ago and it has taken a long time for provide/inject to address this use case (and they still don't fully address it).

You look at a deeply nested child component. It uses this mysterious property which you have no idea where it is inherited from in the parent chain. And because there are no hints in the template (where the actual composition happens), you have to check the implementation details of every component in the chain to figure out which one of them introduced this property into the scope. - Evan You

Sound's a lot like closures in JS. This sounds great. So why not do this? Well, you said:

The whole point of components is so that you break your code into logically isolated units.

Well, turns out a component is logically isolated whether or not it is using props or closures. At runtime, the parent context must provide a required attribute:

  1. Parent component can declaratively pass the attribute as a prop
  2. Child components can access parent attributes via this.$parent
  3. Parent component can provide the arrtibute
    or my suggestion
  4. Parent component can have all data available for reference via closure

Regardless of passing method, the required attribute must exist in parent scope.

So that's not a good reason, because these methods all have the same logical isolation at runtime.

My question is, do these input's need to be declared twice in a component, both in props and in the code? Why can't props be inferred from the code, like closures in JS?

When I want declarative input, I have that: through props (parameters) .

If I want inferred input, I should have that in a convenient and familiar way, like closures provide in JS.

Why is this decision being made for me?

Ultimately I understand, performance is king, Vue prevents you from shooting your feet off, which is why Vuex (inject global state), provide/inject (selectively inject some state) and the this.$parent (dangerous walk the chain yourself) options exist to carefully limit and select what must be passed down.

Regarding @jochantrelle (don't be a jerk)

I think FEA5T gets it, however, this rant may just be so the rest of us can admit to our awe in FEA5T's brilliance.

What I now find ironic now is, my requested solution was implement (differently) with provide/inject which were introduced in Vue 2.2. Let me quote:

For each injected property, inject would traverse the parent chain until first provider is reached.

With provide/inject, you can provide data to distant descendant and that allows to create amazing functionalities.

You FEA5T are indeed smarter than the teams of people developing frameworks, Vue included. May we get your resume?

Well:

If you are familiar with React, provide/inject is very similar to React’s context feature, or ng-controller.

Looks like I came out on the right side of this.

Regarding decoupling:

The explicit data passing makes the individual component decoupled from its usage context, makes it easier to reason about and maintain, and makes the component reusable if necessary. - Evan You

No it doesn't. If a component need's a prop myAttr, where does that prop come from at runtime? It comes from the context that the parent component has at runtime.

The real difference is in declarative vs implied, not decoupling

Well, sometimes, we want declarative props, and sometimes we don't. Why doesn't Vue give dev's "sharp knives" to let them use the right tool for the job? Like JS does...

I would even further argue that provide and inject are my solution, but with unfortunately even tighter coupling of parents to children than I wanted, because the parent has to explicitly declare what it is providing. In a closure scenario, the child is free to reference parent data without the parent knowing. This is a cleaner, less dependent (and admittedly poorly performing) solution. The key here is that a child says "I need this.myAttr", and if I don't have it, walk the chain until you find it.

Regarding Modularity/Re-usability:

the loss of DRY by repeating data declarations props etc... is a small violation considering the benefits, modularity, re-usability

Except modularity and re-usability aren't lost.

The dependency on this.myAttr from somewhere in the component chain is there, whether that dependency is expressed using Vuex, explicitly with props, this.$parent, inject, etc, the dependency on myAttr still exists! The component is still re-usable no matter which approach is used. The child component still ideologically requires this.myAttr whether it is passed it explicitly by the parent with provide, or not.

The only reason not to do this is performance, and dev's should have the option:

new Vue({ passScope: true, //default: false inheritScope: 3 //default: 0, inherits 3 parents of scope });

So that all my components don't need to start like this:

Vue.component('myComp', { data: function() { return this.$parent; } });

Solution: Better provide/inject

Any attribute that a component uses that isn't a func, data attr, computed, etc should automatically be added to the props list at runtime, and if that prop is not passed by a parent and inheritScope: true then it should check the parent scope chain, and if nothing yet is still found, the default prop value should be used, and if there is no default, it should silently fail.

Alternatively, a more powerful inject and provide should be avaliable.

provideAll: true injects $data to root of all descendants
provideDeep: ['x: obj1'] injects obj1 to all descendants at this.x, like vuex does for this.$store.
injectAll: true inherits all parent attributes into current instance
injectDeep: ['x'] will walk the chain and inject the first x, regardless of what is provided by parents
injectTo: 3 inherits 3 levels of parent attributes into current instance

or just a better provide/inject syntax:

inject: [{attr: 'all'}}
inject: [{attr: 'x', depth: 3}}
inject: [{attr: 'all', depth: 3}}
inject: [{attr: 'all', depth: *}}```

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

No branches or pull requests

8 participants