Hone logo
Hone
Problems

Typed Provide/Inject in Vue with TypeScript

This challenge focuses on implementing a robust and type-safe provide/inject mechanism in Vue.js using TypeScript. Standard Vue's provide and inject lack strong typing, leading to potential runtime errors. This challenge asks you to create a system that leverages TypeScript to ensure type safety when providing and injecting dependencies across the component tree.

Problem Description

You need to create a generic TypedProvide and TypedInject utility function that allows you to provide and inject values with strong TypeScript typing. These utilities should work similarly to Vue's built-in provide and inject, but with the added benefit of type checking.

What needs to be achieved:

  • Create a TypedProvide<T> function that accepts a key and a value of type T and provides it to all descendants.
  • Create a TypedInject<T, K> function that accepts a key K and a default value of type T (optional) and injects the provided value of type T. If the key is not provided, it should throw an error.
  • The TypedInject function should ensure that the injected value's type matches the declared type T.
  • The system should be compatible with Vue 3's Composition API.

Key Requirements:

  • Type Safety: The primary goal is to ensure type safety. The TypeScript compiler should catch errors if the provided value's type doesn't match the injected type.
  • Generics: Utilize generics to achieve type safety for both the provided value and the injected value.
  • Key-Based Injection: Injection should be based on a unique key.
  • Optional Default Value: TypedInject should accept an optional default value of type T.
  • Vue 3 Compatibility: The solution must be compatible with Vue 3's Composition API.

Expected Behavior:

  • When a value is provided using TypedProvide, all descendant components that inject the same key should receive that value.
  • TypedInject should return the provided value if the key is found.
  • If the key is not found and a default value is provided, TypedInject should return the default value.
  • If the key is not found and no default value is provided, TypedInject should return undefined.
  • The TypeScript compiler should issue an error if the type of the provided value does not match the type specified in TypedInject.

Edge Cases to Consider:

  • What happens if the same key is provided multiple times in different parent components? (The last provided value should take precedence.)
  • How should the system handle circular dependencies (e.g., A provides to B, and B provides back to A)? (This is a complex issue, and a simple solution is acceptable for this challenge. A warning in the console is sufficient.)
  • What happens if you try to inject a key that doesn't exist and no default value is provided?

Examples

Example 1:

// ParentComponent.tsx
import { provide, reactive } from 'vue';
import { TypedProvide } from './your-solution'; // Assuming your solution is in this file

const user = reactive({ name: 'Alice', age: 30 });

provide('user', user);

// ChildComponent.tsx
import { inject, defineComponent } from 'vue';
import { TypedInject } from './your-solution';

export default defineComponent({
  setup() {
    const user = TypedInject<typeof user, 'user'>();

    return { user };
  }
});

Output: The user property in ChildComponent will be of type typeof user (reactive object with name and age). Explanation: The user object is provided in the parent and injected in the child with type safety.

Example 2:

// ParentComponent.tsx
import { provide } from 'vue';
import { TypedProvide } from './your-solution';

TypedProvide<string, 'apiKey'>('apiKey', 'your_api_key');

// ChildComponent.tsx
import { inject, defineComponent } from 'vue';
import { TypedInject } from './your-solution';

export default defineComponent({
  setup() {
    const apiKey = TypedInject<string, 'apiKey'>();

    return { apiKey };
  }
});

Output: The apiKey property in ChildComponent will be of type string. Explanation: A string is provided and injected with type safety.

Example 3: (Edge Case - Type Mismatch)

// ParentComponent.tsx
import { provide } from 'vue';
import { TypedProvide } from './your-solution';

TypedProvide<string, 'message'>('message', 123); // Providing a number instead of a string

// ChildComponent.tsx
import { inject, defineComponent } from 'vue';
import { TypedInject } from './your-solution';

export default defineComponent({
  setup() {
    const message = TypedInject<string, 'message'>();

    return { message };
  }
});

Output: The TypeScript compiler will issue an error indicating that the provided value (number) does not match the expected type (string). Explanation: Demonstrates the type safety feature.

Constraints

  • Code Size: Keep the solution concise and readable. Aim for under 100 lines of code.
  • Vue 3: The solution must be compatible with Vue 3's Composition API.
  • No External Libraries: Do not use any external libraries beyond Vue and TypeScript.
  • Performance: While not a primary focus, avoid unnecessarily complex or inefficient implementations.

Notes

  • Consider using TypeScript's utility types (e.g., typeof, Extract) to enhance type safety and code clarity.
  • Think about how to handle the key type safely. Using a string literal type for the key can provide even more type safety.
  • The circular dependency handling doesn't need to be perfect; a simple console warning is sufficient.
  • Focus on the core functionality of providing and injecting values with strong typing. Error handling beyond type checking can be simplified.
  • Remember to export your TypedProvide and TypedInject functions so they can be imported and used in other components.
Loading editor...
typescript