Design Patterns in Modern JavaScript Frameworks
Modular and Component-Based Design
Modern JavaScript frameworks like React, Vue, and Angular adopt modular and component-based design principles. This pattern breaks down the UI into independent, reusable components, with each component managing its own state and behavior. Component-based design follows the Single Responsibility Principle, making the code easier to maintain and test.
// React functional component example
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
In Vue, Single-File Components (SFCs) encapsulate templates, logic, and styles in a single file:
<!-- Vue Single-File Component example -->
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
State Management Patterns
As application complexity grows, state management becomes a critical challenge. Libraries like Redux and Vuex implement the Flux architecture, adopting a single source of truth and unidirectional data flow principles. This pattern ensures predictable and traceable state changes.
// Redux state management example
const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
}
const store = createStore(counterReducer);
Modern frameworks also introduce lighter state management solutions, such as React's Context API:
// React Context example
const CountContext = createContext();
function App() {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={{ count, setCount }}>
<ChildComponent />
</CountContext.Provider>
);
}
function ChildComponent() {
const { count, setCount } = useContext(CountContext);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
Observer Pattern and Reactive Systems
Vue's core is its reactive system, implemented using the Observer pattern. When data changes, views dependent on that data automatically update. This pattern is achieved through Object.defineProperty
or Proxy
for data interception.
// Simplified Vue reactivity implementation
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
dep.depend(); // Collect dependencies
return val;
},
set(newVal) {
val = newVal;
dep.notify(); // Notify updates
}
});
}
class Dep {
constructor() {
this.subscribers = [];
}
depend() {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target);
}
}
notify() {
this.subscribers.forEach(sub => sub());
}
}
React's Hooks also employ a similar observation mechanism, where useEffect
can subscribe to state changes:
// React useEffect example
function Example({ someProp }) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]); // Executes only when count changes
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Higher-Order Components and Render Props
Higher-Order Components (HOCs) are an advanced technique in React for reusing component logic. This pattern essentially applies the concept of higher-order functions from functional programming at the component level.
// React Higher-Order Component example
function withLoading(WrappedComponent) {
return function WithLoading({ isLoading, ...props }) {
return isLoading ? <div>Loading...</div> : <WrappedComponent {...props} />;
};
}
const EnhancedComponent = withLoading(MyComponent);
Render Props is another way to share code by passing a render function via props:
// Render Props example
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
// Usage
<MouseTracker render={({ x, y }) => (
<h1>The mouse position is ({x}, {y})</h1>
)} />
Dependency Injection and Inversion of Control
The Angular framework heavily uses Dependency Injection (DI) patterns, declaring dependencies via decorators, with the framework handling instantiation and injection.
// Angular service and DI example
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) {}
getData() {
return this.http.get('/api/data');
}
}
@Component({
selector: 'app-example',
templateUrl: './example.component.html'
})
export class ExampleComponent {
constructor(private dataService: DataService) {}
loadData() {
this.dataService.getData().subscribe(data => {
// Process data
});
}
}
React's Context API can also be seen as a lightweight implementation of Dependency Injection:
// React Context as a DI container
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Themed Button</button>;
}
Strategy Pattern and Composite Pattern
The Strategy pattern is useful in scenarios like form validation, allowing dynamic switching of algorithms or behaviors:
// Form validation Strategy pattern
const validationStrategies = {
required: (value) => !!value || 'Required field',
email: (value) => /.+@.+\..+/.test(value) || 'Invalid email',
minLength: (length) => (value) =>
value.length >= length || `Minimum ${length} characters`
};
function validate(formData, rules) {
return Object.entries(rules).reduce((errors, [field, fieldRules]) => {
fieldRules.forEach(({ strategy, params }) => {
const validator = typeof strategy === 'function'
? strategy
: validationStrategies[strategy];
const error = validator(formData[field], ...(params || []));
if (error) errors[field] = error;
});
return errors;
}, {});
}
The Composite pattern is common in UI tree structures, with React components embodying this pattern:
// React composite component example
function Card({ children }) {
return <div className="card">{children}</div>;
}
function CardHeader({ title }) {
return <div className="card-header">{title}</div>;
}
function CardBody({ content }) {
return <div className="card-body">{content}</div>;
}
// Composite usage
<Card>
<CardHeader title="Example Card" />
<CardBody content="This is card content..." />
</Card>
Proxy Pattern and Memento Pattern
The Proxy pattern is powerful in modern JavaScript, with Vue 3 using it for reactivity:
// Proxy implementation for reactivity
function reactive(target) {
const handler = {
get(target, key, receiver) {
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
trigger(target, key);
return true;
}
};
return new Proxy(target, handler);
}
function track(target, key) {
// Collect dependencies
}
function trigger(target, key) {
// Trigger updates
}
The Memento pattern is common in state management, such as implementing undo/redo functionality:
// Simple Memento pattern implementation
class EditorHistory {
constructor() {
this.states = [];
this.current = -1;
}
pushState(state) {
this.states = this.states.slice(0, this.current + 1);
this.states.push(JSON.parse(JSON.stringify(state)));
this.current = this.states.length - 1;
}
undo() {
if (this.current <= 0) return null;
this.current--;
return this.states[this.current];
}
redo() {
if (this.current >= this.states.length - 1) return null;
this.current++;
return this.states[this.current];
}
}
Factory Pattern and Singleton Pattern
The Factory pattern is useful for creating complex objects, especially when different types of objects need to be created based on conditions:
// Component factory example
function createComponent(type, props) {
switch (type) {
case 'button':
return new ButtonComponent(props);
case 'input':
return new InputComponent(props);
case 'select':
return new SelectComponent(props);
default:
throw new Error(`Unknown component type: ${type}`);
}
}
class ButtonComponent {
constructor(props) {
this.props = props;
}
render() {
return `<button class="${this.props.className}">${this.props.label}</button>`;
}
}
The Singleton pattern ensures a class has only one instance, with the Redux store being a classic example:
// Redux store Singleton
let storeInstance = null;
function configureStore(preloadedState) {
if (storeInstance) {
return storeInstance;
}
storeInstance = createStore(
rootReducer,
preloadedState,
applyMiddleware(thunk, logger)
);
return storeInstance;
}
Adapter Pattern and Facade Pattern
The Adapter pattern bridges incompatible interfaces, commonly seen in API wrappers:
// API Adapter example
class OldApi {
request(data, callback) {
// Legacy callback API
setTimeout(() => callback(data), 1000);
}
}
class ApiAdapter {
constructor(oldApi) {
this.oldApi = oldApi;
}
async fetch(data) {
return new Promise((resolve) => {
this.oldApi.request(data, resolve);
});
}
}
// Usage
const adapter = new ApiAdapter(new OldApi());
const result = await adapter.fetch({ id: 1 });
The Facade pattern provides a simplified interface to complex subsystems, such as Axios for HTTP requests:
// HTTP client Facade
const http = {
get(url, config) {
return axios.get(url, config);
},
post(url, data, config) {
return axios.post(url, data, config);
},
put(url, data, config) {
return axios.put(url, data, config);
},
delete(url, config) {
return axios.delete(url, config);
}
};
// Simplified interface usage
http.get('/api/users')
.then(response => console.log(response.data));
Middleware Pattern and Plugin Systems
The Middleware pattern allows extending functionality without modifying core code, as seen in Express and Redux:
// Redux middleware mechanism
const loggerMiddleware = store => next => action => {
console.log('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
return result;
};
const thunkMiddleware = store => next => action => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
return next(action);
};
const store = createStore(
rootReducer,
applyMiddleware(loggerMiddleware, thunkMiddleware)
);
Plugin systems allow third-party extensions of framework functionality, such as Vue plugins:
// Vue plugin example
const MyPlugin = {
install(Vue, options) {
// Add global methods or properties
Vue.myGlobalMethod = function () {
// Logic...
};
// Add global directives
Vue.directive('my-directive', {
bind(el, binding, vnode, oldVnode) {
// Logic...
}
});
// Inject component options
Vue.mixin({
created() {
// Logic...
}
});
// Add instance methods
Vue.prototype.$myMethod = function (methodOptions) {
// Logic...
};
}
};
// Plugin usage
Vue.use(MyPlugin, { someOption: true });
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:设计模式的优缺点分析
下一篇:设计模式的学习路径与资源推荐