当前位置: 首页 > news >正文

详解Vue设计模式

在这里插入图片描述

详解 vue 设计模式

​ Vue.js 作为一个流行的前端框架,拥有许多设计模式,这些设计模式帮助开发者更好地组织和管理代码,提升代码的可维护性、可扩展性和可读性。Vue 设计模式主要体现在以下几个方面:

1. 组件化设计模式 (Component-based Design)

1.1 单文件组件(Single File Components, SFC)

Vue 提供了专门的 .vue 文件格式,使得开发者可以将模板 (HTML)、逻辑 (JavaScript) 和样式 (CSS) 集成到一个文件中,形成高度封装的独立模块。这种组织方式方便了开发者从功能上对代码进行分割。

一个典型的 .vue 文件结构如下:

<template><div class="example"><h1>{{ title }}</h1><button @click="handleClick">点击我</button></div>
</template><script>
export default {data() {return {title: "组件化设计示例",};},methods: {handleClick() {alert("按钮被点击!");},},
};
</script><style scoped>
.example {text-align: center;color: #333;
}
</style>

展开

  • 模板<template> 包含了组件的 HTML 结构,描述了组件的 UI。
  • 逻辑<script> 定义了组件的数据、方法和生命周期。
  • 样式<style> 定义了组件的样式,scoped 属性确保样式只作用于当前组件。

1.2 父子组件通信

父子组件的通信是 Vue 组件化设计的重要机制。主要通过 props事件 实现。

  • Props:用于父组件向子组件传递数据。
  • 事件:用于子组件向父组件传递消息,通常通过 $emit 方法触发自定义事件。

父子通信示例:

  1. 父组件代码:
<template><div><h1>父组件</h1><child-component :message="parentMessage" @child-event="handleChildEvent" /></div>
</template><script>
import ChildComponent from "./ChildComponent.vue";export default {components: {ChildComponent,},data() {return {parentMessage: "这是来自父组件的消息",};},methods: {handleChildEvent(data) {alert(`接收到子组件的数据:${data}`);},},
};
</script>
  1. 子组件代码:
<template><div><h2>子组件</h2><p>{{ message }}</p><button @click="sendToParent">向父组件发送消息</button></div>
</template><script>
export default {props: ["message"],methods: {sendToParent() {this.$emit("child-event", "这是子组件的数据");},},
};
</script>

运行效果:

  • 父组件通过 props 向子组件传递了 parentMessage
  • 子组件通过 $emit 向父组件传递了自定义事件 child-event,父组件通过监听事件接收到消息并作出响应。

1.3 插槽(Slots)

Vue 的插槽允许父组件在子组件的特定位置插入自定义内容,从而提高了组件的灵活性。

插槽示例:

<!-- 父组件 -->
<template><div><h1>父组件</h1><child-component><template v-slot:header><h2>这是自定义的标题</h2></template><template v-slot:default><p>这是默认插槽的内容。</p></template></child-component></div>
</template><script>
import ChildComponent from "./ChildComponent.vue";export default {components: {ChildComponent,},
};
</script>
<!-- 子组件 -->
<template><div><header><slot name="header">默认标题</slot></header><main><slot>默认内容</slot></main></div>
</template><script>
export default {};
</script>

运行效果:

  • 父组件通过 v-slot 指令向子组件的 header 和默认插槽插入自定义内容。
  • 子组件的默认内容被父组件提供的内容覆盖。

2. 组件化设计的优点

2.1 模块化和复用性

  • 每个组件都是独立的单元,可以在项目的多个地方重复使用。
  • 使用组件化设计可以避免代码冗余,提高开发效率。

2.2 易于维护

  • 组件的代码逻辑和样式高度封装,改动一个组件不会影响其他组件。
  • 分而治之的思想让项目的结构更加清晰,便于调试和维护。

2.3 可测试性

  • 组件作为独立单元,可以单独进行功能测试,减少测试的复杂度。

2.4 高扩展性

  • 在大型项目中,可以通过组合多个组件快速构建复杂的页面。
  • 插槽和动态组件的特性让组件更灵活,适配不同场景需求。

3. 微型实现:计数器组件

以下是一个简单的计数器组件,展示了组件的独立性和通信机制。

计数器组件 (Counter.vue):

<template><div><h2>{{ title }}</h2><p>当前计数:{{ count }}</p><button @click="increment">增加</button><button @click="decrement">减少</button></div>
</template><script>
export default {props: ["title"],data() {return {count: 0,};},methods: {increment() {this.count++;this.$emit("update-count", this.count);},decrement() {this.count--;this.$emit("update-count", this.count);},},
};
</script>

父组件使用计数器:

<template><div><h1>计数器示例</h1><counter title="计数器 A" @update-count="handleCountUpdate" /><counter title="计数器 B" @update-count="handleCountUpdate" /><p>最新计数值:{{ latestCount }}</p></div>
</template><script>
import Counter from "./Counter.vue";export default {components: {Counter,},data() {return {latestCount: 0,};},methods: {handleCountUpdate(count) {this.latestCount = count;},},
};
</script>

运行效果:

  • 父组件可以同时包含多个计数器组件。
  • 计数器组件在更新计数值时通过事件通知父组件,父组件显示最新的计数值。

4. 总结

Vue 的组件化设计模式通过单文件组件、父子通信和插槽等特性,将页面拆分为多个独立模块。开发者可以方便地创建高内聚、低耦合的组件,提高了项目的复用性和可维护性。通过组件化开发,复杂的项目结构可以被分解为简单、独立的单元,使得开发更高效、代码更清晰,尤其在中大型项目中,这种模式的优势尤为明显。

2. 观察者模式 (Observer Pattern)

2.1 观察者模式的基本概念

2.1.1 角色组成
  • 目标 (Subject):被观察的对象,负责维护观察者列表并通知它们。
  • 观察者 (Observer):订阅目标的对象,一旦目标发生变化就会被通知。
  • 订阅与通知:观察者需要先订阅目标,当目标的状态改变时,目标会向所有订阅者发布通知。

观察者模式的典型伪代码:

class Subject {constructor() {this.observers = []; // 存储观察者}attach(observer) {this.observers.push(observer); // 添加观察者}notify() {this.observers.forEach(observer => observer.update()); // 通知所有观察者}
}class Observer {update() {console.log("Observer updated!");}
}// 使用示例
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();subject.attach(observer1);
subject.attach(observer2);subject.notify(); // 所有观察者收到通知

2. 2 Vue 中的响应式系统

Vue 将观察者模式引入响应式系统中,当数据发生变化时,自动通知依赖于数据的视图更新,从而实现 数据驱动视图

2.2.1 数据劫持

Vue 的响应式原理通过 数据劫持 实现,Vue 2.x 使用 Object.defineProperty,Vue 3.x 则采用更强大的 Proxy

  • Vue 2.x 数据劫持示例:
    使用 Object.defineProperty 劫持对象的每个属性,在 get 中收集依赖,在 set 中派发更新:

    function defineReactive(obj, key) {let value = obj[key];const dep = new Dep(); // 创建依赖管理器Object.defineProperty(obj, key, {get() {Dep.target && dep.addWatcher(Dep.target); // 收集依赖return value;},set(newValue) {if (newValue !== value) {value = newValue;dep.notify(); // 通知依赖更新}},});
    }
    
  • Vue 3.x 数据劫持示例:
    Vue 3 使用 Proxy 对整个对象进行代理,动态拦截所有操作。

