Skip to content

Instantly share code, notes, and snippets.

@iamsonnn
Last active January 9, 2024 04:29
Show Gist options
  • Save iamsonnn/dbf6d8407a0959f7dc66f6d24231799c to your computer and use it in GitHub Desktop.
Save iamsonnn/dbf6d8407a0959f7dc66f6d24231799c to your computer and use it in GitHub Desktop.
Simplize Vue's template refs, make it work with IDE suggestion & compiler

Purpose: Make Vue's template ref (https://vuejs.org/guide/essentials/template-refs.html) more typescript friendly, make IDE suggestion and compiler validator better

Create a file name useRefs.ts in your utils/helpers directory

import { reactive } from 'vue'

export const useRefs = <T extends object>() => {
  const refs = reactive<T>({} as T)
  const toRef = (refName: keyof T) => (el: any) => ((refs as T)[refName as keyof T] = el)

  return {
    refs,
    toRef,
  }
}

Example usage:

<template>
  <input :ref="toRef('input')" />
</template>

<script setup lang="ts">
import { onMounted } from 'vue'
import { useRefs } from '@common/utils/useRefs'

const { refs, toRef } = useRefs<{
  input: InstanceType<typeof HTMLInputElement>
}>()

onMounted(() => {
  refs.input.focus()
})
</script>

What you got:

  • No more IDE warning
  • IDE suggestion every code with template refs
  • Complier checker works with Vue's exposes
@zhangenming
Copy link

// Is it possible to achieve ?
const { refs, toRef } = useRefs<{
  input: HTMLInputElement
}>()

@iamsonnn
Copy link
Author

We got same idea, that is exactly my first version. However, I got compiler errors when using that for my own Vue component. You can check this official Vue docs for the detail: https://vuejs.org/guide/typescript/composition-api.html#typing-component-template-refs

@zhenjie1
Copy link

I am someone who has just started learning, and I don't know if my code is reasonable.Please enlighten me, senior.

export default function useRefs<
  T extends Record<string, any>,
  R extends Record<string, any> = {
    [k in keyof T]: InstanceType<T[k]>
  },
>() {
  const refs = reactive<R>({} as R)
  const toRef = (refName: keyof T) => (el: any) => ((refs as T)[refName as keyof T] = el)

  return {
    refs,
    toRef,
  }
}

@AlexVagrant
Copy link

add unit test

useEvent.test.ts

import { defineComponent, h, } from 'vue';
import { describe, test, expect, } from 'vitest';
import { mount } from '../.test/mount';

import useRefs from '.';


describe('useRefs', () => {
	test("useRef", () => {
		const vm = mount(defineComponent({
      setup() {
				const { refs, toRef } = useRefs<{
					input: InstanceType<typeof HTMLInputElement>
				}>()
        return {
          refs,
					toRef
        }
      },
      render() {
        return h('input', { ref: this.toRef('input') })
      },
    }));
		expect(vm.refs.input).toBe(vm.$el);
	})
});

main.ts

import type { InjectionKey, Ref } from 'vue'

import { createApp } from 'vue'

type InstanceType<V> = V extends { new (...arg: any[]): infer X } ? X : never
type VM<V> = InstanceType<V> & { unmount(): void }

export function mount<V>(Comp: V) {
  const el = document.createElement('div')
  const app = createApp(Comp as any)

  const unmount = () => app.unmount()
  const comp = app.mount(el) as any as VM<V>
  comp.unmount = unmount
  return comp
}

@varHarrie
Copy link

varHarrie commented Jan 9, 2024

Other version:

<template>
  <div :ref="divRef.bind"></div>
</template>

<script setup lang="ts">
function useMyRef<T>() {
  const state = reactive({
    value: undefined as T | undefined,
    bind: (el: any) => {
      state.value = el;
    },
  });

  return state;
}

const divRef = useMyRef<HTMLDivElement>();

onMounted(() => {
  console.log(divRef.value);
})
</script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment