vue 3ソース分析-プロセス分析の実行


仮想domおよびエージェントは、vue 3の実行プロセスを説明する他のブログを参照できます.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メソッドの実行を完了します.义齿update=effect(fn,createDeviceOptions)は、effectをマウントし、レンダリングロジックを実行します.createDevEffectOptionsは、一括更新メソッドを作成します.この2つの関数を実行すると、データのエージェントと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プロパティはcreateDeviceffectOptionsメソッドで定義され、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); ほとんどのプロセスは終了しました.