vue基本api

71 阅读11分钟

h函数

vue提供了一个h()函数用于创建vnodes;一个更准确的名称应该是 createVnode()

// 完整参数签名
function h(
  type: string | Component,
  props?: object | null,
  children?: Children | Slot | Slots
): VNode

// 省略 props
function h(type: string | Component, children?: Children | Slot): VNode

type Children = string | number | boolean | VNode | null | Children[]

type Slot = () => Children

type Slots = { [name: string]: Slot }

h函数:第一个参数既可以是一个字符串 (用于原生元素) 也可以是一个 Vue 组件定义。第二个参数是要传递的 prop,第三个参数是子节点。 vue官方文档

位运算符的应用

export const enum ShapeFlags {
  ELEMENT = 1, // 虚拟节点是一个元素
  FUNCTIONAL_COMPONENT = 1 << 1, // 函数式组件
  STATEFUL_COMPONENT = 1 << 2, // 普通组件
  TEXT_CHILDREN = 1 << 3, // 儿子是文本的
  ARRAY_CHILDREN = 1 << 4, // 儿子是数组
  SLOTS_CHILDREN = 1 << 5, // 插槽
  TELEPORT = 1 << 6,
  SUSPENSE = 1 << 7,
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
  COMPONENT_KEPT_ALIVE = 1 << 9,
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT|ShapeFlags.FUNCTIONAL_COMPONENT,
}
  • 1<<1,是1左移1位是2的1次方为2(左移乘以,右移除以),
  • &与运算符,8位2进制,都是1才是1,所以2&4=0;
  • |或运算符,只要有一个是1就是1,2|4=6;
  • 用大的数和小的数做与&运算大于0就说明涵盖这个类型

创建的虚拟节点,使用位运算描述自己的类型和children的关系

export function isVNode(vnode) {
  return vnode.__v_isVnode == true;
}

export function isSameVNode(n1, n2) {
  return n1.type === n2.type && n1.key === n2.key;
}
export function createVNode(type, props = null, children = null) {
  // 用标识来区分 对应的虚拟节点类型 , 这个表示采用的是位运算的方式 可以方便组合
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : isTeleport(type) // 因为teleport 也是对象 为了区分 增加标识
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.COMPONENT
    : 0;
  // 虚拟节点要对应真实节点********(虚拟节点上保存着真实的节点)
  const vnode = {
    __v_isVnode: true, // 添加标识是不是vnode
    type,
    props,
    children,
    shapeFlag,
    key: props?.key,
    el: null, // 对应的真实节点
  };
  if (children) {
    let type = 0;
    if (Array.isArray(children)) {
      // [vnode,'文本']
      type = ShapeFlags.ARRAY_CHILDREN;
    } else if (isObject(children)) {
      type = ShapeFlags.SLOTS_CHILDREN; // 孩子是插槽
    } else {
      // 文本
      type = ShapeFlags.TEXT_CHILDREN;
    }
    vnode.shapeFlag |= type; // a+b     a|b = c        c&b > 0 有b     c&b == 0 没有b
  }
  return vnode; // 根据 * vnode.shapeFlag  来判断自己的类型和孩子的类型*
}

h函数通过调用createVnode,生成虚拟节点

export function h(type, propsOrChildren?, children?) {
  const l = arguments.length;
  // h(type,{})   h(type,h('span')) => h(type,[h('span')])  /    h(type,[])   h(type,'文本')
  // h函数参数有2个的时候 h(type,{}),h(type,vnode),h(type,[])
  // h函数参数有3个的时候 h(type,{},文本),h(type,{},vnode),h(type,{},{default:()=>{}})(插槽函数)
  // h函数参数大于3个的时候,h(type,{},'',''),第二个参数必须是描述节点的属性
  if (l == 2) {
    //是对象不是数组:h(type,{})或者是h(type,h('span'))
    if (isObject(propsOrChildren) && !Array.isArray(propsOrChildren)) {
      if (isVNode(propsOrChildren)) {
        //第二个是虚拟对象,直接包装成children
        return createVNode(type, null, [propsOrChildren]);
      }
      return createVNode(type, propsOrChildren);
    } else {
      // 数组 或者文本 h('div','aaa'),h('div',[])
      return createVNode(type, null, propsOrChildren);
    }
  } else {
    if (l > 3) {
      children = Array.from(arguments).slice(2);
      // h('div',{},'a','b','c') 这样操作第二个参数必须是属性 h('div','e','a','b','c')

      // h('div',{},h('span')) => h('div',{},[h('span')])
    } else if (l === 3 && isVNode(children)) {
      children = [children];
    }
    return createVNode(type, propsOrChildren, children);
    // l == 3
  }
}

createRenderer

创建一个自定义渲染器。

import { createRenderer } from '@vue/runtime-core'

const { render, createApp } = createRenderer({
  patchProp,
  insert,
  remove,
  createElement
  // ...
})

// `render` 是底层 API
// `createApp` 返回一个应用实例
export { render, createApp }

// 重新导出 Vue 的核心 API
export * from '@vue/runtime-core'

该函数的返回值包含render函数,render函数用于把虚拟dom(h函数生成的虚拟dom)生成真实的dom,也可以直接使用从runtime-dom导出的render函数直接生成浏览器的dom

export const render = (vnode, container) => {
  return createRenderer(renderOptions).render(vnode, container);
};
// renderOptions是浏览器的dom生成api(不用自己定义了),这个函数就是对createRenderer的render的包装

createRenderer函数的简单实现

export function createRenderer(options) {
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    querySelector: hostQuerySelector,
  } = options;
  //处理孩子,这里是递归处理
  const mountChildren = (children, el, anchor = null, parent = null) => {
    for (let i = 0; i < children.length; i++) {
       //初次渲染,第一个参数是null
      patch(null, children[i], el, anchor, parent);
    }
  };
  const mountElement = (vnode, container, anchor, parent) => {
    const { type, props, children, shapeFlag } = vnode;
    // 创建元素,虚拟节点上要保存真实的节点**********
    const el = (vnode.el = hostCreateElement(type));
    // 增添属性
    if (props) {
      for (let key in props) {
        hostPatchProp(el, key, null, props[key]);
      }
    }
    // 根据ShapeFlags的类型,处理子节点,位运算符计算
    if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      //递归处理子节点
      mountChildren(children, el, anchor, parent);
    } else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      hostSetElementText(el, children);
    }
    hostInsert(el, container, anchor);
  };
  const patch = (n1,n2,container)=>{
      if(n1 = n2){
          return; //无需更新
      } 
      if(n1==null){
          //初次渲染
          mountElement(n2,container)
      } else {
          //diff算法
      }
  }
  // 虚拟节点上的真实节点移除
  const unmount = (vnode, parent) => {
    hostRemove(vnode.el);
  };
  const render = (vnode, container, parent = null) => {
    if (vnode == null) {
      // 卸载:删除节点
      if (container._vnode) {
        // 说明渲染过了,我才需要进行卸载操作
        unmount(container._vnode, parent);
      }
    } else {
      // 初次渲染  更新
      patch(container._vnode || null, vnode, container);
    }
    container._vnode = vnode; // 第一次渲染保存虚拟节点*********
  };
  return {
    // createRenderer 可以用户自定义渲染方式
    // createRenderer 返回的render方法 接受参数是虚拟节点和容器
    render,
  };
}

defineAsyncComponent()

定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。

function defineAsyncComponent(
  source: AsyncComponentLoader | AsyncComponentOptions
): Component
//异步函数
type AsyncComponentLoader = () => Promise<Component>
//选项对象
interface AsyncComponentOptions {
  loader: AsyncComponentLoader //将要加载的组件
  loadingComponent?: Component //loading组件
  errorComponent?: Component  //错误显示组件
   // 展示加载组件前的延迟时间,默认为 200ms在加载组件显示之前有一个默认的 200ms 延迟——
   //这是因为在网络状况较好时,加载完成得很快,
   //加载组件和最终组件之间的替换太快可能产生闪烁,反而影响用户感受。
   delay?: number
  timeout?: number // 超时时间
  suspensible?: boolean
  onError?: (    //发生错误时回调函数
    error: Error,
    retry: () => void, //错误时继续请求组件,这里是一个promise链
    fail: () => void,
    attempts: number  //尝试错误的次数
  ) => any
}

源码学习

import { Fragment, Text } from "./vnode";
import { h } from "./h";
import { ref } from "@vue/reactivity";
export function defineAsyncComponent(options) {
  //判断是函数还是对象
  if (typeof options === "function") {
    options = { loader: options };
  }
  let Component = null;
  // 超时时间定时器和loading加载定时器
  let timer = null;
  let loadingTimer = null;
  return {
    setup() {
      let { loader } = options;
      const loaded = ref(false); //控制组件的显示
      const error = ref(false); //控制错误组件的显示
      const loading = ref(false);
      //封装的函数
      function load() {
        return loader().catch((err) => {
          //如果配置项,有onError选项,返回一个promise,形成一个promise链条
          if (options.onError) {
            return new Promise((resolve, reject) => {
              // resolve 一个promise
              // 一个promise会等待另一个promise执行完毕
              const retry = () => resolve(load()); //这里是循环请求load函数,产生链
              const fail = () => reject(err);
              options.onError(err, retry, fail);
            });
          } else {
            throw err;
          }
        });
      }
      if (options.delay) {
        loadingTimer = setTimeout(() => {
          loading.value = true;
        }, options.delay);
      }
      load()
        .then((c) => {
          Component = c;
          loaded.value = true;
          clearTimeout(timer); //清除定时器
        })
        .catch((err) => (error.value = err)) //加载组件失败时
        .finally(() => {
          // 无论成功或者失败都要把loading层清除掉
          loading.value = false;
          clearTimeout(loadingTimer);
        });
      //上面是异步的,超过了timeout就显示错误组件,没有的话上面成功时会清除这个定时器
      if (options.timeout) {
        //超过定时时间,就显示错误的组件
        timer = setTimeout(() => {
          error.value = true;
        }, options.timeout);
      }

      return () => {
        if (loaded.value) {
          return h(Component); // 成功组件
        } else if (error.value && options.errorComponent) {
          return h(options.errorComponent); // 错误组件,渲染错误组件
        } else if (loading.value && options.loadingComponent) {
          //loading层的加载
          return h(options.loadingComponent);
        }
        return h(Fragment, []);
      };
    },
  };
}

KeepAlive

创建一个div用于缓存keepalive中的插槽的dom元素,切换的时候从缓存中获取,设计到max的时候,会有一个lru的算法,就是把最新的放到末尾,超出长度时,删除最老的那一个

