发布时间:2022-11-19 文章分类:WEB开发 投稿人:樱花 字号: 默认 | | 超大 打印

前情提要

上文我们讲解了执行createApp(App).mount('#root')中的mount函数,我们分析了创建虚拟节点的几个方法,以及setRef的执行机制、本文我们继续讲解mountComponent,挂载组件的流程。

本文主要内容

mountComponent

const mountComponent = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
//创建组件实例
const instance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
));
setupComponent(instance);
if (instance.asyncDep) {
//处理异步逻辑,suspense的时候在进行讲解
}
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
);
};

创建组件实例

{
app: null,
config: {
globalProperties: {},
optionMergeStrategies: {},
compilerOptions: {},
},
mixins: [],
components: {},
directives: {},
};
function normalizePropsOptions(comp, appContext, asMixin = false) {
//获取props的缓存
const cache = appContext.propsCache;
const cached = cache.get(comp);
//这个缓存是一个type对应一个[normalized, needCastKeys]
//normalized表示合并了mixins和extends后的props
if (cached) {
return cached;
}
const raw = comp.props;
const normalized = {};
const needCastKeys = [];
let hasExtends = false;
if (!shared.isFunction(comp)) {
//用于合并props的函数,因为extends和mixins
//中还可以写mixins和extends所以需要递归合并
/**
* 例如const mixins = [{
*    extends:{},
*    mixins:[{props}],
*    props
* }]
*/
const extendProps = (raw) => {
hasExtends = true;
const [props, keys] = normalizePropsOptions(raw, appContext, true);
//normalized为合并后的props
shared.extend(normalized, props);
if (keys) needCastKeys.push(...keys);
};
//首先合并全局注册的mixins中的props属性(最先合并的优先级最低)
if (!asMixin && appContext.mixins.length) {
appContext.mixins.forEach(extendProps);
}
//然后合并extends属性(中间合并的优先级居中)
//(extends功能与mixins几乎一样)但是更注重于继承
//并且extends不能是数组
if (comp.extends) {
extendProps(comp.extends);
}
//最后合并组件自身的mixins(最后合并的优先级最高)
if (comp.mixins) {
comp.mixins.forEach(extendProps);
}
}
//省略第二部分的代码...
}
function normalizePropsOptions(comp, appContext, asMixin = false) {
//省略第一部分的代码...
//如果没有props且没有全局的mixins
//组件本身的mixins、extends则设置
//当前实例的props缓存为空
if (!raw && !hasExtends) {
if (shared.isObject(comp)) {
cache.set(comp, shared.EMPTY_ARR);
}
return shared.EMPTY_ARR;
}
//处理这种类型props:['msg','hello']
if (shared.isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
if (!shared.isString(raw[i])) {
console.warn(
`props must be strings when using array syntax. ${raw[i]}`
);
}
//将v-data-xxx转化为驼峰式vDataXxx
//但是不能以$开头
const normalizedKey = shared.camelize(raw[i]);
if (validatePropName(normalizedKey)) {
//将其变为props:{"msg":{}}
normalized[normalizedKey] = shared.EMPTY_OBJ;
}
}
}
//省略第三部分的代码...
}
export function normalizePropsOptions(comp, appContext, asMixin = false) {
//省略第二部分代码...
if (shared.isArray(raw)) {
//省略...
}
//处理props:{msg:String}
else if (raw) {
if (!shared.isObject(raw)) {
warn(`invalid props options`, raw);
}
//循环遍历所有的key
for (const key in raw) {
//"v-data-xxx"=>"vDataXxx"变为小驼峰式
const normalizedKey = shared.camelize(key);
//检验key是否合法
if (validatePropName(normalizedKey)) {
const opt = raw[key]; //获取value
//如果获取的value是数组或函数转化则为{type:opt}
//props:{"msg":[]||function(){}}=>
//props:{"msg":{type:msg的值}}
const prop = (normalized[normalizedKey] =
shared.isArray(opt) || shared.isFunction(opt) ? { type: opt } : opt);
if (prop) {
//找到Boolean在prop.type中的位置 Boolean,["Boolean"]
const booleanIndex = getTypeIndex(Boolean, prop.type);
//找到String在prop.type中的位置
const stringIndex = getTypeIndex(String, prop.type);
prop[0] = booleanIndex > -1; //type中是否包含Boolean
//type中不包含String或者Boolean的位置在String前面
//例如:"msg":{type:["Boolean","String"]}
prop[1] = stringIndex < 0 || booleanIndex < stringIndex;
//如果有default属性,或者type中包含Boolean放入needCastKeys中
if (booleanIndex > -1 || shared.hasOwn(prop, "default")) {
needCastKeys.push(normalizedKey);
}
}
}
}
}
const res = [normalized, needCastKeys];
//设置缓存
if (shared.isObject(comp)) {
cache.set(comp, res);
}
return res; //返回合并后的normalized
}
export function normalizeEmitsOptions(comp, appContext, asMixin = false) {
//获取appContext中的缓存
const cache = appContext.emitsCache;
const cached = cache.get(comp);
//如果已经读取过返回缓存
if (cached !== undefined) {
return cached;
}
//获取组件的emits属性{emits:['']}
//还可以对象写法{emits:{'':null||function(){}}}
const raw = comp.emits;
//最终的合并对象
let normalized = {};
let hasExtends = false;
if (!shared.isFunction(comp)) {
//合并emits的方法
const extendEmits = (raw) => {
const normalizedFromExtend = normalizeEmitsOptions(raw, appContext, true);
if (normalizedFromExtend) {
hasExtends = true;
shared.extend(normalized, normalizedFromExtend);
}
};
//最先合并appContext中的(优先级最低)
if (!asMixin && appContext.mixins.length) {
appContext.mixins.forEach(extendEmits);
}
//然后合并实例的extends(优先级居中)
if (comp.extends) {
extendEmits(comp.extends);
}
//最后合并组件自身的(优先级最高)
if (comp.mixins) {
comp.mixins.forEach(extendEmits);
}
}
if (!raw && !hasExtends) {
//设置缓存
if (shared.isObject(comp)) {
cache.set(comp, null);
}
return null;
}
//即使emits:[]是数组最终也会被转化为对象
//emits:['m']=>emits:{'m':null}
if (shared.isArray(raw)) {
raw.forEach((key) => (normalized[key] = null));
} else {
//合并
shared.extend(normalized, raw);
}
if (shared.isObject(comp)) {
cache.set(comp, normalized);
}
return normalized;
}
<Comp id="a"></Comp>
//Comp组件 没有对id的声明,那么会放入attrs中
export default {
props:{}
}
function createDevRenderContext(instance) {
const target = {};
//可通过_访问实例对象
Object.defineProperty(target, `_`, {
configurable: true,
enumerable: false,
get: () => instance,
});
Object.keys(publicPropertiesMap).forEach((key) => {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
get: () => publicPropertiesMap[key](instance),
set: shared.NOOP,
});
});
return target;
}
const publicPropertiesMap = shared.extend(Object.create(null), {
$: (i) => i, //获取当前实例
$el: (i) => i.vnode.el,
$data: (i) => i.data, //获取实例的data
$props: (i) => reactivity.shallowReadonly(i.props), //获取props
$attrs: (i) => reactivity.shallowReadonly(i.attrs),
$slots: (i) => reactivity.shallowReadonly(i.slots),
$refs: (i) => reactivity.shallowReadonly(i.refs),
$emit: (i) => i.emit,
//获取options
$options: (i) => resolveMergedOptions(i),
//强制更新
$forceUpdate: (i) => i.f || (i.f = () => queueJob(i.update)),
// $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)),
$watch: (i) => instanceWatch.bind(i),
$parent: (i) => getPublicInstance(i.parent),
$root: (i) => getPublicInstance(i.root),
});
function emit(instance, event, ...rawArgs) {
//已经卸载无须在执行
if (instance.isUnmounted) return;
//获取props
const props = instance.vnode.props || shared.EMPTY_OBJ;
//获取经过标准化的emits和props
//emits:{方法名:null||function(){}}
//如果为function代表的是验证函数
const {
emitsOptions,
propsOptions: [propsOptions],
} = instance;
if (emitsOptions) {
//警告用户:调用了emit 但是没有在emitOptions中找到,代表没有声明
if (!(event in emitsOptions)) {
if (!propsOptions || !(shared.toHandlerKey(event) in propsOptions)) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
`the emits option nor as an "${shared.toHandlerKey(event)}" prop.`
);
}
}
//获取验证函数
else {
const validator = emitsOptions[event];
if (shared.isFunction(validator)) {
//调用验证函数,返回false则警告
const isValid = validator(...rawArgs);
if (!isValid) {
warn(
`Invalid event arguments: event validation failed for event "${event}".`
);
}
}
}
}
//省略第二部分代码...
}
<template>
<Comp v-model.trim.number = "a" />
</template>
//编译后
function render(_ctx, _cache) {
const _component_Comp = _resolveComponent("Comp", true)
return (_openBlock(), _createBlock(_component_Comp, {
modelValue: _ctx.a,
"onUpdate:modelValue": $event => ((_ctx.a) = $event),
modelModifiers: { trim: true,number:true }
}, null, 8, ["modelValue"]))
}
function emit(instance, event, ...rawArgs) {
//省略第一部分代码...
let args = rawArgs;
//判断是否是v-model事件 => update:modelValue
const isModelListener = event.startsWith("update:");
const modelArg = isModelListener && event.slice(7); //modelValue
if (modelArg && modelArg in props) {
//获取modifiersKey=>modelModifiers
const modifiersKey = `${
modelArg === "modelValue" ? "model" : modelArg
}Modifiers`;
//当给组件传递v-model的时候
//<Button v-model.trim="a"></Button>
//当这样传递的时候会收到modelModifiers={trim:true}
const { number, trim } = props[modifiersKey] || shared.EMPTY_OBJ;
//对emit('',...args)传递给父组件的参数执行trim()
if (trim) {
args = rawArgs.map((a) => a.trim());
}
if (number) {
args = rawArgs.map(shared.toNumber);
}
}
//省略第三部分代码...
}
function emit(instance, event, ...rawArgs) {
//省略第二部分的代码...
//这里是简单写法,源码实际上对
//event这个字符串做了改造。
let handler = props[event]
//如果存在则执行
if (handler) {
handler.apply(instance,args)
}
//如果有once事件,存入emitted,并执行,以后不再执行
const onceHandler = props[handlerName + `Once`];
if (onceHandler) {
if (!instance.emitted) {
instance.emitted = {};
} else if (instance.emitted[handlerName]) {
return;
}
instance.emitted[handlerName] = true;
handler.apply(instance,args);
}
}
export default {
mixins:[{beforeCreate(){}}]
beforeCreate(){}
}
//那么bc:[createBefore1,createBefore2]
function createComponentInstance(vnode, parent, suspense) {
const type = vnode.type;
const appContext = (parent ? parent.appContext : vnode.appContext) || {};
const instance = {
uid: uid++, //当前实例的id
vnode, //当前实例对应的vnode
type, //当前实例对应的编译后的.vue生成的对象
parent, //当前实例的父实例
appContext, //app的上下文包含全局注入的插件,自定义指令等
root: null, //当前组件实例的根实例
//响应式触发的更新next为null,
//在更新的过程中父组件调用了子组件的
//instance.update会赋值next为最新组件vnode
next: null,
subTree: null, //调用render函数后的Vnode(处理了透传)
effect: null, //实例的ReactiveEffect
update: null, //副作用的scheduler
scope: new EffectScope(true),
//template编译结果或setup返回值为函数
//或.vue文件写的template编译为的render函数
render: null, //渲染函数
proxy: null, //代理后的ctx
exposed: null, //调用了ctx.expose()方法(限制暴露的数据)
exposeProxy: null, //调用了getExposeProxy方法后的expose
withProxy: null,
//当前组件的provides,父实例有则读取父实例的否则读取app上的
//父组件的provides后续会挂载到prototype上,重新赋值当前真实
//的provide上,这样可以通过原型链访问到所有上代组件中的provide
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null,
renderCache: [],
components: null, //当前组件的可用组件
directives: null, //当前组件的自定义指令
//合并mixins和extends中的props属性
propsOptions: normalizePropsOptions(type, appContext),
//合并mixins和extends中的emits属性
emitsOptions: normalizeEmitsOptions(type, appContext),
emit: null, //当前实例调用emit的函数
emitted: null, //含有once修饰符的,执行一次后放入这里不再执行
propsDefaults: {}, //默认props
//是否透传attrs
inheritAttrs: type.inheritAttrs,
// state
ctx: {}, //当前实例的上下文也就是this
data: {}, //data函数返回的值,被代理后才放入
props: {}, //接受到的组件属性
attrs: {}, //接受到的标签属性
slots: {}, //组件传递的插槽内容
refs: {}, //存入的refs
setupState: {}, //setup的返回值
//expose attrs slots emit
setupContext: null, //传递给setup的ctx(只有四个属性)
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
asyncDep: null, //setup使用了async修饰符 返回的promise保存在这里
asyncResolved: false,
isMounted: false, //是否挂载
isUnmounted: false, //是否卸载
isDeactivated: false,
bc: null, //beforeCreate
c: null, //create
bm: null, //beforeMount
m: null, //mount
bu: null, //beforeUpdate
u: null, //update
um: null, //unmount
bum: null, //beforeUnmount
//若组件实例是 <KeepAlive> 缓存树的一部分,
//当组件从 DOM 中被移除时调用。deactivated
da: null,
//若组件实例是 <KeepAlive> 缓存树的一部分,
//当组件被插入到 DOM 中时调用。activated
a: null,
//在一个响应式依赖被组件触发了重新渲染之后调用。
//renderTriggered
rtg: null,
//在一个响应式依赖被组件的渲染作用追踪后调用。
//renderTracked
rtc: null,
/**
* 错误捕获钩子
* 组件渲染
* 事件处理器
* 生命周期钩子
* setup() 函数
* 侦听器
* 自定义指令钩子
* 过渡钩子
* 错误捕获钩子
*/
ec: null, //errorHandler
sp: null, //serverPrefetch
};
//创建实例的上下文
instance.ctx = createDevRenderContext(instance);
//当前实例的根实例
instance.root = parent ? parent.root : instance;
instance.emit = emit.bind(null, instance);
if (vnode.ce) {
vnode.ce(instance);
}
return instance;
}

总结

以上就是Vue3组件挂载之创建组件实例详解的详细内容,更多关于Vue3 组件挂载创建实例的资料请关注本站其它相关文章!