Closed jods4 closed 4 years ago
Why would you want a more implicit behavior without saying it's a ref
? Why is this more confusing to you? Is it because before it was that implicit as well: it was only necessary to declare ref="a"
in the template and that gave you access to the Component/Element reference?
@posva Do you find the behavior of the first repro above intuitive for a newcomer?
I ended up in that situation (more involved, but basically this root cause) and I lost a lot of time figuring out why I didn't get the element. It was not a nice experience.
Moreover I didn't try but I expect this to not work either:
setup() {
let scope = reactive({ b1: null })
return scope
}
Because there is no ref
involved, just a proxy. This pattern is found on the Vue Composition API cheatsheet.
It's also inconsistent with other stuff that updates the scope, like v-model
. Why would v-model
write to any property; but ref
doesn't?
This is bad developer experience. What are the arguments against making it work with any property?
If you don't fix it, you should document this more clearly, with a red box in the docs and create a warning in DEV mode if you detect this situation (property exists but is not a ref).
@jods4
If you don't fix it, you should document this more clearly, with a red box in the docs and create a warning in DEV mode if you detect this situation (property exists but is not a ref).
I'm asking you the questions to get a better understanding, never said this was or wasn't going to be fixed 🙂 .
To answer your questions:
Do you find the behavior of the first repro above intuitive for a newcomer?
I honestly don't see how a newcomer will figure out how refs
work without reading documentation about it because they cannot just come up with the idea of:
ref="a"
on the templatesetup
So using ref
or not here is more of a technical detail than being intuitive
It's also inconsistent with other stuff that updates the scope, like v-model. Why would v-model write to any property; but ref doesn't?
I'm sorry, I don't understand what you are exactly referring to
I honestly don't see how a newcomer will figure out how refs work without reading documentation about it because they cannot just come up with the idea of:
Well, I did? (obviously I read some docs)
This is feedback from a new Vue user: I tried to grab an element with ref
and I had a bad time getting it to work. To make things worse, it's hard to debug why this doesn't work.
Disclosure: I'm trying out Vue but I have years of experience with several other frameworks and UI development in general. I may not be representative of a beginner.
In Aurelia for example you can bind a property in your viewmodel to an element in DOM using the same syntax ref="vmProperty"
.
I'm sorry, I don't understand what you are exactly referring to
Here's a piece of code:
<input v-model="name" ref="box" />
setup() {
return reactive({ name: 'John', box: null })
}
To me it's surprising (in a bad way) that name
will be updated by the view; but box
won't.
Well, I did? (obviously I read some docs)
That's a bit contradictory: you didn't figure it out if you read the docs 😄 . Did you read this https://vue-composition-api-rfc.netlify.com/api.html#template-refs or something else? I don't think we have proper docs on this yet, so it's something we can gather feedback from
Thanks for the example, now I see how v-model
and ref
may look like they would behave the same way while they don't, and I think this is where the confusion comes from and it makes sense if there is no experience with Vue 2. Here is some context:
setup
, so name
is a variable.ref
takes a string as a value (unless used with v-bind
), it used to bind the references to an object called $refs
-> this.$refs.box
with no property box
declared anywhere but here that difference is no longer clear because both name
and box
are keys returned in setup
and both are variables as wellRegarding the technical difference, when using the reference to the element/component, it is reactive, meaning that we need to use a reactive variable to retrieve that ref in the js:
setup() {
const el = ref(null)
// notice the `value` like other reactive properties
const textFromEl = computed(() => el.value.textContent)
return { el }
}
This wouldn't be possible if we had el = null
at the beginning since there is no way to modify the original variable.
Where in the code were you using the box
/b2
reference? Do have a more concrete sample of what you were doing with that HTML element?
Did you read this
I did but unfortunately I found it only after figuring out what was the problem on my own.
That documentation is good but the ref
caveat is really only mentionned once in passing:
[...], if a VNode's ref key corresponds to a ref on the render context, [...]
So it's there but it's easy to overlook.
I've read the source code and understand that the two directives work very differently under the hood.
Forgive me if I'm a total noob but I have the impression it is so because of Vue 2 and how the API was before.
When you say: "this.$refs.box
with no property box declared anywhere" that applies to Vue 2, right? Because in Vue 3 there is no $refs
anymore? So when you use the new Composition API, you have to provide a property named properly in the scope?
The current behavior makes a lot of sense in the old context of $refs
but I think it's not most intuitive with the new API.
when using the reference to the element/component, it is reactive
I am fully aware of that. As I said in the original issue, many times an element won't change over the lifetime of your component, so that's fine. watch
runs after the component is mounted, which is very convenient in this regard. (see example below)
More annoyingly, I might use reactive({ button: null })
to get a reactive element and this won't work either. Some of the available documentation currently promotes reactive({ x, y })
as an alternative to x = ref(), y = ref()
but in this case it breaks.
Where in the code were you using the box/b2 reference? Do have a more concrete sample of what you were doing with that HTML element?
Sure, this is what I was experimenting with: https://codesandbox.io/s/little-microservice-lffu5
(The code editor doesn't load for me in FF, but it does in Chrome)
You can collapse draw
which is just painting logic and look at the remaining Vue glue.
You'll notice this code does a toRefs
before returning the scope.
It does so only to make the canvas work.
the ref caveat is really only mentionned once in passing:
That's not true, it appears in all three code samples as well. The paragraph that mentions it is also the only explanation. That's rather hard to overlook.
When you say: "this.$refs.box with no property box declared anywhere" that applies to Vue 2, right? Because in Vue 3 there is no $refs anymore? So when you use the new Composition API, you have to provide a property named properly in the scope?
Yes, I'm referring to the difference from Vue 2 and Vue 3 trying to give some background to explain why this could make sense to us and make it clearer why it doesn't necessarily makes sense in its current state.
No, this.$refs
do not exist anymore.
I am fully aware of that. As I said in the original issue, many times an element won't change over the lifetime of your component
Sure, but we cannot be aware of that condition, so it has to be treated as the larger case: it could change -> it should be a reactive variable.
Thanks for the example, it gives more context! Instead of using toRefs
, using ref(null)
instead of null
seems easier imo.
I agree that a warning makes total sense here. I still feel it's only a patch because of the comparison with v-model
: the difference between what value is passed to ref
is not as clear as it was in Vue2 even though one is a directive and the other is an attribute
Thanks for the example, it gives more context! Instead of using toRefs, using ref(null) instead of null seems easier imo.
Yeah but I'm using reactive
to avoid declaring several ref
and having to use value
.
It's definitely weird to have to include one ref
in the lot because of how it's used in the template.
Yeah but I'm using
reactive
to avoid declaring severalref
and having to usevalue
. It's definitely weird to have to include oneref
in the lot because of how it's used in the template.
Personally I didn't expect someone to return the object returned by reactive
, in which scenario having aRef: null
makes sense
I wish this behavior would be changed, esp. since alpha 8.
In Vue 3 you don't think in refs names anymore. With the API you think in an object that represent a reactive state.
So it's natural to think that ref='abc'
in a template would set the abc
property of your state to that element.
Forcing a ref here is not cool, e.g. you can also have reactive state by returning a reactive object:
setup() {
return reactive({ element: null })
}
This code really doesn't make much difference compared to: return { element: ref(null) }
.
The fix doesn't look complicted:
toRaw
call here, it's not needed:
https://github.com/vuejs/vue-next/blob/11d2fb2594e234bf7f2277194dbd47f5776f9335/packages/runtime-core/src/renderer.ts#L1800ref
as today but add an else renderContext[ref] = value
branch.
https://github.com/vuejs/vue-next/blob/11d2fb2594e234bf7f2277194dbd47f5776f9335/packages/runtime-core/src/renderer.ts#L1817-L1819That's about it?
The docs are really outdated or I have found a bug.
At Usage inside v-for they are showing how to put the elements to the array. But inside the ref function, you can't access the state.
:ref="el => { divs[i] = el }">
so here the divs variable is not defined.
Quick example:
<template>
<p :ref="el => test.push(el)"></p>
</template>
...
setup() {
return {test: ref([])};
}
Results in runtime-core.esm-bundler.js?5c40:223 ReferenceError: test is not defined.
Using "vue": "^3.0.0-alpha.8", "@vue/compiler-sfc": "^3.0.0-alpha.8".
Do you plan to put back something like $children? In my case I have manually defined 100 icons and need to foreach them all to run a function inside each specific component. Appending to 100 lines something like :ref="r => array.push(r)" looks quite weird to me.
// Edit, I just realized maybe the v-for is setting "this" context for template variables? If it does then it means I can't define multiple components to the same array. Hack will be to define foreach loop with 1 dummy iteration. (just checked it and it is not this case)
@sionzeecz your case is working as intended. If you want to report a bug, open a new issue with actual reproduction instead of commenting on other issues.
@yyx990803 I'm sorry for commenting under this issue. Anyway when you gave me already a response. If this works as is intended then the documentation is written wrongly? I can't spot almost any difference between code above and documentation.
Just found I can access _ctx.
<template>
<p :ref="el => _ctx.test.push(el)"></p>
</template>
...
setup() {
return {test: ref([])};
}
At least this works as workaround (I don't think this is intended way to do it)
By "working as intended" I mean the code you showed is working. There is no bug. If there is one, you should provide a reproduction (e.g. a codepen, and in a separate issue).
Closing the original issue since a ref
will always be required to use refs.
@yyx990803 Can I ask what's the motivation?
The changes I proposed seemed simple enough... why can't I put a DOM element reference into a reactive
object?
Because the user may do this:
const state = {
foo: null
}
onMounted(() => {
state.foo // expect to get a ref but won't, because local `state` is not reactive
})
return state
A ref
is more consistent and more explicit.
With the change I proposed it would totally work, wouldn't it? And that's kind of the point.
I don't see how ref
is more consistent or explicit.
It may be for users coming from a Vue 2 background, I understand that and it should definitely work for them as they expect.
At the same time, Vue 2 is the past, Vue 3 is gonna have new users that never had exposure to Vue 2; or that might have experience with other frameworks. They might be surprised that your example (with or without reactive) doesn't work.
Others framework have a ref
keyword to grab a DOM element (e.g. Aurelia) and it just works on the model. That might create false expectations and frustrations. If aligning is possible, I think you should.
As a new user I genuinely don't understand the logic here and I feel this is a limitation more than an enhancement. After looking at the code, it seems rather trivial to change.
What problem does this feature solve?
The way it currently (doesn't) works is really surprising for users. It took me a long amount of time to understand why I couldn't get a DOM element from my code.
Here's a simplified repro:
Looking at this code, my intuition is that
b1
will be a ref containing the first HTMLButton andb2
will be a non-reactive field containing the second HTMLButton.With vue-next@3.0.0-alpha.3,
b1
works as expected butb2
staysnull
. This is confusing, especially since in templating theref
nature ofb1
is hidden -- code usingb1
andb2
is the same in template expressions.Moreover for better usability you may be tempted to hide all your refs behind a getter/setter, I'm doing this:
And now even though
b1
is reactive, it'snull
just likeb2
because Vue doesn't see theref
hidden inside the getter/setter.Similar issues arise when using the built-in
reactive({ b1: null, b2: null})
.What does the proposed API look like?
There is no new API surface.
I propose to change the behavior of
<div ref=x>
so that the HTML element is available insetup
state whether the field is aref
or not.If there is a technical reason to not do that, I suggest that you detect this and provide a warning in DEV mode.