import { ShapeFlags } from "@vue/shared";
import { getCurrentInstance } from "./component";
import { onMounted, onUpdated } from "./apiLifecycle";
import { isVNode } from "./vnode";
//******************keepAlive中放的是插槽
export const KeepAliveImpl = {
  __isKeepAlive: true,
  props: {
    include: {}, // ['缓存名字','缓存字']  ’my1,my2‘ 正则
    exclude: {},
    max: {}, //这里涉及到了lru算法了
  },
  setup(props, { slots }) {
    // 缓存的组件有哪些,方便查找 key 来描述
    // key 对应的组件的定义
    const keys = new Set();
    const cache = new Map(); // key -> 组件

    const instance = getCurrentInstance();
    //只有keepalive组件才有ctx属性
    let { createElement, move, unmount } = instance.ctx.renderer;
    //创建盒子,用于缓存dom
    let storageContainer = createElement("div");
    //缓存的组件激活时,没有触发组件的生成流程,而是从缓存中拿的
    instance.ctx.activate = function (vnode, container) {
      //组件激活的时候 需要从缓存中拿出来就可以了
      move(vnode, container); // 将刚才缓存的dom,拿到容器中
    };
    //组件卸载的时候,直接把dom移动到盒子中去了
    instance.ctx.deactivate = function (vnode) {
      // 给这个虚拟节点对应的dom  移动到隐藏的盒子中就可以了
      move(vnode, storageContainer);
    };

    let pendingCacheKey = null; //保存将要缓存组件的key

    const _unmount = (vnode) => {
      let shapeFlag = vnode.shapeFlag;
      if (shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        shapeFlag -= ShapeFlags.COMPONENT_KEPT_ALIVE;
      }
      if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
        shapeFlag -= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
      }
      vnode.shapeFlag = shapeFlag;
      // 卸载真实dom
      unmount(vnode, null);
      // 这里要移除
    };
    const pruneCaheEntry = (key) => {
      // 这里只是拿出第一个  [1,2,3,4] . -> 2 . [1,3,2]
      let cached = cache.get(key);
      cache.delete(key);
      keys.delete(key);
      //还原节点的标识
      _unmount(cached);
    };
    const cacheSubTree = () => {
      if (pendingCacheKey) {
        cache.set(pendingCacheKey, instance.subTree);
      }
    };
    // 加载完毕后要将组件进行缓存,缓存的是dom元素
    onMounted(cacheSubTree);
    onUpdated(cacheSubTree);
    return () => {
      //1
      let vnode = slots.default();
      let name = vnode.type.name;

      const { include, exclude, max } = props;
      // keepAlive中缓存的是vnode,还必须是状态组件  2222
      if (
        !isVNode(vnode) ||
        !(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT)
      ) {
        return vnode;
      }
      const comp = vnode.type;
      const key = vnode.props?.key == null ? comp : vnode.props.key;
      // 3
      let cacheVNode = cache.get(key);
      pendingCacheKey = key; // 在组件加载完毕后缓存的key的名字

      if (
        (name && include && !include.split(",").includes(name)) ||
        (exclude && exclude.split(",").includes(name))
      ) {
        return vnode;
      }
      //4
      if (cacheVNode) {
        // 复用组件的实例,告诉这个组件不要在渲染了,这里就走到组件的加载了,这里是从缓存中拿的组件
        vnode.component = cacheVNode.component;
        // 修改组件的标识位
        vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
      } else {
        // lru 算法,删除key,再添加上key  [1,2]添加3,[1,2,3]把1删除,访问2的时候把2先删除,再添加2,[3,2],永远让访问的保持最新
        keys.delete(key); // ****将原有的移除到尾部, 让其保持最新
        keys.add(key);
        // 超过缓存限制了, 删除第一个
        if (max && keys.size > max) {
          // it.next()
          pruneCaheEntry(keys.values().next().value);
        }
      }
      // **********稍后组件卸载的时候 不要卸载。 后续可以复用这个组件的dom元素
      // ***********添加标识位,说明这是一个要缓存的组件
      vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
      return vnode;
    };
  },
};

export const isKeepAlive = (vnode) => vnode.type.__isKeepAlive;

provide和inject

vue2中的provide可以直接一层层的向上查找,vue3的话直接从父级上查找就行了,就近原则(子会找父亲的provide,如果自身和父亲的provide属性相同,就会覆盖,再添加自己的属性)

// vue2 中provide 是 一层层向上找  component.parent.parent
// vue3 中是只找父级,就近原则(子会找父亲的provide,如果自身和父亲的provide属性相同,就会覆盖,再添加自己的属性)
// provide 和 inject 适合编写插件,不适合用在业务代码中  (数据来源不明确)

