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

Access to template refs with composition API #146

Open
filipsobol opened this issue Mar 23, 2020 · 3 comments
Open

Access to template refs with composition API #146

filipsobol opened this issue Mar 23, 2020 · 3 comments

Comments

@filipsobol
Copy link

filipsobol commented Mar 23, 2020

Hi,

I was playing around with Composition API and IMHO access to template refs is confusing, slightly complicated and might cause unexpected errors in the code.

1. Problem

Currently to use template refs I have to:

<!-- 1. Add ref="" attribute to the element -->
<input
    type="text"
    ref="input">
setup() {
    const input = ref<HTMLElement>(null); // 2. Create empty ref with name matching the name of the ref in the template

    function onValidationError() {
        // Do some magic
        input.value?.focus();
    }

    return {
        input, // 3. Return ref from setup method
        onValidationError,
    };
}

Problems with this approach:
1.) Without type annotation this line isn't in any way different from regular refs used to store reactive data: const input = ref(null);.
2.) Name of the variable must match ref="" in the template. I personally like to add Element (eg. inputElement) to variable names to indicate that they are DOM elements.
3.) I have to look into both setup method and template to know that this is a reference to DOM element.
4.) When ref="" in the template is renamed, the name of the variable must also be updated.
5.) There might a case when someone tries to use ref with the same name as ref="" in the template and assign some value to it - Vue will keep overriding it to the DOM element.

2. Possible solution

In Vue 2 with @vue/composition-api plugin it's possible to access template refs from context like so (TypeScript will throw an error, because refs doesn't exists on SetupContext interface, however it works fine):

setup(props, { refs }) {
    console.log(refs.input);
}

However the same property doesn't exist in Vue 3 (SetupContext interface).

I think that this approach is much better, but I'd like to know your opinion.

@jods4
Copy link

jods4 commented Mar 24, 2020

Your proposition makes sense to me.
Vue 3 still exposes $refs in views, so why not expose it inside SetupContext?
It would be more consistent.

I, too, think that the current design for using DOM refs with composition apis is not intuitive. It caused me a fair bit of frustration on my first try.

Related: vuejs/core#660, I suggested that when using composition api template refs should be written to any property, even if it's not backed by a ref (could be a reactive object, or even just a plain object if you don't care about reactivity). Didn't get much support.

@aztalbot
Copy link

I don't disagree on some points, but I am also unsure if one or the other is better.

I personally like the ref API as it is, but I'll admit it has felt a little awkward when explaining to others. The link between ref() and ref= feels more like a coincidence than a logical relationship.

That said, being able to populate reactive refs with DOM refs allows for more easily decoupling the logic around those refs. With refs on the setup context interface, you would have to pass that to composition functions that deal with refs, and I don't see any good way you could rename them. For example:

setup(_, { refs }) {
    const { focus /* method */ } = useFocus(refs)
    return { focus }
}

The problem I see with this example is that we have to look into the useFocus function to know what our ref must be named. And the only way I see to provide the ability to rename the ref is an extra parameter to that function to use as a dynamic key. When using ref as the API has it now, it's as simple as renaming the variable returned from useFocus.

Also, on a couple of the points you bring up, I'm not sure the alternative works any better:

  • For "(2) Name of the variable must match ref="" in the template." The name of the key you are accessing off of the refs object in the alternative API also has to match the ref in the template. You don't get autocomplete, so you need to spell the key correctly, same as spelling the variable correctly.
  • "(4) When ref="" in the template is renamed, the name of the variable must also be updated." When you rename the ref in the template you also need to rename the key you are accessing off of the refs object.

Other thoughts:

  • "(3) I have to look into both setup method and template to know that this is a reference to DOM element." Fair enough, but you could easily create a one line helper function in your code base that just aliases ref as templateRef. That would make it clear.
  • "(5) There might be a case when someone tries to use ref with the same name as ref="" in the template and assign some value to it - Vue will keep overriding it to the DOM element." Also a fair point, but along the same line as (3), you could have a templateRef helper with a return type of readonly (it just wouldn't actually be readonly) so that folks will know not to touch it.
  • (1) is basically the point as (5), it seems. And if you have a templateRef helper, you could do other useful things like adding dev mode validation to ensure it actually gets populated, and with the correct kind of thing.

@jods4
Copy link

jods4 commented Mar 24, 2020

That said, being able to populate reactive refs with DOM refs allows for more easily decoupling the logic around those refs. With refs on the setup context interface, you would have to pass that to composition functions that deal with refs, and I don't see any good way you could rename them

Fair point. The same could be said about passing a prop to a function, which is probably a lot more common than passing a ref. I think the only way to do the currently is:

setup(props, { refs }) {
  // Example with refs, props works in exactly the same way
  const { el1, el2: renamed } = toRefs(refs);
  useFocus(renamed);
}

I think passing DOM elements to mixins should be an unusual case. The "better" way to compose stuff that manipulates DOM is to encapsulate the behavior in either a directive or a component.
You example useFocus screams v-focus to me (I know, it's just an example. Not saying there are no good use cases, but saying they shouldn't be common).

It seems to me that using the ref inside the component itself is the main use case and I wouldn't mind doing refs.element.

One thing that annoys me a lot is that there would be no strong typing, unlike props. Unless you declare refs in some way, the best you could do is refs: Record<string, Element>. Which falls short of validating el2 is the right name and fails to provide a stronger type for it (e.g. HTMLInputElement).
And that's not even correct since refs can also be arrays (when used in v-for).
(Can Vetur help?)

Typing is a big drawback :(

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

3 participants