Изучение исходного кода Vue 3 road-01- структура исходного кода и создание приложения

внешний интерфейс Vue.js

Version: 3.0.11

1. Подготовка - структура исходного кода

1.1 Структура каталогов

Прежде чем официально изучить исходный код, сначала зайдите на официальный сайт github Vue.Скачать исходный код, каталог распаковки после скачивания наверное такой.

vue-next.png

  • compiler-core

compiler-core.png

  • compiler-dom

compiler-dom.png

  • compiler-sfc

compiler-sfc.png

  • compiler-ssr

compiler-ssr.png

  • reactivity

reactivity.png

  • Создание среда выполнения

Во-первых, давайте сделаем снимок, чтобы увидеть, что находится в ядре среды выполнения.

runtime-core.png

Из приведенного выше рисунка нетрудно увидеть, что все основные основные API-интерфейсы Vue находятся в ядре среды выполнения, а все Vue3 используют TypeScript в качестве языка разработки.

В runtime-core поговорим об основных модулях vnode, h,components, apiCreateApp, apiLifecycle, так как этот модуль является ядром для реализации функции Vue3compositionApi.

  • runtime-dom

runtime-dom.png

  • server-render

server-render.png

  • sfc-playground

sfc-playground.png

  • shared

shared.png

  • size-check
  • template-explorer
  • vue

vue.png

1.2 Как выглядит исходный код после компиляции

Наверное так выглядит! Не сложно найти, что каждый функциональный модуль имеет 3 файла js и один ts.В первую очередь поговорим о файлах js.При разработке в режиме dev вызывается версия с esm-bundler, и проект запаковывается При сборке вызывается prod версия.Не понятно когда вызывается остальная версия.Тип интерфейса определяется в ts файле,который не является реальным работающим кодом.Вывод типа данных используется для вспомогательной разработки.

vue-next-core01.png

vue-next-core02.jpg

2 Что делает createApp

2.1 Начните с createApp из main.js

Во-первых, давайте взглянем на код инициализации Vue 3. Инициализация vue 3 заключается в создании экземпляра vue путем выполнения метода createApp, который создается с помощью new Vue() в vue 2. Итак, давайте посмотрим, что делает createApp.

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Через выполнение точки останова браузера devtool мы видим метод createApp, сначала выполняем следующий код.

Здесь выполняются три основных действия: первое — создание средства визуализации, второе — создание экземпляра приложения и третье — переписывание функции монтирования. Сначала создайте средство рендеринга с помощью метода sureRenderer, затем создайте экземпляр приложения с помощью createApp и, наконец, перепишите метод app.mount, чтобы он возвращал экземпляр прокси-компонента прокси.

const createApp = ((...args) => {
    const app = ensureRenderer().createApp(...args);
    if ((process.env.NODE_ENV !== 'production')) {
        injectNativeTagCheck(app);
        injectCustomElementCheck(app);
    }
    const { mount } = app;
    app.mount = (containerOrSelector) => {
        const container = normalizeContainer(containerOrSelector);
        if (!container)
            return;
        const component = app._component;
        if (!isFunction(component) && !component.render && !component.template) {
            component.template = container.innerHTML;
        }
        // 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;
});

2.2 Создание средства визуализации

Затем обратите внимание, что первым выполняемым методом является sureRender(). Из комментариев к исходному коду я знаю, что средство визуализации создается лениво. Причина этого в том, что пользователь может убедиться, что встряхивание дерева доступно, когда только реактивность вводится через Вью. Если средство визуализации было создано, возвращайте созданное средство визуализации напрямую, в противном случае создайте новый средство визуализации с помощью createRenderer.


const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps);
// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
let renderer;
let enabledHydration = false;
function ensureRenderer() {
    return renderer || (renderer = createRenderer(rendererOptions));
}

Метод createRenderer получает параметр rendererOption, а метод, инкапсулированный в nodeOps, представляет собой операцию узла dom, которая вызывается при изменении данных шаблона шаблона.

patchProp обновляет свойства dom, включая стиль ограничения стиля, стиль класса и привязку события patchEvent.

Посмотрите еще раз на метод createRenderer, выполните метод baseCreateRenderer и передайте параметр option.


function createRenderer(options) {
    console.log('%c调用 baseCreateRenderer >>>','color:red')
    return baseCreateRenderer(options);
}

Затем посмотрите на метод baseCreateRenderer.Многие коды внутренних функций пропускаются первыми.Вызов метода baseCreateRenderer возвращает объект, который содержит три элемента: render, hydr и createApp.

var count = 0
function baseCreateRenderer(options, createHydrationFns) {
    

    // 代码过长省略N行 ...
   
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    };
}

2.3 Создайте экземпляр приложения

В возвращаемых параметрах мы видим, что вызывается метод createAppAPI, а это значит, что когда мы выполняем createApp в нашем main.js, мы фактически вызываем createAppAPI и возвращаем объект приложения.Мы можем посмотреть, какие параметры имеет объект приложения через createAppAPI.

