面试官:Vue和React源码里用到了哪些设计模式?

引言

最近的求职之路不太平坦,面试机会不多,还在一场关键的面试中,被一个关于框架源码设计模式的问题卡住了。实际上最近这几年已经不关注 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),例如组件的渲染函数、计算属性等。
  • 工作流程

    1. 观察者(effect)执行时访问响应式数据,触发 get 拦截。
    2. 目标(响应式对象)收集当前观察者作为依赖。
    3. 数据变化时通过 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-elseswitch-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 就是"发布"更新事件。

简化流程:

  1. 订阅 (Subscription): 当函数组件执行时,useState 等 Hooks 会将组件本身(对应的 Fiber 节点)与这个状态更新队列关联起来,建立起"订阅"关系。
  2. 发布 (Publish): 你调用 setState(newValue)
  3. 调度中心 (Scheduler/Reconciler): 这个调用不会立即更新组件,而是创建一个更新对象(update),并将其放入对应组件的更新队列中。然后通知调度中心:"有活干了!"。
  4. 通知 (Notify): 调度中心根据优先级调度,在合适的时机(比如浏览器空闲时)告诉协调器开始工作。协调器遍历所有待处理的更新,计算出新的状态,并最终"通知"到订阅了该更新的组件------即安排它重新渲染。

源码佐证 (极度简化,反映思想):

  1. 发布 (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);
    }
  2. 调度中心处理 (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> 的组件

工作流程:

  1. 订阅:useContext(MyContext) 在组件中执行时,React 会将当前组件的 Fiber 节点添加到该 Context 对象的订阅者链表中。
  2. 发布:Providervalue 属性发生变化时。
  3. 通知: React 会遍历整个 Context 的订阅者链表,标记所有订阅了该 Context 的组件需要更新。然后, again,由调度中心和协调器来安排这些组件的重新渲染。

源码佐证:

  1. 订阅 (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;
    }
  2. 发布与通知 (updateContextProvider & propagateContextChange):

    javascript 复制代码
    function 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.renderprops.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的组件组合更彻底,将一切视为组件

四、从框架设计模式中学习什么

  1. 模式是工具而非教条:两大框架灵活运用模式但不被其限制,开发者应理解模式本质而非生搬硬套
  2. 关注点分离:Vue的工厂模式创建VNode、React的策略模式处理Diff算法都体现了单一职责原则
  3. 性能与可维护性平衡:Vue的观察者模式优化了细粒度更新,React的组合模式优化了组件复用
  4. 模式组合创新:高级架构往往是多种模式的组合创新,如Vuex同时使用单例和观察者模式

五、面试技巧:如何回答设计模式问题

当面试官问及框架设计模式时,建议采用结构化回答

  1. 明确模式定义:先简要说明设计模式的概念
plaintext 复制代码
"观察者模式定义了一种一对多的依赖关系,当目标对象状态改变时,
所有依赖它的对象都会收到通知..."
  1. 具体框架实现:结合框架特性和源码举例

    "在Vue中,这个模式用于实现响应式系统。当数据属性被访问时,
    Dep类会收集当前Watcher作为依赖;当数据变化时,Dep会通知所有Watcher更新..."

  2. 对比分析:展示对多框架的理解深度

    "与Vue的自动依赖追踪不同,React需要开发者通过useEffect等API显式声明依赖关系,
    这体现了两种框架不同的设计哲学..."

  3. 实际应用:展示如何将模式应用到日常开发

vbnet 复制代码
"理解这个模式后,我在项目中创建了基于Pub/Sub的事件中心,
解耦了复杂的组件通信..."

结束语

  • 目标导向学习: 如果你正在准备面试,按照本文思路,优先深入理解每个框架最核心的2-3个模式(如Vue的观察者+组合+策略,React的观察者思想+Fiber状态+Hooks组合)及其应用场景和价值。理解"Why"比死磕每一行源码更高效。
  • 实践出真知: 尝试在自己的项目中应用这些模式。例如,用策略模式封装不同算法,用发布-订阅解耦模块通信,用组合模式构建UI组件。
  • never stop learning,优秀源码永远是学习设计模式的最佳教材。大家共勉,一起努力,争取都能实现自己的目标!
相关推荐
青红光硫化黑2 分钟前
React-native之组件
javascript·react native·react.js
菠萝+冰4 分钟前
在 React 中,父子组件之间的通信(传参和传方法)
前端·javascript·react.js
庚云6 分钟前
一套代码如何同时适配移动端和pc端
前端
胡gh7 分钟前
聊一聊构建工具:Vite和Webpack
面试·webpack·vite
Jinuss8 分钟前
Vue3源码reactivity响应式篇Reflect和Proxy详解
前端·vue3
海天胜景16 分钟前
vue3 el-select 默认选中第一个
前端·javascript·vue.js
小小怪下士_---_35 分钟前
uniapp开发微信小程序自定义导航栏
前端·vue.js·微信小程序·小程序·uni-app
前端W37 分钟前
腾讯地图组件使用说明文档
前端
页面魔术39 分钟前
无虚拟dom怎么又流行起来了?
前端·javascript·vue.js
胡gh40 分钟前
如何聊懒加载,只说个懒可不行
前端·react.js·面试