Skip to content

TypeScript - incorrect types for provide / inject / InjectionKey #8744

Open
@matthew-dean

Description

@matthew-dean

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    need more infoFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions