Home > Enterprise >  How to correctly determine the type for a generic component
How to correctly determine the type for a generic component

Time:08-01

Let's make a list component that works with an array of objects.

<script setup lang="ts">
const props = defineProps<{
  items: Record<string, unknown>[],
  selected: Record<string, unknown> | null
  field: string
}>()
const emit = defineEmits<{
  (e: 'update:selected', value: Record<string, unknown> | null): void
}>()
</script>
<template>
  <div v-for="(item,idx) in items" :key="idx">
    <div 
         @click="emit('update:selected',item)" 
         style="cursor: pointer">
      {{ item[field] }}
    </div>
  </div>
</template>

Let's try to use it by passing a list of employees.

<script setup lang='ts'>
import MyList from './MyList.vue'
import {Ref, ref} from "vue";
interface Employee {
  name: string;
  age: number;
}
const employees: Employee[] = [
  {name: 'Mike', age: 34},
  {name: 'Kate', age: 19},
  {name: 'Den', age: 54},
]
const selectedEmployee=ref<Employee | null>(null)
</script>
<template>
  Age: {{selectedEmployee?selectedEmployee.age:'not selected'}}
  <MyList :items="employees" v-model:selected="selectedEmployee" field="name"/>
</template>

Everything is working. But, if you do a build, an error occurs TS2322: “Type 'Employee' is not assignable to type 'Record<string, unknown>'". A generic component would be the solution. But it's not there yet. What is the best way to solve this problem? vue playground

CodePudding user response:

Try to extract the Employee interface and put inside another file, then import it in both files and use it instead of Record<string, unknown> :

employee.ts

export default interface Employee {
  name: string;
  age: number;
}

MyList.vue

<script setup lang="ts">
  import Employee from './employee'
const props = defineProps<{
  items: Employee[],
  selected: Employee | null
  field: string
}>()
const emit = defineEmits<{
  (e: 'update:selected', value: Employee | null): void
}>()
</script>

App.vue

<script setup lang='ts'>
import MyList from './MyList.vue'
import {Ref, ref} from "vue";
import Employee from './employee'
const employees: Employee[] = [
  {name: 'Mike', age: 34},
  {name: 'Kate', age: 19},
  {name: 'Den', age: 54},
]
const selectedEmployee=ref<Employee | null>(null)
</script>

CodePudding user response:

It is possible to create a method.

export const wrapSelected = <T>(value: Ref<T | null>) => {
    return computed<Record<string, unknown> | null>({
        get: () => value.value as unknown as (Record<string, unknown> | null),
        set: (val: Record<string, unknown> | null) => {
            value.value = val as unknown as T | null
        }
    })
}

App.vue

...
const wrapperSelected=wrapSelected(selectedEmployee)
</script>

<template>
  Age: {{ selectedEmployee ? selectedEmployee?.age : 'not selected' }}
  <my-list :items="wrapperItems" v-model:selected="wrapperSelected" field-name="name"/>
</template>

Though it's not very aesthetically pleasing. It's better than good old any?

  • Related