文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

Vue3源码分析组件挂载初始化props与slots

2022-11-13 18:41

关注

前情提要

本文主要内容

初始化组件

(1).setupComponent

function setupComponent(instance) {
  //获取vnode的props(真正传递的props)
  const { props, children } = instance.vnode;
  //判断当前是否是有状态组件组件
  const isStateful = isStatefulComponent(instance);
  //通过传递的真实props和声明的props 分离组件参数
  //组件参数放入props中 其余放入instance.attrs
  //处理了props的default情况等
  initProps(instance, props, isStateful);
  //初始化插槽
  initSlots(instance, children);
  //验证名称是否合法,components中的组件名称是否
  //合法,代理instance.ctx,创建setup函数的ctx,调用setup函数
  //处理得到的结果
  const setupResult = isStateful ? 
        setupStatefulComponent(instance) : undefined;
  return setupResult;
}
function isStatefulComponent(instance) {
  return instance.vnode.shapeFlag & 
         ShapeFlags.STATEFUL_COMPONENT;
}

(2).initProps

function initProps(instance, rawProps, isStateful) {
  //定义需要放入的
  const props = {};
  const attrs = {};
  //attrs.__vInternal = 1
  shared.def(attrs, InternalObjectKey, 1);
  //创建propsDefaults
  instance.propsDefaults = Object.create(null);
  //将真实传递的props分配给instance的props和attrs
  setFullProps(instance, rawProps, props, attrs);
  //遍历normalized(合并和的props)
  for (const key in instance.propsOptions[0]) {
    if (!(key in props)) {
      props[key] = undefined;
    }
  }
  //最后将分配好的props和attrs赋值到instance
  if (isStateful) {
    instance.props = reactivity.shallowReactive(props);
  } else {
    //不存在type.props则让props为attrs
    if (!instance.type.props) {
      instance.props = attrs;
    } else {
      instance.props = props;
    }
  }
  instance.attrs = attrs;
}
function setFullProps(instance, rawProps, props, attrs) {
  //获取通过mixins和extends合并的props
  const [options, needCastKeys] = instance.propsOptions;
  let hasAttrsChanged = false; //attrs是否发生改变
  let rawCastValues;
  if (rawProps) {
    for (let key in rawProps) {
      //如果key是"ref" "key" "ref_for" "ref_key"
      //"onVnodeBeforeMount" "onVnodeMounted"
      //"onVnodeBeforeUpdate "onVnodeUpdated"
      //"onVnodeBeforeUnmount" "onVnodeUnmounted"
      //那么就跳过
      if (shared.isReservedProp(key)) {
        continue;
      }
      //获取rawProps:{a:1}=>value=1
      const value = rawProps[key];
      let camelKey; //小驼峰式的key
      if (
        options &&
        shared.hasOwn(options, (camelKey = shared.camelize(key)))
      ) {
        //这个key不是含有default属性的
        if (!needCastKeys || !needCastKeys.includes(camelKey)) {
          props[camelKey] = value;
        }
        //props:{"msg":{default:"a"}}
        //含有default属性的放入rawCastValues中
        else {
          (rawCastValues || (rawCastValues = {}))[camelKey] = value;
        }
      }
      //判断当前的key是否是用于emits的
      else if (!isEmitListener(instance.emitsOptions, key)) {
        //不是emit自定义事件的key也不是组件参数那么就是attrs
        if (!(key in attrs) || value !== attrs[key]) {
          attrs[key] = value;
          hasAttrsChanged = true;
        }
      }
    }
  }
  
  if (needCastKeys) {
    //获取非响应式的props
    const rawCurrentProps = reactivity.toRaw(props);
    const castValues = rawCastValues || {};
    for (let i = 0; i < needCastKeys.length; i++) {
      const key = needCastKeys[i]; //msg
      //对于有default的属性进行重设
      //props:{msg:{default:"a"}}
      props[key] = resolvePropValue(
        options, //合并mixins和extends后的props(定义方)
        rawCurrentProps, //非响应式的props(接受方)
        key, //(含有default)的key "msg"
        //例如传递了"msg":1 定义了:props:{msg:{default:"a"}}
        //castValues[key]=1
        castValues[key],
        instance, //实例
        !shared.hasOwn(castValues, key)
      );
    }
  }
  return hasAttrsChanged;
}
function resolvePropValue(options, props, key, value, instance, isAbsent) {
  //获取{msg:{default:"a"}}中的{default:"a"}
  const opt = options[key];
  if (opt != null) {
    //判断是否有default属性
    const hasDefault = shared.hasOwn(opt, "default");
    //如果定义了default但是没有接受到value值
    if (hasDefault && value === undefined) {
      const defaultValue = opt.default;
      //如果需要接受的类型不是函数,但是接受到了函数
      //看看实例的propsDefaults是否有当前key的值
      //还是没有则调用这个defaultValue函数取得值
      if (opt.type !== Function && shared.isFunction(defaultValue)) {
        const { propsDefaults } = instance;
        if (key in propsDefaults) {
          value = propsDefaults[key];
        } else {
          //包裹是为了在调用这个函数的时候
          //获取当前实例不会出错
          setCurrentInstance(instance);
          value = propsDefaults[key] = defaultValue.call(null, props);
          unsetCurrentInstance();
        }
      }
      //设置为默认值
      else {
        value = defaultValue;
      }
    }
    //需要接受的类型是Boolean
    if (opt[0]) {
      //没有设置默认值,也没有传递这个值则为false
      if (isAbsent && !hasDefault) {
        value = false;
      }
      //<Comp yes></Comp>并且声明了yes则设置为true
      else if (opt[1] && value === "") {
        value = true;
      }
    }
  }
  return value;
}

(3).initSlots

function initSlots(instance, children) {
  //判断当前实例的children是否是slots
  if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
    const type = children._; //获取shapeSlots
    //有"_"标识表示是通过compiler编译得到的
    if (type) {
      //如果有SLOTS_CHILDREN标识 表示children就是slots属性
      instance.slots = reactivity.toRaw(children);
      //将_属性变为不可枚举属性
      shared.def(children, "_", type);
    } else {
      
      normalizeObjectSlots(children, (instance.slots = {}));
    }
  } else {
    instance.slots = {};
    //如果children为字符串或者null或数组情况
    if (children) {
      normalizeVNodeSlots(instance, children);
    }
  }
  //标识slots为内部属性
  shared.def(instance.slots, InternalObjectKey, 1);
}
<template>
  <Comp>
    我是插槽内容
  </Comp>
</template>
//编译后
function render(_ctx, _cache) {
  const _component_Comp = _resolveComponent("Comp", true)
  return (_openBlock(),
          _createBlock(_component_Comp, null, {
    default: _withCtx(() => [
      _createTextVNode(" 我是插槽内容 ")
    ]),
    _: 1 
  }))
}
const normalizeObjectSlots = (rawSlots, slots, instance) => {
  const ctx = rawSlots._ctx;
  for (const key in rawSlots) {
    //_开头或者$stable跳过
    //这将允许设置不进行标准化的插槽
    if (isInternalKey(key)) continue;
    //获取slots的值
    const value = rawSlots[key];
    //如果value已经是一个函数了,需要包裹withCtx执行
    //进行标准化 都需要改成通过编译的样子
    if (shared.isFunction(value)) {
      //给instance.slots赋值
      slots[key] = normalizeSlot(key, value, ctx);
    }
    
    else if (value != null) {
      console.warn(
        `Non-function value encountered for slot "${key}". ` +
          `Prefer function slots for better performance.`
      );
      //经过normalizeSlotValue处理 返回的createVnode一定通过数组包裹
      const normalized = normalizeSlotValue(value);
      slots[key] = () => normalized;
    }
  }
};
const normalizeSlot = (key, rawSlot, ctx) => {
  //已经经过标准化的slot不需要在进行标准化
  if (rawSlot._n) {
    return rawSlot;
  }
  const normalized = withCtx((...args) => {
    if (getCurrentInstance()) {
      warn(
        `Slot "${key}" invoked outside of the render function: ` +
          `this will not track dependencies used in the slot. ` +
          `Invoke the slot function inside the render function instead.`
      );
    }
    //标准化插槽的值 rawSlot=> default:()=>createVnode('div',null)
    return normalizeSlotValue(rawSlot(...args));
  }, ctx);
  //表示不是经过compiler编译的,是用户自己写的render函数
  normalized._c = false;
  return normalized;
};
function withCtx(
  fn,
  ctx = getCurrentRenderingInstance(),
  isNonScopedSlot
) {
  if (!ctx) return fn;
  if (fn._n) {
    return fn;
  }
  //设置currentRenderingInstance,通过闭包确保调用fn的时候
  //currentRenderingInstance实例为当前实例
  
  const renderFnWithContext = (...args) => {
    //禁止块追踪,将isBlockTreeEnabled设置为0将会停止追踪
    if (renderFnWithContext._d) {
      setBlockTracking(-1);
    }
    const prevInstance = setCurrentRenderingInstance(ctx);
    const res = fn(...args);
    setCurrentRenderingInstance(prevInstance);
    //开启块追踪
    if (renderFnWithContext._d) {
      setBlockTracking(1);
    }
    return res;
  };
  //如果已经是renderFnWithContext则不需要在包装了
  renderFnWithContext._n = true; //_n表示已经经过renderFnWithContext包装
  renderFnWithContext._c = true; //表示经过compiler编译得到
  //true代表禁止块追踪,false代表开启块追踪
  renderFnWithContext._d = true;
  return renderFnWithContext;
}
function normalizeSlotValue(value){
  if(shared.isArray(value)){
    return value.map(normalizeVNode)
  }
  return [normalizeVNode(value)] 
}
//自己写render函数
export default {
  render(){
    return createVNode(Comp,null,{
      default:()=>([
       createVNode('div',null),
       createVNode('div',null)
       ])
    })
  }
}
//如果是正常编译获得的那么应该是
export default{
  render(){
    return createVNode(Comp,null,{
      default:()=>createTextVNode('123')
    })
  }
}
//自己写render函数
export default {
  render(){
    return createVNode(Comp,null,{
      default:()=>123
    })
  }
}
function normalizeVNode(child) {
  if (child == null || typeof child === "boolean") {
    //没有child或者没有实质性内容创建注释节点
    return createVNode(Comment);
  } else if (shared.isArray(child)) {
    //用户直接写了一个数组,需要包裹一层Fragment
    return createVNode(Fragment, null, child.slice());
  }
  //如果这个节点已经挂载过了克隆这个节点(复用节点)
  else if (typeof child === "object") {
    return cloneIfMounted(child);
  }
  //string 或者 number
  else {
    return createVNode(Text, null, String(child));
  }
}
render(){
  return createVNode(Comp,null,{
    default:createVNode('div')
  })
}
//经过标准化后,相当于
render(){
  return createVNode(Comp,null,{
    default:withCtx(()=>[createVNode('div')])
  })
}
//其他的情况都差不多,都是为了标准化为
//满足上面四个条件的样子
const normalizeVNodeSlots = (instance, children) => {
  const normalized = normalizeSlotValue(children);
  instance.slots.default = () => normalized;
};

额外内容

//挂载过的vnode有el属性
function cloneIfMounted(child) {
  return child.el === null || child.memo ? 
         child : cloneVNode(child);
}
function mergeProps(...args) {
  const ret = {};
  for (let i = 0; i < args.length; i++) {
    const toMerge = args[i];
    for (const key in toMerge) {
      //结合class
      if (key === "class") {
        if (ret.class !== toMerge.class) {
          ret.class = shared.normalizeClass([ret.class, toMerge.class]);
        }
      }
      //结合style属性
      else if (key === "style") {
        ret.style = shared.normalizeStyle([ret.style, toMerge.style]);
      }
      else if (key !== "") {
        ret[key] = toMerge[key];
      }
    }
  }
  return ret;
}
function cloneVNode(vnode, extraProps, mergeRef = false) {
  const { props, ref, patchFlag, children } = vnode;
  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;
  const cloned = {
    //省略了大量属性,其他的属性和传递的
    //vnode一样,这里只列举了可能被改变的
    key: mergedProps && normalizeKey(mergedProps),
    ref:
      extraProps && extraProps.ref
        ? mergeRef && ref
          ? shared.isArray(ref)
            ? ref.concat(normalizeRef(extraProps))
            : [ref, normalizeRef(extraProps)]
          : normalizeRef(extraProps)
        : ref,
    children:
      patchFlag === PatchFlags.HOISTED && shared.isArray(children)
        ? children.map(deepCloneVNode)
        : children,
    shapeFlag: vnode.shapeFlag,
    patchFlag:
      extraProps && vnode.type !== Fragment
        ? patchFlag === PatchFlags.HOISTED
          ? PatchFlags.FULL_PROPS
          : patchFlag | PatchFlags.FULL_PROPS
        : patchFlag,
  };
  return cloned;
}
function deepCloneVNode(vnode) {
    const cloned = cloneVNode(vnode);
    if (shared.isArray(vnode.children)) {
        cloned.children = vnode.children.map(deepCloneVNode);
    }
    return cloned;
}

总结

以上就是Vue3源码分析组件挂载初始化props与slots的详细内容,更多关于Vue3组件挂载初始化的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

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

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

软考中级精品资料免费领

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

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

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

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

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

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

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