简体   繁体   中英

JavaScript component conversion to TypeScript: What is the type of props

Migrating VueJS project from JavaScript + Options API to TypeScript + Composition API, I have gradually found equivalents of most of the stuff. One thing that I'm struggling with is the v-model feature. I found a good article on implementing it using Composition API , where author creates a composable function that can be reused in components that want to implement v-model . I'm trying to now write an equivalent function using TypeScript.

Here is the original JS code:

import { computed } from 'vue'
export function useModelWrapper(props, emit, name = 'modelValue') { 
  return computed({ 
    get: () => props[name], 
    set: (value) => emit(`update:${name}`, value) 
  })
}

My TS implementation looks like this:

import { computed, ComputedRef } from '@vue/composition-api'

export function useModelWrapper<T> (props: Record<string, unknown>, emit: (event: string, value: T) => void, name: 'modelValue') : ComputedRef {
  return computed({
    get: () => props[name],
    set: (value) => emit(`update:${name}`, <T>value)
  })
}

This compiles fine but Vue is not too happy about it. I get

No overload matches this call

error if I use this function in a component.

My suspicion is that the type of props in the TS version is not correct, but I was not able to figure out what type I should use there. I have tried it with Object , unknown and any , but none of them allows me to do props[name] in the getter ( any does but TS complains that I shouldn't use any as the type of props ).

What am I doing wrong here?

Edit

Here is the full error text:

{
    "resource": "../ProjectsDropdown.vue",
    "owner": "_generated_diagnostic_collection_name_#6",
    "code": "2769",
    "severity": 8,
    "message": "No overload matches this call.
Overload 1 of 3, '(options: ComponentOptionsWithoutProps<unknown, unknown, Data, {}, {}>): VueProxy<unknown, unknown, Data, {}, {}>', gave the following error.
Type '{ modelValue: PropType<Project>; }' is not assignable to type 'undefined'.
Overload 2 of 3, '(options: ComponentOptionsWithArrayProps<string, Data, Data, {}, {}, Readonly<{ [x: string]: any; }>>): VueProxy<Readonly<{ [x: string]: any; }>, Data, Data, {}, {}>', gave the following error.
Type '{ modelValue: PropType<Project>; }' is not assignable to type 'string[]'.
  Object literal may only specify known properties, and 'modelValue' does not exist in type 'string[]'.
Overload 3 of 3, '(options: ComponentOptionsWithProps<ComponentPropsOptions<Data>, Data, Data, {}, {}, ({ [x: number]: string; } & { [iterator]?: IterableIterator<string> | undefined; ... 32 more ...; 
toLocaleString?: string | undefined; }) | ({} & { ...; })>): VueProxy<...>', gave the following error.
Type '{ modelValue: PropType<Project>; }' is not assignable to type 'ComponentPropsOptions<Data> | undefined'.
  Types of property 'modelValue' are incompatible.
    Type 'PropType<Project>' is not assignable to type 'Prop<unknown, unknown> | null | undefined'.
      Type 'new (...args: never[]) => Project & object' is not assignable to type 'Prop<unknown, unknown> | null | undefined'.
        Type 'new (...args: never[]) => Project & object' is not assignable to type 'new (...args: string[]) => Function'.
          Types of parameters 'args' and 'args' are incompatible.
            Type 'string' is not assignable to type 'never'.",
    "source": "Vetur",
    "startLineNumber": 59,
    "startColumn": 16,
    "endLineNumber": 119,
    "endColumn": 3
}

Edit 2

Here is how I'm using this function in a component:

<script lang='ts'>
export default defineComponent({
  ...
  import { useModelWrapper } from '../usemodelWrapper'
  props: {
    modelValue: Object as PropType<Project>
  },
  setup (props, { emit }) {
  return {
    selectedProject: useModelWrapper<Project>(props, emit, 'modelValue')
  }
})
</script>

Just in case it helps someone down the road, it turned out that I was using wrong version of PropType in the component to define my v-model prop. The real PropType class should be imported like this:

import { PropType } from '@vue/composition-api'

and not:

import Vue, { PropType } from 'vue'

After seeing your own answer, I feel like this could be even stricter.

import { computed, WritableComputedRef } from '@vue/composition-api'

export function useModelWrapper<TProps, TKey extends keyof TProps> (
  props: TProps,
  emit: (event: string, value: TProps[TKey]) => void,
  name: TKey = 'modelValue' as TKey
) : WritableComputedRef<TProps[TKey]> {
  return computed<TProps[TKey]>({
    get: () => props[name],
    set: (value: TProps[TKey]) => emit('input', value)
  })
}

I haven't tested it with Vue specifically but that function should restrict which key you can pass it as third argument and correctly infer the return type depending on which name was passed instead of a union of all values of props .

This is an extension of @emeraldsanto's answer but the type inferences are the Vue 3 component emit strings

export function useModelWrapper<TProps extends Record<string, unknown> | { modelValue: unknown },
                                TKey extends keyof TProps & string = 'modelValue',
                                TEmit extends (event: `update:${TKey}`, value: TProps[TKey]) => void = (event: `update:${TKey}`, value: TProps[TKey]) => void > (
  props: TProps,
  emit: TEmit,
  name: TKey = 'modelValue' as TKey
) : WritableComputedRef<TProps[TKey]> {
  return computed<TProps[TKey]>({
    get: () => props[name],
    set: (value: TProps[TKey]) => emit(`update:${name}` as `update:${TKey}`, value)
  })
}

use like

const props = defineProps<{
  modelValue: string
  foo?: number
}> ()

const emit = defineEmits<{
  (e: 'update:modelValue', id: string): void
  (e: 'update:foo', value: number | undefined): void
}> ()
const value = useModelWrapper(props, emit)
const fooValue = useModelWrapper(props, emit, 'foo')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM