引言
最近的求职之路不太平坦,面试机会不多,还在一场关键的面试中,被一个关于框架源码设计模式的问题卡住了。实际上最近这几年已经不关注 Vue/React 这类框架的源码了,当时只想到了 Vue 响应式里使用的观察者模式,其他一时语塞。最后经过三轮面试,还是痛失 offer 了。
之后开始复盘,在现在充满挑战的前端就业市场(叠加 AI 的冲击),再投入大量时间通读 Vue/React 整个源码,其投入产出比(ROI)可以说太低了。当然,研读优秀源码永远有益,但时间有限、目标明确(比如冲刺面试)时,聚焦核心才是更务实的选择。
因此,我决定将聚焦放在关键源码所运用的设计模式上,做了系统性的梳理和解析。武装自己,争取下次面试拿到offer💪🏻。
设计模式对于开发的价值
设计模式 作为软件工程中的最佳实践 ,在现代前端框架的架构中扮演着核心角色。Vue和React虽实现思路不同,但都巧妙运用了多种设计模式解决复杂问题,构建出高质量的开源项目。对于我们开发来说,掌握好设计模式可以提高代码的可维护性、可扩展性、复用性,当然最重要的是可以跟面试官吹水🐶。
这篇文章我们将深入剖析Vue和React源码中应用最广泛、最关键的几个模式:观察者/发布-订阅 (Observer/Pub-Sub) 、策略模式 (Strategy) 、组合模式 (Composite) 、工厂模式 (Factory) 、代理模式 (Proxy) ,以及React特有的状态模式 (State) 和 Hooks背后的组合思想。
注:以下提及的源码均为简化版,实际的源码复杂得多,包括各种环境、SSR、状态、边界情况等判断逻辑,不易于快速理解,本文重点在于解析源码应用的设计模式。
如果要看完整源码,可以通过文中的函数名快速定位找到对应源码
一、Vue源码中的设计模式解析
1. 观察者模式
先了解观察者模式是什么:观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。
Vue的响应式系统核心正是观察者模式的应用实现。通过建立响应式数据与依赖之间的自动通知机制,实现数据变化到UI更新的自动化过程。
源码实现(Vue 2.x) :
javascript。
// 1. 依赖收集器:管理每个属性的依赖(观察者)
class Dep {
constructor() {
this.subscribers = []; // 存储依赖的观察者
}
// 添加观察者
addSub(sub) {
if (sub && sub.update) {
this.subscribers.push(sub);
}
}
// 通知所有观察者更新
notify() {
this.subscribers.forEach(sub => {
sub.update();
});
}
}
// 2. 观察者:负责更新视图或执行回调
class Watcher {
constructor(vm, key, cb) {
this.vm = vm; // Vue实例
this.key = key; // 要观察的属性名
this.cb = cb; // 数据变化时执行的回调
this.value = this.get(); // 初始化时获取值,同时触发依赖收集
}
// 获取值并建立依赖关系
get() {
Dep.target = this; // 将当前观察者标记为目标
const value = this.vm[this.key]; // 访问属性,触发getter,完成依赖收集
Dep.target = null; // 清除标记
return value;
}
// 数据变化时更新
update() {
const newValue = this.vm[this.key];
const oldValue = this.value;
if (newValue !== oldValue) {
this.value = newValue;
this.cb(newValue, oldValue); // 执行回调
}
}
}
// 3. 数据劫持:将对象转为响应式
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
// 遍历对象属性,设置getter和setter
Object.keys(data).forEach(key => {
let value = data[key];
const dep = new Dep(); // 每个属性对应一个依赖收集器
// 递归处理子属性
observe(value);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
// 获取属性时收集依赖
get() {
// 如果有目标观察者,添加到依赖中
if (Dep.target) {
dep.addSub(Dep.target);
}
return value;
},
// 设置属性时通知依赖更新
set(newValue) {
if (newValue !== value) {
value = newValue;
observe(newValue); // 新值如果是对象,也需要转为响应式
dep.notify(); // 通知所有依赖更新
}
}
});
});
}
在 Vue 3中,这一模式改为使用Proxy 实现,性能更佳。这里涉及了两个设计模式,通过 代理模式 拦截数据访问、观察者模式 管理依赖关系。
reactive()
:创建响应式代理对象。effect()
/ReactiveEffect
:类似Watcher
,代表一个会产生副作用的函数(如渲染函数)。在副作用函数执行时访问响应式属性,自动建立依赖关系。targetMap
:全局WeakMap,存储响应式对象(target
)到其属性(key
)对应的Dep
(在Vue3中通常叫deps
或依赖集合)的映射。
javascript
function createReactiveObject(target) {
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
// 返回属性值(如果是对象则递归代理)
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
// 更新值
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 触发更新(仅当值变化时)
if (oldValue !== value) {
trigger(target, key);
}
return result;
}
});
return proxy;
}
-
角色划分:
- Subject(目标) :响应式对象(通过
reactive()
创建)。 - Observer(观察者) :依赖(
effect
),例如组件的渲染函数、计算属性等。
- Subject(目标) :响应式对象(通过
-
工作流程:
- 观察者(
effect
)执行时访问响应式数据,触发get
拦截。 - 目标(响应式对象)收集当前观察者作为依赖。
- 数据变化时通过
set
拦截通知所有依赖更新。
- 观察者(
2. 发布订阅模式(事件系统的核心)
发布-订阅模式的核心思想是:将消息的发送者(发布者)与接收者(订阅者)解耦,通过一个中间的"调度中心"或"事件通道"来管理订阅关系并分发消息。发布者不直接通知订阅者,而是通知调度中心,由调度中心再统一通知所有订阅了该消息的订阅者。
常见的应用场景,比如聊天室、跨组件通信,还有微前端主应用与子应用、子应用之间需通信等等。流程图如下:

Vue的自定义事件系统基于发布-订阅模式,实现了组件间解耦通信 。组件通过$emit
发布事件,其他组件通过$on
订阅事件。这里我们看下源码里EventBus的封装:
src\core\instance\events.ts:
javascript
function eventsMixin(Vue) {
const hookRE = /^hook:/;
/**
* 订阅事件 - 添加事件监听器
* @param {string|Array<string>} event - 事件名称或事件名称数组
* @param {Function} fn - 事件触发时执行的回调函数
* @returns {Component} 返回组件实例,支持链式调用
*/
Vue.prototype.$on = function (event, fn) {
const vm = this;
// 如果传入的是事件数组,递归为每个事件添加监听器
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn);
}
} else {
// 核心逻辑:将回调函数添加到对应事件的数组中
// 如果事件不存在,先创建空数组,然后添加回调函数
(vm._events[event] || (vm._events[event] = [])).push(fn);
// 优化:如果是生命周期钩子事件,标记组件有钩子事件
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
return vm;
};
/**
* 订阅事件(一次性)- 事件触发后自动取消订阅
* @param {string} event - 事件名称
* @param {Function} fn - 事件触发时执行的回调函数
* @returns {Component} 返回组件实例
*/
Vue.prototype.$once = function (event, fn) {
const vm = this;
// 创建一个包装函数,执行后自动取消订阅
function on() {
vm.$off(event, on); // 取消订阅
fn.apply(vm, arguments); // 执行原始回调函数
}
on.fn = fn; // 保存原始函数引用,用于取消订阅时的比较
vm.$on(event, on); // 订阅包装后的函数
return vm;
};
/**
* 取消订阅事件 - 移除事件监听器
* @param {string|Array<string>} [event] - 事件名称或事件名称数组,不传则取消所有事件
* @param {Function} [fn] - 具体的回调函数,不传则取消该事件的所有监听器
* @returns {Component} 返回组件实例
*/
Vue.prototype.$off = function (event, fn) {
const vm = this;
// 情况1:没有参数,取消所有事件的所有监听器
if (!arguments.length) {
vm._events = Object.create(null);
return vm;
}
// 情况2:传入事件数组,递归取消每个事件
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn);
}
return vm;
}
// 情况3:传入具体事件名称
const cbs = vm._events[event];
if (!cbs) {
return vm; // 事件不存在,直接返回
}
// 情况4:没有传入具体回调函数,取消该事件的所有监听器
if (!fn) {
vm._events[event] = null;
return vm;
}
// 情况5:传入具体回调函数,只取消该函数的监听
let cb;
let i = cbs.length;
while (i--) {
cb = cbs[i];
// 比较函数引用,支持 $once 包装的函数
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break;
}
}
return vm;
};
/**
* 发布事件 - 触发事件,通知所有订阅者
* @param {string} event - 事件名称
* @returns {Component} 返回组件实例
*/
Vue.prototype.$emit = function (event) {
const vm = this;
let cbs = vm._events[event];
if (cbs) {
// 将回调函数数组转换为真正的数组(避免修改原数组)
cbs = cbs.length > 1 ? Array.prototype.slice.call(cbs) : cbs;
// 获取传递给回调函数的参数(排除事件名称)
const args = Array.prototype.slice.call(arguments, 1);
// 错误信息,用于调试
const info = `event handler for "${event}"`;
// 遍历所有订阅者,执行回调函数
for (let i = 0, l = cbs.length; i < l; i++) {
// 这里应该调用错误处理函数,简化版本直接调用
try {
cbs[i].apply(vm, args);
} catch (error) {
console.error(info, error);
}
}
}
return vm;
};
}
3. 工厂模式(组件创建)
工厂模式的核心是提供一个统一的接口(函数)来创建不同类型的对象 。new Vue使用工厂模式标准化组件创建流程,组件实例、虚拟节点(VNode)的创建过程都是由工厂模式封装的。
-
创建组件实例:
Vue.extend()
(Vue 2) 或内部createComponentInstance()
(Vue 3) 就像工厂。它们负责合并选项(Options)、处理mixins
、应用全局配置、初始化生命周期和事件系统等复杂逻辑,最终生成一个可用的组件实例。 -
创建虚拟DOM (VNode):
createElement
/h
函数是VNode工厂。它根据传入的标签名、属性、子节点等信息,创建并返回一个描述真实DOM结构的VNode对象。核心渲染器只与VNode接口交互,不关心具体创建细节。 -
解决的问题: 封装创建对象(组件实例、VNode)的复杂过程 ,提供一个统一的创建接口 。解耦了对象的创建过程和使用过程,使系统更灵活(例如,可以轻松替换创建逻辑或创建不同类型的产品)。
虚拟DOM工厂示例:
javascript
// 简化版VNode工厂函数
export function createElement(tag, data, children) {
return new VNode(tag, data, children, undefined, undefined);
}
class VNode {
constructor(tag, data, children, text, elm) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
}
}
4. 组合模式(组件树的构建)
Vue的组件树结构是组合模式的直接体现,允许将简单组件组合成复杂界面,Vue应用就是一棵组件树。
-
核心应用:构建组件树的统一模型。 Vue 的核心抽象是组件树。
-
组件树结构: 一个父组件可以包含多个子组件,子组件又可以包含自己的子组件,形成树形结构(Composite Tree)。Vue 在渲染时,会递归处理这棵树。
-
解决的问题: 以统一的方式处理单个对象(叶子组件)和组合对象(父组件) 。客户端代码(Vue核心渲染器)可以一致地处理整个树结构,无需关心当前处理的是简单组件还是复杂组件树。简化了复杂UI结构的构建和管理。
典型实现:
vue
<template>
<div>
<NavigationBar />
<MainContent>
<Sidebar />
<ArticleView />
</MainContent>
<AppFooter />
</div>
</template>
5. 策略模式(编译器决策)
策略模式是指定义一系列算法/行为,使得这些算法可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法。
比如支付场景,有多个支付渠道,每个支付逻辑不一样,流水账式的代码是在支付时通过if-else,根据不同支付渠道写不同的逻辑,而使用策略模式,可以定义这些支付渠道的逻辑,根据场景匹配算法,而不用每次某个支付逻辑变动时需要修改支付方法,符合开闭原则的设计思想。
Vue编译器正是使用了策略模式,针对不同指令应用不同编译策略,实现关注点分离。
策略应用场景:
- v-if:条件编译策略
- v-for:循环编译策略
- v-model:双向绑定策略
每种指令都有对应的编译策略对象,编译器根据指令类型选择相应策略处理。
-
生命周期钩子调用: Vue 核心并不关心每个具体钩子(
created
,mounted
等)做了什么,它只是按照固定流程(初始化、挂载、更新、销毁)去调用 注册在这些钩子上的策略函数(用户定义的方法)。不同的钩子阶段对应不同的调用策略。 -
指令 (
v-bind
,v-model
,v-for
等) 的实现: 每种指令都是一个独立的"策略"。解析模板时,遇到不同的指令,会调用对应的指令处理函数(如bind
,update
)来处理DOM操作或逻辑。新增一个指令(v-xxx
)就是新增一个策略,核心指令解析逻辑无需修改。 -
解决的问题: 避免在核心流程中使用庞大的
if-else
或switch-case
来判断执行哪种行为。将算法或行为封装在独立的策略对象/函数中,使其易于切换、扩展和维护。
6. 单例模式(全局状态管理)
Vue通过单例模式确保全局对象(如Vuex store)的唯一实例。
javascript
let Vue; // 保存Vue引用
export function install(_Vue) {
if (Vue && _Vue === Vue) {
return; // 防止重复安装
}
Vue = _Vue;
applyMixin(Vue);
}
这种模式保证了全局状态单一数据源,避免重复实例化造成的状态错乱的问题。
二、React源码中的设计模式剖析
1. 发布-订阅模式
前面说过,发布-订阅模式有三个角色:发布者,订阅者和调度中心,而在 React 中,这个"调度中心"就是其强大的 Fiber 架构和协调器(Reconciler) 。让我们看几个最核心的体现。
应用一:状态更新机制 (setState
/ useState
)
这是最核心的发布-订阅应用。组件通过 useState
"订阅"了状态的更新,而 setState
就是"发布"更新事件。
简化流程:
- 订阅 (Subscription): 当函数组件执行时,
useState
等 Hooks 会将组件本身(对应的 Fiber 节点)与这个状态更新队列关联起来,建立起"订阅"关系。 - 发布 (Publish): 你调用
setState(newValue)
。 - 调度中心 (Scheduler/Reconciler): 这个调用不会立即更新组件,而是创建一个更新对象(
update
),并将其放入对应组件的更新队列中。然后通知调度中心:"有活干了!"。 - 通知 (Notify): 调度中心根据优先级调度,在合适的时机(比如浏览器空闲时)告诉协调器开始工作。协调器遍历所有待处理的更新,计算出新的状态,并最终"通知"到订阅了该更新的组件------即安排它重新渲染。
源码佐证 (极度简化,反映思想):
-
发布 (
dispatchAction
-setState
的真身):javascript// react-reconciler/src/ReactFiberHooks.old.js function dispatchAction<S, A>( fiber: Fiber, // 关联的组件Fiber节点 queue: UpdateQueue<S, A>, // 该状态对应的更新队列 action: A // 你要设置的新值或更新函数 ) { // 1. 创建更新对象(代表一个发布的事件) const update: Update<S, A> = { action, next: null, }; // 2. 将更新对象加入到队列中(将事件放入调度中心的待处理列表) const pending = queue.pending; if (pending === null) { update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update; // 3. !!!核心:通知调度中心 ------ "有更新了,快来处理!" scheduleUpdateOnFiber(fiber, lane); }
-
调度中心处理 (
scheduleUpdateOnFiber
):javascript// react-reconciler/src/ReactFiberWorkLoop.old.js function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane) { // ... 标记更新链路,找到根节点(RootFiber)... const root = markUpdateLaneFromFiberToRoot(fiber, lane); if (root !== null) { // !!!告知调度器(Scheduler),这才是真正的中央调度中心 ensureRootIsScheduled(root, eventTime); } }
调度器 (Scheduler
) 会决定是立即执行、稍后执行(setTimeout
)还是等到浏览器空闲时执行(requestIdleCallback
polyfill)。当它决定执行时,会启动一个协调(Reconciliation) 过程,最终计算出哪些组件需要重新渲染,并通知渲染器(如 ReactDOM)去更新DOM。
应用二:Context API
这是 React 内置的、最经典的发布-订阅模型实现。
- 发布者 (Publisher):
Context.Provider
- 调度中心 (Channel):
Context
对象本身(内部的_currentValue
和订阅者链表) - 订阅者 (Subscribers): 使用
useContext(MyContext)
或<MyContext.Consumer>
的组件
工作流程:
- 订阅: 当
useContext(MyContext)
在组件中执行时,React 会将当前组件的 Fiber 节点添加到该 Context 对象的订阅者链表中。 - 发布: 当
Provider
的value
属性发生变化时。 - 通知: React 会遍历整个 Context 的订阅者链表,标记所有订阅了该 Context 的组件需要更新。然后, again,由调度中心和协调器来安排这些组件的重新渲染。
源码佐证:
-
订阅 (
readContext
-useContext
的内部实现):javascript// react-reconciler/src/ReactFiberNewContext.old.js function readContext<T>(context: ReactContext<T>): T { // 将当前正在渲染的组件(Fiber节点)订阅到该context上 const contextItem = { context: context, memoizedValue: value, // 缓存当前值 next: null, }; if (lastContextDependency === null) { // 这是组件的第一个依赖 currentlyRenderingFiber.dependencies = { lanes: NoLanes, firstContext: contextItem, responders: null, }; } else { // 添加到依赖链表的末尾 lastContextDependency.next = contextItem; } // ... return context._currentValue; }
-
发布与通知 (
updateContextProvider
&propagateContextChange
):javascriptfunction updateContextProvider( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes ) { const context = workInProgress.type._context; const newValue = workInProgress.pendingProps.value; // ... 比较新老value ... if (oldValue !== newValue) { // !!!Context值变了,开始传播变化(通知所有订阅者) propagateContextChange( workInProgress, // Provider对应的Fiber context, // Context对象 renderLanes, // 调度优先级 ); } // ... 继续渲染子组件 ... } function propagateContextChange( workInProgress: Fiber, context: ReactContext<mixed>, renderLanes: Lanes, ): void { let fiber = workInProgress.child; // 向下遍历整个Fiber子树 while (fiber !== null) { // 检查这个Fiber节点是否依赖了当前发生变化的Context const dependencies = fiber.dependencies; if (dependencies !== null) { const contextDependencies = dependencies.firstContext; let dependency = contextDependencies; while (dependency !== null) { // 如果依赖项中找到了这个Context if (dependency.context === context) { // !!!标记这个订阅了Context的组件需要更新 // 这相当于向这个订阅者发出了通知 scheduleWorkOnParentPath(fiber.return, renderLanes); fiber.lanes = mergeLanes(fiber.lanes, renderLanes); // ... 同时标记其alternate ... break; } dependency = dependency.next; } } fiber = fiber.sibling; } }
被标记了更新的组件,就会进入我们第一部分所说的状态更新流程,等待调度中心的统一调度和渲染。
2. 组合模式(组件即积木)
React的核心设计哲学是组合优于继承。通过组件嵌套组合构建复杂UI。
-
核心应用:组件树是React的灵魂。 JSX语法天生就是描述UI树形结构的。
-
组件嵌套: 组件通过
props.children
接收并渲染其子组件,或者使用更明确的"插槽"模式(如props.render
或props.children
函数)。父组件与子组件组合形成完整的UI。 -
Fiber树: React Fiber架构底层维护着一棵与组件树对应的Fiber节点树。每个Fiber节点对应一个组件实例或DOM节点。协调过程(Reconciliation)就是在这棵Fiber树上进行diff计算。
-
解决的问题: 同Vue。统一处理整个UI树结构,无论节点是简单还是复杂。开发者可以用声明式的方式组合组件构建复杂界面。
-
与 Vue 对比: 核心理念高度一致。React主要通过
props.children
传递子元素,组合方式更"原生"(JS语法);Vue提供了更模板化的<slot>
机制。
经典实现:
jsx
function App() {
return (
<Layout>
<Header />
<Content>
<Sidebar />
<MainArea />
</Content>
<Footer />
</Layout>
);
}
这种树形结构使React能够递归协调组件树,形成高效的渲染流水线810。
3. 观察者模式(Hooks的依赖追踪)
React Hooks的依赖跟踪系统本质上是观察者模式的变体。
useEffect依赖追踪简化实现:
javascript
let currentEffect;
function useEffect(fn, deps) {
const effect = {
execute() {
currentEffect = effect;
fn();
},
deps
};
effect.execute();
}
// 依赖变更检查
function checkDeps(effect) {
// 比较新旧依赖
// 依赖变化则重新执行effect
}
Hooks机制自动跟踪依赖关系,当依赖变化时触发副作用重新执行。
4. 代理模式(保护与增强)
React广泛使用代理模式,最典型的是SyntheticEvent(合成事件)系统。
事件代理原理:
javascript
// 事件委托到根容器
const rootNode = document.getElementById('root');
rootNode.addEventListener('click', (nativeEvent) => {
// 创建代理事件
const syntheticEvent = createSyntheticEvent(nativeEvent);
// 触发对应组件的处理函数
dispatchEvent(syntheticEvent);
});
React将所有事件委托到根节点,然后通过代理事件进行分发,极大提升性能并保证跨浏览器一致性。
5. 策略模式(Diff算法的智慧)
React的协调算法(Reconciliation)使用策略模式针对不同节点类型应用不同Diff策略。
Diff策略应用:
- 相同组件类型:递归比较子节点
- 不同组件类型:直接卸载替换
- 列表元素:使用key优化移动检测
每种情况都有对应的比较算法策略,大幅提升更新效率。
6. 渲染属性模式(Render Props)
Render Props是React特有的设计模式,用于组件逻辑复用。
经典实现:
jsx
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (e) => {
this.setState({ x: e.clientX, y: e.clientY });
};
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.children(this.state)}
</div>
);
}
}
// 使用
<MouseTracker>
{({ x, y }) => <div>Cursor at ({x}, {y})</div>}
</MouseTracker>
通过函数属性传递渲染逻辑,解决了高阶组件可能带来的命名冲突和嵌套过深问题.
7.工厂模式 (Factory)
-
核心应用:构建UI描述和实例。
-
创建React元素 (React Element):
React.createElement(type, props, children)
或 JSX编译后的结果,就是一个工厂函数。它接收元素类型、属性和子元素,创建并返回一个普通的JavaScript对象 (即React Element),这个对象描述了要在屏幕上显示的内容。这是React声明式UI的基础。 - 创建组件实例: 对于类组件,其实例的创建 (new ClassComponent(props)
) 发生在协调器(Reconciler
)的工作过程中(主要在mountClassInstance
阶段)。协调器根据Fiber节点的类型,决定并执行创建组件实例的逻辑。 -
解决的问题: 封装创建React元素(描述UI的轻量JS对象)的过程 。协调器根据元素类型创建对应的Fiber节点和(类组件)实例,隔离了创建细节。
- 面试点: 理解
React.createElement
是工厂,创建描述UI的JS对象;类组件实例由协调器在需要时创建。
- 面试点: 理解
三、Vue与React设计模式对比分析
虽然两大框架都应用了多种设计模式,但其侧重点和实现哲学有显著差异:
设计模式 | Vue应用场景 | React应用场景 | 核心差异 |
---|---|---|---|
观察者模式 | 响应式数据系统 | Hooks依赖追踪 | Vue自动依赖跟踪,React需显式声明依赖 |
发布-订阅模式 | 事件总线、组件通信 | useState、Redux状态管理、Context API | Vue内置事件系统,React依赖生态 |
组合模式 | 组件树组织 | 组件组合 | 两者实现理念相近 |
策略模式 | 指令编译 | Diff算法决策 | Vue面向模板,React面向JSX |
代理模式 | Vue3的Proxy响应式 | 合成事件系统 | Vue用于数据,React用于事件 |
核心理念差异:
- Vue采用自动化的响应式系统,通过观察者模式实现数据到UI的"魔法"连接
- React遵循显式更新原则,通过发布-订阅模式传递状态变化,强调不可变数据
- Vue的模板编译大量使用策略模式优化输出
- React的组件组合更彻底,将一切视为组件
四、从框架设计模式中学习什么
- 模式是工具而非教条:两大框架灵活运用模式但不被其限制,开发者应理解模式本质而非生搬硬套
- 关注点分离:Vue的工厂模式创建VNode、React的策略模式处理Diff算法都体现了单一职责原则
- 性能与可维护性平衡:Vue的观察者模式优化了细粒度更新,React的组合模式优化了组件复用
- 模式组合创新:高级架构往往是多种模式的组合创新,如Vuex同时使用单例和观察者模式
五、面试技巧:如何回答设计模式问题
当面试官问及框架设计模式时,建议采用结构化回答:
- 明确模式定义:先简要说明设计模式的概念
plaintext
"观察者模式定义了一种一对多的依赖关系,当目标对象状态改变时,
所有依赖它的对象都会收到通知..."
-
具体框架实现:结合框架特性和源码举例
"在Vue中,这个模式用于实现响应式系统。当数据属性被访问时,
Dep类会收集当前Watcher作为依赖;当数据变化时,Dep会通知所有Watcher更新..." -
对比分析:展示对多框架的理解深度
"与Vue的自动依赖追踪不同,React需要开发者通过useEffect等API显式声明依赖关系,
这体现了两种框架不同的设计哲学..." -
实际应用:展示如何将模式应用到日常开发
vbnet
"理解这个模式后,我在项目中创建了基于Pub/Sub的事件中心,
解耦了复杂的组件通信..."
结束语
- 目标导向学习: 如果你正在准备面试,按照本文思路,优先深入理解每个框架最核心的2-3个模式(如Vue的观察者+组合+策略,React的观察者思想+Fiber状态+Hooks组合)及其应用场景和价值。理解"Why"比死磕每一行源码更高效。
- 实践出真知: 尝试在自己的项目中应用这些模式。例如,用策略模式封装不同算法,用发布-订阅解耦模块通信,用组合模式构建UI组件。
- never stop learning,优秀源码永远是学习设计模式的最佳教材。大家共勉,一起努力,争取都能实现自己的目标!