Subscribe on changes!

Allow constraining ref attribute to not allow string when using tsx.

avatar
Mar 22nd 2021

What problem does this feature solve?

When writing a component in tsx, passing a string to a ref attribute on a native element does not work and typescript does not tell you that anything is wrong.

This is not a problem with custom components because we can override ref typing like:

import * as RuntimeCore from '@vue/runtime-core'
declare module '@vue/runtime-core' {
  declare interface ComponentCustomProps {
    ref?:
      | RuntimeCore.Ref
      | ((ref: Element | RuntimeCore.ComponentInternalInstance | null) => void)
  }
}

What does the proposed API look like?

I'm not sure the best way to solve this problem but here is where the problem is:

// @vue/runtime-dom/dist

type ReservedProps = {
  key?: string | number
  ref?:
    | string
    | RuntimeCore.Ref
    | ((ref: Element | RuntimeCore.ComponentInternalInstance | null) => void)
}

// need to be able to override to say

type ReservedProps = {
  key?: string | number
  ref?:
    | RuntimeCore.Ref
    | ((ref: Element | RuntimeCore.ComponentInternalInstance | null) => void)
}
avatar
Mar 22nd 2021

This should work:

defineComponent({
  setup() {
    const dom = ref(null)

    return {
      dom
    }
  },
  render() {
    return <div ref="dom"></div>
  }
})

what do you actually mean?

avatar
Mar 22nd 2021

Using jsx-next, recommended here, for writing tsx files and using this syntax.

import { defineComponent } from "vue";

defineComponent({
  setup() {
    const dom = ref(null)
    return () => (
      <div ref={dom} />
    );
  },
});
avatar
Mar 22nd 2021

But according to the example I provided above, it allows specifying ref as a string

avatar
Mar 22nd 2021

jsx-next provides two syntaxes for writing render functions. It does work the way you wrote it but it does not work the way I wrote it. I prefer returning a render function from setup as it's more succinct and typescript tells me when a custom component is not within the template scope. jsx-next offers a way to write it the way I like so I'd just like to be able to control what is allowed for ref values on native input elements. For those that choose this method for their whole application they should be able to restrict what is passed to ref to suit their style.

// Bulkier syntax
import { defineComponent } from "vue";
import CustomComponent from './CustomComponent'

defineComponent({
  components: {
    CustomComponent
  }
  setup() {
    const dom = ref(null)
    return {
      dom,
       title: 'info'
    }
  },
  render () {
      return  (
        <div ref="dom" >
          <CustomComponent title={this.title} />
        </div>
      );
  }
});
// more succinct syntax
import { defineComponent } from "vue";
import CustomComponent from './CustomComponent'

defineComponent({
  setup() {
    const dom = ref(null)
    const title = 'info'
    return () => (
      <div ref={dom}>
        <CustomComponent title={title} />
      </div>
    );
  },
});
avatar
Mar 22nd 2021

To be clear this doesn't work and I'd like typescript to tell me what what is being passed to ref is an error

import { defineComponent } from "vue";
import CustomComponent from './CustomComponent'

defineComponent({
  setup() {
    const dom = ref(null)
    const title = 'info'
    return () => (
      <div ref="dom">
        <CustomComponent title={title} />
      </div>
    );
  },
});
avatar
Mar 22nd 2021

I know what you mean: you mean that if jsx is used as the return value of setup then it is not allowed to be specified as a string, otherwise, it can. But we cannot restrict ref to not be able to specify a string, because it actually allows the use of string types. And I don’t think we can limit ref correctly unless we can analyze its context correctly.

avatar
Mar 22nd 2021

I'm not saying that vue should limit it for everyone. I'm saying that consumers of vue should be able to customize it for their own purposes. For example, by adding this to my application type definition files I fix this issue for custom components, because it ends up overriding what is defined in @vue/runtime-dom.

import * as RuntimeCore from '@vue/runtime-core'
declare module '@vue/runtime-core' {
  declare interface ComponentCustomProps {
    ref?:
      | RuntimeCore.Ref
      | ((ref: Element | RuntimeCore.ComponentInternalInstance | null) => void)
  }
}

now typescript will hint that ref is incorrect below

import { defineComponent } from "vue";
import CustomComponent from './CustomComponent'

defineComponent({
  setup() {
    const customRef = ref(null)
    return () => (
        <CustomComponent ref="customRef" />
    );
  },
});

I'd like the same ability to define a type definition file in my application to override what is in @vue/runtime-dom so that for my application you cannot pass strings to a native element ref attribute.

avatar
Mar 22nd 2021

@trainiac I made a PR https://github.com/vuejs/vue-next/pull/3458, but I’m not sure if it will eventually be accepted

avatar
Mar 22nd 2021

Thank you @HcySunYang