vue3 源码分析-运行流程分析

虚拟dom和代理可以参考其他博客,此博客介绍vue3运行流程。方便理解vue的运行流程。
一、初次渲染流程

const createApp = ((...args) => {
     
      const app = ensureRenderer().createApp(...args);
      {
     
          injectNativeTagCheck(app);
      }
      const {
      mount } = app;
      app.mount = (containerOrSelector) => {
     
          const container = normalizeContainer(containerOrSelector);
          if (!container)
              return;
          const component = app._component;
          if (!isFunction(component) && !component.render && !component.template) {
     
              component.template = container.innerHTML;
          }
          // clear content before mounting
          container.innerHTML = '';
          const proxy = mount(container);
          container.removeAttribute('v-cloak');
          container.setAttribute('data-v-app', '');
          return proxy;
      };
      return app;
  });


ensureRenderer:创建render函数
  return {
     
          render,
          hydrate,
          createApp: createAppAPI(render, hydrate)
      };

createAppAPI 返回createApp函数;
createApp创建了app对象。app对象有mount方法。
mount 调用render方法,render调用patch方法,patch方法判断shapeFlag & 6 /* COMPONENT */,执行processComponent方法,此时还没有生产虚拟dom。
processComponent判断首次渲染执行mountComponent。
mountComponent创造instance对象,先执行setupComponent方法,setupComponent主要作用就是生产各种属性和方法,例如props、render、type等;setupComponent执行setupStatefulComponent方法,在setupStatefulComponent里finishComponentSetup方法执行compile方法,编译出render方法,render是instance对象一个属性,rende方法可以编译出虚拟dom。

"const _Vue = Vue
const {
      createVNode: _createVNode, createTextVNode: _createTextVNode } = _Vue

