文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Vue3组件挂载之创建组件实例详解

2022-11-13 18:41

关注

前情提要

上文我们讲解了执行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 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,
    
    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 组件挂载创建实例的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