Subscribe on changes!

TypeScript - incorrect types for provide / inject / InjectionKey

avatar
Jul 8th 2023

Vue version

3.3.4

Link to minimal reproduction

https://codesandbox.io/p/sandbox/nifty-dust-hvxpw7?welcome=true

See: Consumer.vue

Steps to reproduce

<template>{{ fooBar }} {{ nulls }}</template>

<script setup lang="ts">
import { inject, type InjectionKey } from "vue";

// # PROBLEM 1 - Default values are assumed as the type

// This type should be `unknown | 'fooDefault'`
// Instead the type is "fooDefault", which is not guaranteed
const foo = inject("foo", "fooDefault");
// This type should be `unknown | 'bar'`
const bar = inject("bar", "bar");

// This should throw a TypeScript error, because both values should be `unknown`
const fooBar = foo + bar;

// illustrated more clearly
const null1 = inject("null1", "foo");
const null2 = inject("null2", "bar");

// This absolutely should be a TypeScript error
const nulls = null1 + null2;

// # PROBLEM 2 - Following the documentation leads to loss of `symbol`

const sym1 = Symbol() as InjectionKey<string>;

/**
 * This is a TypeScript error but _should not be_.
 * @see https://github.com/microsoft/TypeScript/issues/54885
 */
let vueTestUtilsMountingOptions = {
  global: {
    provide: {
      [sym1]: "value",
    },
  },
};

// Instead, Vue should be typing like:
const sym2 = Symbol() as symbol & InjectionKey<string>;

//However, typing as the above breaks `inject()` return type

// Alternatively, it could be typed like:
type Key<T> = symbol & InjectionKey<T>;

const sym3: Key<string> = Symbol();

// Works now! But provide() / inject() would still need to be fixed.
vueTestUtilsMountingOptions = {
  global: {
    provide: {
      [sym3]: "value",
    },
  },
};
</script>

What is expected?

  1. inject() / provide() should return correct types
  2. InjectionKey<T> should not override the fact that Symbol() is a symbol

What is actually happening?

inject() and provide() are not type-safe

System Info

System:
    OS: macOS 13.4
    CPU: (10) arm64 Apple M1 Pro
    Memory: 765.81 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.13.0 - ~/Library/Caches/fnm_multishells/28527_1688831171209/bin/node
    Yarn: 1.22.19 - ~/Library/Caches/fnm_multishells/28527_1688831171209/bin/yarn
    npm: 8.19.3 - ~/Library/Caches/fnm_multishells/28527_1688831171209/bin/npm
  Browsers:
    Chrome: 114.0.5735.198
    Safari: 16.5
  npmPackages:
    vue: ^3.3.4 => 3.3.4

Any additional comments?

I wrote a package that provides a much more type-safe pattern, is more akin to other state solutions. I think it might be worth considering something like this in Vue core? https://github.com/matthew-dean/vue-atoms