Home > Enterprise >  Typed computed variable in Vue
Typed computed variable in Vue

Time:08-05

I want to have a computed array of type Todo[], but I get this error:

No overload matches this call. Overload 1 of 2, '(getter: ComputedGetter<Todo[]>, debugOptions?: DebuggerOptions | undefined): ComputedRef<Todo[]>', gave the following error. Overload 2 of 2, '(options: WritableComputedOptions<Todo[]>, debugOptions?: DebuggerOptions | undefined): WritableComputedRef<Todo[]>', gave the following error.

This is what I've tried:

todo.ts

export class Todo {
  checked: boolean;
  text: string
 
  constructor(checked: boolean, text: string) {
    this.checked = checked;
    this.text = text
  }
}

Todos.vue

<script setup lang="ts">
import { computed } from '@vue/reactivity';
import { ref } from 'vue'
import { Todo } from '../todo'

let todos = ref<Todo[]>([]);

const filter = ref<string>('all')
const filteredTodos = computed<Todo[]>(() => {
  if(filter.value == "all") return todos;
  if(filter.value == "active") return todos.value.filter((item) => !item.checked)
  if(filter.value == "completed") return todos.value.filter((item) => item.checked)
})
</script>

CodePudding user response:

TypeScript is correct.

You have three conditions and return Todo[] each time but TypeScript doesn't know all cases are covered by those conditions, so in the event none of them is satisfied, the returned result is undefined.

To pass TS this information, you'd need something like this:

const filteredTodos: Todo[] = computed(() => {
  if (filter.value === "active")
    return todos.value.filter((item) => !item.checked)
  if (filter.value === "completed")
    return todos.value.filter((item) => item.checked)

  // this covers all other cases:
  return todos
})

...which could also be written as:

const filteredTodos: Todo[] = computed(() =>
  filter.value === "active"
    ? todos.value.filter((item) => !item.checked)
    : filter.value === "completed"
      ? todos.value.filter((item) => item.checked)
      : todos
)

...or, for readability:

const filteredTodos: Todo[] = computed(() => {
  switch (true) {
    case filter.value === "active":
      return todos.value.filter((item) => !item.checked)
    case filter.value === "completed":
      return todos.value.filter((item) => item.checked)
    default:
      return todos
  }
})

Notes:

  • I've changed your loose equality to strict equality, on general principles. Avoid loose equality at all costs, as it's likely to generate subtle bugs (the type which potentially leads to obscene time sinks and eventually hair loss).
  • to be precise, this does change the logic. The computed now returns all todos whenever filter.value is anything other than 'active' or 'completed'.
    The 'all' filter value is implicit and doesn't need to be specified anymore. If you don't like this change and you'd rather return an empty array when filter value is something unspecified, you could use:
const filteredTodos: Todo[] = computed(() => {
  switch (true) {
    case filter.value === "all":
      return todos
    case filter.value === "active":
      return todos.value.filter((item) => !item.checked)
    case filter.value === "completed":
      return todos.value.filter((item) => item.checked)
    default:
      return []
  }
})
  • Related