Version: 3.0.11
1. Подготовка - структура исходного кода
1.1 Структура каталогов
Прежде чем официально изучить исходный код, сначала зайдите на официальный сайт github Vue.Скачать исходный код, каталог распаковки после скачивания наверное такой.
- compiler-core
- compiler-dom
- compiler-sfc
- compiler-ssr
- reactivity
- Создание среда выполнения
Во-первых, давайте сделаем снимок, чтобы увидеть, что находится в ядре среды выполнения.
Из приведенного выше рисунка нетрудно увидеть, что все основные основные API-интерфейсы Vue находятся в ядре среды выполнения, а все Vue3 используют TypeScript в качестве языка разработки.
В runtime-core поговорим об основных модулях vnode, h,components, apiCreateApp, apiLifecycle, так как этот модуль является ядром для реализации функции Vue3compositionApi.
- runtime-dom
- server-render
- sfc-playground
- shared
- size-check
- template-explorer
- vue
1.2 Как выглядит исходный код после компиляции
Наверное так выглядит! Не сложно найти, что каждый функциональный модуль имеет 3 файла js и один ts.В первую очередь поговорим о файлах js.При разработке в режиме dev вызывается версия с esm-bundler, и проект запаковывается При сборке вызывается prod версия.Не понятно когда вызывается остальная версия.Тип интерфейса определяется в ts файле,который не является реальным работающим кодом.Вывод типа данных используется для вспомогательной разработки.
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