function createAppContext() {
    return {
        app: null,
        config: {
            isNativeTag: NO,
            performance: false,
            globalProperties: {},
            optionMergeStrategies: {},
            isCustomElement: NO,
            errorHandler: undefined,
            warnHandler: undefined
        },
        mixins: [],
        components: {},
        directives: {},
        provides: Object.create(null)
    };
}

let uid = 0;
function createAppAPI(render, hydrate) {
    return function createApp(rootComponent, rootProps = null) {
        if (rootProps != null && !isObject(rootProps)) {
            (process.env.NODE_ENV !== 'production') && warn(`root props passed to app.mount() must be an object.`);
            rootProps = null;
        }
        const context = createAppContext();
        const installedPlugins = new Set();
        let isMounted = false;
        const app = (context.app = {
            _uid: uid++,
            _component: rootComponent,
            _props: rootProps,
            _container: null,
            _context: context,
            version,
            get config() {
                return context.config;
            },
            set config(v) {
                if ((process.env.NODE_ENV !== 'production')) {
                    warn(`app.config cannot be replaced. Modify individual options instead.`);
                }
            },
            use(plugin, ...options) {
                if (installedPlugins.has(plugin)) {
                    (process.env.NODE_ENV !== 'production') && 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 ((process.env.NODE_ENV !== 'production')) {
                    warn(`A plugin must either be a function or an object with an "install" ` +
                        `function.`);
                }
                return app;
            },
            mixin(mixin) {
                if (__VUE_OPTIONS_API__) {
                    if (!context.mixins.includes(mixin)) {
                        context.mixins.push(mixin);
                        // global mixin with props/emits de-optimizes props/emits
                        // normalization caching.
                        if (mixin.props || mixin.emits) {
                            context.deopt = true;
                        }
                    }
                    else if ((process.env.NODE_ENV !== 'production')) {
                        warn('Mixin has already been applied to target app' +
                            (mixin.name ? `: ${mixin.name}` : ''));
                    }
                }
                else if ((process.env.NODE_ENV !== 'production')) {
                    warn('Mixins are only available in builds supporting Options API');
                }
                return app;
            },
            component(name, component) {
                if ((process.env.NODE_ENV !== 'production')) {
                    validateComponentName(name, context.config);
                }
                if (!component) {
                    return context.components[name];
                }
                if ((process.env.NODE_ENV !== 'production') && context.components[name]) {
                    warn(`Component "${name}" has already been registered in target app.`);
                }
                context.components[name] = component;
                return app;
            },
            directive(name, directive) {
                if ((process.env.NODE_ENV !== 'production')) {
                    validateDirectiveName(name);
                }
                if (!directive) {
                    return context.directives[name];
                }
                if ((process.env.NODE_ENV !== 'production') && context.directives[name]) {
                    warn(`Directive "${name}" has already been registered in target app.`);
                }
                context.directives[name] = directive;
                return app;
            },
            mount(rootContainer, isHydrate, isSVG) {
                if (!isMounted) {
                    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 ((process.env.NODE_ENV !== 'production')) {
                        context.reload = () => {
                            render(cloneVNode(vnode), rootContainer, isSVG);
                        };
                    }
                    if (isHydrate && hydrate) {
                        hydrate(vnode, rootContainer);
                    }
                    else {
                        render(vnode, rootContainer, isSVG);
                    }
                    isMounted = true;
                    app._container = rootContainer;
                    rootContainer.__vue_app__ = app;
                    if ((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) {
                        devtoolsInitApp(app, version);
                    }
                    return vnode.component.proxy;
                }
                else if ((process.env.NODE_ENV !== 'production')) {
                    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 ((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) {
                        devtoolsUnmountApp(app);
                    }
                    delete app._container.__vue_app__;
                }
                else if ((process.env.NODE_ENV !== 'production')) {
                    warn(`Cannot unmount an app that is not mounted.`);
                }
            },
            provide(key, value) {
                if ((process.env.NODE_ENV !== 'production') && key in context.provides) {
                    warn(`App already provides property with key "${String(key)}". ` +
                        `It will be overwritten with the new value.`);
                }
                // TypeScript doesn't allow symbols as index type
                // https://github.com/Microsoft/TypeScript/issues/24587
                context.provides[key] = value;
                return app;
            }
        });
        return app;
    };
}

Ядром createAppAPI является вызов createAppContext для возврата объекта базового параметра, содержащего приложение, конфигурацию, примеси, компоненты, директивы и ресурсы. Затем создайте объект context.app, который является экземпляром объекта vue, включая внутренние параметры и номера версий, и предоставьте использование, компонент, монтирование, миксин, директиву, размонтирование и предоставьте программные API для использования.

Автор этой статьи: Цзя Венли, Ziru Front-end R&D Center