Design patterns in state management libraries (Redux/Vuex)
State management libraries like Redux and Vuex are core tools in modern frontend development for handling complex application states. They achieve centralized data management and predictable updates through specific design patterns. The design philosophies behind these libraries are closely related to classic design patterns, and understanding these patterns can lead to more efficient use of state management tools.
Singleton Pattern and Global State Management
The core design of both Redux and Vuex adopts the singleton pattern, ensuring there is only one global state tree for the entire application. This design avoids synchronization issues caused by scattered states while providing a unified entry point for state access.
// Singleton implementation of the store in Redux
import { createStore } from 'redux';
const initialState = { count: 0 };
function reducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
}
const store = createStore(reducer); // Globally unique store instance
Vuex's implementation is similar:
// Singleton store in Vuex
import { createStore } from 'vuex';
const store = createStore({
state() {
return { count: 0 };
},
mutations: {
increment(state) {
state.count++;
}
}
});
Observer Pattern and State Subscription
State management libraries use the observer pattern to achieve automatic synchronization between components and state. When the state changes, all components subscribed to that state receive notifications and update accordingly.
Redux's subscription mechanism:
// Subscribe to state changes
const unsubscribe = store.subscribe(() => {
console.log('State changed:', store.getState());
});
// Trigger state changes
store.dispatch({ type: 'INCREMENT' });
// Unsubscribe
unsubscribe();
Vuex handles dependency tracking automatically through Vue's reactivity system:
<template>
<div>{{ $store.state.count }}</div>
</template>
<script>
export default {
mounted() {
// Automatically establishes dependencies
console.log(this.$store.state.count);
}
}
</script>
Command Pattern and Action/Commit
State changes are performed through explicit commands (Action/Mutation), reflecting the command pattern. Encapsulating state modifications as discrete commands makes changes traceable and replayable.
Actions in Redux:
// Action creator function
function increment() {
return { type: 'INCREMENT' };
}
// Trigger an action
store.dispatch(increment());
// Action with parameters
function addTodo(text) {
return { type: 'ADD_TODO', payload: { text } };
}
Mutations in Vuex:
const store = createStore({
mutations: {
addTodo(state, payload) {
state.todos.push(payload.text);
}
}
});
// Commit a mutation
store.commit('addTodo', { text: 'Learn Vuex' });
Decorator Pattern and Middleware
Redux's middleware system is a classic application of the decorator pattern, enhancing the dispatch
function without modifying the core logic.
// Custom logger middleware
const logger = store => next => action => {
console.log('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
return result;
};
// Apply middleware
const store = createStore(
reducer,
applyMiddleware(logger)
);
Vuex has a similar plugin mechanism:
const myPlugin = store => {
store.subscribe((mutation, state) => {
console.log(mutation.type, mutation.payload);
});
};
const store = createStore({
// ...
plugins: [myPlugin]
});
Strategy Pattern and Reducer Design
Redux's reducer function embodies the strategy pattern, selecting different state-handling strategies based on action types.
function todosReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: todo
);
default:
return state;
}
}
Composite Pattern and Modularization
Large-scale applications require modular organization of state management, reflecting the composite pattern.
Redux's combineReducers
:
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
todos: todosReducer,
visibilityFilter: visibilityFilterReducer
});
Vuex's module system:
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
Memento Pattern and Time-Travel Debugging
Redux DevTools' time-travel functionality is based on the memento pattern, allowing state snapshots to be saved and restored.
// Manual implementation of simple time travel
const states = [];
let currentState = 0;
function saveState(state) {
states.push(JSON.parse(JSON.stringify(state)));
currentState = states.length - 1;
}
function undo() {
if (currentState > 0) {
currentState--;
return states[currentState];
}
return null;
}
function redo() {
if (currentState < states.length - 1) {
currentState++;
return states[currentState];
}
return null;
}
Proxy Pattern and Getter Computations
Vuex's getters provide a mechanism for derived state, similar to the proxy pattern, enabling additional computations when accessing state.
const store = createStore({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done);
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length;
}
}
});
// Accessing a getter
store.getters.doneTodos; // -> [{ id: 1, text: '...', done: true }]
Factory Pattern and Action Creation
Redux's action creator functions and Vuex's action methods embody the factory pattern, encapsulating the creation process of action objects.
// Redux action factory function
function makeActionCreator(type, ...argNames) {
return function(...args) {
const action = { type };
argNames.forEach((arg, index) => {
action[argNames[index]] = args[index];
});
return action;
};
}
const ADD_TODO = 'ADD_TODO';
const addTodo = makeActionCreator(ADD_TODO, 'text', 'id');
console.log(addTodo('Learn Redux', 1));
// { type: 'ADD_TODO', text: 'Learn Redux', id: 1 }
Asynchronous actions in Vuex:
actions: {
async fetchUser({ commit }, userId) {
try {
const user = await api.fetchUser(userId);
commit('SET_USER', user);
} catch (error) {
commit('SET_ERROR', error);
}
}
}
State Pattern and State Machines
Complex state logic can be managed using the state pattern, which is reflected in both Redux and Vuex.
// Order state machine
const orderStateMachine = {
pending: {
confirm: 'confirmed',
cancel: 'cancelled'
},
confirmed: {
ship: 'shipped',
cancel: 'cancelled'
},
shipped: {
deliver: 'delivered'
}
};
function orderReducer(state = { status: 'pending' }, action) {
const nextStatus = orderStateMachine[state.status]?.[action.type];
if (!nextStatus) return state;
return { ...state, status: nextStatus };
}
Dependency Injection and Provider Pattern
React-Redux's Provider
component implements the dependency injection pattern, injecting the store into the component tree.
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// Components access the store via connect
function connect(mapStateToProps) {
return function(WrappedComponent) {
return function(props) {
const state = useReduxState();
const stateProps = mapStateToProps(state);
return <WrappedComponent {...props} {...stateProps} />;
};
};
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:组件通信中的设计模式选择