2.0.0-rc.6 & 2.0.0-rc.4
https://jsfiddle.net/oldpig/wtvbck7v/2/
run
the rendered component should have both view and p1 in classList
only view rendered
Comment the render function and uncomment the template to see the expected result.
Hi @Centaur,
thanks for this report. I think I have know the cause, will try to fix it.
Sidnote: When providing reproductions on jsfiddle and similar services, it's better to use the non-minified versions, as that allows us to do a first round of debugging right in the fiddle without having to change anything. Thanks!
@LinusBorg Ok, I will use the non-minified version next time.
This is actually not a bug, but rather the wrong usage of render functions.
You can skip this part because it's just my deduction.
If you were curious and took a look at the compiled result, you would have found the following:
h("router-view", {"class": "view"}, [])
So, the observation is that the compiled result used {"class": "view"} rather than {attrs: {"class": "view"}}.
I guess it's enough to make an assumption, that we should write like the compiled result to bind classes.
As for the explanation, Vue has a class merging strategy when you use {class: 'foo'} in data objects for render functions, but when you use {attrs: ...}, vue assumes that you want to manipulate dom attrs by yourself, and render it as-is, so no class merging in this case. The working fiddle is here: https://jsfiddle.net/wtvbck7v/3/.
I believe this will be covered by @chrisvfritz's guide when it's complete. But for now, I can only give you my explanation.
(As a side note, this is covered in vue's jsx babel plugin docs https://github.com/vuejs/babel-plugin-transform-vue-jsx
// class is a special module, same API as `v-bind:class`
class: {
foo: true,
bar: false
},
)
Thanks, that saved me some work, I missed the wrong config :dancer:
As additional information, here's the options for the render functions data object:
https://rc.vuejs.org/guide/render-function.html#The-Data-Object-In-Depth
I guess we can make it clearer in the guide that you have to put classes in the class option rather than the attrs: option when rendering components and wanting to merge classes with the root element.
Noted as an improvement.
This is really an inconsistent api. To make it less confusing, you can just require all class definitions go into class and are not allowed in attrs.
Absolutely not. One is creating bindings the other isn't
One is about vue the other about html attrs
It may however, be confusing because they both have the same name.
Some Vue special bindings are available at the root of options. What is wrong about that?
That is the point. In this case I am not creating a binding.
@Centaur,
I can understand your confusion, but it has more to do with the different way components and normal elements look in templates(!) when it comes to the class attribute:
Normal element:
<div clas="default" v-bind:class="{ danger: true }"></div>
// in render fn:
data: {
attrs: { class: 'default'},
class: {
danger: true
}
}
Component
<!-- class= looks like a normal attribute but Vue uses a binding behind the scenes. -->
<my-component class="default"></my-component>
// in render fn:
data: {
class: {
danger: true
}
}
You're right in that _you_ are not creating a binding - but technically, Vue is creating a binding - it is binding the class from a component (which is just a JS object, not an HTML element) to the class of its root element.
So this probably is for technical reasons ( @yyx990803 surely can explain better than me), but I feel it can indeed lead to false conclusions about how to do replicate the template functionality in a render function.
This is really an inconsistent api.
I'd think of it as a flexible API with a fine grained control. You can let vue handle things for you using {class}, or you can override that behavior using {attrs: {class}}. It's like adding/deleting classes withDOMElement.classList vs. managing everything yourself with DOMElement.className.
But as always, feel free to think otherwise.
@fnlctrl I think the API of the render function is fine. The template syntax is misleading when trying to "translate" it to the render fn data properties. see my post above.
@LinusBorg
<!-- class= looks like a normal attribute but Vue uses a binding behind the scenes. -->
It is a normal attribute indeed, but it will probably get merged with other classes, so vue has to take care of that. It doens't matter if the class was bounded by v-bind.
For example, in the case of the issue:
Component template: <div class="p1">
usage with <router-view> : <router-view class="view">
The expected result: <div class="p1 view">
@fnlctrl I understand the technicalities, and I think the render fn API _is_ consistent with what is happening, but judging from the perspecitve of a normal user, the syntax in templates can be confusing with regards to this.
And turing this around: There is no template equivalent of overriding the clas like we can with:
{attrs: {class: ''something}}
This is in itself not a problem because we can do many things in render functions that we can't do in templates (they _are_ more flexible after all), but it shows that the two things don't translate 1:1
@LinusBorg
In my opinion render functions are really touching the core of vue, and I would regard it as something reserved for advanced users who want to hack vue.
I also think a normal user wouldn't want to write render functions by hand, given that there are two more user-friendly ways: template and jsx.
@fnlctrl Don't get me wrong, I don't think that is bad enough that we should introduce a breaking change in order to fix this. The API should stay as it is.
I just wanted to express that I can understand the mistake @Centaur made, and how it came to it.
He seems to be a "normal" user who wants to use a render function, so there is at least one of those ;)
@fnlctrl @LinusBorg I was forced to use render function because jsfiddle doesn't do jsx transformation for me. Without it I don't know how to reproduce problems I encounter in my jsx code.
Anything inside attr will simply be set with setAttribute, so attrs: { class: cls } is equivalent to el.setAttribute('class', cls), which just overwrites previous classes.
The auto appending/merging only happens when you are using the top-level class field. It also supports Array/Object syntax. The render function guide does mention it, but we should probably make it more prominent. (/cc @chrisvfritz)
In templates, <div class="a"> actually gets translated to { staticClass: 'a' } so that it does not overwrite dynamic classes.
So in render function the non-binding classes should be defined as staticClass, and can get merged. https://jsfiddle.net/oldpig/wtvbck7v/4/ This is a 1:1 translation from the template syntax and is semantically correct. "Never define classes in attrs" should be the ultimate rule.
In render functions you don't really need to use staticClass - just using class is fine. It exists only because in templates you don't want to write <div :class="'some-class'"> everywhere, and people may use class and :class at the same time.
Thanks for the clarification. It is all clear now.