Implementing a Custom Store Subscription Mechanism in Vue
This challenge focuses on building a core piece of reactive state management in Vue: the subscription mechanism. You'll implement a way for components to react to changes in a central store, enabling dynamic updates and a more robust application architecture. This is fundamental to how many popular state management libraries like Pinia and Vuex operate.
Problem Description
Your task is to create a simplified store with a subscribe method that allows external code (typically Vue components) to register callback functions. These callbacks should be invoked whenever the store's state changes.
What needs to be achieved:
You need to design and implement a Store class in TypeScript that manages a piece of state and provides a mechanism to subscribe to its changes.
Key requirements:
- State Management: The
Storeshould hold a piece of state. - State Mutation: A method (e.g.,
setStateormutate) should exist to update the state. - Subscription: A
subscribemethod should allow registering callback functions. - Callback Invocation: When the state is mutated, all registered subscribers should be called.
- Unsubscription: A mechanism to unsubscribe from future state changes is crucial.
Expected behavior:
- When
subscribeis called, the provided callback function is added to a list of active subscribers. - When the state is updated using the mutation method, each function in the list of subscribers is executed.
- When an unsubscribe function (returned by
subscribe) is called, the corresponding callback is removed from the subscriber list, and it will no longer be invoked for future state changes.
Edge cases to consider:
- Subscribing multiple times with the same callback.
- Unsubscribing multiple times.
- Mutating the state when there are no subscribers.
- What happens if a subscriber function throws an error? (For this challenge, we assume subscribers will not throw errors to keep the scope manageable).
Examples
Let's illustrate with a conceptual example.
Example 1: Basic Subscription and Mutation
// Assume a simplified Store class exists with subscribe and setState methods
// Initial state
const store = new Store({ count: 0 });
// Callback function to be notified of changes
const subscriber = (newState: { count: number }) => {
console.log('State changed:', newState);
};
// Subscribe to state changes
const unsubscribe = store.subscribe(subscriber);
// Mutate the state
store.setState({ count: 1 });
// Expected Console Output:
// State changed: { count: 1 }
// Mutate the state again
store.setState({ count: 2 });
// Expected Console Output:
// State changed: { count: 2 }
Example 2: Unsubscribing
// Assume a simplified Store class exists with subscribe and setState methods
const store = new Store({ user: 'Guest' });
const subscriber1 = (newState: { user: string }) => {
console.log('Subscriber 1 notified:', newState);
};
const subscriber2 = (newState: { user: string }) => {
console.log('Subscriber 2 notified:', newState);
};
const unsubscribe1 = store.subscribe(subscriber1);
store.subscribe(subscriber2); // No need to store unsubscribe for this example
store.setState({ user: 'Alice' });
// Expected Console Output:
// Subscriber 1 notified: { user: 'Alice' }
// Subscriber 2 notified: { user: 'Alice' }
unsubscribe1(); // Unsubscribe subscriber1
store.setState({ user: 'Bob' });
// Expected Console Output:
// Subscriber 2 notified: { user: 'Bob' }
// (Subscriber 1 is no longer called)
Constraints
- The
Storeclass should be implemented using TypeScript. - The state can be of any generic type.
- The
subscribemethod should return a function that, when called, removes the subscriber. - Efficiency is a secondary concern for this challenge; correctness and clarity of implementation are paramount.
Notes
- Think about how you will store the list of subscribers. An array or a Set might be suitable.
- Consider the type safety provided by TypeScript. Use generics to make your
Storeflexible. - The
setStatemethod should be responsible for triggering the notifications. - Your implementation of
setStateshould likely take the entire new state object, not just partial updates, for simplicity in this challenge.