// vue是如何把一个type是一个组件配置对象的vnode变成实际可以被插入到dom树中的vnode的呢?
/**
* 我们一般会这样声明一个root组件
* {
* data() {
* ...
* },
* mounted() {...},
* template: '<div class="xxx">...</div>'
* }
*
* 而实际可以直接插入的对应vnode结构会有一个组件实例存储用户的钩子函数,同时有一个render函数控制生成template对应的dom结构
* 下面来看vue的实现:
*
*/
// 实例的mount方法使用的render就是来自这里
const render = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true);
}
}
else {
// 直接调用patch 把vnode的内容当做补丁打到实际的dom跟节点上
patch(container._vnode || null, vnode, container);
}
flushPostFlushCbs();
container._vnode = vnode;
};
// 来自与全局render对象的 patch 方法实现了vnode的转化
// 把n2 vnode映射到实际dom(父节点 container 插入锚点anchor之前)中
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {
// n1为之前状态的vnode
// patching & not same type, unmount old tree
// 之前存在过vnode节点 说明这是一次更新操作
// 如果2次节点类型不同 说明可以直接卸载之前的vnode了
if (n1 && !isSameVNodeType(n1, n2)) {
// 更新待插入的锚点位置
anchor = getNextHostNode(n1);
// 卸载n1
unmount(n1, parentComponent, parentSuspense, true);
// 置空
n1 = null;
}
// -2 子内容没有动态内容 不需要再进行diff对比了
if (n2.patchFlag === -2 /* BAIL */) {
// 不进行优化
optimized = false;
n2.dynamicChildren = null;
}
// 初始化的时候直接走这里
const { type, ref, shapeFlag } = n2;
switch (type) {
// text node 已确定的元素类型vnode 直接调用底层方法
case Text:
processText(n1, n2, container, anchor);
break;
// 注释 node 已确定的元素类型vnode 直接调用底层方法
case Comment:
processCommentNode(n1, n2, container, anchor);
break;
// 直接插入html 如v-html的内容 也是直接调用底层方法
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG);
}
else {
patchStaticNode(n1, n2, container, isSVG);
}
break;
// 文档片段 由于含有多个vnode 所以需要单独调用 processFragment
case Fragment:
processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
break;
default:
// 元素vnode (如普通dom节点 div等)
if (shapeFlag & 1 /* ELEMENT */) {
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
// 4 | 2 说明目前这个vnode还是用的组件对象作为标签 无法直接处理 需要再转化
else if (shapeFlag & 6 /* COMPONENT */) {
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
// TELEPORT 组件采用自己的 process 方法处理patch 见后文
else if (shapeFlag & 64 /* TELEPORT */) {
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
}
// SUSPENSE 同上
else if (shapeFlag & 128 /* SUSPENSE */) {
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
}
else {
warn('Invalid VNode type:', type, `(${typeof type})`);
}
}
// set ref
// 在当前组件或者元素的patch结束之后 已经完成实际的dom更新了
// 这时候可以更新父组件所持有的ref信息了
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2);
}
};
// 主要看 processComponent 方法:
// 处理组件
const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
if (n1 == null) {
if (n2.shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {
parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized);
}
else {
// 初始化走这里 挂载组件到根目标节点
mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
}
else {
updateComponent(n1, n2, optimized);
}
};
// 再看 mountComponent
// 挂载组件
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
// 先得到这个组件对应的组件实例
const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));
console.log('initialVNode.component instance: ', instance);
if (instance.type.__hmrId) {
registerHMR(instance);
}
{
pushWarningContext(initialVNode);
startMeasure(instance, `mount`);
}
// inject renderer internals for keepAlive
if (isKeepAlive(initialVNode)) {
instance.ctx.renderer = internals;
}
// resolve props and slots for setup context
{
startMeasure(instance, `init`);
}
// 设置实例的 props 和 attrs slot 属性
setupComponent(instance);
{
endMeasure(instance, `init`);
}
// setup() is async. This component relies on async logic to be resolved
// before proceeding
if (instance.asyncDep) {
parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect);
// Give it a placeholder if this is not hydration
// TODO handle self-defined fallback
if (!initialVNode.el) {
const placeholder = (instance.subTree = createVNode(Comment));
processCommentNode(null, placeholder, container, anchor);
}
return;
}
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
{
popWarningContext();
endMeasure(instance, `mount`);
}
};
// 主要看三个方法 1. createComponentInstance 2. setupComponent 3. setupRenderEffect
const emptyAppContext = createAppContext();
let uid$2 = 0;
function createComponentInstance(vnode, parent, suspense) {
const type = vnode.type;
// inherit parent app context - or - if root, adopt from root vnode
// 所有组件共享一个app上下文
const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
// 组件实例对象
// 因为组件内部的状态需要维持 用户设置的变量和方法都需要维持
const instance = {
uid: uid$2++,
vnode,
type,
parent,
appContext,
root: null,
next: null,
// vnode子树
subTree: null,
// 控制更新的effect函数
update: null,
// render函数
render: null,
// 这个代理很关键 我们再钩子函数业务函数中的this其实就是它
proxy: null,
exposed: null,
withProxy: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null,
renderCache: [],
// local resovled assets
components: null,
directives: null,
// resolved props and emits options
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),
// emit
emit: null,
emitted: null,
// state
// 几个跟数据有关的字段
ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
setupState: EMPTY_OBJ,
setupContext: null,
// suspense related
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
asyncDep: null,
asyncResolved: false,
// lifecycle hooks
// not using enums here because it results in computed properties
isMounted: false,
isUnmounted: false,
isDeactivated: false,
bc: null,
c: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null
};
{
// ctx是一个数据仓库 上面放了内置默认的实例方法 以及后面用户的data数据prop数据都会一一导入到这个仓库中
instance.ctx = createRenderContext(instance);
}
// 绑定root
instance.root = parent ? parent.root : instance;
// 实例的$emit方法
instance.emit = emit.bind(null, instance);
return instance;
}
// 属性很多 不用一次性全部弄清楚 分析某个细节功能的时候再单独跟踪某个属性就好了 先看下 createRenderContext
function createRenderContext(instance) {
const target = {};
// expose internal instance for proxy handlers
Object.defineProperty(target, `_`, {
configurable: true,
enumerable: false,
get: () => instance
});
// expose public properties
Object.keys(publicPropertiesMap).forEach(key => {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
get: () => publicPropertiesMap[key](instance),
// intercepted by the proxy so no need for implementation,
// but needed to prevent set errors
set: NOOP
});
});
// expose global properties
const { globalProperties } = instance.appContext.config;
Object.keys(globalProperties).forEach(key => {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
get: () => globalProperties[key],
set: NOOP
});
});
return target;
}
const getPublicInstance = (i) => {
if (!i)
return null;
if (isStatefulComponent(i))
return i.exposed ? i.exposed : i.proxy;
return getPublicInstance(i.parent);
};
const publicPropertiesMap = extend(Object.create(null), {
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
$props: i => (shallowReadonly(i.props) ),
$attrs: i => (shallowReadonly(i.attrs) ),
$slots: i => (shallowReadonly(i.slots) ),
$refs: i => (shallowReadonly(i.refs) ),
$parent: i => getPublicInstance(i.parent),
$root: i => getPublicInstance(i.root),
$emit: i => i.emit,
$options: i => (resolveMergedOptions(i) ),
$forceUpdate: i => () => queueJob(i.update),
$nextTick: i => nextTick.bind(i.proxy),
$watch: i => (instanceWatch.bind(i) )
});
// 可以看到ctx上被挂载了的内置默认实例方法
function setupComponent(instance, isSSR = false) {
isInSSRComponentSetup = isSSR;
const { props, children } = instance.vnode;
const isStateful = isStatefulComponent(instance);
initProps(instance, props, isStateful, isSSR);
initSlots(instance, children);
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined;
isInSSRComponentSetup = false;
return setupResult;
}
// 主要看这个 setupStatefulComponent
function setupStatefulComponent(instance, isSSR) {
const Component = instance.type;
{
if (Component.name) {
validateComponentName(Component.name, instance.appContext.config);
}
if (Component.components) {
const names = Object.keys(Component.components);
for (let i = 0; i < names.length; i++) {
validateComponentName(names[i], instance.appContext.config);
}
}
if (Component.directives) {
const names = Object.keys(Component.directives);
for (let i = 0; i < names.length; i++) {
validateDirectiveName(names[i]);
}
}
}
// 0. create render proxy property access cache
instance.accessCache = Object.create(null);
// 1. create public instance / render proxy
// also mark it raw so it's never observed
// 创建ctx的代理
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);
{
// 导入props的属性到实例上
exposePropsOnRenderContext(instance);
}
// 2. call setup()
const { setup } = Component;
// 可以看到 有setup选项的就以它的结果为准 不再执行finishComponentSetup了
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null);
currentInstance = instance;
pauseTracking();
// 执行setup方法
const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [shallowReadonly(instance.props) , setupContext]);
resetTracking();
currentInstance = null;
// 根据返回类型处理
// promise代表异步组件
if (isPromise(setupResult)) {
if (isSSR) {
// return the promise so server-renderer can wait on it
return setupResult.then((resolvedResult) => {
// 以后单独分析setup的时候再说
handleSetupResult(instance, resolvedResult);
});
}
else {
// async setup returned Promise.
// bail here and wait for re-entry.
// 异步标记
instance.asyncDep = setupResult;
}
}
else {
handleSetupResult(instance, setupResult);
}
}
else {
finishComponentSetup(instance);
}
}
// 控制实例上的属性handlers 主要是注意取值和赋值的优先级
const PublicInstanceProxyHandlers = {
get({ _: instance }, key) {
const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
// let @vue/reactivity know it should never observe Vue public instances.
if (key === "__v_skip" /* SKIP */) {
return true;
}
// for internal formatters to know that this is a Vue instance
if (key === '__isVue') {
return true;
}
// data / props / ctx
// This getter gets called for every property access on the render context
// during render and is a major hotspot. The most expensive part of this
// is the multiple hasOwn() calls. It's much faster to do a simple property
// access on a plain object, so we use an accessCache object (with null
// prototype) to memoize what access type a key corresponds to.
let normalizedProps;
if (key[0] !== '$') {
const n = accessCache[key];
if (n !== undefined) {
switch (n) {
case 0 /* SETUP */:
return setupState[key];
case 1 /* DATA */:
return data[key];
case 3 /* CONTEXT */:
return ctx[key];
case 2 /* PROPS */:
return props[key];
// default: just fallthrough
}
}
else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
accessCache[key] = 0 /* SETUP */;
return setupState[key];
}
else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache[key] = 1 /* DATA */;
return data[key];
}
else if (
// only cache other properties when instance has declared (thus stable)
// props
(normalizedProps = instance.propsOptions[0]) &&
hasOwn(normalizedProps, key)) {
accessCache[key] = 2 /* PROPS */;
return props[key];
}
else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache[key] = 3 /* CONTEXT */;
return ctx[key];
}
else if (!isInBeforeCreate) {
accessCache[key] = 4 /* OTHER */;
}
}
const publicGetter = publicPropertiesMap[key];
let cssModule, globalProperties;
// public $xxx properties
if (publicGetter) {
if (key === '$attrs') {
track(instance, "get" /* GET */, key);
markAttrsAccessed();
}
return publicGetter(instance);
}
else if (
// css module (injected by vue-loader)
(cssModule = type.__cssModules) &&
(cssModule = cssModule[key])) {
return cssModule;
}
else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
// user may set custom properties to `this` that start with `$`
accessCache[key] = 3 /* CONTEXT */;
return ctx[key];
}
else if (
// global properties
((globalProperties = appContext.config.globalProperties),
hasOwn(globalProperties, key))) {
return globalProperties[key];
}
else if (currentRenderingInstance &&
(!isString(key) ||
// #1091 avoid internal isRef/isVNode checks on component instance leading
// to infinite warning loop
key.indexOf('__v') !== 0)) {
if (data !== EMPTY_OBJ &&
(key[0] === '$' || key[0] === '_') &&
hasOwn(data, key)) {
warn(`Property ${JSON.stringify(key)} must be accessed via $data because it starts with a reserved ` +
`character ("$" or "_") and is not proxied on the render context.`);
}
else if (instance === currentRenderingInstance) {
warn(`Property ${JSON.stringify(key)} was accessed during render ` +
`but is not defined on instance.`);
}
}
},
set({ _: instance }, key, value) {
const { data, setupState, ctx } = instance;
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
setupState[key] = value;
}
else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
data[key] = value;
}
else if (hasOwn(instance.props, key)) {
warn(`Attempting to mutate prop "${key}". Props are readonly.`, instance);
return false;
}
if (key[0] === '$' && key.slice(1) in instance) {
warn(`Attempting to mutate public property "${key}". ` +
`Properties starting with $ are reserved and readonly.`, instance);
return false;
}
else {
if (key in instance.appContext.config.globalProperties) {
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
value
});
}
else {
ctx[key] = value;
}
}
return true;
},
has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }, key) {
let normalizedProps;
return (accessCache[key] !== undefined ||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
hasOwn(ctx, key) ||
hasOwn(publicPropertiesMap, key) ||
hasOwn(appContext.config.globalProperties, key));
}
};
function exposePropsOnRenderContext(instance) {
const { ctx, propsOptions: [propsOptions] } = instance;
if (propsOptions) {
Object.keys(propsOptions).forEach(key => {
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => instance.props[key],
set: NOOP
});
});
}
}
// 重点看 finishComponentSetup
function finishComponentSetup(instance, isSSR) {
const Component = instance.type;
// template / render function normalization
if (!instance.render) {
// could be set from setup()
if (compile && Component.template && !Component.render) {
{
startMeasure(instance, `compile`);
}
// 在这里编译模板得到render函数
// 在这里经过了模板编译 得到template对应的vnode结构 生成了render函数 详细实现后面再分析
// 目前只需要知道它调用可以得到一个vnode 这个vnode包含了用户设置的信息 可以被patch直接使用产生各类dom元素就好了
Component.render = compile(Component.template, {
isCustomElement: instance.appContext.config.isCustomElement,
delimiters: Component.delimiters
});
{
endMeasure(instance, `compile`);
}
}
instance.render = (Component.render || NOOP);
// for runtime-compiled render functions using `with` blocks, the render
// proxy used needs a different `has` handler which is more performant and
// also only allows a whitelist of globals to fallthrough.
if (instance.render._rc) {
instance.withProxy = new Proxy(instance.ctx, RuntimeCompiledPublicInstanceProxyHandlers);
}
}
// support for 2.x options
{
currentInstance = instance;
pauseTracking();
// 这里就设置用户选项的地方了
applyOptions(instance, Component);
resetTracking();
currentInstance = null;
}
// warn missing template/render
if (!Component.render && instance.render === NOOP) {
/* istanbul ignore if */
if (!compile && Component.template) {
warn(`Component provided template option but ` +
`runtime compilation is not supported in this build of Vue.` +
(` Use "vue.global.js" instead.`
) /* should not happen */);
}
else {
warn(`Component is missing template or render function.`);
}
}
}
// 重点看这里 applyOptions
let isInBeforeCreate = false;
function applyOptions(instance, options, deferredData = [], deferredWatch = [], deferredProvide = [], asMixin = false) {
// 从这里可以看出我们可以设置哪些选项
const {
// composition
mixins, extends: extendsOptions,
// state
data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions,
// assets
components, directives,
// lifecycle
beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured,
// public API
expose } = options;
// 这个this 就是proxy
const publicThis = instance.proxy;
const ctx = instance.ctx;
// 来自于app上下文的mixins
const globalMixins = instance.appContext.mixins;
if (asMixin && render && instance.render === NOOP) {
instance.render = render;
}
// applyOptions is called non-as-mixin once per instance
// 钩子 beforeCreate
if (!asMixin) {
isInBeforeCreate = true;
callSyncHook('beforeCreate', "bc" /* BEFORE_CREATE */, options, instance, globalMixins);
isInBeforeCreate = false;
// global mixins are applied first
applyMixins(instance, globalMixins, deferredData, deferredWatch, deferredProvide);
}
// extending a base component...
if (extendsOptions) {
applyOptions(instance, extendsOptions, deferredData, deferredWatch, deferredProvide, true);
}
// local mixins
if (mixins) {
applyMixins(instance, mixins, deferredData, deferredWatch, deferredProvide);
}
const checkDuplicateProperties = createDuplicateChecker() ;
{
const [propsOptions] = instance.propsOptions;
if (propsOptions) {
for (const key in propsOptions) {
checkDuplicateProperties("Props" /* PROPS */, key);
}
}
}
// options initialization order (to be consistent with Vue 2):
// - props (already done outside of this function)
// - inject
// - methods
// - data (deferred since it relies on `this` access)
// - computed
// - watch (deferred since it relies on `this` access)
// 处理inject
if (injectOptions) {
if (isArray(injectOptions)) {
for (let i = 0; i < injectOptions.length; i++) {
const key = injectOptions[i];
// 注入inject的字段 从 provide中
ctx[key] = inject(key);
{
checkDuplicateProperties("Inject" /* INJECT */, key);
}
}
}
else {
for (const key in injectOptions) {
const opt = injectOptions[key];
if (isObject(opt)) {
ctx[key] = inject(opt.from || key, opt.default, true /* treat default function as factory */);
}
else {
ctx[key] = inject(opt);
}
{
checkDuplicateProperties("Inject" /* INJECT */, key);
}
}
}
}
// 用户的方法
if (methods) {
for (const key in methods) {
const methodHandler = methods[key];
if (isFunction(methodHandler)) {
// In dev mode, we use the `createRenderContext` function to define methods to the proxy target,
// and those are read-only but reconfigurable, so it needs to be redefined here
{
Object.defineProperty(ctx, key, {
// 注意这里的this publicThis
value: methodHandler.bind(publicThis),
configurable: true,
enumerable: true,
writable: true
});
}
{
checkDuplicateProperties("Methods" /* METHODS */, key);
}
}
else {
warn(`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
`Did you reference the function correctly?`);
}
}
}
if (!asMixin) {
if (deferredData.length) {
deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis));
}
// 处理data函数
if (dataOptions) {
// @ts-ignore dataOptions is not fully type safe
resolveData(instance, dataOptions, publicThis);
}
{
const rawData = toRaw(instance.data);
// 把用户data的内容导入到ctx上
for (const key in rawData) {
checkDuplicateProperties("Data" /* DATA */, key);
// expose data on ctx during dev
if (key[0] !== '$' && key[0] !== '_') {
Object.defineProperty(ctx, key, {
configurable: true,
enumerable: true,
get: () => rawData[key],
set: NOOP
});
}
}
}
}
else if (dataOptions) {
deferredData.push(dataOptions);
}
// 前文分析过的computed
if (computedOptions) {
for (const key in computedOptions) {
const opt = computedOptions[key];
const get = isFunction(opt)
? opt.bind(publicThis, publicThis)
: isFunction(opt.get)
? opt.get.bind(publicThis, publicThis)
: NOOP;
if (get === NOOP) {
warn(`Computed property "${key}" has no getter.`);
}
const set = !isFunction(opt) && isFunction(opt.set)
? opt.set.bind(publicThis)
: () => {
warn(`Write operation failed: computed property "${key}" is readonly.`);
}
;
const c = computed$1({
get,
set
});
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => c.value,
set: v => (c.value = v)
});
{
checkDuplicateProperties("Computed" /* COMPUTED */, key);
}
}
}
// watch字段
if (watchOptions) {
deferredWatch.push(watchOptions);
}
if (!asMixin && deferredWatch.length) {
deferredWatch.forEach(watchOptions => {
for (const key in watchOptions) {
createWatcher(watchOptions[key], ctx, publicThis, key);
}
});
}
if (provideOptions) {
deferredProvide.push(provideOptions);
}
// 组件实例的provide会被子组件所继承 然后任意子组件可以inject注入其中的字段
if (!asMixin && deferredProvide.length) {
deferredProvide.forEach(provideOptions => {
const provides = isFunction(provideOptions)
? provideOptions.call(publicThis)
: provideOptions;
Reflect.ownKeys(provides).forEach(key => {
provide(key, provides[key]);
});
});
}
// asset options.
// To reduce memory usage, only components with mixins or extends will have
// resolved asset registry attached to instance.
if (asMixin) {
if (components) {
extend(instance.components ||
(instance.components = extend({}, instance.type.components)), components);
}
if (directives) {
extend(instance.directives ||
(instance.directives = extend({}, instance.type.directives)), directives);
}
}
// lifecycle options
if (!asMixin) {
callSyncHook('created', "c" /* CREATED */, options, instance, globalMixins);
}
// 下面的方法基本上都是给实例注入对应的用户钩子函数 存入对应的钩子数组中 到了指定了阶段执行就好了
if (beforeMount) {
onBeforeMount(beforeMount.bind(publicThis));
}
if (mounted) {
onMounted(mounted.bind(publicThis));
}
if (beforeUpdate) {
onBeforeUpdate(beforeUpdate.bind(publicThis));
}
if (updated) {
onUpdated(updated.bind(publicThis));
}
if (activated) {
onActivated(activated.bind(publicThis));
}
if (deactivated) {
onDeactivated(deactivated.bind(publicThis));
}
if (errorCaptured) {
onErrorCaptured(errorCaptured.bind(publicThis));
}
if (renderTracked) {
onRenderTracked(renderTracked.bind(publicThis));
}
if (renderTriggered) {
onRenderTriggered(renderTriggered.bind(publicThis));
}
if (beforeDestroy) {
warn(`\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`);
}
if (beforeUnmount) {
onBeforeUnmount(beforeUnmount.bind(publicThis));
}
if (destroyed) {
warn(`\`destroyed\` has been renamed to \`unmounted\`.`);
}
if (unmounted) {
onUnmounted(unmounted.bind(publicThis));
}
if (isArray(expose)) {
if (!asMixin) {
if (expose.length) {
const exposed = instance.exposed || (instance.exposed = proxyRefs({}));
expose.forEach(key => {
exposed[key] = toRef(publicThis, key);
});
}
else if (!instance.exposed) {
instance.exposed = EMPTY_OBJ;
}
}
else {
warn(`The \`expose\` option is ignored when used in mixins.`);
}
}
}
// 经过上面这么多的过程 我们终于看到了平常用到的this是什么 同时我们设置的对象的选项参数都是怎么被挂载到vue的组件实例上的
// 最后再看 setupRenderEffect
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
// create reactive effect for rendering
// 每个组件实例有一个update方法 在属性变化的是调用 更新vnode 和 dom视图
// 前文分析过 effect得到一个包装函数 内置的方法在依赖源发生变化后会执行 而componentEffect方法中有调用render导致了依赖收集
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
let vnodeHook;
const { el, props } = initialVNode;
const { bm, m, parent } = instance;
// beforeMount hook
// 钩子在这里
if (bm) {
invokeArrayFns(bm);
}
// onVnodeBeforeMount
// vnode也是钩子函数的
if ((vnodeHook = props && props.onVnodeBeforeMount)) {
invokeVNodeHook(vnodeHook, parent, initialVNode);
}
// render
{
startMeasure(instance, `render`);
}
// 在这里 我们得到了render产生的vnode子树
const subTree = (instance.subTree = renderComponentRoot(instance));
{
endMeasure(instance, `render`);
}
if (el && hydrateNode) {
{
startMeasure(instance, `hydrate`);
}
// vnode has adopted host node - perform hydration instead of mount.
hydrateNode(initialVNode.el, subTree, instance, parentSuspense);
{
endMeasure(instance, `hydrate`);
}
}
else {
{
startMeasure(instance, `patch`);
}
// 最终测试的vnode子树 可以被patch所接受 它具备的信息足够支撑渲染出我们想要的dom结构了
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
{
endMeasure(instance, `patch`);
}
initialVNode.el = subTree.el;
}
// mounted hook
// 钩子
if (m) {
queuePostRenderEffect(m, parentSuspense);
}
// onVnodeMounted
if ((vnodeHook = props && props.onVnodeMounted)) {
const scopedInitialVNode = initialVNode;
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook, parent, scopedInitialVNode);
}, parentSuspense);
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
const { a } = instance;
if (a &&
initialVNode.shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */) {
queuePostRenderEffect(a, parentSuspense);
}
instance.isMounted = true;
{
devtoolsComponentAdded(instance);
}
// #2458: deference mount-only object parameters to prevent memleaks
initialVNode = container = anchor = null;
}
else {
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let { next, bu, u, parent, vnode } = instance;
let originNext = next;
let vnodeHook;
{
pushWarningContext(next || instance.vnode);
}
if (next) {
next.el = vnode.el;
updateComponentPreRender(instance, next, optimized);
}
else {
next = vnode;
}
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu);
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode);
}
// render
{
startMeasure(instance, `render`);
}
// 得到最新的子树
const nextTree = renderComponentRoot(instance);
{
endMeasure(instance, `render`);
}
const prevTree = instance.subTree;
instance.subTree = nextTree;
{
startMeasure(instance, `patch`);
}
// 对比子树 进行更新
patch(prevTree, nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el),
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree), instance, parentSuspense, isSVG);
{
endMeasure(instance, `patch`);
}
next.el = nextTree.el;
if (originNext === null) {
// self-triggered update. In case of HOC, update parent component
// vnode el. HOC is indicated by parent instance's subTree pointing
// to child component's vnode
updateHOCHostEl(instance, nextTree.el);
}
// updated hook
if (u) {
queuePostRenderEffect(u, parentSuspense);
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook, parent, next, vnode);
}, parentSuspense);
}
{
devtoolsComponentUpdated(instance);
}
{
popWarningContext();
}
}
}, createDevEffectOptions(instance) );
};
// 再看下 renderComponentRoot
function renderComponentRoot(instance) {
const { type: Component, vnode, proxy, withProxy, props, propsOptions: [propsOptions], slots, attrs, emit, render, renderCache, data, setupState, ctx } = instance;
let result;
currentRenderingInstance = instance;
{
accessedAttrs = false;
}
try {
let fallthroughAttrs;
if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
// withProxy is a proxy with a different `has` trap only for
// runtime-compiled render functions using `with` block.
// 这里很重要 调用render得到vnode子树 完成依赖收集
const proxyToUse = withProxy || proxy;
result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));
// 组件可以继承在它身上加的属性(出去已经被prop和emit声明的之外) 都会被处理放在attrs字段中
fallthroughAttrs = attrs;
}
else {
// 先不看
// functional
const render = Component;
// in dev, mark attrs accessed if optional props (attrs === props)
if (true && attrs === props) {
markAttrsAccessed();
}
result = normalizeVNode(render.length > 1
? render(props, true
? {
get attrs() {
markAttrsAccessed();
return attrs;
},
slots,
emit
}
: { attrs, slots, emit })
: render(props, null /* we know it doesn't need it */));
fallthroughAttrs = Component.props
? attrs
: getFunctionalFallthrough(attrs);
}
// attr merging
// in dev mode, comments are preserved, and it's possible for a template
// to have comments along side the root element which makes it a fragment
let root = result;
let setRoot = undefined;
// 一种特殊情况 片段结构中 多个子节点 只有一个是有效的元素 其余都是注释 这种情况
if (true &&
result.patchFlag > 0 &&
result.patchFlag & 2048 /* DEV_ROOT_FRAGMENT */) {
;
[root, setRoot] = getChildRoot(result);
}
// 组件的继承属性实现 对应文档中详细的描述
if (Component.inheritAttrs !== false && fallthroughAttrs) {
const keys = Object.keys(fallthroughAttrs);
const { shapeFlag } = root;
if (keys.length) {
if (shapeFlag & 1 /* ELEMENT */ ||
shapeFlag & 6 /* COMPONENT */) {
if (propsOptions && keys.some(isModelListener)) {
// If a v-model listener (onUpdate:xxx) has a corresponding declared
// prop, it indicates this component expects to handle v-model and
// it should not fallthrough.
// related: #1543, #1643, #1989
fallthroughAttrs = filterModelListeners(fallthroughAttrs, propsOptions);
}
// 属性继承到vnode中
root = cloneVNode(root, fallthroughAttrs);
}
// render过程中如果有元素显示获取$attrs accessedAttrs这个值在handler的get就会被置位
else if (true && !accessedAttrs && root.type !== Comment) {
const allAttrs = Object.keys(attrs);
const eventAttrs = [];
const extraAttrs = [];
for (let i = 0, l = allAttrs.length; i < l; i++) {
const key = allAttrs[i];
if (isOn(key)) {
// ignore v-model handlers when they fail to fallthrough
if (!isModelListener(key)) {
// remove `on`, lowercase first letter to reflect event casing
// accurately
eventAttrs.push(key[2].toLowerCase() + key.slice(3));
}
}
else {
extraAttrs.push(key);
}
}
// 对应文档中的警告信息
if (extraAttrs.length) {
warn(`Extraneous non-props attributes (` +
`${extraAttrs.join(', ')}) ` +
`were passed to component but could not be automatically inherited ` +
`because component renders fragment or text root nodes.`);
}
if (eventAttrs.length) {
warn(`Extraneous non-emits event listeners (` +
`${eventAttrs.join(', ')}) ` +
`were passed to component but could not be automatically inherited ` +
`because component renders fragment or text root nodes. ` +
`If the listener is intended to be a component custom event listener only, ` +
`declare it using the "emits" option.`);
}
}
}
}
// inherit directives
if (vnode.dirs) {
if (true && !isElementRoot(root)) {
warn(`Runtime directive used on component with non-element root node. ` +
`The directives will not function as intended.`);
}
root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs;
}
// inherit transition data
if (vnode.transition) {
if (true && !isElementRoot(root)) {
warn(`Component inside <Transition> renders non-element root node ` +
`that cannot be animated.`);
}
root.transition = vnode.transition;
}
if (true && setRoot) {
setRoot(root);
}
else {
result = root;
}
}
catch (err) {
handleError(err, instance, 1 /* RENDER_FUNCTION */);
result = createVNode(Comment);
}
currentRenderingInstance = null;
return result;
}
// 好了,有了完整的组件实例和对应的vnode信息了,已经可以准备把我们写的template对应的vnode通过patch映射到实际的dom中了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。