const _hoisted_1 = /*#__PURE__*/_createVNode("h1", null, "Latest Vue.js Commits", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createTextVNode(" - ")
const _hoisted_3 = {
      class: "message" }
const _hoisted_4 = /*#__PURE__*/_createVNode("br", null, null, -1 /* HOISTED */)
const _hoisted_5 = /*#__PURE__*/_createTextVNode(" by ")
const _hoisted_6 = {
      class: "author" }
const _hoisted_7 = /*#__PURE__*/_createTextVNode(" at ")
const _hoisted_8 = {
      class: "date" }

return function render(_ctx, _cache) {
     
  with (_ctx) {
     
    const {
      createVNode: _createVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, vModelRadio: _vModelRadio, withDirectives: _withDirectives, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode } = _Vue

    return (_openBlock(), _createBlock(_Fragment, null, [
      _hoisted_1,
      (_openBlock(true), _createBlock(_Fragment, null, _renderList(branches, (branch) => {
     
        return (_openBlock(), _createBlock(_Fragment, null, [
          _withDirectives(_createVNode("input", {
     
            type: "radio",
            id: branch,
            value: branch,
            name: "branch",
            "onUpdate:modelValue": $event => (currentBranch = $event)
          }, null, 8 /* PROPS */, ["id", "value", "onUpdate:modelValue"]), [
            [_vModelRadio, currentBranch]
          ]),
          _createVNode("label", {
      for: branch }, _toDisplayString(branch), 9 /* TEXT, PROPS */, ["for"])
        ], 64 /* STABLE_FRAGMENT */))
      }), 256 /* UNKEYED_FRAGMENT */)),
      _createVNode("p", null, "vuejs/vue@" + _toDisplayString(currentBranch), 1 /* TEXT */),
      _createVNode("ul", null, [
        (_openBlock(true), _createBlock(_Fragment, null, _renderList(commits, ({
      html_url, sha, author, commit }) => {
     
          return (_openBlock(), _createBlock("li", null, [
            _createVNode("a", {
     
              href: html_url,
              target: "_blank",
              class: "commit"
            }, _toDisplayString(sha.slice(0, 7)), 9 /* TEXT, PROPS */, ["href"]),
            _hoisted_2,
            _createVNode("span", _hoisted_3, _toDisplayString(truncate(commit.message)), 1 /* TEXT */),
            _hoisted_4,
            _hoisted_5,
            _createVNode("span", _hoisted_6, [
              _createVNode("a", {
     
                href: author.html_url,
                target: "_blank"
              }, _toDisplayString(commit.author.name), 9 /* TEXT, PROPS */, ["href"])
            ]),
            _hoisted_7,
            _createVNode("span", _hoisted_8, _toDisplayString(formatDate(commit.author.date)), 1 /* TEXT */)
          ]))
        }), 256 /* UNKEYED_FRAGMENT */))
      ])
    ], 64 /* STABLE_FRAGMENT */))
  }
}"

执行完compile后,继续执行applyOptions方法,applyOptions里执行resolveData, resolveData执行 instance.data = reactive(data);就生成代理对象。
setupComponent执行完执行setupRenderEffect方法。 setupRenderEffect方法instance.update = effect(fn,createDevEffectOptions(instance)),挂载effect,执行渲染逻辑。createDevEffectOptions创建批量更新方法。执行完这两个函数,就完成了数据的代理,和effect的挂载。

function createDevEffectOptions(instance) {
     
      return {
     
          scheduler: queueJob,
          onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
          onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
      };
  }

挂载完effect函数后,会立即调用effect函数。

 instance.update = effect(function componentEffect() {
     
               // debugger;   
              if (!instance.isMounted) {
     
                  let vnodeHook;
                  const {
      el, props } = initialVNode;
                  const {
      bm, m, parent } = instance;
                  // beforeMount hook
                  if (bm) {
     
                      invokeArrayFns(bm);
                  }
                  // onVnodeBeforeMount
                  if ((vnodeHook = props && props.onVnodeBeforeMount)) {
     
                      invokeVNodeHook(vnodeHook, parent, initialVNode);
                  }
                  // render
                  {
     
                      startMeasure(instance, `render`);
                  }
                  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`);
                      }
                      // debugger
                      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)) {
     
                      queuePostRenderEffect(() => {
     
                          invokeVNodeHook(vnodeHook, parent, initialVNode);
                      }, 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;
              }
              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) {
     
                      updateComponentPreRender(instance, next, optimized);
                  }
                  else {
     
                      next = vnode;
                  }
                  next.el = vnode.el;
                  // 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;
                  // reset refs
                  // only needed if previous patch had refs
                  if (instance.refs !== EMPTY_OBJ) {
     
                      instance.refs = {
     };
                  }
                  {
     
                      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) );

effect函数的调用renderComponentRoot函数。renderComponentRoot执行instance上的render方法生产虚拟dom。
结构如下:

anchor: null
appContext: null
children: Array(13)
0: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
1: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
2: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
3: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
4: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
5: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
6: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
7: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
8: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
9: {
     __v_isVNode: true, __v_skip: true, type: Symbol(Comment), props: null, key: null,}
10: {
     __v_isVNode: true, __v_skip: true, type: "div", props: null, key: null,}
11: {
     __v_isVNode: true, __v_skip: true, type: "div", props: null, key: null,}
12: {
     __v_isVNode: true, __v_skip: true, type: "button", props: {
     }, key: null,}
length: 13
__proto__: Array(0)
component: null
dirs: null
dynamicChildren: (3) [{
     }, {
     }, {
     }]
dynamicProps: null
el: null
key: null
patchFlag: 64
props: null
ref: null
scopeId: null
shapeFlag: 16
staticCount: 0
suspense: null
target: null
targetAnchor: null
transition: null
type: Symbol(Fragment)
  const subTree = (instance.subTree = renderComponentRoot(instance));

然后执行patch方法。
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);页面就初次渲染完毕。

二、更新流程

当代理对象属性改变后,Proxy相关可以参考其他博客,不是很难理解。会触发trigger方法。

function trigger(target, type, key, newValue, oldValue, oldTarget) {
     
      // debugger;
      const depsMap = targetMap.get(target);
      if (!depsMap) {
     
          // never been tracked
          return;
      }
      const effects = new Set();
      const add = (effectsToAdd) => {
     
          if (effectsToAdd) {
     
              effectsToAdd.forEach(effect => effects.add(effect));
          }
      };
      if (type === "clear" /* CLEAR */) {
     
          // collection being cleared
          // trigger all effects for target
          depsMap.forEach(add);
      }
      else if (key === 'length' && isArray(target)) {
     
          depsMap.forEach((dep, key) => {
     
              if (key === 'length' || key >= newValue) {
     
                  add(dep);
              }
          });
      }
      else {
     
          // schedule runs for SET | ADD | DELETE
          if (key !== void 0) {
     
              add(depsMap.get(key));
          }
          // also run for iteration key on ADD | DELETE | Map.SET
          const shouldTriggerIteration = (type === "add" /* ADD */ &&
              (!isArray(target) || isIntegerKey(key))) ||
              (type === "delete" /* DELETE */ && !isArray(target));
          if (shouldTriggerIteration ||
              (type === "set" /* SET */ && target instanceof Map)) {
     
              add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY));
          }
          if (shouldTriggerIteration && target instanceof Map) {
     
              add(depsMap.get(MAP_KEY_ITERATE_KEY));
          }
      }
      const run = (effect) => {
     
          if ( effect.options.onTrigger) {
     
              effect.options.onTrigger({
     
                  effect,
                  target,
                  key,
                  type,
                  newValue,
                  oldValue,
                  oldTarget
              });
          }
          if (effect.options.scheduler) {
     
              console.log(effect);
              effect.options.scheduler(effect);
          }
          else {
     
              effect();
          }
      };
      effects.forEach(run);
  }

最后调用 effect.options.scheduler(effect)方法。scheduler属性在createDevEffectOptions方法上定义,就是在挂载effect函数时定义。scheduler属性指向queueJob方法。queueJob方法先判断全局变量queue数据有没有包含一样类型的job,即effect,如果没有就添加到队列中。

 function queueJob(job) {
     
      // debugger;
      // the dedupe search uses the startIndex argument of Array.includes()
      // by default the search index includes the current job that is being run
      // so it cannot recursively trigger itself again.
      // if the job is a watch() callback, the search will start with a +1 index to
      // allow it recursively trigger itself - it is the user's responsibility to
      // ensure it doesn't end up in an infinite loop.
      if ((!queue.length ||
          !queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) &&
          job !== currentPreFlushParentJob) {
     
          console.log(1,job);
          queue.push(job);
          queueFlush();
      }
      console.log(2,job);
  }
 function queueFlush() {
     
      debugger;
      if (!isFlushing && !isFlushPending) {
     
          isFlushPending = true;
          currentFlushPromise = resolvedPromise.then(flushJobs);
      }
  }

queueFlush方法异步微任务方法,即调用effects.forEach(run)结束后再执行一次effect函数,保证effect只执行一次。
efect函数就是初次渲染的函数。执行effect函数就是执行这个方法
const nextTree = renderComponentRoot(instance);生产新的虚拟dom方法。再执行patch方法,即对比虚拟dom节点,渲染页面。prevTree是前一次渲染页面的虚拟dom,是instance一个属性。
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);
大部分流程就结束了。

你可能感兴趣的:(js,vue,vue,proxy,dom)