js中的设计模式
文章目录
- 一、创建型模式
- 1、单例模式(Singleton):确保一个类只有一个实例
- vue中的单例模式:
- 1、全局状态管理(如 vuex/Pinia):vuex或pinia的store是典型的单例模式实现,确保全局状态的唯一性
- 2、单例组件:全局弹框/toast
- react中的单例模式
- 1、全局状态管理(如redux/context api)
- 2、单例hooks
- 2、工厂模式:它提供了一种创建对象的最佳方式,而无需向客户端暴露对象创建的逻辑
- 在vue中的应用
- 1、组件工厂:可以使用工厂函数动态创建组件:
- 2、vue插件工厂
- 3、异步组件工厂
- react中的应用
- 1、组件工厂
- 2、高阶组件工厂
- 3、上下文组件工厂
- 3、建造者模式:分步骤构建复杂对象
- 在vue中的应用
- 1、复杂表单构建:可以使用建造者模式来构建包含多个字段、验证规则和提交逻辑的复杂表单
- 2、复杂组件配置
- 在react中的应用
- 1、表单构建器:在react中同样可以使用建造者构建复杂表单,尤其是结合hooks使用更加的灵活
- 2、复杂组件配置
- 二、结构型模式
- 1、装饰器模式-高阶组件(HOC)
- 2、适配器模式-API兼容
- 3、代理模式(Proxy)
- 在vue中应用
- 1、适配器模式:vue中适配模式的典型应用就是组件接口适配:
- 2、装饰器:vue结合typescript时可以使用ES装饰器
- react中应用
- 1、装饰器:react中高阶组件本质上就是装饰器模式实现的
- 2、适配器模式:适配不同的数据源或者适配不同组件库
- 3、 适配组件库
- 三、行为型模式
- 1、观察者模式:定义对象间一对多依赖关系,当一个对象状态改变时,所有依赖它的对象均会被改变
- 在vue中的应用
- 1、vue2响应式系统基于Object.defineProperty实现观察者模式,包含三个核心大类:
- 2、计算属性和侦听器也是基于观察者模式实现的
- 3、自定义事件的实现:vue组件间的自定义事件也是观察着模式的应用
- react中的应用
- 1、自定义事件总线实现跨组件间通信:在React中,当需要实现不相关组件间的通信时,可以创建一个事件总线(Event Bus)作为观察者模式的实现
- 2、 解决父子组件不必要的重新渲染问题
- 3、使用MobX状态管理库
- 2、发布-订阅模式(pub/sub):消息的发送者(发布者)不会直接将消息发送给特定的接收者(订阅者),而是通过消息通道广播
- 在vue中的应用
- 1、EventBus(事件总线):Vue中常用EventBus实现发布-订阅模式
- 2、vuex的订阅机制:Vuex的store.subscribe方法也实现了发布-订阅模式:
- 在react中的应用
- 1、自定义事件总线:react中没有内置的事件总线,但可以自己自定义
- 2、使用Context API和useReducer:React的Context API结合useReducer可以实现类似发布-订阅的模式:
- 四、其他应用模式
- 1、模块模式:私有变量实现
- 在vue中的应用
- 1、单文件组件(SFC): vue的单文件组件本身就是一个模块模式的体现
- 2、组合式API的模块化
- 3、vuex模块
- 在react中的应用
- 1、自定义Hooks模块
- 2、高阶组件模块
- 3、Context模块
- 2、中间件模式:Express中间件
- 在vue中的应用
- 1、vuex中间件(插件)
- 2、Vue Router导航守卫:Vue Router的导航守卫本质上是一种中间件模式
- 3、自定义指定中的中间件
- react中的应用
- 1、Redux中间件:React中最典型的中间件
- 2、 React Router中间件:可以通过高阶组件实现路由守卫等功能
- 3、自定义Hooks中间件模式
一、创建型模式
1、单例模式(Singleton):确保一个类只有一个实例
class Logger {constructor() {if (!Logger.instance) {this.logs = [];Logger.instance = this;}return Logger.instance;}log(message) {this.logs.push(message);console.log(message);}
}// 使用
const logger1 = new Logger();
const logger2 = new Logger();
console.log(logger1 === logger2); // true
logger1.log("Hello Singleton");
单例模式在前端框架vue和react中的应用主要体现在全局状态管理和组件复用上,确保某些关键对象或组件在整个应用中只存在一个实例。
vue中的单例模式:
1、全局状态管理(如 vuex/Pinia):vuex或pinia的store是典型的单例模式实现,确保全局状态的唯一性
// store.js (Vuex 示例)
import { createStore } from 'vuex';const store = createStore({state: { count: 0 },mutations: { increment(state) { state.count++; } }
});export default store; // 整个应用共享同一个 store 实例
特点:全局共享一个store,避免重复创建,保持状态的唯一性
2、单例组件:全局弹框/toast
<!-- Toast.vue -->
<template><div v-if="isVisible">{{ message }}</div>
</template><script>
let instance = null;export default {data() {return { isVisible: false, message: '' };},methods: {show(msg) {if (!instance) instance = this;this.message = msg;this.isVisible = true;setTimeout(() => this.hide(), 2000);},hide() { this.isVisible = false; }}
};
</script>
使用方式:
const toast = new Vue(Toast).$mount();
document.body.appendChild(toast.$el);
toast.show('操作成功!'); // 全局唯一实例
react中的单例模式
1、全局状态管理(如redux/context api)
redux store 或 react context 的provides是单例模式的典型应用:
// store.js (Redux 示例)
import { createStore } from 'redux';const store = createStore(rootReducer);
export default store; // 整个应用共享同一个 store
特点:避免多个store 实例导致状态不一致
2、单例hooks
// useCounter.js (单例 Hook)
import { useState,useEffect } from 'react';let globalState = { count: 0 };
const listeners = new Set();export function useCounter() {const [state, setState] = useState(globalState);const setGlobalState = (newState) => {globalState = { ...globalState, ...newState };listeners.forEach(listener => listener(globalState));};useEffect (() => {listeners.add(setState);return () => listeners.delete(setState);}, []);return [state, setGlobalState];
}
使用方法
// 在任何组件中使用,共享同一个状态
const [counter, setCounter] = useCounter();
2、工厂模式:它提供了一种创建对象的最佳方式,而无需向客户端暴露对象创建的逻辑
工厂模式核心思想就是将对象的创建和使用分离,通过一个公共的方法来创建对象。提高代码的可维护性和可扩展性
工厂模式主要分为三种:简单的工厂模式、工厂方法模式、抽象工厂模式
在vue中的应用
1、组件工厂:可以使用工厂函数动态创建组件:
// 组件工厂函数
function createComponent(options) {return {data() {return {message: options.message || 'Default message'}},template: `<div>{{ message }}</div>`}
}// 使用工厂创建组件
const component1 = createComponent({ message: 'Hello from Factory' })
const component2 = createComponent() // 使用默认消息// 全局注册
Vue.component('dynamic-component', createComponent({ message: 'Registered' }))
2、vue插件工厂
function createPlugin(options = {}) {return {install(Vue) {Vue.prototype.$myPlugin = {showMessage(msg) {alert(options.prefix + msg)}}}}
}// 使用
Vue.use(createPlugin({ prefix: 'Notice: ' }))
3、异步组件工厂
const AsyncComponent = () => ({component: import('./MyComponent.vue'),loading: LoadingComponent,error: ErrorComponent,delay: 200,timeout: 3000
})
react中的应用
1、组件工厂
function createButton(type) {const Button = ({ children, onClick }) => {const className = `btn btn-${type}`;return (<button className={className} onClick={onClick}>{children}</button>);};return Button;
}// 使用工厂创建不同类型的按钮
const PrimaryButton = createButton('primary');
const DangerButton = createButton('danger');// 使用
<PrimaryButton onClick={handleClick}>Submit</PrimaryButton>
2、高阶组件工厂
function withLoading(WrappedComponent, loadingMessage = 'Loading...') {return function WithLoading({ isLoading, ...props }) {return isLoading ? <div>{loadingMessage}</div>: <WrappedComponent {...props} />;};
}// 使用
const UserListWithLoading = withLoading(UserList, 'Fetching users...');
3、上下文组件工厂
function createThemeContext(defaultTheme) {const ThemeContext = React.createContext(defaultTheme);function ThemeProvider({ children, theme }) {return (<ThemeContext.Provider value={theme || defaultTheme}>{children}</ThemeContext.Provider>);}function useTheme() {return React.useContext(ThemeContext);}return { ThemeProvider, useTheme };
}// 使用
const { ThemeProvider, useTheme } = createThemeContext('light');
注意:
1、使用createContext来创建上下文的时候,组件内部包含了 Provide 和 Consumer组件
ThemeContext.Provider 是标准的 context provide用法
useContext(themeContext)获取
2、组件工厂和高阶组件工厂的区别:类似与函数和高阶函数的区别
组件工厂和高阶组件工厂都是基于工厂模式实现的,区别在于组件工厂是直接生产react组件的函数,它封装了组件的创建逻辑,返回了一个可用的react组件,而高阶组件是生成高阶组件的函数,它接收一个组件作为参数,返回一个增强后的新组件
3、建造者模式:分步骤构建复杂对象
class PizzaBuilder {constructor() {this.pizza = {};}setBase(base) {this.pizza.base = base;return this;}setSauce(sauce) {this.pizza.sauce = sauce;return this;}setCheese(cheese) {this.pizza.cheese = cheese;return this;}build() {return this.pizza;}
}const pizza = new PizzaBuilder().setBase('thin crust').setSauce('tomato').setCheese('mozzarella').build();
建造者模式将复杂对象的构建过程进行细化,使得同样的构建过程可以创建不同的表示,在前端框架Vue和React中,建造者模式可以用于构建复杂的组件、表单或应用配置
在vue中的应用
1、复杂表单构建:可以使用建造者模式来构建包含多个字段、验证规则和提交逻辑的复杂表单
// FormBuilder.js - 建造者类
class FormBuilder {constructor() {this.fields = [];this.submitHandler = null;}addField(name, type, options = {}) {this.fields.push({name,type,label: options.label || name,required: options.required || false,validation: options.validation || null});return this; // 支持链式调用}setSubmitHandler(handler) {this.submitHandler = handler;return this;}build() {return {data: () => ({formData: this.fields.reduce((acc, field) => {acc[field.name] = '';return acc;}, {}),fields: this.fields}),methods: {validate() {return this.fields.every(field => {if (field.required && !this.formData[field.name]) return false;if (field.validation && !field.validation(this.formData[field.name])) return false;return true;});},onSubmit() {if (this.validate() && this.submitHandler) {this.submitHandler(this.formData);}}}};}
}// 使用建造者创建Vue表单组件
const loginForm = new FormBuilder().addField('username', 'text', { required: true, validation: val => val.length >= 6 }).addField('password', 'password', { required: true, validation: val => val.length >= 8 }).setSubmitHandler(formData => {console.log('Form submitted:', formData);}).build();// 在Vue组件中使用
new Vue({el: '#app',...loginForm
});
这种实现方式将表单的构建过程与表单组件本身分离,使得表单的创建更加灵活和可维护
2、复杂组件配置
对于需要多个配置选项的Vue组件,可以使用建造者模式来简化配置过程
// ModalBuilder.js
class ModalBuilder {constructor() {this.title = 'Modal';this.content = '';this.buttons = [];this.size = 'md';this.onClose = null;}setTitle(title) {this.title = title;return this;}setContent(content) {this.content = content;return this;}addButton(text, handler, options = {}) {this.buttons.push({ text, handler, ...options });return this;}setSize(size) {this.size = size;return this;}setOnClose(handler) {this.onClose = handler;return this;}build() {return {template: `<div class="modal" :class="'modal-' + size"><div class="modal-header"><h3>{{ title }}</h3><button @click="close">×</button></div><div class="modal-body">{{ content }}</div><div class="modal-footer"><button v-for="(btn, index) in buttons" :key="index"@click="btn.handler":class="btn.class">{{ btn.text }}</button></div></div>`,props: {title: { type: String, default: this.title },content: { type: String, default: this.content },buttons: { type: Array, default: () => this.buttons },size: { type: String, default: this.size }},methods: {close() {if (this.onClose) this.onClose();this.$emit('close');}}};}
}// 使用建造者创建模态框组件
const confirmModal = new ModalBuilder().setTitle('确认删除').setContent('您确定要删除此项吗?此操作不可撤销。').addButton('取消', () => console.log('取消')).addButton('确定', () => console.log('确定'), { class: 'btn-primary' }).setOnClose(() => console.log('模态框关闭')).build();// 注册为全局组件
Vue.component('confirm-modal', confirmModal);
这种方式使得复杂组件的配置更加直观和易于管理
在react中的应用
1、表单构建器:在react中同样可以使用建造者构建复杂表单,尤其是结合hooks使用更加的灵活
// FormBuilder.js
class FormBuilder {constructor() {this.fields = [];this.initialValues = {};this.validations = {};}addField(name, type, options = {}) {this.fields.push({ name, type, ...options });this.initialValues[name] = options.initialValue || '';if (options.validation) {this.validations[name] = options.validation;}return this;}build() {const fields = this.fields;const initialValues = this.initialValues;const validations = this.validations;return function FormComponent({ onSubmit }) {const [values, setValues] = React.useState(initialValues);const [errors, setErrors] = React.useState({});const handleChange = (name) => (e) => {setValues({ ...values, [name]: e.target.value });};const validate = () => {const newErrors = {};let isValid = true;Object.keys(validations).forEach(name => {const validateFn = validations[name];if (!validateFn(values[name])) {newErrors[name] = true;isValid = false;}});setErrors(newErrors);return isValid;};const handleSubmit = (e) => {e.preventDefault();if (validate() && onSubmit) {onSubmit(values);}};return (<form onSubmit={handleSubmit}>{fields.map(field => (<div key={field.name}><label>{field.label || field.name}</label><inputtype={field.type}value={values[field.name]}onChange={handleChange(field.name)}/>{errors[field.name] && <span>验证失败</span>}</div>))}<button type="submit">提交</button></form>);};}
}// 使用建造者创建表单组件
const LoginForm = new FormBuilder().addField('username', 'text', { label: '用户名', validation: val => val.length >= 6 }).addField('password', 'password', { label: '密码', validation: val => val.length >= 8 }).build();// 使用表单组件
function App() {const handleSubmit = (values) => {console.log('表单提交:', values);};return (<div><h1>登录</h1><LoginForm onSubmit={handleSubmit} /></div>);
}
这种实现方式将表单的构建逻辑与渲染逻辑分离,使得表单的创建更加灵活
2、复杂组件配置
React中可以使用建造者模式来构建具有多个配置选项的复杂组件。
// CardBuilder.js
class CardBuilder {constructor() {this.title = 'Card';this.content = '';this.actions = [];this.styles = {};this.onClick = null;}setTitle(title) {this.title = title;return this;}setContent(content) {this.content = content;return this;}addAction(label, handler) {this.actions.push({ label, handler });return this;}setStyles(styles) {this.styles = styles;return this;}setOnClick(handler) {this.onClick = handler;return this;}build() {const title = this.title;const content = this.content;const actions = this.actions;const styles = this.styles;const onClick = this.onClick;return function Card(props) {return (<div style={{ border: '1px solid #ddd', borderRadius: '4px', padding: '16px', margin: '16px', ...styles,...props.style }}onClick={onClick}>{title && <h3>{title}</h3>}<div>{content}</div>{actions.length > 0 && (<div style={{ marginTop: '16px' }}>{actions.map((action, index) => (<button key={index} onClick={action.handler}style={{ marginRight: '8px' }}>{action.label}</button>))}</div>)}</div>);};}
}// 使用建造者创建卡片组件
const UserCard = new CardBuilder().setTitle('用户信息').setContent('姓名: 张三\n年龄: 30').addAction('编辑', () => console.log('编辑')).addAction('删除', () => console.log('删除')).setStyles({ backgroundColor: '#f5f5f5' }).build();// 使用卡片组件
function App() {return (<div><UserCard /></div>);
}
工厂模式和建造者模式的区别:
工厂模式是提供一个创建对象的接口,让子类决定实例化那个类
建造者模式是将一个复杂对象的构建与其分离,使得同样的构建过程可以创建不同的表示
主要区别:
对比维度 | 工厂模式 | 建造者模式 |
---|---|---|
关注点 | 对象创建(生产什么) | 对象构建过程(如何生产) |
复杂度 | 创建单一产品 | 创建复杂对象(由多个部分组成) |
构建过程 | 一步到位,直接返回完整对象 | 分步骤构建,最后返回完整对象 |
灵活性 | 相对固定,创建特定类型对象 | 更灵活,可以控制构建过程和最终表示 |
产品变化 | 通过不同工厂创建不同产品 | 通过不同建造者或不同构建步骤创建不同表示 |
典型场景 | 创建单一对象,隐藏具体类 | 创建复杂对象,需要多个步骤或配置 |
二、结构型模式
1、装饰器模式-高阶组件(HOC)
// React高阶组件示例
function withLoading(WrappedComponent) {return function WithLoading({ isLoading, ...props }) {if (isLoading) {return <div>Loading...</div>;}return <WrappedComponent {...props} />;};
}// 普通组件
function DataList({ data }) {return (<ul>{data.map(item => <li key={item.id}>{item.name}</li>)}</ul>);
}// 增强后的组件
const DataListWithLoading = withLoading(DataList);// 使用
<DataListWithLoading isLoading={true}data={[{id: 1, name: 'Item 1'}]}
/>
2、适配器模式-API兼容
// 新旧API适配
// 旧版API
class OldAPI {getUsers() {return fetch('/api/users.json').then(res => res.json());}
}// 新版API
class NewAPI {fetchUsers() {return axios.get('/api/v2/users');}
}// 适配器
class APIAdapter {constructor(api) {this.api = api;}getUsers() {if (this.api.getUsers) {return this.api.getUsers();} else if (this.api.fetchUsers) {return this.api.fetchUsers().then(res => res.data);}throw new Error('API method not supported');}
}// 使用
const oldApi = new OldAPI();
const adapter = new APIAdapter(oldApi);
adapter.getUsers().then(users => console.log(users));
两种模式区别:
特性 | 装饰器模式 | 适配器模式 |
---|---|---|
目的 | 动态扩展功能 | 接口转换兼容 |
接口关系 | 装饰器和被装饰对象实现相同接口 | 适配器实现目标接口,适配者有不同接口 |
对象关系 | 装饰类持有被装饰对象引用 | 适配类持有被适配对象引用 |
扩展性 | 支持叠加多个装饰器 | 通常一对一适配 |
典型场景 | 功能增强(如缓冲、日志) | 旧系统适配、第三方库集成 |
3、代理模式(Proxy)
特点:控制对象的访问
const person = {name: "John",age: 30
};const personProxy = new Proxy(person, {get(target, prop) {if (prop === 'age') {return target[prop] + ' years';}return target[prop];},set(target, prop, value) {if (prop === 'age' && value < 0) {throw new Error("Age cannot be negative");}target[prop] = value;return true;}
});console.log(personProxy.age); // "30 years"
personProxy.age = 25; // OK
personProxy.age = -5; // Error
代理模式(Proxy Pattern)在前端框架Vue和React中主要用于解决跨域请求、API转发和开发环境下的接口代理问题(此处省略代码)
在vue中应用
1、适配器模式:vue中适配模式的典型应用就是组件接口适配:
// 适配不同滑动组件接口的例子
<template><!-- 进行接口转换 --><nb-swiper :prop-x="propX" :prop-yy="propZ" :prop-z="propW" />
</template><script>
export default {props: {// 接受原本Swiper的props和NbSwiper支持的propspropX: String,propY: String,propYy: String,propZ: String,propW: String,}
}
</script>
2、装饰器:vue结合typescript时可以使用ES装饰器
// 表单校验装饰器
export function Validate(refName: string) {return function (target: any, name: string, descriptor: PropertyDescriptor) {const fn = target[name];descriptor.value = function (...args: any[]) {(this as any).$refs[refName].validate((valid: boolean) => {if (valid) {fn.call(this, ...args);} else {console.log('error submit!!');return false;}});};};
}// 使用装饰器
import { Validate } from '@/utils/decorator'
export default class TestForm extends Vue {@Validate('formName')async submitForm() {// 业务逻辑}
}
react中应用
1、装饰器:react中高阶组件本质上就是装饰器模式实现的
// 布局装饰器高阶组件
function ExcludeLayout(opt) {return function(WrappedComponent) {return class extends Component {render() {const { location = { pathname: '' } } = this.props;if (opt.routes.indexOf(location.pathname) > -1) {return <BlankLayout {...this.props} />}return <WrappedComponent {...this.props} />;}}}
}// 使用装饰器
@ExcludeLayout({routes: ['/org/department']
})
class BasicLayout extends Component {// 布局实现
}
2、适配器模式:适配不同的数据源或者适配不同组件库
适配数据源:
class APIAdapter {constructor(legacyAPI) {this.api = legacyAPI;}fetchData() {// 将旧API的响应格式适配为组件需要的格式return this.api.getData().then(response => ({items: response.data.list,total: response.data.count}));}
}
3、 适配组件库
const ButtonAdapter = ({ variant, children }) => {// 将设计系统的variant映射到具体组件库的propsconst mappedVariant = variant === 'primary' ? 'default' : 'text';return <ThirdPartyButton type={mappedVariant}>{children}</ThirdPartyButton>;
};
三、行为型模式
1、观察者模式:定义对象间一对多依赖关系,当一个对象状态改变时,所有依赖它的对象均会被改变
class Subject {constructor() {this.observers = [];}subscribe(observer) {this.observers.push(observer);}unsubscribe(observer) {this.observers = this.observers.filter(obs => obs !== observer);}notify(data) {this.observers.forEach(observer => observer.update(data));}
}class Observer {update(data) {console.log('Received data:', data);}
}const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();subject.subscribe(observer1);
subject.subscribe(observer2);subject.notify('Hello observers!');
在vue中的应用
1、vue2响应式系统基于Object.defineProperty实现观察者模式,包含三个核心大类:
observer类:负责数据劫持,在数据存取时进行依赖收集并通知更新
dep类:作为依赖收集器,存储所有订阅该数据的Watcher
Watcher类:观察者,负责订阅Dep并在数据更新时进行更新操作
// 简化版实现
function defineReactive(obj, key, val) {const dep = new Dep();// 递归观察子属性let childOb = observe(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = val;if (Dep.target) {dep.depend(); // 收集依赖if (childOb) {childOb.dep.depend(); // 收集子对象的依赖if (Array.isArray(value)) {dependArray(value); // 处理数组依赖}}}return value;},set: function reactiveSetter(newVal) {const value = val;if (newVal === value) {return;}val = newVal;childOb = observe(newVal); // 新值也需要被观察dep.notify(); // 通知更新}});
}
每个组件实例对应一个渲染Watcher,当数据变化时,Dep会通知所有订阅的Watcher进行更新
2、计算属性和侦听器也是基于观察者模式实现的
export default {data() {return {count: 0};},computed: {doubleCount() {return this.count * 2;}},watch: {count(newVal, oldVal) {console.log(`count changed from ${oldVal} to ${newVal}`);}}
};
3、自定义事件的实现:vue组件间的自定义事件也是观察着模式的应用
// 子组件
this.$emit('custom-event', data);// 父组件
<child-component @custom-event="handleEvent" />
react中的应用
1、自定义事件总线实现跨组件间通信:在React中,当需要实现不相关组件间的通信时,可以创建一个事件总线(Event Bus)作为观察者模式的实现
class EventBus {subscribers = {};subscribe = (event, callback) => {if (!this.subscribers[event]) {this.subscribers[event] = [];}this.subscribers[event].push(callback);};unsubscribe = (event, callback) => {if (this.subscribers[event]) {this.subscribers[event] = this.subscribers[event].filter((cb) => cb !== callback,);}};publish = (event, data) => {if (this.subscribers[event]) {this.subscribers[event].forEach((callback) => {callback(data);});}};
}export default new EventBus();
2、 解决父子组件不必要的重新渲染问题
React中父组件状态变化会导致所有子组件重新渲染,使用观察者模式可以避免这种情况:
// 父组件
function Parent() {const eventBus = useEventBus();return (<><ChildL eventBus={eventBus} /><ChildR eventBus={eventBus} /></>);
}// 子组件L
function ChildL({ eventBus }) {const [data, setData] = useState(null);useEffect(() => {const handler = (newData) => setData(newData);eventBus.subscribe('data-change', handler);return () => eventBus.unsubscribe('data-change', handler);}, [eventBus]);// ...
}
3、使用MobX状态管理库
MobX是基于观察者模式的React状态管理库,其核心概念:
import { observable, action } from 'mobx';class Store {@observable count = 0;@actionincrement() {this.count += 1;}
}const store = new Store();// React组件
import { observer } from 'mobx-react';const Counter = observer(() => {return (<div><h1>Count: {store.count}</h1><button onClick={store.increment}>Increment</button></div>);
});
MobX自动追踪被观察数据的变化,并只更新依赖这些数据的组件,实现了高效的状态管理
2、发布-订阅模式(pub/sub):消息的发送者(发布者)不会直接将消息发送给特定的接收者(订阅者),而是通过消息通道广播
class EventBus {constructor() {this.events = {};}subscribe(event, callback) {if (!this.events[event]) {this.events[event] = [];}this.events[event].push(callback);}publish(event, data) {if (!this.events[event]) return;this.events[event].forEach(callback => callback(data));}
}const bus = new EventBus();bus.subscribe('message', data => {console.log('First subscriber:', data);
});bus.subscribe('message', data => {console.log('Second subscriber:', data);
});bus.publish('message', 'Hello subscribers!');
在vue中的应用
1、EventBus(事件总线):Vue中常用EventBus实现发布-订阅模式
// 创建事件总线
const EventBus = new Vue();// 发布者组件
EventBus.$emit('event-name', payload);// 订阅者组件
EventBus.$on('event-name', (payload) => {// 处理事件
});// 取消订阅
EventBus.$off('event-name');
2、vuex的订阅机制:Vuex的store.subscribe方法也实现了发布-订阅模式:
store.subscribe((mutation, state) => {// 每次mutation后调用console.log(mutation.type);console.log(mutation.payload);
});
在react中的应用
1、自定义事件总线:react中没有内置的事件总线,但可以自己自定义
class EventEmitter {constructor() {this.events = {};}on(event, listener) {if (!this.events[event]) {this.events[event] = [];}this.events[event].push(listener);}emit(event, ...args) {if (this.events[event]) {this.events[event].forEach(listener => listener(...args));}}off(event, listener) {if (this.events[event]) {this.events[event] = this.events[event].filter(l => l !== listener);}}
}// 全局事件总线
export const eventBus = new EventEmitter();
2、使用Context API和useReducer:React的Context API结合useReducer可以实现类似发布-订阅的模式:
const StateContext = React.createContext();
const DispatchContext = React.createContext();function App() {const [state, dispatch] = useReducer(reducer, initialState);return (<StateContext.Provider value={state}><DispatchContext.Provider value={dispatch}>{/* 子组件 */}</DispatchContext.Provider></StateContext.Provider>);
}// 子组件中使用
function Child() {const state = useContext(StateContext);const dispatch = useContext(DispatchContext);// 发布动作const handleClick = () => {dispatch({ type: 'ACTION_TYPE', payload: data });};// 订阅状态return <div>{state.value}</div>;
}
观察者模式和订阅者模式看似相似,其实还是有很多不同
特性 | 观察者模式 | 发布-订阅模式 |
---|---|---|
耦合度 | 观察者和被观察者直接耦合 | 发布者和订阅者通过中介解耦 |
通信方式 | 直接通知 | 通过消息通道/事件总线 |
关系 | 一对多 | 多对多 |
实现复杂度 | 相对简单 | 相对复杂 |
动态性 | 运行时关系不易改变 | 订阅关系可以动态变化 |
典型实现 | 主题(subject)维护观察者列表 | 有独立的事件调度中心 |
四、其他应用模式
1、模块模式:私有变量实现
// 使用闭包实现私有变量
const counterModule = (() => {let count = 0; // 私有变量const increment = () => {count++;console.log(`Count: ${count}`);};const reset = () => {count = 0;console.log('Counter reset');};return {increment,reset,getCurrentCount: () => count};
})();counterModule.increment(); // Count: 1
counterModule.increment(); // Count: 2
console.log(counterModule.getCurrentCount()); // 2
counterModule.reset(); // Counter reset
在vue中的应用
1、单文件组件(SFC): vue的单文件组件本身就是一个模块模式的体现
<template><!-- 视图模板 -->
</template><script>
// 私有逻辑
const privateData = 'hidden';export default {// 公共接口data() {return {publicData: 'visible'}},methods: {publicMethod() {console.log(privateData);}}
}
</script><style>
/* 私有样式 */
</style>
2、组合式API的模块化
// useCounter.js
export function useCounter() {// 私有状态let count = ref(0);// 私有方法function incrementBy(amount) {count.value += amount;}// 公共接口return {count,increment: () => incrementBy(1),decrement: () => incrementBy(-1)};
}
3、vuex模块
// userModule.js
const state = { user: null }; // 私有状态const mutations = { // 私有mutationsSET_USER(state, user) {state.user = user;}
};const actions = { // 公共接口login({ commit }, credentials) {return api.login(credentials).then(user => {commit('SET_USER', user);});}
};export default {namespaced: true,state,mutations,actions
};
在react中的应用
1、自定义Hooks模块
// useCounter.js
function useCounter(initialValue = 0) {// 私有状态const [count, setCount] = useState(initialValue);// 私有方法const incrementBy = (amount) => {setCount(c => c + amount);};// 公共接口return {count,increment: () => incrementBy(1),decrement: () => incrementBy(-1)};
}export default useCounter;
2、高阶组件模块
// withAuth.js
function withAuth(WrappedComponent) {// 私有逻辑const isAuthenticated = checkAuth();// 返回增强的组件(公共接口)return function(props) {if (!isAuthenticated) {return <Redirect to="/login" />;}return <WrappedComponent {...props} />;};
}export default withAuth;
3、Context模块
// ThemeContext.js
const ThemeContext = createContext();// 私有值
const themes = {light: { foreground: '#000', background: '#eee' },dark: { foreground: '#fff', background: '#222' }
};// 公共接口
export function ThemeProvider({ children }) {const [theme, setTheme] = useState('light');const toggleTheme = () => {setTheme(prev => prev === 'light' ? 'dark' : 'light');};return (<ThemeContext.Provider value={{ theme: themes[theme], toggleTheme }}>{children}</ThemeContext.Provider>);
}export function useTheme() {return useContext(ThemeContext);
}
模块化优势:
1、封装性:隐藏实现接口,只暴露必要接口
2、可维护性:相关代码组织在一起,方便维护
3、可复用性:模块可以在多个地方复用
4、命名空间:避免全局命名冲突
5、依赖管理:明确模块之间的依赖关系
模块模式、工厂模式、建造者模式区别
核心概念区别:
模式 | 主要目的 | 核心思想 | 典型应用场景 |
---|---|---|---|
模块模式 | 代码组织和封装 | 将相关功能封装为独立单元,隐藏实现细节 | 功能模块、工具库、组件逻辑封装 |
工厂模式 | 对象创建解耦 | 将对象创建过程抽象,由工厂决定创建哪种对象 | 需要根据不同条件创建不同类型对象的场景 |
建造者模式 | 复杂对象构造 | 分步骤构建复杂对象,分离构造过程和表示 | 需要多步骤、多配置构建复杂对象的场景 |
关键区别总结:
特性 | 模块模式 | 工厂模式 | 建造者模式 |
---|---|---|---|
主要目的 | 代码组织 | 对象创建 | 复杂对象构建 |
关注点 | 封装性 | 创建什么对象 | 如何构建对象 |
灵活性 | 中等 | 高(可创建不同类型) | 很高(分步构建) |
复杂度 | 低 | 中等 | 高 |
典型应用 | 工具库、组件 | 多态对象创建 | 配置复杂对象 |
2、中间件模式:Express中间件
// Express风格的中间件实现
class Middleware {constructor() {this.middlewares = [];}use(fn) {this.middlewares.push(fn);}execute(context) {const run = (index) => {if (index < this.middlewares.length) {this.middlewares[index](context, () => run(index + 1));}};run(0);}
}// 使用
const middleware = new Middleware();middleware.use((ctx, next) => {ctx.user = { name: 'John' };console.log('Middleware 1');next();
});middleware.use((ctx, next) => {ctx.user.age = 30;console.log('Middleware 2');next();
});middleware.use((ctx) => {console.log('User:', ctx.user);// 不调用next()终止链
});const context = {};
middleware.execute(context);
/*
输出:
Middleware 1
Middleware 2
User: { name: 'John', age: 30 }
*/
中间件是一种非常常见的设计模式,在vue和react中有着广泛的应用,主要用于处理数据流、状态管理和副作用等场景
在vue中的应用
1、vuex中间件(插件)
// Vuex插件(中间件)
const logger = store => {store.subscribe((mutation, state) => {console.log('mutation:', mutation);console.log('state after mutation:', state);});
};const store = new Vuex.Store({// ...plugins: [logger]
});
2、Vue Router导航守卫:Vue Router的导航守卫本质上是一种中间件模式
router.beforeEach((to, from, next) => {if (to.meta.requiresAuth && !isAuthenticated()) {next('/login');} else {next();}
});
3、自定义指定中的中间件
Vue.directive('permission', {inserted: (el, binding, vnode) => {if (!checkPermission(binding.value)) {el.parentNode.removeChild(el);}}
});
react中的应用
1、Redux中间件:React中最典型的中间件
// 创建带有中间件的Redux store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';const store = createStore(rootReducer,applyMiddleware(thunk, logger)
);// 中间件的工作流程:action -> middleware1 -> middleware2 -> ... -> reducer
2、 React Router中间件:可以通过高阶组件实现路由守卫等功能
function authMiddleware(Component) {return (props) => {if (!isAuthenticated()) {return <Redirect to="/login" />;}return <Component {...props} />;};
}
3、自定义Hooks中间件模式
function useLogger(useStateResult) {const [value, setValue] = useStateResult;useEffect(() => {console.log('Value changed:', value);}, [value]);return [value, setValue];
}// 使用
const [count, setCount] = useLogger(useState(0));