- A+
vue3 地址 https://github.com/vuejs/core
首先看看vue文档什么是 Vue?
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
下面是一个最基本的示例:
import { createApp, ref } from 'vue' createApp({ setup() { return { count: ref(0) } } }).mount('#app')
学习vue 开发 都是从这个例子开始学习,在这个例子中涉及了这些api
- 1 createApp
- 2 mount
- 3 ref
- 4 setup
其中 ref 属于reactivity:反应系统 就暂时先不深究了
setup 属于vue3 新的语法糖 也先不深究了
就先看看最简单的createApp 和mount
createApp
先看看vue 仓库中的packages/vue/src/index.ts
地址 https://github.com/vuejs/core/blob/main/packages/vue/src/index.ts
export { compileToFunction as compile } export * from '@vue/runtime-dom'
可以看到到处了一个编译的方法 和 @vue/runtime-dom 中的方法
根据上文
runtime-dom:针对浏览器的运行时。包括原生 DOM API、属性、属性、事件处理程序等的处理。
在runtime-dom 包中找到相关的方法
地址 https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/index.ts
export const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) if (__DEV__) { injectNativeTagCheck(app) injectCompilerOptionsCheck(app) } const { mount } = app app.mount = (containerOrSelector: Element | ShadowRoot | string): any => { const container = normalizeContainer(containerOrSelector) if (!container) return const component = app._component if (!isFunction(component) && !component.render && !component.template) { // __UNSAFE__ // Reason: potential execution of JS expressions in in-DOM template. // The user must make sure the in-DOM template is trusted. If it's // rendered by the server, the template should not contain any user data. component.template = container.innerHTML // 2.x compat check if (__COMPAT__ && __DEV__) { for (let i = 0; i < container.attributes.length; i++) { const attr = container.attributes[i] if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) { compatUtils.warnDeprecation( DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null ) break } } } } // clear content before mounting container.innerHTML = '' const proxy = mount(container, false, container instanceof SVGElement) if (container instanceof Element) { container.removeAttribute('v-cloak') container.setAttribute('data-v-app', '') } return proxy } return app }) as CreateAppFunction<Element> function ensureRenderer() { return ( renderer || (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)) ) }
走查代码可以发现 选是创建了一个渲染器Renderer
然后调用了渲染器的方法 createApp
查看具体方法
地址 https://github.com/vuejs/core/blob/main/packages/runtime-core/src/renderer.ts
创建渲染器的方法是一个比较长的方法
里面的很多方法看名称,更多的设计对dom的操作,不过我们还是先关注createApp 干了些什么
function baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions ): any { ...... return { render, hydrate, createApp: createAppAPI(render, hydrate) } }
发现createAppAPI 来自apiCreateApp 文件
地址 https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiCreateApp.ts
import { createAppAPI, CreateAppFunction } from './apiCreateApp'
代码如下
export function createAppAPI<HostElement>( render: RootRenderFunction<HostElement>, hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { if (!isFunction(rootComponent)) { rootComponent = extend({}, rootComponent) } if (rootProps != null && !isObject(rootProps)) { __DEV__ && warn(`root props passed to app.mount() must be an object.`) rootProps = null } const context = createAppContext() // TODO remove in 3.4 if (__DEV__) { Object.defineProperty(context.config, 'unwrapInjectedRef', { get() { return true }, set() { warn( `app.config.unwrapInjectedRef has been deprecated. ` + `3.3 now always unwraps injected refs in Options API.` ) } }) } const installedPlugins = new WeakSet() let isMounted = false const app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, _instance: null, version, get config() { return context.config }, set config(v) { if (__DEV__) { warn( `app.config cannot be replaced. Modify individual options instead.` ) } }, 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 }, mixin(mixin: ComponentOptions) { if (__FEATURE_OPTIONS_API__) { if (!context.mixins.includes(mixin)) { context.mixins.push(mixin) } else if (__DEV__) { warn( 'Mixin has already been applied to target app' + (mixin.name ? `: ${mixin.name}` : '') ) } } else if (__DEV__) { warn('Mixins are only available in builds supporting Options API') } return app }, component(name: string, component?: Component): any { if (__DEV__) { validateComponentName(name, context.config) } if (!component) { return context.components[name] } if (__DEV__ && context.components[name]) { warn(`Component "${name}" has already been registered in target app.`) } context.components[name] = component return app }, directive(name: string, directive?: Directive) { if (__DEV__) { validateDirectiveName(name) } if (!directive) { return context.directives[name] as any } if (__DEV__ && context.directives[name]) { warn(`Directive "${name}" has already been registered in target app.`) } context.directives[name] = directive return app }, mount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean ): 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.` ) } const vnode = createVNode(rootComponent, rootProps) // store app context on the root VNode. // this will be set on the root instance on initial mount. vnode.appContext = context // HMR root reload if (__DEV__) { context.reload = () => { render(cloneVNode(vnode), rootContainer, isSVG) } } if (isHydrate && hydrate) { hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else { render(vnode, rootContainer, isSVG) } isMounted = true app._container = rootContainer // for devtools and telemetry ;(rootContainer as any).__vue_app__ = app if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = vnode.component devtoolsInitApp(app, version) } return getExposeProxy(vnode.component!) || vnode.component!.proxy } 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)`` ) } }, unmount() { if (isMounted) { render(null, app._container) if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = null devtoolsUnmountApp(app) } delete app._container.__vue_app__ } else if (__DEV__) { warn(`Cannot unmount an app that is not mounted.`) } }, provide(key, value) { if (__DEV__ && (key as string | symbol) in context.provides) { warn( `App already provides property with key "${String(key)}". ` + `It will be overwritten with the new value.` ) } context.provides[key as string | symbol] = value return app }, runWithContext(fn) { currentApp = app try { return fn() } finally { currentApp = null } } }) if (__COMPAT__) { installAppCompatProperties(app, context, render) } return app } }
可以发现createApp 的第一个参数是rootComponent
需要传递的是一个组件,作为根组件
第二个参数rootProps是这个给这个组件传递的参数
通过走查 文件可以发现一些常用的api 也是出现在这里
例如 use、mixin、component、directive、mount、unmount、provide
我们要找的mount 也是对这里mount的调用
mount
可以看到 mount主要是参数是rootContainer 另外两个是可选参数
在通过createVNode 创建一个vnode 之后
调用getExposeProxy
返回当前刚才创建的vnode的代理
export function getExposeProxy(instance: ComponentInternalInstance) { if (instance.exposed) { return ( instance.exposeProxy || (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), { get(target, key: string) { if (key in target) { return target[key] } else if (key in publicPropertiesMap) { return publicPropertiesMap[key](instance) } }, has(target, key: string) { return key in target || key in publicPropertiesMap } })) ) } }
代码中实际调用的mount
依然在createAppAPI 里面 这里的传参更友好 了可以传入Selector 方便选择dom节点
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => { const container = normalizeContainer(containerOrSelector) if (!container) return const component = app._component if (!isFunction(component) && !component.render && !component.template) { // __UNSAFE__ // Reason: potential execution of JS expressions in in-DOM template. // The user must make sure the in-DOM template is trusted. If it's // rendered by the server, the template should not contain any user data. component.template = container.innerHTML // 2.x compat check if (__COMPAT__ && __DEV__) { for (let i = 0; i < container.attributes.length; i++) { const attr = container.attributes[i] if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) { compatUtils.warnDeprecation( DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null ) break } } } } // clear content before mounting container.innerHTML = '' const proxy = mount(container, false, container instanceof SVGElement) if (container instanceof Element) { container.removeAttribute('v-cloak') container.setAttribute('data-v-app', '') } return proxy }