import { currentInstance } from "./component";
// 父  -》 name  provides  ********
// 子1 name属性 provides
// 子2 使用name属性? provides
export function provide(key, value) {
  // 如何判断 在不在组件中?
  if (!currentInstance) return;
  // 第一次我的provides 是来自于父亲的
  // 所以一样就拷贝一个 作为自己新的provides
  // 下一次调用provides 用的是自己的provides肯定和父亲的不是一个
  // 我就不创建provides了;
  let provides = currentInstance.provides;

  // 我要知道在当前组件中我是第一次调用的provide 还是不是第一次
  const parentProvides =
    currentInstance.parent && currentInstance.parent.provides;
  // ********第一次是来自父亲的provides,2者相同,然后create了一个新对象,第二次更新后就是另一个对象了,就不走这一步了,直接往对象中赋值了
  //这里只有第一次能走进来
  if (provides === parentProvides) {
    // 每个组件都有自己的provides, 这样实现每次调用provide 都会产生一个新的
    //  Object.create创建了一个新的对象,下次就不能走进来了
    provides = currentInstance.provides = Object.create(provides);

    // xxx.__proto__ = provides
  }
  provides[key] = value;
}

export function inject(key, value) {
  if (!currentInstance) return;
  const provides = currentInstance.parent?.provides;
  // 上级有提供过这个属性
  if (provides && key in provides) {
    return provides[key];
  } else if (value) {
    return value;
  }
}

// 父 provides ={} . 儿子 = provides  孙子 =provides
// . 儿子 = provides

// 儿子提供数据了  儿子 拷贝一份增添属性作为

teleport

就是一个dom在生成的时候会产生移动的过程

export const TeleportImpl = {
  __isTeleport: true, // 此组件是一个特殊的组件类型
  process(n1, n2, container, anchor, operators) {
    // 等会组件初始化会调用此方法
    let { mountChildren, patchChildren, move, query } = operators;
    if (!n1) {
      const target = (n2.target = query(n2.props.to));
      if (target) {
        mountChildren(n2.children, target, anchor);
      }
    } else {
      //更新后,n2就变成了n1,所以从n1上取target
      patchChildren(n1, n2, n1.target); // 只是比较了儿子的差异
      n2.target = n1.target;
      //属性to发生了变化,重新赋值
      if (n2.props.to !== n1.props.to) {
        const nextTarget = (n2.target = query(n2.props.to));
        n2.children.forEach((child) => move(child, nextTarget, anchor));
      }
    }
  },
};

export const isTeleport = (type) => !!type.__isTeleport;

生命周期函数

只能在setup中使用vue3新的生命周期钩子,onMounted等,并且setup执行是在beforCreate之前执行的,就在vnode的实例上依赖收集的一个过程

import { currentInstance, setCurrentInstance } from "./component";

export const enum LifecycleHoos {
  BEFORE_MOUNT = "bm",
  MOUNTED = "m",
  BEFORE_UPDATE = "bu",
  UPDATED = "u",
}
// bm = []
// bu = []
// m = []
// u = []

function createHook(type) {
  // type 是绑定到哪里  , hook 就是用户传递的钩子   获取当前的实例
  // instance[type] = [hook,hook]
  return (hook, target = currentInstance) => {
    //这里的hook就是要执行的生命周期内的函数
    if (target) {
      // 生命周期 必须在setup中使用
      const wrapperHook = () => {
        //  为了能在函数中使用instance,使用闭包函数获得instance
        setCurrentInstance(target);
        hook();
        setCurrentInstance(null);
      };
      const hooks = target[type] || (target[type] = []); // 在这里挂载了生命周期函数
      hooks.push(wrapperHook);
    }
  };
}
export const onBeforeMount = createHook(LifecycleHoos.BEFORE_MOUNT);
export const onMounted = createHook(LifecycleHoos.MOUNTED);
export const onBeforeUpdate = createHook(LifecycleHoos.BEFORE_UPDATE);
export const onUpdated = createHook(LifecycleHoos.UPDATED);