【Vue3源码解析】应用实例创建及页面渲染
- 其他
- 2025-09-03 02:42:01

下载源码 git clone github /vuejs/core.git
写该文章时的Vue版本为:
"version": "3.5.13",这里要注意 pnpm 的版本不能太低,我此时的版本为 9.15.4。更新 pnpm 版本:
npm install -g pnpm然后安装依赖:
pnpm i然后打包开发环境:
pnpm run dev之后就可以编写自己的测试程序,debugger 调试代码。
Vue3 整体架构在打包后的文件夹内创建一个自己用来测试的 demo 文件夹,我这里叫做 heo 文件夹
编写测试代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <h1>计数器案例</h1> <h2>当前计数: {{counter}}</h2> <button @click="increment">+1</button> <button @click="decrement">-1</button> </div> <script src="../dist/vue.global.js"></script> <script> const {ref, createApp} = Vue // 根组件 const App = { setup() { const counter = ref(0) function increment() { counter.value++ } function decrement() { counter.value-- } return { counter, increment, decrement } } } // 创建全局 app对象,调用 mount 方法挂载到页面上 const app = createApp(App) app.mount('#app') </script> </body> </html>然后再使用内置插件自带的服务器预览当前页面:
createApp 的创建过程找到创建 app 实例的 createApp 函数:
从创建 app 对象到创建 渲染器的过程:
/** * 创建一个 Vue 应用实例(入口)。 * * @param {...any} args - 传递给内部创建应用方法的参数。 * @returns {CreateAppFunction<Element>} 返回一个带有自定义 `mount` 方法的应用实例对象 app。 */ export const createApp = ((...args) => { // 调用确保渲染器存在并创建应用实例 app // ensureRenderer() 实例化一个渲染器 渲染器就是把 Vue 代码(组件)渲染到DOM 上 // ensureRenderer() 最终的返回值 是一个对象 // { // render, // 核心函数 负责将 VNode 渲染成真实 DOM // hydrate, // SSR 的函数 // createApp: createAppAPI(render, hydrate), // } const app = ensureRenderer().createApp(...args) // 在开发模式下注入原生标签检查和编译选项检查 if (__DEV__) { injectNativeTagCheck(app) injectCompilerOptionsCheck(app) } // 获取原始的 mount 方法 const { mount } = app // 自定义(重写) mount 方法,接受DOM或选择器作为参数 当我们调用mount时 本质上在调用重写的 mount 方法 // 装饰器模式 不改变原有实现 对 mount 进行增强 app.mount = (containerOrSelector: Element | ShadowRoot | string): any => { // 将传入的选择器或元素转换为实际的 DOM 元素 也就是我们传入的要挂载的 DOM // normalizeContainer 转换DOM 比如将 "#app" 转为 querySelector 函数拿到DOM const container = normalizeContainer(containerOrSelector) if (!container) return // 如果DOM不存在,直接返回 const component = app._component // 获取应用的根组件 // 如果根组件没有 render 函数或 template 属性,则从容器(#app)内的HTML作为组件的模版 if (!isFunction(component) && !component.render && !component.template) { // __UNSAFE__ // 原因:潜在的执行在 DOM 内的模板中的 JS 表达式。 // 用户必须确保该模板是可信的。如果由服务器渲染,则模板不应包含任何用户数据。 component.template = container.innerHTML // 2.x 兼容性检查 if (__COMPAT__ && __DEV__ && container.nodeType === 1) { for (let i = 0; i < (container as Element).attributes.length; i++) { const attr = (container as Element).attributes[i] // 检查是否有 v- 或 : 或 @ 开头的属性,提示用户使用新的挂载方式 if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) { compatUtils.warnDeprecation( DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null, ) break } } } } // 在挂载前清除容器的内容 if (container.nodeType === 1) { container.textContent = '' } // 调用原始的 mount 方法进行挂载,并返回代理实例 const proxy = mount(container, false, resolveRootNamespace(container)) // 如果容器是一个元素,移除 v-cloak 属性并添加 data-v-app 属性 if (container instanceof Element) { container.removeAttribute('v-cloak') container.setAttribute('data-v-app', '') } return proxy } return app }) as CreateAppFunction<Element>我们自己开发的时候根组件都是 template 属性,而如果没有template 或者 render ,则从容器(#app)内的HTML作为组件的模版,例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--将该 div 作为 作为组件的模版--> <div id="app"> <h1>计数器案例</h1> <h2>当前计数: {{counter}}</h2> <button @click="increment">+1</button> <button @click="decrement">-1</button> </div> <script src="../dist/vue.global.js"></script> <script> const {ref, createApp} = Vue // 根组件 const App = { setup() { const counter = ref(0) function increment() { counter.value++ } function decrement() { counter.value-- } return { counter, increment, decrement } } } // 创建全局 app对象,调用 mount 方法挂载到页面上 const app = createApp(App) app.mount('#app') </script> </body> </html> 创建渲染器和 mount 的执行从创建虚拟节点到渲染页面的过程:
// nodeOps 节点操作(比如insert/remove/createComment创建注释元素 等)和 patchProp 属性操作(比如 class/style/@click 等) const rendererOptions = /*@__PURE__*/ extend({ patchProp }, nodeOps) // lazy create the renderer - this makes core renderer logic tree-shakable // in case the user only imports reactivity utilities from Vue. let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer /** * 确保渲染器已被创建 * * 此函数用于检查全局变量 `renderer` 是否已存在,如果不存在,则使用给定的 `rendererOptions` * 创建一个新的渲染器并赋值给 `renderer`,以确保渲染器的单例模式 * 这种方法保证了在整个应用中只有一个渲染器实例,从而优化性能 如果有多个渲染器实例可能导致状态管理和渲染更新的冲突和重复 * * rendererOptions - 定制化 针对不同平台(移动端/SSR)的渲染器配置 * * @returns {Renderer} 已存在的渲染器或新创建的渲染器实例 */ function ensureRenderer() { return ( renderer || (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)) ) } /** * The createRenderer function accepts two generic arguments: * HostNode and HostElement, corresponding to Node and Element types in the * host environment. For example, for runtime-dom, HostNode would be the DOM * `Node` interface and HostElement would be the DOM `Element` interface. * * Custom renderers can pass in the platform specific types like this: * * ```js * const { render, createApp } = createRenderer<Node, Element>({ * patchProp, * ...nodeOps * }) * ``` */ export function createRenderer< HostNode = RendererNode, HostElement = RendererElement, >(options: RendererOptions<HostNode, HostElement>): Renderer<HostElement> { return baseCreateRenderer<HostNode, HostElement>(options) }因此 createRenderer 才是创建渲染器的代码实现(有2000行代码,这里不再粘代码)。它的返回值很重要是:
{ render, // 核心函数 负责将 VNode 渲染成真实 DOM hydrate, // SSR 的函数 createApp: createAppAPI(render, hydrate), } export function createAppAPI<HostElement>( render: RootRenderFunction<HostElement>, hydrate?: RootHydrateFunction, ): CreateAppFunction<HostElement> { // 返回的函数 也就是我们调用createApp()函数创建的真实app(这里使用函数柯里化传递了render和hydrate) return function createApp(rootComponent, rootProps = null) { // 如果不是函数组件 转化为对象的形式 if (!isFunction(rootComponent)) { rootComponent = extend({}, rootComponent) } // rootProps 如果不是对象则警告 if (rootProps != null && !isObject(rootProps)) { __DEV__ && warn(`root props passed to app.mount() must be an object.`) rootProps = null } // 创建app上下文实例 存储 app配置信息 const context = createAppContext() // 安装 plugins 使用set防止重复安装 const installedPlugins = new WeakSet() const pluginCleanupFns: Array<() => any> = [] // 目前刚刚创建app实例 没有挂载到dom上 let isMounted = false // app 实例对象 函数的最后返回 该app实例对象 const app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, _instance: null, version, //... use(plugin: Plugin, ...options: any[]) { if (installedPlugins.has(plugin)) { __DEV__ && warn(`Plugin has already been applied to target app.`) } else if (plugin && isFunction(plugin.install)) { installedPlugins.add(plugin) plugin.install(app, ...options) } else if (isFunction(plugin)) { installedPlugins.add(plugin) plugin(app, ...options) } else if (__DEV__) { warn( `A plugin must either be a function or an object with an "install" ` + `function.`, ) } return app }, //... mount( rootContainer: HostElement, isHydrate?: boolean, namespace?: boolean | ElementNamespace, ): any { if (!isMounted) { // #5571 if (__DEV__ && (rootContainer as any).__vue_app__) { warn( `There is already an app instance mounted on the host container.\n` + ` If you want to mount another app on the same host container,` + ` you need to unmount the previous app by calling \`app.unmount()\` first.`, ) } // 根据传入的根组件和属性 创建对应的 VNode const vnode = app._ceVNode || createVNode(rootComponent, rootProps) // store app context on the root VNode. // this will be set on the root instance on initial mount. // 在 vnode 存储上下文 vnode.appContext = context //... if (isHydrate && hydrate) { hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else { // 非ssr 则将上面的虚拟节点渲染到dom上 render(vnode, rootContainer, namespace) } // 设置已经挂载的标记 isMounted = true app._container = rootContainer // for devtools and telemetry ;(rootContainer as any).__vue_app__ = app if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = vnode ponent devtoolsInitApp(app, version) } return getComponentPublicInstance(vnode ponent!) } else if (__DEV__) { warn( `App has already been mounted.\n` + `If you want to remount the same app, move your app creation logic ` + `into a factory function and create fresh app instances for each ` + `mount - e.g. \`const createMyApp = () => createApp(App)\``, ) } }, //... return app } }patch 代码执行过程:
patch 组件的 children 过程:
组件内部 effect 处理过程:
createVNode 的过程(h 函数的本质)packages/runtime-core/src/h.ts
/** * 创建一个虚拟节点(VNode),基于提供的类型、属性和子节点。 * 对外暴露为 h 函数,内部使用 createVNode 函数来创建虚拟节点。 * * @param type VNode的组件类型,通常是一个表示DOM元素或组件函数的字符串。 * @param propsOrChildren VNode的属性或子节点,取决于上下文。 * @param children VNode的子节点,可以是单个VNode或VNode数组。 * @returns 返回创建的VNode。 */ export function h(type: any, propsOrChildren?: any, children?: any): VNode { // 获取参数的数量,以确定如何处理它们。 const l = arguments.length // 如果只提供了两个参数,则进一步根据第二个参数的类型进行区分。 if (l === 2) { // 判断第二个参数是否为对象且不是数组,以决定它是属性还是子节点。 if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { // 单个VNode且没有属性 if (isVNode(propsOrChildren)) { return createVNode(type, null, [propsOrChildren]) } // 有属性但没有子节点 return createVNode(type, propsOrChildren) } else { // 省略属性 return createVNode(type, null, propsOrChildren) } } else { // 处理超过三个参数的情况,将第二个参数之后的所有参数转换为子节点数组。 if (l > 3) { children = Array.prototype.slice.call(arguments, 2) } else if (l === 3 && isVNode(children)) { // 如果正好提供了三个参数且第三个参数是单个VNode,则将其封装到数组中。 children = [children] } // 最后,使用属性和子节点创建VNode。 return createVNode(type, propsOrChildren, children) } } (type, propsOrChildren) } else { // 省略属性 return createVNode(type, null, propsOrChildren) } } else { // 处理超过三个参数的情况,将第二个参数之后的所有参数转换为子节点数组。 if (l > 3) { children = Array.prototype.slice.call(arguments, 2) } else if (l === 3 && isVNode(children)) { // 如果正好提供了三个参数且第三个参数是单个VNode,则将其封装到数组中。 children = [children] } // 最后,使用属性和子节点创建VNode。 return createVNode(type, propsOrChildren, children) } }【Vue3源码解析】应用实例创建及页面渲染由讯客互联其他栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【Vue3源码解析】应用实例创建及页面渲染”