Hone logo
Hone
Problems

Vue Injection Keys: Type-Safe Dependency Injection

Vue 3 offers a powerful way to manage dependencies across your application using provide and inject. However, without proper type safety, it's easy to introduce runtime errors due to typos or mismatches in the keys used for providing and injecting. This challenge focuses on implementing a type-safe system for Vue's injection keys using TypeScript.

Problem Description

Your task is to create a robust and type-safe system for managing provide and inject keys in a Vue 3 application using TypeScript. This system should ensure that the type of the injected value strictly matches the type of the provided value, preventing common runtime errors.

Key Requirements:

  1. Type Safety: The injected value must be guaranteed to be of the same type as the provided value.
  2. Key Definition: Define a mechanism to declare injection keys along with their associated types.
  3. Provider Component: Create a component or composable function that provides a value using a defined key.
  4. Consumer Component: Create a component or composable function that injects a value using the same defined key.
  5. Error Handling (Implicit): The type system should prevent compilation errors if types don't match.

Expected Behavior:

  • When a parent component provides a value with a specific key and type, a child component that injects using the same key should receive a value of that exact type.
  • Attempting to inject a value with a key that hasn't been provided should result in a compilation error or the injected value being undefined (if a default value is provided and the type allows for undefined).
  • Attempting to provide or inject values of mismatched types for the same key should result in a compilation error.

Edge Cases to Consider:

  • Providing undefined or null values.
  • Providing default values for injection.
  • Handling complex types (e.g., objects, arrays, functions) as injected values.

Examples

Example 1: Simple String Injection

// Define the injection key
const messageKey = Symbol('message'); // In a real scenario, we'd use a typed approach

// Provider Component (Conceptual)
// provide(messageKey, 'Hello, Vue!');

// Consumer Component (Conceptual)
// const message = inject(messageKey); // message would be of type string | undefined

// Target Solution's Expected Usage:
// Define the key with its type
const greetingKey = createInjectionKey<string>('greeting');

// Provider Component
// ... in setup()
// provide(greetingKey, 'Hello, Vue!');

// Consumer Component
// ... in setup()
// const greeting = inject(greetingKey); // greeting is guaranteed to be string | undefined

Explanation: The goal is to create a createInjectionKey function that returns an object allowing provide and inject to be type-aware.

Example 2: Object Injection

// Define the injection key with an object type
interface User {
  id: number;
  name: string;
}

const userKey = createInjectionKey<User>('user');

// Provider Component (Conceptual)
// provide(userKey, { id: 1, name: 'Alice' });

// Consumer Component (Conceptual)
// const user = inject(userKey);
// if (user) {
//   console.log(user.id); // This would pass type checking in the target solution
// }

// Target Solution's Expected Usage:
// Define the key with its type
const currentUserKey = createInjectionKey<User | undefined>('currentUser', undefined); // With a default

// Provider Component
// ... in setup()
// provide(currentUserKey, { id: 1, name: 'Alice' });

// Consumer Component
// ... in setup()
// const user = inject(currentUserKey); // user is guaranteed to be User | undefined
// if (user) {
//   console.log(user.id); // Type-safe access
// }

Explanation: Demonstrates how complex types, including nullable types, should be handled.

Example 3: Mismatched Types (Compilation Error Scenario)

// Define the injection key for a number
const countKey = createInjectionKey<number>('count');

// Provider Component (Conceptual)
// provide(countKey, 10);

// Consumer Component (Conceptual - Will cause a TS error)
// const countAsString = inject(countKey); // TS should flag this as an error: Type 'number | undefined' is not assignable to type 'string | undefined'.

// Target Solution's Expected Behavior:
// A TypeScript error will occur if a consumer tries to inject a value
// that is expected to be a string but is provided as a number.

Explanation: This highlights how type mismatches should be caught at compile time.

Constraints

  • The solution must be written in TypeScript.
  • The solution should leverage Vue 3's provide and inject APIs.
  • The solution should not rely on external Vue ecosystem libraries specifically designed for injection keys (e.g., vue-use).
  • The solution should be efficient and not introduce significant overhead.

Notes

  • Consider how to associate a unique key (like a Symbol) with its type.
  • Think about how to handle the case where an injected value might not be provided (e.g., using default values).
  • Your solution should provide a factory function or a class that facilitates the creation of these type-safe injection keys.
  • The goal is to emulate and improve upon the basic Symbol() approach for injection keys by adding TypeScript generics.
Loading editor...
typescript