Minimalist Redux Implementation in React
This challenge asks you to build a simplified version of Redux from scratch, focusing on the core concepts of a store, actions, and reducers. Implementing Redux yourself provides a deep understanding of its inner workings and how state management can be effectively handled in React applications. This exercise will solidify your understanding of unidirectional data flow and immutability.
Problem Description
You are tasked with creating a basic Redux implementation consisting of a Store, an Action, and a Reducer. The Store will hold the application's state, dispatch actions, and notify subscribers of state changes. Actions are plain JavaScript objects that describe an event that occurred. Reducers are pure functions that take the current state and an action and return a new state.
Key Requirements:
- Store:
- Should accept a reducer function during initialization.
- Should have a
dispatchmethod that accepts an action and calls the reducer. - Should have a
getStatemethod that returns the current state. - Should have a
subscribemethod that accepts a callback function. This callback should be invoked whenever the state changes. The callback should receive the new state as an argument.
- Action: A plain JavaScript object with a
typeproperty (required) and an optionalpayloadproperty. - Reducer: A pure function that takes the current state and an action as arguments and returns a new state. It should handle different action types and update the state accordingly.
Expected Behavior:
- Creating a store with a reducer should initialize the state to the initial state returned by the reducer when called with
undefinedas the initial state. - Dispatching an action should call the reducer with the current state and the action.
- The reducer should return a new state based on the action type.
- Subscribers should be notified of state changes after each dispatch.
- Multiple subscriptions should be handled correctly.
- Unsubscribing should prevent further notifications.
Edge Cases to Consider:
- What happens if the reducer doesn't handle a specific action type? (Should it return the current state unchanged).
- How to handle multiple subscribers?
- How to unsubscribe from the store?
- Ensure immutability of the state. The reducer must return a new state object, not modify the existing one.
Examples
Example 1:
Input:
Store Initialization:
const store = new Store( (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
});
Subscription:
store.subscribe( (newState) => console.log("State changed:", newState));
Dispatch Actions:
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'DECREMENT' });
Output:
State changed: 1
State changed: 2
State changed: 1
Example 2:
Input:
Store Initialization:
const store = new Store( (state = { count: 0 }, action) => {
switch (action.type) {
case 'ADD_COUNT':
return { ...state, count: state.count + action.payload };
default:
return state;
}
});
Dispatch Actions:
store.dispatch({ type: 'ADD_COUNT', payload: 5 });
store.dispatch({ type: 'ADD_COUNT', payload: 2 });
Output:
(Console logs within subscribers would show the state changes)
Example 3: (Unsubscribe)
Input:
Store Initialization:
const store = new Store( (state = 0, action) => state + action.payload);
Subscription:
const unsubscribe = store.subscribe( (newState) => console.log("State changed:", newState));
Dispatch Actions:
store.dispatch({ type: 'ADD_VALUE', payload: 10 });
Unsubscribe:
unsubscribe();
Dispatch Actions:
store.dispatch({ type: 'ADD_VALUE', payload: 5 });
Output:
State changed: 10
(No further console logs because of unsubscribe)
Constraints
- The
Storeshould maintain a single state object. - The reducer must be a pure function.
- The
subscribemethod should allow for multiple subscriptions. - The
unsubscribemethod should correctly remove a subscription. - The implementation should be reasonably efficient for a basic Redux implementation. Avoid unnecessary overhead.
- The code must be written in TypeScript.
Notes
- Focus on the core functionality of Redux. You don't need to implement middleware, combineReducers, or other advanced features.
- Think about how to handle state immutability. Returning a new state object is crucial.
- Consider using closures to maintain the state within the
Storeclass. - Test your implementation thoroughly with different action types and state updates.
- This is a simplified implementation. Real-world Redux has more features and optimizations. The goal here is to understand the fundamental principles.