Vue3源码解析-createApp

887 阅读4分钟

createApp

返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。

  • 参数: 该函数接收一个根组件选项对象作为第一个参数:
const app = createApp({
  data() {
    return {
      ...
    }
  },
  methods: {...},
  computed: {...}
  ...
})

使用第二个参数,我们可以将根 prop 传递给应用程序:

const app createApp(
  {
    props: ['username']
  },
  { username'Evan' }
)
<div id="app">
  <!-- 会显示 'Evan' -->
  {{ username }}
</div>
  • 核心源码:

 // 创建一个Vue实例 -- createApp(公共API)
 const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args);
  {
   injectNativeTagCheck(app);
  }
  const { mount } = app;
  // 重新定义mount挂载元素 e.g. createApp({ data: {} }).mount("#app")
  app.mount = (containerOrSelector) => {
   const container = normalizeContainer(containerOrSelector);
   if (!container)
    return;
   // createApp创建Vue实例传递的options
   const component = app._component;
   if (!isFunction(component) && !component.render && !component.template) {
    component.template = container.innerHTML;
   }
   // 重置content,在mounting之前
   container.innerHTML = '';
   // 执行app原始的mount挂载元素
   const proxy = mount(container);
   // v-clock的作用是防止页面在加载时出现
   container.removeAttribute('v-cloak');
   container.setAttribute('data-v-app', '');
   return proxy;
  };
  return app;
 });
 
 // 确保renderer渲染函数存在
 function ensureRenderer() {
  return renderer || (renderer = createRenderer(rendererOptions));
 }
 
 /**
  * 创建普通的render函数
  */
 function createRenderer(options) {
  return baseCreateRenderer(options);
 }
 
 /**
  * 创建普通的render函数,可以接收两个泛型参数:
  * 
  * 自定义渲染器可以在平台中传递特定类型,像这样:
  * 
  * ``` js
  * const { render, createApp } = createRenderer<Node, Element>({
  *  patchProp,
  *  ...nodeOps
  * })
  * ```
  */
 function baseCreateRenderer(options, createHydrationFns) {
  // ...
  return {
   render,
   hydrate,
   createApp: createAppAPI(render, hydrate)
  };
 }
 
 /**
  * 创建renderAPI
  * @param {Function} render render函数
  * @param {Boolean} hydrate 是否为服务端渲染
  * @returns 
  */
 function createAppAPI(render, hydrate) {
  // Vue.createApp(options, rootProps) => 第一个参数为options,第二个参数为rootProps
  return function createApp(rootComponent, rootProps = null) {
   // 如果传递了第二个参数,则第二个参数必须为一个对象
   if (rootProps != null && !isObject(rootProps)) {
    warn(`root props passed to app.mount() must be an object.`);
    rootProps = null;
   }
   // 创建上下文对象 => { app, config: {}, mixins: [], components: {}, directives: {}, provides: {} }
   const context = createAppContext();
   const installedPlugins = new Set();
   // 是否已挂载
   let isMounted = false;
   // 返回一个app对象
   const app = (context.app = {
    _uid: uid$1++,
    _component: rootComponent,
    _props: rootProps,
    _container: null,
    _context: context,
    version,
    get config() {
     return context.config;
    },
    set config(v) {
     {
      warn(`app.config cannot be replaced. Modify individual options instead.`);
     }
    },
    // ...
    // 应用API
    // ...
    // 开始挂载元素
    // e.g. app.mount("#app")
    mount(rootContainer, isHydrate /* 是否水合,服务端渲染 */) {
     if (!isMounted) {
      // 创建VNode
      const vnode = createVNode(rootComponent, rootProps);
      vnode.appContext = context;
      {
       context.reload = () => {
        render(cloneVNode(vnode), rootContainer);
       };
      }
      if (isHydrate && hydrate) {
       hydrate(vnode, rootContainer);
      }
      else {
       // 开始render
       render(vnode, rootContainer);
      }
      isMounted = true;
      app._container = rootContainer;
      rootContainer.__vue_app__ = app;
      {
       devtoolsInitApp(app, version);
      }
      return vnode.component.proxy;
     }
     else {
      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;
  };
 }

附录:github.com/fanqiewa/vu…