    const data = { count: 0 };
    const reactiveData = new Proxy(data, {get(target, key) {console.log(`读取属性:${key}`);return target[key];},set(target, key, value) {console.log(`设置属性:${key} = ${value}`);target[key] = value;return true;},
    });console.log(reactiveData.count); // 读取属性:count
    reactiveData.count = 10;        // 设置属性:count = 10
    
2.2.2 依赖收集

Vue 在组件渲染时,建立起数据与组件之间的依赖关系。每个响应式数据对应一个 依赖管理器 (Dep),当数据被访问时,将当前组件(观察者)注册到依赖管理器中。

class Dep {constructor() {this.watchers = []; // 存储依赖}addWatcher(watcher) {this.watchers.push(watcher); // 添加观察者}notify() {this.watchers.forEach(watcher => watcher.update()); // 通知所有观察者}
}
2.2.3 派发更新

当响应式数据发生变化时,Vue 的 setter 会触发依赖管理器的 notify 方法,通知所有观察者更新视图。


2.3 Vue 响应式系统微型实现

以下是一个简单的 Vue 响应式系统的模拟实现:

// 依赖管理器
class Dep {constructor() {this.subscribers = new Set(); // 存储观察者}depend() {if (Dep.activeWatcher) {this.subscribers.add(Dep.activeWatcher); // 收集观察者}}notify() {this.subscribers.forEach(watcher => watcher()); // 通知观察者更新}
}Dep.activeWatcher = null;// 响应式数据劫持
function reactive(obj) {Object.keys(obj).forEach(key => {let value = obj[key];const dep = new Dep();Object.defineProperty(obj, key, {get() {dep.depend(); // 收集依赖return value;},set(newValue) {if (newValue !== value) {value = newValue;dep.notify(); // 派发更新}},});});return obj;
}// 模拟 Vue 的 watch 函数
function watch(effect) {Dep.activeWatcher = effect;effect(); // 触发依赖收集Dep.activeWatcher = null;
}// 测试示例
const state = reactive({ count: 0 });watch(() => {console.log(`Count is: ${state.count}`);
});state.count++; // 输出:Count is: 1
state.count = 10; // 输出:Count is: 10

2. 4 Vue 响应式系统的优势

2.4.1 自动更新

Vue 的观察者模式实现了 数据驱动视图,开发者只需专注于数据逻辑,无需手动操作 DOM 或管理视图更新。

2.4.2 高效的依赖追踪

通过依赖收集,Vue 能够精准追踪每个组件所依赖的数据,避免不必要的渲染。

2.4.3 细粒度更新

在数据变化时,Vue 只会更新受影响的视图部分,而不是重新渲染整个页面。


2.5. 总结

Vue 的响应式系统是观察者模式的典型应用。通过数据劫持、依赖收集和派发更新,Vue 实现了 数据与视图的自动同步。这种模式的优点在于:

  • 解耦数据和视图,开发者只需管理数据逻辑。
  • 提高渲染效率,避免重复操作。
  • 提供了简洁的开发体验,尤其适用于动态和交互性强的场景。

观察者模式的引入使 Vue 成为一个高效、优雅的前端框架,为开发者构建响应式应用提供了极大的便利。

3. 发布-订阅模式 (Pub-Sub Pattern)

​ 发布-订阅模式是一种常见的软件设计模式,用于实现模块之间的松耦合。它通过一个中央代理(通常称为事件总线或消息中心)管理事件的发布与订阅,不同模块只需关注自己需要的事件,而无需直接依赖其他模块。Vue 的事件机制和 Vuex 状态管理库都采用了这一模式。


3.1 发布-订阅模式的基本概念

3.1.1 角色组成
  1. 发布者 (Publisher):触发某个事件并向代理发布消息的模块。
  2. 订阅者 (Subscriber):订阅某个事件的模块,一旦事件触发,会收到通知。
  3. 事件总线 (Event Bus):中央代理,负责管理所有事件的发布和订阅。
3.1.2 发布-订阅模式的伪代码
class EventBus {constructor() {this.events = {}; // 存储事件}subscribe(eventName, callback) {if (!this.events[eventName]) {this.events[eventName] = []; // 初始化事件列表}this.events[eventName].push(callback);}publish(eventName, data) {if (this.events[eventName]) {this.events[eventName].forEach(callback => callback(data));}}
}// 示例使用
const eventBus = new EventBus();eventBus.subscribe("event1", data => {console.log(`事件1触发,接收到数据:${data}`);
});eventBus.publish("event1", "Hello World!"); // 输出:事件1触发,接收到数据:Hello World!

3.2 发布-订阅模式在 Vue 中的应用

3.2.1 事件总线

事件总线是 Vue 2.x 中实现组件间通信的一种方式,适用于 兄弟组件 或者 父子组件嵌套层级较深 的场景。

事件总线实现

在 Vue 2.x 中,可以通过一个空的 Vue 实例作为事件总线:

// 创建事件总线
const EventBus = new Vue();// 发布事件
EventBus.$emit("my-event", "Hello EventBus!");// 订阅事件
EventBus.$on("my-event", data => {console.log(`接收到事件数据:${data}`);
});
事件总线的组件通信示例
  1. 组件 A(发布事件):
<template><button @click="sendMessage">发布消息</button>
</template><script>
import EventBus from "@/event-bus"; // 引入事件总线export default {methods: {sendMessage() {EventBus.$emit("custom-event", "来自组件 A 的消息");},},
};
</script>
  1. 组件 B(订阅事件):
<template><div>接收到的消息:{{ message }}</div>
</template><script>
import EventBus from "@/event-bus"; // 引入事件总线export default {data() {return {message: "",};},created() {EventBus.$on("custom-event", data => {this.message = data;});},
};
</script>

运行结果:

  • 组件 A 点击按钮后触发事件。
  • 组件 B 自动接收到事件并更新视图。
3.2.2 Vuex 状态管理

Vuex 是 Vue 的状态管理库,核心采用发布-订阅模式管理全局状态。所有组件都可以订阅状态,当状态发生变化时,订阅的组件会自动更新。

Vuex 的基本使用
  1. 安装 Vuex
npm install vuex --save
  1. 创建 Vuex 仓库
import Vue from "vue";
import Vuex from "vuex";Vue.use(Vuex);export default new Vuex.Store({state: {count: 0, // 全局状态},mutations: {increment(state) {state.count++;},},actions: {incrementAsync({ commit }) {setTimeout(() => {commit("increment");}, 1000);},},getters: {doubleCount(state) {return state.count * 2;},},
});
  1. 在组件中使用 Vuex
  • 读取状态:
<template><div>全局计数:{{ count }}</div>
</template><script>
import { mapState } from "vuex";export default {computed: mapState(["count"]),
};
</script>
  • 更新状态:
<template><button @click="increment">增加计数</button>
</template><script>
import { mapMutations } from "vuex";export default {methods: mapMutations(["increment"]),
};
</script>

3.3 发布-订阅模式的优点

3.3.1 松耦合

定义:
松耦合是软件设计中的重要原则,指模块之间的依赖尽量减少,使得系统更容易维护、测试和扩展。在发布-订阅模式中,发布者和订阅者之间通过事件总线(或类似的中间桥梁)进行交互,而不直接依赖彼此。

优点分析:

  • 发布者只需要关心“发布了什么事件”,不需要了解事件由谁处理。
  • 订阅者只需要关注自己感兴趣的事件,不关心事件是由谁发布的。
  • 这种模式使得系统的模块间彼此独立,可以独立开发、测试和重构,而不影响其他模块。

实际例子:
在 Vue 中,组件 A 和组件 B 如果通过事件总线进行通信:

  • A 只需要发布一个事件 EventBus.$emit("custom-event", data)
  • B 只需要订阅这个事件 EventBus.$on("custom-event", callback)

如果未来 A 替换为另一个模块,B 不需要做任何修改,因为两者之间没有直接依赖。

扩展性场景:

  • 在大型项目中,不同开发团队可以分别开发发布者和订阅者模块,确保模块间的低耦合性。
  • 在项目重构或功能模块替换时,松耦合使得改动更少,测试更轻松。

3.3.2 灵活性

定义:
发布-订阅模式的灵活性表现在它支持多对多的通信模式,允许一个事件有多个订阅者,也允许一个模块同时发布或订阅多个事件。

优点分析:

  • 跨层次通信: 适用于组件层次较深的场景,比如兄弟组件通信、跨多层的父子组件通信。
  • 动态性: 发布者可以动态发布事件,而订阅者可以随时订阅新事件,这种动态行为非常适合复杂应用。
  • 多订阅: 一个事件可以被多个订阅者监听,所有订阅者都能收到消息。

实际例子:
假设有一个电商应用:

  • 发布者:一个产品详情组件,当用户改变产品数量时,它会触发一个事件 quantity-changed
  • 订阅者:
    • 购物车组件:监听 quantity-changed 更新购物车状态。
    • 价格计算组件:监听 quantity-changed 更新总价格。
    • 数据日志模块:监听 quantity-changed 记录用户操作日志。

当事件触发时,所有订阅者都能收到消息并执行各自的逻辑,而无需了解彼此的存在。这种多对多的通信机制让应用的逻辑更加灵活且扩展性更强。


3.3.3 可扩展性

定义:
发布-订阅模式的可扩展性是指在已有系统中,可以轻松添加新的订阅者或发布者,而无需修改现有模块的代码。

优点分析:

  • 新功能快速集成: 添加新的发布者或订阅者,只需在事件总线上注册即可,无需改变已有模块的逻辑。
  • 事件多样性: 中央事件总线可以管理多个事件类型,不同模块可以针对不同事件进行扩展。
  • 低成本扩展: 新功能的实现只需要接入现有的事件机制,无需重构系统。

实际例子:

  1. 添加新订阅者:
    如果在上例中的电商应用中需要增加一个通知模块,在用户更改产品数量时弹出提示:

    EventBus.$on("quantity-changed", data => {alert(`商品数量已更新为:${data}`);
    });
    

    这个新功能的实现不需要修改购物车组件或价格计算组件,只需新增一个订阅者即可。

  2. 添加新发布者:
    如果我们希望在订单页面也可以更改产品数量,只需让订单页面组件发布 quantity-changed 事件:

    EventBus.$emit("quantity-changed", newQuantity);
    

    原有的订阅者(购物车、价格计算、日志模块)会自动响应,无需额外改动。

实际场景:
在 Vuex 中,当需要在某个新组件中操作全局状态:

  • 只需通过 commitdispatch 向 Vuex 的 store 发布事件,无需修改其他组件。
  • 所有订阅状态的组件会自动更新,保持数据一致性。

扩展性带来的好处:

  • 系统设计更易于适应未来的变化。
  • 随着项目需求增长,可以在不破坏已有功能的情况下增加新的功能模块。
  • 事件驱动的架构支持更复杂的动态交互和多模块协作。

3.4 Vuex 与事件总线的比较

特性事件总线Vuex
适用场景小型项目,临时通信大型项目,全局状态管理
复杂性简单复杂
数据持久性无持久性状态存储在全局 Store 中
维护性难以追踪事件流,容易变得混乱状态集中管理,可预测性强
性能高效(无额外中间层)比事件总线稍低(需通过 Store 操作)

3.5 发布-订阅模式的微型实现

以下是一个基于发布-订阅模式的微型实现示例,模拟 Vue 的事件总线功能:

// 创建事件总线
class EventBus {constructor() {this.events = {}; // 存储事件及其订阅者}// 订阅事件on(eventName, callback) {if (!this.events[eventName]) {this.events[eventName] = [];}this.events[eventName].push(callback);}// 取消订阅off(eventName, callback) {if (!this.events[eventName]) return;this.events[eventName] = this.events[eventName].filter(fn => fn !== callback);}// 发布事件emit(eventName, data) {if (this.events[eventName]) {this.events[eventName].forEach(callback => callback(data));}}
}// 测试使用
const bus = new EventBus();function handler(data) {console.log(`接收到数据:${data}`);
}// 订阅事件
bus.on("test-event", handler);// 发布事件
bus.emit("test-event", "Hello Pub-Sub!");// 取消订阅
bus.off("test-event", handler);bus.emit("test-event", "This will not be logged."); // 无输出

3.6 总结

Vue 的事件机制和 Vuex 状态管理库都采用了发布-订阅模式,提供了高效的组件通信和状态管理方式:

  • 事件总线:适合小型项目或短期通信需求,通过一个中央对象实现组件间的事件通信。
  • Vuex:专为大型项目设计,集中管理全局状态并提供可预测的状态变更机制。

发布-订阅模式的引入,让 Vue 能够灵活地处理组件间的复杂关系,同时确保代码的清晰和可维护性。

4. 工厂模式 (Factory Pattern)

工厂模式是一种常见的创建型设计模式,其核心思想是通过工厂方法将对象的创建过程封装起来,根据传入的参数动态生成不同的实例。Vue 中的很多功能,比如动态组件、指令、插件等,都能体现工厂模式的运用。

在实际开发中,工厂模式的应用可以有效降低对象创建的复杂度,增强代码的扩展性和灵活性。


4.1 动态组件

Vue 的动态组件功能可以看作工厂模式的典型应用。动态组件允许根据条件渲染不同的组件,通过 is 属性动态生成不同的组件实例。

动态组件的微型实现

以下示例展示如何通过工厂模式实现动态组件的功能:

代码实现:
<template><div><component :is="currentComponent" :message="message" /><button @click="switchComponent">切换组件</button></div>
</template><script>
import ComponentA from "./ComponentA.vue";
import ComponentB from "./ComponentB.vue";export default {components: {ComponentA,ComponentB,},data() {return {currentComponent: "ComponentA", // 初始组件名称message: "这是传递给组件的消息",};},methods: {switchComponent() {// 动态切换组件this.currentComponent =this.currentComponent === "ComponentA" ? "ComponentB" : "ComponentA";},},
};
</script>
ComponentA.vue
<template><div><h2>组件 A</h2><p>{{ message }}</p></div>
</template><script>
export default {props: ["message"],
};
</script>
ComponentB.vue
<template><div><h2>组件 B</h2><p>{{ message }}</p></div>
</template><script>
export default {props: ["message"],
};
</script>

运行效果:

  1. 页面初始加载时,渲染 ComponentA
  2. 点击按钮后,动态切换为 ComponentB,并共享相同的 message 数据。

动态组件的优点
  • 灵活性高: 通过传入参数动态生成实例,适配多种场景。
  • 解耦逻辑: 将组件的创建与使用分离,代码更易维护。
  • 复用性强: 组件可以通过参数适配不同功能,减少重复开发。

4.2 插件工厂

Vue 插件机制常用于为 Vue 添加全局功能,插件的实现通常依赖工厂模式。通过 Vue.use() 方法注册插件,工厂函数会根据传入的参数生成不同的实例或功能。

插件工厂的微型实现
创建一个日志插件:
// LoggerPlugin.js
export default {install(Vue, options) {// 添加全局方法Vue.prototype.$log = function (message) {const prefix = options?.prefix || "Log";console.log(`[${prefix}] ${message}`);};},
};
注册并使用插件:
// main.js
import Vue from "vue";
import LoggerPlugin from "./LoggerPlugin";// 注册插件,并传入自定义参数
Vue.use(LoggerPlugin, { prefix: "MyApp" });new Vue({created() {// 调用插件方法this.$log("应用已启动");},
}).$mount("#app");

运行效果:

  • 应用启动时,控制台输出:[MyApp] 应用已启动
插件的动态生成:

通过工厂模式,插件可以根据传入参数生成不同的功能。例如,可以为不同模块生成专属的日志工具。

// main.js
Vue.use(LoggerPlugin, { prefix: "AdminModule" });
Vue.use(LoggerPlugin, { prefix: "UserModule" });

4.3 自定义指令工厂

自定义指令是 Vue 中的一个功能,它允许开发者为 DOM 元素绑定特定行为。工厂模式可以用于动态创建不同功能的指令。

自定义指令的微型实现
代码实现:
// DynamicDirective.js
export default function createDirective(options) {return {bind(el) {el.style.color = options.color || "black"; // 默认颜色},update(el, binding) {el.style.color = binding.value || options.color;},};
}
使用自定义指令:
// main.js
import Vue from "vue";
import createDirective from "./DynamicDirective";// 注册自定义指令
Vue.directive("dynamic-color", createDirective({ color: "blue" }));new Vue({data: {dynamicColor: "red",},template: `<p v-dynamic-color="dynamicColor">动态指令示例</p>`,
}).$mount("#app");

运行效果:

  • 页面加载时,文本颜色为蓝色。
  • 数据 dynamicColor 修改为其他颜色时,文本颜色动态更新。

4.4 工厂模式的扩展实现

动态表单生成器

工厂模式还可以用于动态生成表单组件。例如,根据后端返回的表单配置,动态生成对应的输入框、选择框等。

动态表单工厂:
// FormFactory.js
export function createFormField(type, props = {}) {if (type === "input") {return {render(h) {return h("input", { attrs: { type: "text", ...props } });},};} else if (type === "select") {return {render(h) {return h("select",{ attrs: { ...props } },props.options.map(option =>h("option", { domProps: { value: option.value } }, option.label)));},};}return null;
}
动态生成表单:
<template><div><component v-for="field in fields" :is="field.component" :key="field.id" /></div>
</template><script>
import { createFormField } from "./FormFactory";export default {data() {return {fields: [{ id: 1, component: createFormField("input", { placeholder: "请输入姓名" }) },{id: 2,component: createFormField("select", {options: [{ value: "1", label: "选项1" },{ value: "2", label: "选项2" },],}),},],};},
};
</script>

运行效果:

  • 表单根据配置动态生成,并适配不同的字段类型。

4.5 工厂模式的优势

  1. 统一创建逻辑:
    • 工厂模式封装了复杂的创建过程,开发者只需关注接口和参数,无需关心内部实现。
  2. 灵活性:
    • 动态组件、插件、自定义指令等都可以根据传入参数生成不同的实例,适应多样化需求。
  3. 可维护性:
    • 组件的逻辑与创建过程分离,新增或修改实例类型只需调整工厂逻辑,代码更清晰。
  4. 扩展性:
    • 添加新的组件类型或插件功能时,只需扩展工厂方法,而无需改动已有逻辑。

4.6 总结

工厂模式在 Vue 中广泛应用于动态组件、插件、自定义指令等场景。通过工厂方法封装对象的创建逻辑,不仅简化了代码,还提升了代码的灵活性和扩展性。这种模式非常适合动态需求多、组件类型多样化的项目。


5. 策略模式 (Strategy Pattern)

策略模式是一种行为型设计模式,旨在定义一系列算法或逻辑,并将每种算法封装起来,使得它们可以互换使用。Vue 中的许多功能运用策略模式实现,在运行时根据不同的条件选择适当的策略执行对应的逻辑。


5.1 路由守卫

Vue Router 的导航守卫是策略模式的典型应用。根据不同的条件(目标路由、用户状态等),可以执行不同的逻辑,来决定是否允许路由跳转。

示例:实现权限控制的路由守卫
实现代码:
import Vue from "vue";
import VueRouter from "vue-router";Vue.use(VueRouter);const routes = [{ path: "/home", component: () => import("./Home.vue") },{ path: "/admin", component: () => import("./Admin.vue") },
];const router = new VueRouter({ routes });// 模拟用户权限
function isUserAdmin() {return false; // 假设当前用户不是管理员
}// 定义全局前置守卫
router.beforeEach((to, from, next) => {if (to.path === "/admin" && !isUserAdmin()) {alert("无权限访问");next("/home"); // 重定向到首页} else {next(); // 允许导航}
});export default router;
运行效果:
  • 如果用户尝试访问 /admin 且不是管理员,路由守卫会拦截跳转,并重定向到 /home
  • 如果用户拥有管理员权限或访问其他路由,则允许导航。
策略模式的实现分析:
  1. 策略集合: 定义了多种导航守卫策略(如 beforeEachbeforeResolve)。
  2. 运行时策略选择: 根据路由目标和用户权限动态决定执行哪种策略。
  3. 封装独立逻辑: 每种策略(如权限验证)都封装在守卫回调中,逻辑清晰,互不影响。

5.2 生命周期钩子

Vue 的生命周期钩子函数可以视为策略模式的一种实现形式。组件在不同生命周期阶段触发相应的钩子,开发者可以在这些钩子中选择不同的策略来执行逻辑。

示例:实现动态数据加载与 DOM 操作
实现代码:
export default {data() {return {message: "",};},created() {// 数据逻辑console.log("组件已创建");this.message = "数据已初始化";},mounted() {// DOM 操作console.log("组件已挂载到 DOM");document.getElementById("app").style.backgroundColor = "#f0f0f0";},
};
运行效果:
  1. created 钩子中执行数据初始化逻辑。
  2. mounted 钩子中操作 DOM 元素。
策略模式的实现分析:
  1. 策略集合: 不同生命周期提供不同的策略钩子(如 createdmounted)。
  2. 运行时策略选择: Vue 根据组件生命周期阶段动态调用合适的钩子。
  3. 独立封装逻辑: 每个钩子中只包含与当前阶段相关的逻辑,代码结构清晰。

5.3 自定义策略的实现

开发者可以通过自定义函数来实现策略模式的具体应用场景。以下是一个简单的例子,展示如何使用策略模式动态选择计算逻辑。

示例:动态计算折扣
实现代码:
// 策略集合
const discountStrategies = {noDiscount: price => price,percentage: (price, rate) => price * rate,fixedAmount: (price, amount) => price - amount,
};// 工厂函数
function calculateDiscount(price, type, param) {const strategy = discountStrategies[type];if (!strategy) {throw new Error("无效的折扣策略");}return strategy(price, param);
}// 使用策略
const originalPrice = 100;
console.log(calculateDiscount(originalPrice, "noDiscount")); // 输出:100
console.log(calculateDiscount(originalPrice, "percentage", 0.8)); // 输出:80
console.log(calculateDiscount(originalPrice, "fixedAmount", 20)); // 输出:80
运行效果:
  1. 无折扣策略返回原价。
  2. 按比例折扣策略返回 80% 的原价。
  3. 固定金额折扣策略减去 20 的金额后返回价格。
5.3.1策略模式的实现分析:
  1. 策略集合: 定义了多种折扣算法(无折扣、按比例折扣、固定金额折扣)。
  2. 运行时策略选择: 根据参数动态选择合适的折扣策略。
  3. 独立封装逻辑: 每种策略的实现互不影响,新增策略时无需修改已有逻辑。

5.4 策略模式的优点

5.4.1 行为灵活

策略模式最大的优势在于其灵活性,它允许根据条件动态选择适合当前场景的策略来完成任务,而不需要修改原有的代码结构。这种灵活性使得策略模式特别适合复杂、多变的业务场景。

举例:路由守卫中的策略灵活性

在 Vue Router 中,导航守卫通过定义多种类型的回调(如 beforeEachbeforeResolveafterEach),实现了根据不同的路由目标、用户权限或环境条件,动态执行相应的逻辑。例如:

  1. 用户权限策略:

    • 如果目标路由需要管理员权限,则导航守卫会检查用户权限,如果没有权限,则拒绝导航并重定向到登录页或首页。
    • 如果用户具备相应权限,则允许正常导航。
    router.beforeEach((to, from, next) => {if (to.meta.requiresAuth && !isUserAuthenticated()) {next("/login"); // 未认证用户重定向到登录页} else if (to.meta.requiresAdmin && !isUserAdmin()) {next("/home"); // 非管理员用户重定向到首页} else {next(); // 放行}
    });
    
  2. 环境切换策略:

    • 在开发环境中,导航守卫可以打印出路由跳转信息,帮助调试。
    • 在生产环境中,导航守卫只执行必要的验证逻辑,不打印调试信息。
    router.beforeEach((to, from, next) => {if (process.env.NODE_ENV === "development") {console.log(`导航从 ${from.path}${to.path}`);}next();
    });
    
生命周期钩子中的策略灵活性

Vue 的生命周期钩子函数允许在不同的生命周期阶段执行特定的逻辑。例如,在组件创建时加载数据,在组件挂载后操作 DOM,在组件销毁时清理资源。这些钩子函数的存在为开发者提供了高度灵活的选择空间:

  1. 数据初始化策略:

    • created 钩子中,初始化组件的核心数据,例如从服务器获取初始数据。
    created() {this.fetchData();
    }
    
  2. 性能优化策略:

    • mounted 钩子中,延迟加载某些非关键资源,例如图片懒加载或非核心数据的请求。
    mounted() {setTimeout(() => {this.loadLazyData();}, 1000);
    }
    
  3. 资源清理策略:

    • beforeDestroydestroyed 钩子中,移除定时器或事件监听,释放内存。
    beforeDestroy() {clearInterval(this.timer);window.removeEventListener("resize", this.handleResize);
    }
    

    ​ 通过策略模式的灵活性,开发者可以根据当前的运行环境、组件状态或外部条件,动态选择合适的处理逻辑,使得代码更加高效和灵活。


5.4.2 代码清晰

策略模式通过将不同的策略封装成独立的逻辑模块,使得代码结构更加清晰,易于维护和理解。每种策略只负责处理自身的逻辑,避免了将所有逻辑混杂在一起的情况。

举例:路由守卫中的清晰逻辑

在复杂的项目中,路由守卫可能需要处理多种逻辑,例如:

  • 权限验证
  • 路由跳转前的全局检查
  • 跳转后的日志记录

通过策略模式,这些逻辑可以分解为独立的策略函数,每个函数只处理特定的任务。例如:

// 定义策略函数
const checkAuth = (to, from, next) => {if (to.meta.requiresAuth && !isUserAuthenticated()) {next("/login");} else {next();}
};const logNavigation = (to, from) => {console.log(`用户从 ${from.path} 跳转到 ${to.path}`);
};// 在守卫中调用策略
router.beforeEach((to, from, next) => {checkAuth(to, from, next);
});router.afterEach((to, from) => {logNavigation(to, from);
});

通过拆分逻辑,代码更易于阅读和调试,每个函数的职责明确且独立。

表单验证中的清晰逻辑

表单验证是一个复杂的场景,通常涉及多种规则(如必填、邮箱格式、密码长度等)。通过策略模式,可以将每种验证规则封装成独立的策略函数,避免在一个函数中处理所有逻辑。

const validationStrategies = {required: value => !!value || "字段不能为空",email: value => /.+@.+\..+/.test(value) || "邮箱格式不正确",minLength: (value, min) => value.length >= min || `最少需要 ${min} 个字符`,
};// 调用策略进行验证
function validateField(value, rules) {const errors = [];rules.forEach(rule => {const [strategy, param] = rule.split(":");const result = validationStrategies[strategy](value, param);if (result !== true) errors.push(result);});return errors;
}// 示例
console.log(validateField("test", ["required", "email"])); // ["邮箱格式不正确"]
console.log(validateField("", ["required", "minLength:5"])); // ["字段不能为空", "最少需要 5 个字符"]

通过封装独立的验证规则,代码结构更加清晰,也便于新增或修改规则。


5.4.3 易扩展

策略模式的另一个显著优点是易于扩展。当需求发生变化或需要新增功能时,只需新增相应的策略,而不需要修改现有的代码逻辑,降低了代码的耦合度和维护成本。

举例:新增路由守卫策略

如果需要在路由守卫中新增一个检查用户是否已完成某些任务的逻辑,可以轻松添加新的策略,而无需修改原有逻辑:

// 新增策略函数
const checkTaskCompletion = (to, from, next) => {if (to.meta.requiresTask && !isTaskCompleted()) {alert("请先完成任务!");next("/task");} else {next();}
};// 在守卫中调用新的策略
router.beforeEach((to, from, next) => {checkAuth(to, from, next);checkTaskCompletion(to, from, next);
});

通过这种方式,可以灵活扩展守卫逻辑,保持代码结构的稳定性。

新增验证规则

对于表单验证场景,如果需要新增一个验证规则(如验证手机号格式),只需在 validationStrategies 中添加新规则即可:

validationStrategies.phone = value =>/^1[3-9]\d{9}$/.test(value) || "手机号格式不正确";

无需改动验证逻辑即可支持新的规则,扩展性极高。


5.5 策略模式的应用场景

5.5.1 路由守卫

Vue Router 的导航守卫通过策略模式实现以下功能:

  • 全局策略:定义通用的导航逻辑,例如权限验证和跳转日志记录。
  • 局部策略:为特定路由设置单独的验证逻辑。
5.5.2 生命周期钩子

Vue 的生命周期钩子提供了一种内置的策略模式,开发者可以根据组件生命周期的阶段选择适当的逻辑:

  • created 中初始化数据。
  • mounted 中操作 DOM。
  • destroyed 中清理资源。
5.5.3 自定义策略

策略模式在表单验证、折扣计算、数据处理等复杂场景中尤为常见:

  • 表单验证:根据规则动态验证字段。
  • 数据处理:根据需求选择不同的计算逻辑。
  • 动态渲染:根据用户角色动态调整显示内容。

5.6 总结

策略模式在 Vue 中的应用非常广泛,无论是内置的功能(如路由守卫、生命周期钩子)还是开发者自定义的逻辑,都可以通过策略模式实现灵活的逻辑选择。策略模式的主要优势包括:

  • 提供灵活的运行时行为。
  • 使代码结构清晰,易于维护。
  • 增强扩展性,支持新增逻辑而不破坏已有代码。

通过合理运用策略模式,开发者可以有效应对复杂场景的需求变化,提升代码的复用性和可维护性。


6. 状态机模式 (State Machine Pattern)

状态机模式是一种行为设计模式,专注于管理对象的有限状态及其状态之间的转换规则。在现代前端框架(如 Vue)中,状态机模式常被用于处理复杂的状态流转问题,确保系统在任意时间点都具有明确、可预测的状态。


6.1 Vuex 状态管理

Vuex 是 Vue 的状态管理工具,天然体现了状态机模式的核心思想。通过 state 保存状态,通过 mutations 修改状态,actions 调度异步操作来完成状态的更新。

6.1.1 核心概念与结构
  1. 状态 (State):存储应用的当前状态。
  2. 突变 (Mutations):定义状态的变化规则。
  3. 动作 (Actions):触发异步逻辑后,再调用对应的突变修改状态。
微型实现:状态流转
实现代码:
import Vue from "vue";
import Vuex from "vuex";Vue.use(Vuex);const store = new Vuex.Store({state: {status: "idle", // 初始状态},mutations: {setLoading(state) {state.status = "loading"; // 切换到加载状态},setSuccess(state) {state.status = "success"; // 切换到成功状态},setError(state) {state.status = "error"; // 切换到错误状态},},actions: {async fetchData({ commit }) {commit("setLoading");try {await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟异步请求commit("setSuccess");} catch {commit("setError");}},},
});export default store;
状态切换示例:

在组件中使用该状态机:

<template><div><p>当前状态:{{ status }}</p><button @click="fetchData">加载数据</button></div>
</template><script>
import { mapState, mapActions } from "vuex";export default {computed: mapState(["status"]),methods: mapActions(["fetchData"]),
};
</script>
运行效果:
  1. 点击按钮,状态切换为 loading
  2. 模拟请求完成后,状态切换为 success
  3. 如果请求失败,状态切换为 error
分析:状态机模式的体现
  • 有限状态: 系统始终处于 idleloadingsuccesserror 中的一种状态。
  • 明确规则: 状态只能通过 mutations 定义的规则进行切换,避免了无序修改。
  • 状态可追踪: 所有状态切换都有明确的触发来源(如用户操作、请求结果)。

6.2 表单控件的状态绑定

表单控件通过 Vue 的 v-model 实现双向绑定,这本质上也是一种状态机模式:控件的显示状态与内部绑定数据的状态保持同步,用户交互和数据变更都触发状态流转。

微型实现:表单状态机
代码实现:
<template><div><form @submit.prevent="handleSubmit"><input v-model="formData.username" placeholder="用户名" /><select v-model="formData.role"><option value="user">普通用户</option><option value="admin">管理员</option></select><button type="submit">提交</button></form><p>当前用户名:{{ formData.username }}</p><p>当前角色:{{ formData.role }}</p><p>表单状态:{{ formState }}</p></div>
</template><script>
export default {data() {return {formData: {username: "",role: "user",},formState: "idle", // 初始状态};},methods: {handleSubmit() {if (!this.formData.username) {this.formState = "error"; // 表单验证失败} else {this.formState = "submitting"; // 提交中setTimeout(() => {this.formState = "submitted"; // 提交完成}, 1000);}},},
};
</script>
运行效果:
  1. 用户填写表单内容,输入框和下拉框的值与 formData 保持同步。
  2. 点击提交按钮:
    • 如果用户名为空,状态切换为 error,提示用户补全信息。
    • 否则,状态切换为 submitting,模拟表单提交过程。
    • 提交完成后,状态切换为 submitted
分析:状态机模式的体现
  • 有限状态: 表单的状态为 idleerrorsubmittingsubmitted
  • 状态流转: 状态的切换规则由 handleSubmit 方法定义,确保逻辑一致性。
  • 双向绑定: 表单数据与控件显示状态保持同步。

6.3 状态机模式的优势

6.3.1 状态可控

状态机模式通过明确的状态集合和状态切换规则,避免了逻辑混乱或意外行为:

  • 集中式管理: 状态由单一数据源管理(如 Vuex 的 state),状态切换必须通过定义好的规则(如 mutations)。
  • 可预测性: 系统的行为可以通过当前状态完全决定,开发者无需担心状态的不可控变化。

例如:

  • 在表单控件的状态机中,状态只能通过用户操作或方法调用进行切换,确保了状态流转的安全性。
6.3.2 行为可预测

状态机模式的行为是有限且明确的,可以通过状态流转图或代码逻辑完全预测其行为。这种特性在复杂交互场景中尤为重要。

状态流转图示例:

[ idle ] ---> [ loading ] ---> [ success ]|                 ^v                 |[ error ] <----------
6.3.3 适合复杂场景

状态机模式非常适合处理多状态的复杂交互。例如:

  • 异步请求: 请求的状态流转从 loadingsuccesserror
  • 购物车逻辑: 商品可以处于 未选中选中移除 等状态,且状态的切换需要满足一定规则。
  • 表单验证: 表单的状态会在 未填写验证失败验证成功 等多个状态之间切换。

6.4 状态机模式的完整实现案例:购物车状态管理

6.4.1 代码实现:
const store = new Vuex.Store({state: {cart: [],cartState: "idle", // idle, adding, added, error},mutations: {setAdding(state) {state.cartState = "adding";},setAdded(state) {state.cartState = "added";},setError(state) {state.cartState = "error";},addItem(state, item) {state.cart.push(item);},},actions: {async addItemToCart({ commit }, item) {commit("setAdding");try {await new Promise(resolve => setTimeout(resolve, 500)); // 模拟请求commit("addItem", item);commit("setAdded");} catch {commit("setError");}},},
});
6.4.2 使用示例:
<template><div><button @click="addItemToCart({ id: 1, name: '商品 A' })">添加到购物车</button><p>购物车状态:{{ cartState }}</p></div>
</template><script>
import { mapState, mapActions } from "vuex";export default {computed: mapState(["cartState"]),methods: mapActions(["addItemToCart"]),
};
</script>
6.5.3 运行效果:
  1. 点击按钮,状态从 idle 切换到 adding
  2. 模拟异步请求成功后,状态切换到 added,并将商品加入购物车。
  3. 如果请求失败,状态切换到 error

6.5 总结

状态机模式在 Vue 中通过 Vuex、表单控件双向绑定、组件状态流转等得到广泛应用。它的核心优势包括:

  • 明确的状态集合和规则: 避免状态不可控的混乱。
  • 状态切换行为可预测: 提升代码的可维护性和调试效率。
  • 适应复杂交互场景: 特别适合多状态、多步骤的流程
    管理。

通过状态机模式,开发者可以清晰地定义系统的行为逻辑,使代码更加结构化、可扩展、可靠。


7. 装饰器模式 (Decorator Pattern)

装饰器模式是一种结构型设计模式,用于动态地为对象添加功能,而不改变其原有的结构。在 Vue 中,装饰器模式的体现尤为明显,例如通过 自定义指令Mixin 来为组件动态添加行为或共享功能。


7.1 自定义指令

Vue 的自定义指令允许开发者为 DOM 元素动态绑定特定的功能或行为,这种动态功能的添加正是装饰器模式的体现。自定义指令可以在不更改 DOM 元素结构的前提下,为其动态附加行为。

核心特性
  1. 动态扩展: 根据需要为元素绑定行为,例如自动聚焦、滚动、悬停提示等。
  2. 解耦逻辑: 将行为逻辑与 DOM 元素分离,增强代码复用性。
自定义指令的实现
自动聚焦指令

为输入框绑定自动聚焦功能:

Vue.directive("focus", {inserted(el) {el.focus();},
});
使用示例
<template><input v-focus />
</template>
运行效果

页面加载时,输入框会自动获得焦点。


动态颜色指令

创建一个指令,通过属性动态为元素设置背景色:

Vue.directive("color", {bind(el, binding) {el.style.backgroundColor = binding.value;},update(el, binding) {el.style.backgroundColor = binding.value;},
});
使用示例
<template><div v-color="'blue'" style="width: 100px; height: 100px;"></div>
</template>
运行效果

元素会动态应用 blue 背景色,且支持动态更新背景色。


装饰器模式在指令中的体现
  1. 不改变结构: 自定义指令只影响元素的行为或样式,不改变 DOM 元素的结构。
  2. 动态扩展: 根据指令逻辑动态为元素添加功能。
  3. 高复用性: 一次定义,可以在多个组件或页面中复用。

7.2 Mixin

Mixin 是 Vue 提供的一种代码复用机制,通过将可复用逻辑抽离为一个独立的对象,并在多个组件中复用,从而动态为组件扩展功能。Mixin 是装饰器模式的一种高级应用形式。

核心特性
  1. 功能共享: 提供一组独立的功能,可以动态扩展到多个组件。
  2. 结构清晰: 将扩展逻辑与核心逻辑分离,提升代码的可读性和维护性。
Mixin 的实现
日志功能

创建一个日志 Mixin,在组件加载时输出日志信息:

export const logMixin = {created() {console.log(`${this.$options.name} 组件已创建`);},
};
使用示例
<template><div>测试 Mixin</div>
</template><script>
import { logMixin } from "./logMixin";export default {name: "TestComponent",mixins: [logMixin],
};
</script>
运行效果

页面加载时,控制台会输出日志信息:TestComponent 组件已创建


数据共享功能

通过 Mixin 实现多个组件共享相同的数据和方法:

export const sharedDataMixin = {data() {return {sharedValue: "这是共享数据",};},methods: {updateSharedValue(newValue) {this.sharedValue = newValue;},},
};
使用示例
<template><div><p>{{ sharedValue }}</p><button @click="updateSharedValue('新值')">更新共享数据</button></div>
</template><script>
import { sharedDataMixin } from "./sharedDataMixin";export default {mixins: [sharedDataMixin],
};
</script>
运行效果

多个组件可以共享 sharedValue,点击按钮会更新共享数据的值。


装饰器模式在 Mixin 中的体现
  1. 动态扩展: Mixin 可在组件中动态注入功能,如数据共享、方法复用。
  2. 解耦逻辑: 将共享逻辑独立到 Mixin 中,避免重复代码。
  3. 增强复用性: 一次定义,可以为多个组件动态添加功能。

7.3 装饰器模式的优势

7.3.1 动态扩展

装饰器模式的核心在于为对象动态添加功能而不改变其原有结构。在 Vue 中:

  • 自定义指令:通过绑定指令,为 DOM 元素动态添加行为(如自动聚焦、动态样式)。
  • Mixin:通过混入的方式,为组件动态注入数据、方法或生命周期逻辑。

这种动态扩展的特性,使得开发者能够根据需求灵活地为组件或 DOM 元素添加功能。


7.3.2 代码复用

通过自定义指令和 Mixin,开发者可以将常用的逻辑抽离出来,提高代码的复用性:

  • 一个自定义指令可以应用于多个组件或页面,避免重复编写相同的行为逻辑。
  • 一个 Mixin 可以为多个组件提供共享数据或方法,减少重复代码。

7.3.3 结构清晰

装饰器模式通过将功能扩展逻辑与核心逻辑分离,使代码结构更加清晰:

  • 自定义指令的逻辑集中在一个地方,与 DOM 元素的核心功能解耦。
  • Mixin 的功能独立定义,与组件核心逻辑分开,增强了代码的可读性和可维护性。

7.4 综合实现案例:权限验证指令

通过自定义指令实现基于权限的动态显示/隐藏功能。

实现代码
Vue.directive("permission", {inserted(el, binding) {const userPermissions = ["read", "write"]; // 假设用户的权限const requiredPermission = binding.value;if (!userPermissions.includes(requiredPermission)) {el.style.display = "none"; // 隐藏无权限的元素}},
});
使用示例
<template><div><button v-permission="'read'">可见按钮</button><button v-permission="'delete'">隐藏按钮</button></div>
</template>
运行效果
  • 如果用户的权限包含 read,则显示第一个按钮。
  • 如果用户的权限不包含 delete,则隐藏第二个按钮。

7.5 总结

装饰器模式在 Vue 中通过自定义指令和 Mixin 得到了灵活的实现:

  • 自定义指令:为 DOM 元素动态添加行为。
  • Mixin:为组件动态扩展功能。
装饰器模式的优点
  1. 动态扩展:无需修改对象本身,轻松为其添加功能。
  2. 高复用性:将常用逻辑封装成指令或 Mixin,避免重复代码。
  3. 清晰结构:分离扩展功能与核心逻辑,提高代码可读性。

装饰器模式为 Vue 提供了强大的功能扩展能力,在构建灵活、高效的前端架构中发挥了重要作用。


8. 总结

Vue.js 的设计模式为前端开发提供了一个强大的架构,使得从简单应用到复杂企业级应用的开发都变得更加高效和可维护。以下是 Vue 中常见的设计模式及其应用:

  • 工厂模式:简化了对象和组件的创建过程,使得动态生成组件和插件变得简单,提高了代码的灵活性和可重用性。
  • 观察者模式:Vue 的响应式系统核心,使得数据和视图之间的依赖关系自动管理,提高了开发效率和用户体验。
  • 发布-订阅模式:通过事件总线和 Vuex,实现了组件间的松耦合通信,加强了大型应用的数据流管理。
  • 策略模式:Vue Router 的导航守卫等功能通过定义一系列策略来处理不同的路由请求,灵活处理各种导航场景。
  • 状态机模式:在 Vuex 中管理应用的状态转换,确保状态的可预测性和正确性。
  • 装饰器模式:通过自定义指令和 Mixins 动态扩展组件的功能,无需修改组件本身,增加了代码的灵活性和复用性。
  • 工厂模式:动态组件和指令的实现中,根据不同参数生成具体实例,提供了一种标准化的组件生成方式。

这些设计模式的应用不仅优化了 Vue 的内部机制,也为开发者提供了强大的工具,帮助他们构建出高性能、易维护、可扩展的应用。通过深入理解和应用这些设计模式,开发者可以更好地利用 Vue 的强大功能,打造出卓越的前端应用。
在这里插入图片描述

相关文章:

详解Vue设计模式

详解 vue 设计模式 ​ Vue.js 作为一个流行的前端框架&#xff0c;拥有许多设计模式&#xff0c;这些设计模式帮助开发者更好地组织和管理代码&#xff0c;提升代码的可维护性、可扩展性和可读性。Vue 设计模式主要体现在以下几个方面&#xff1a; 1. 组件化设计模式 (Compon…...

webpack 题目

文章目录 webpack 中 chunkHash 和 contentHash 的区别loader和plugin的区别&#xff1f;webpack 处理 image 是用哪个 loader&#xff0c;限制 image 大小的是...&#xff1b;webpack 如何优化打包速度 webpack 中 chunkHash 和 contentHash 的区别 主要从四方面来讲一下区别&…...

Mysql - 存储引擎

一 MYSQL体系结构简介 MYSQL的体系结构可以分为四个层级&#xff0c;从上往下依次为&#xff1a; 1. 连接层: 最上层为客户端以及一些连接服务&#xff0c;包含连接操作&#xff0c;例如JAVA想要与MYSQL建立连接就需要用到JDBC&#xff0c;PHP语言与Python也可以连接到MYSQL&am…...

【实战教程】使用YOLOv8 OBB进行旋转框目标检测的数据集定义与训练【附源码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...

怎么实现邮件营销自动化?

邮件营销能够出色地帮助我们与客户建立良好关系。无论是新客户还是老客户&#xff0c;都可以通过邮件来达成较为良好的客户关系。然而&#xff0c;从消费者的角度来看&#xff0c;每个人都有自己独特的习惯和特点&#xff0c;没有人希望收到千篇一律、营销意味过重的邮件。因此…...

华为服务器使用U盘重装系统

一、准备工作 下载官方系统&#xff08;注意服务器CPU的架构是x86-64还是aarch64&#xff0c;不然可能报意想不到的错&#xff09;制作启动U盘&#xff08;下载rufus制作工具&#xff0c;注意文件系统选FAT32还是NTFS&#xff09; 二、安装步骤 将U盘插入USB接口重启服务器…...

空安全编程的典范:Java 8中的安全应用指南

文章目录 一、Base64 编码解码1.1 基本的编码和解码1.2 URL 和文件名安全的编码解码器1.3 MIME Base64编码和解码 二、Optional类三、Nashorn JavaScript 一、Base64 编码解码 1.1 基本的编码和解码 Base64 编码&#xff1a; 使用 Base64.getEncoder().encodeToString(origin…...

深入解析 Loss 减少方式:mean和sum的区别及其在大语言模型中的应用 (中英双语)

深入解析 Loss 减少方式&#xff1a;mean 和 sum 的区别及其在大语言模型中的应用 在训练大语言模型&#xff08;Large Language Models, LLM&#xff09;时&#xff0c;损失函数&#xff08;Loss Function&#xff09;的处理方式对模型的性能和优化过程有显著影响。本文以 re…...

opencv4.8 ubuntu20.04源码编译 安装报错记录

-- IPPICV: Downloading ippicv_2021.8_lnx_intel64_20230330_general.tgz from https://raw.githubusercontent.com/opencv/opencv_3rdparty/1224f78da6684df04397ac0f40c961ed37f79ccb/ippicv/ippicv_2021.8_lnx_intel64_20230330_general.tgz make -j8 到这咋不动了 代理配…...

16-03、JVM系列之:内存与垃圾回收篇(三)

JVM系列之&#xff1a;内存与垃圾回收篇(三) ##本篇内容概述&#xff1a; 1、执行引擎 2、StringTable 3、垃圾回收一、执行引擎 ##一、执行引擎概述 如果想让一个java程序运行起来&#xff0c;执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。 简…...

在 Spring Boot 中使用 JPA(Java Persistence API)进行数据库操作

步骤 1: 添加依赖 在 pom.xml 文件中添加相关依赖&#xff1a; <dependencies><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><…...

【sqlserver】mssql 批量加载数据文件 bulk copy使用

参考文章&#xff1a; Using bulk copy with the JDBC driver SqlServer数据批量写入 SqlServer批量插入数据方法–SqlBulkCopy sqlserver buld copy需要提供&#xff0c;数据文件的对应表的元数据信息主要的字段的位置、字段的名称、字段的数据类型。 执行bulk load时候不一…...

卷积神经网络(CNN)的层次结构

卷积神经网络&#xff08;CNN&#xff09;是一种以其处理图像和视频数据的能力而闻名的深度学习模型&#xff0c;其基本结构通常包括以下几个层次&#xff0c;每个层次都有其特定的功能和作用&#xff1a; 1. 输入层&#xff08;Input Layer&#xff09;&#xff1a; 卷积神经网…...

使用Excel的COUNTIFS和SUMIFS函数进行高级数据分析

使用Excel的COUNTIFS和SUMIFS函数进行高级数据分析 引言 在处理数据时&#xff0c;Excel 提供了多种内置函数来帮助用户快速获取所需信息。其中&#xff0c;COUNTIFS 和 SUMIFS 是两个非常强大的多条件聚合函数&#xff0c;它们允许你根据一个或多个标准来统计或汇总数据。本…...

上传ssh公钥到目标服务器

创建密钥 ssh-keygen -t rsa -b 4096 -C "xxxx.xx"上传 sudo ssh-copy-id -i /Users/xx/.ssh/id_rsa.pub root127.0.0.1...

在visio2021 中插入MathType公式

首先要确保有着两个软件&#xff0c;且能用。 1、打开visio2021&#xff0c;之后点击“插入”-“对象” 2、打开后&#xff0c;选择MathType&#xff0c;确定 3、确定后就会弹出MathType编辑器...

【计算机视觉】图像的几何变换

最常见的几何变换有仿射变换和单应性变换两种&#xff0c;最常用的仿射变换有缩放、翻转、旋转、平移。 1. 缩放 将图像放大或缩小会得到新的图像&#xff0c;但是多出的像素点如何实现----插值 1.1 插值方法 最近邻插值 双线性插值 cv2.resize() 是 OpenCV 中用于调整图像…...

IS-IS四

目录 点到点中LSP(类似LSA&#xff09;的同步过程 注意LSP只有&#xff08;1类LSA和2类LSA) 查看详细信息&#xff1a;display isis lsdb 0000.0000.0001.00-00 verbose 开摸&#xff1a; ISIS的伪节点LSP&#xff08;类似LSA&#xff09;没有路由信息 L1路由器的路由计算…...

CODA 离线安装及虚幻镜迁移

1、离线安装 1.1 下载Miniconda安装脚本 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh1.2 添加权限 chmod x Miniconda3-latest-Linux-x86_64.sh1.3 执行安装 ./Miniconda3-latest-Linux-x86_64.sh遇到问题&#xff0c;一路回车即可 1.4 …...

【Rive】混合动画

1 混合动画简介 【Rive】动画 中介绍了 Rive 中动画的基础概念和一般动画的制作流程&#xff0c;本文将介绍混合动画的基础概念和一般制作流程。Unity 中混合动画介绍详见→ 【Unity3D】动画混合。 混合动画是指同一时刻多个动画按照一定比例同时执行&#xff0c;这些动画控制的…...

软件体系结构复习-02 软件体系结构定位及构建

软件体系结构复习-02 软件体系结构定位及构建 原文链接&#xff1a;《软件体系结构复习-02 软件体系结构定位及构建》 目录 软件体系结构复习-02 软件体系结构定位及构建 1 什么是软件体系结构 2 软件生命周期中的软件体系结构 2.1 生命周期 2.2 定位与作用 1 规划和需求…...

MySQL-SQL语句

文章目录 一. SQL语句介绍二. SQL语句分类1. 数据定义语言&#xff1a;简称DDL(Data Definition Language)2. 数据操作语言&#xff1a;简称DML(Data Manipulation Language)3. 数据查询语言&#xff1a;简称DQL(Data Query Language)4. 数据控制语言&#xff1a;简称DCL(Data …...

Windows版Docker上不了网怎么办?

1、判断你的config文件、daemon文件的位置。 docker info命令输入&#xff0c; buildx: Docker Buildx (Docker Inc.) Version: v0.17.1-desktop.1 Path: C:\Users\AAA\.docker\cli-plugins\docker-buildx.exe 这个是你电脑这些文件的位置&#xff0c;修改linu…...

Zabbix监控Oracle 19c数据库完整配置指南

Zabbix监控Oracle 19c数据库完整配置指南 本文将详细介绍如何使用Zabbix配置Oracle 19c数据库监控&#xff0c;包括安装、配置、问题排查等全过程。本指南适合新手独立完成配置。 1. 环境准备 1.1 系统要求 Oracle 19c数据库服务器Zabbix服务器&#xff08;版本5.0或更高&a…...

解决 Maven 部署中的 Artifact 覆盖问题:实战经验分享20241204

&#x1f6e0;️ 解决 Maven 部署中的 Artifact 覆盖问题&#xff1a;实战经验分享 &#x1f4cc; 引言 在软件开发过程中&#xff0c;持续集成和持续部署&#xff08;CI/CD&#xff09;是提高开发效率和代码质量的关键手段。Hudson 和 Maven 是两种广泛使用的工具&#xff0…...

mb108里opengl相关

linux/linuxgdi.cpp里CreateWindowExW的 g_signal_connect(self->m_glArea, "render", G_CALLBACK(onRenderGlTextures), self); 绑定了一个渲染事件回调。 另外有 g_signal_connect(self->m_glArea, "realize", G_CALLBACK(onRealizeGlTextures)…...

使用docker让项目持续开发和部署

大多人选择开发时在本地&#xff0c;部署时文件都在容器里&#xff0c;如果没有容器&#xff0c;那就本地开发&#xff0c;没有映射文件&#xff0c;如果部署环境到容器了&#xff0c;容器内部启动时设置执行命令&#xff0c;再将映射的文件进行编译&#xff0c;这就直接能实现…...

数据结构-查找

数据结构——二叉树先序、中序、后序及层次四种遍历&#xff08;C语言版&#xff09;_中序遍历-CSDN博客...

2030. gitLab A仓同步到B仓

文章目录 1 A 仓库备份 到 B 仓库2 B 仓库修改main分支的权限 1 A 仓库备份 到 B 仓库 #!/bin/bash# 定义变量 REPO_DIR"/home/xhome/opt/git_sync/zz_xx_xx" # 替换为你的本地库A的实际路径 REMOTE_ORIGIN"http://192.168.1.66:8181/zzkj_software/zz_xx_xx.…...

Ubuntu防火墙管理(五)——ufw源规则解读与修改

firewalld与nftables 在 /etc/firewalld/firewalld.conf 文件中&#xff0c;FirewallBackend 选项用于指定 Firewalld 使用的防火墙后端实现。具体来说&#xff1a; nftables&#xff1a;这是当前的默认选项&#xff0c;表示 Firewalld 将使用 nftables 作为防火墙后端。nftab…...

Flink+Paimon实时数据湖仓实践分享

随着 Paimon 近两年的推广普及&#xff0c;使用 FlinkPaimon 构建数据湖仓的实践也越来越多。在 Flink 实时数据开发中&#xff0c;对于依赖大量状态 state 的场景&#xff0c;如长周期的累加指标计算、回撤长历史数据并更新等&#xff0c;使用实时数仓作为中间存储来代替 Flin…...

全面解析DApp开发中的智能合约设计

在DApp的开发过程中&#xff0c;智能合约的设计起到了至关重要的作用。智能合约是运行在区块链上的程序&#xff0c;负责处理和执行DApp中的逻辑、交易和数据存储。下面我们将深入探讨智能合约的设计原则、挑战和优化方法&#xff0c;帮助开发者掌握如何设计高效、安全的智能合…...

强化学习新突破:情节记忆与奖励机制引领多智能体协作

简介 本推文介绍了韩国科学技术院发表在人工智能顶会ICLR 2024上的论文《Efficient Episodic Memory Utilization of Cooperative Multi-Agent Reinforcement Learning》。该论文提出创新性高效情节记忆利用&#xff08;Efficient Episodic Memory Utilization&#xff0c;EMU…...

VUE3学习二

教程视频 【尚硅谷Vue3入门到实战&#xff0c;最新版vue3TypeScript前端开发教程】https://www.bilibili.com/video/BV1Za4y1r7KE?p67&vd_sourcef1bd3b5218c30adf0a002c8c937e0a27 零 环境搭建 学习环境 windows10node 18vue3 创建项目 npm create vuelatest 选项中…...

MySQL Group Replication

参考文档&#xff1a; https://dev.mysql.com/doc/refman/8.4/en/group-replication-configuring-instances.html MySQL版本&#xff1a; mysql> select version(); ----------- | version() | ----------- | 8.4.3 | ----------- 1 row in set (0.00 sec)mysql> …...

设计模式学习思路二

设计模式的学习思路_设计模式必须按顺序进行吗-CSDN博客 以下是一些方法和思路可以帮助你更清晰地识别使用了哪种设计模式。 1. 确定模式时的思考步骤 以下是分析代码时&#xff0c;你可以遵循的一些思路和步骤&#xff0c;帮助你识别可能使用的设计模式&#xff1a; a. 识别…...

MySql 笔记

drop database if exists school; create database school default charset utf8; -- 切换到数据库school use school; -- 创建学生表 drop table if exists tb_student; create table tb_student ( stuid int not null comment 学号, stuname varchar(20) not null comment 姓…...

【Qt】QTableView选中行发生变化时触发的信号

问题 QTableView选中的行发生变化时&#xff0c;使用的信号是QTableView的selectionModel()里的currentChanged信号&#xff0c;界面点击行来回切换&#xff0c;发现怎么也触发不了&#xff1f; 原因 信号槽连接放在了QTableView数据初始化前面&#xff0c;这时候QTableView…...

qt图像合成模式分析

文章目录 定义含义示例分析CompositionMode_ClearCompositionMode_SourceCompositionMode_DestinationCompositionMode_SourceOverCompositionMode_DestinationOverCompositionMode_SourceInCompositionMode_DestinationInCompositionMode_SourceOutCompositionMode_Destinatio…...

http与https的区别

加密方式&#xff1a; 加密技术是对信息进行编码和解码的技术&#xff0c;编码是把原来可读信息&#xff08;又称明文&#xff09;译成代码形式&#xff08;又称密文&#xff09;&#xff0c;其逆过程就是解码&#xff08;解密&#xff09;&#xff0c;加密技术的要点是加密算…...

Pyside6 --Qt Designer--Qt设计师--了解+运行ui_demo_1.py

目录 一、打开Qt设计师1.1 Terminal终端1.2 打开env&#xff0c;GUI虚拟环境下的scripts文件1.3 不常用文件介绍&#xff08;Scripts下面&#xff09; 二、了解Qt设计师的各个控件作用2.1 点击widget看看效果&#xff01;2.2 点击Main Window看看效果 三、编写一个简易的UI代码…...

11.17【大数据】Hadoop【DEBUG】

列出hdfs文件系统所有的目录和文件 主节点上 子结点 是一样的 *为什么能登进 slave 02 的主机,但是 master 当中依然显示 slave 02 为 DeadNode?* hadoop坏死节点的重启_hadoop3 子节点重启-CSDN博客 注意hadoop-daemon.sh 实际上位于 Hadoop 的 sbin 目录中&#xff0c;而不…...

MQ:kafka-消费者的三种语义

文章目录 前言(一) 创建topic(二) 生产者&#xff08;三&#xff09;消费者1. At-most-once Kafka Consumer2. At-least-once kafka consumer3. 使用subscribe实现Exactly-once4. 使用assign实现Exactly-once 前言 本文主要是以kafka 09的client为例子&#xff0c;详解kafka c…...

QT 线程锁

在 Qt 中&#xff0c;线程锁是用来同步多线程访问共享资源的机制&#xff0c;防止数据竞争和线程安全问题。Qt 提供了几种线程锁和同步工具&#xff0c;主要包括以下几种&#xff1a; 1. QMutex 功能&#xff1a;QMutex 是 Qt 中最常用的互斥锁&#xff08;mutex&#xff09;…...

C++中protobuf Message与JSON的互相转换

C中protobuf Message与JSON的互相转换 环境&#xff1a; protobuf: v27.3(2024-08-01) abseil: 20240722.0文章目录 C中protobuf Message与JSON的互相转换前言1. 编写通讯录addressbook.proto2. 编译3. C中测试protobuf与json的转换4. 结果 前言 PB转JSON&#xff1a;Protoc…...

Milvus向量数据库03-搜索理论

Milvus向量数据库03-搜索理论 1-ANN搜索 通过 k-最近邻&#xff08;kNN&#xff09;搜索可以找到一个查询向量的 k 个最近向量。kNN 算法将查询向量与向量空间中的每个向量进行比较&#xff0c;直到出现 k 个完全匹配的结果。尽管 kNN 搜索可以确保准确性&#xff0c;但十分耗…...

qt QCryptographicHash详解

1、概述 QCryptographicHash是Qt框架中提供的一个类&#xff0c;用于实现加密散列函数&#xff0c;即哈希函数。哈希函数能够将任意长度的数据转换为固定长度的哈希值&#xff0c;也称为散列值或数据指纹。这个哈希值通常用于数据的完整性校验、密码存储等场景。QCryptographi…...

【论文阅读】具身人工智能(Embodied AI)综述:连接数字与物理世界的桥梁

摘要 具身人工智能&#xff08;Embodied AI&#xff09;对于实现通用人工智能&#xff08;AGI&#xff09;至关重要&#xff0c;是连接数字世界与物理世界的各类应用的基础。近年来&#xff0c;多模态大模型&#xff08;MLMs&#xff09;和世界模型&#xff08;WMs&#xff09…...

使用el-row和el-col混合table设计栅格化,实现表头自适应宽度,表格高度占位

演示效果&#xff1a; 如上图,由于地址信息很长,需要占多个格子,所以需要错开,若想实现这种混合效果,可以这样搭建: 页面效果&#xff1a; 代码分析: 上面使用el-row和el-col搭建表单显示 第一排三个8,第二排8和16 下面混合table实现&#xff0c;并使用border来自适应宽度…...

MQ的基本概念

1 MQ的基本概念 RabbitMQ是一个开源的消息代理和队列服务器&#xff0c;它使用Erlang语言编写并运行在多种操作系统上&#xff0c;如Linux、Windows等。RabbitMQ可以接收、存储和转发消息&#xff08;也称为“事件”&#xff09;到连接的客户端。它适用于多种场景&#xff0c;…...