【vue3】仮想DOM生成からmountedまで（patch処理）

vue3になりましたね
vue2系の仮想DOMの仕組みは以下の文献でわかりやすく書いてありました（感謝😈）
[Vue.jsの仮想DOMと差分レンダリングの仕組み①](https://blog.engineer.adways.net/entry/2018/01/19/200000)
[Vue.jsの仮想DOMと差分レンダリングの仕組み②](https://blog.engineer.adways.net/entry/2018/02/02/183000)
[Vue.jsの仮想DOMと差分レンダリングの仕組み③](https://blog.engineer.adways.net/entry/2018/02/16/190500)

vue3のソースコードもvue2と同じかなと思ったら結構違いました🙄
解説記事もあんまないし、ざっとpatch処理の初回レンダリングの箇所だけ読んで書きます

```package.json
"vue": "^3.0.2-0"
```

`runtime-core/dist/runtime-core.esm-bundler.js`しか基本的に触れないです

# vue3の仮想DOM生成を図解
基本的にはvue2と同じで、コンポーネントが1つのインスタンスで、その中にvnodeのツリーになります  
vue3から、ルートコンポーネントにdivがなくても動くようになったのですが
これは`Symbol(Fragment)`というコンポーネントが自動でラップしてくれると思ってください

例えば

```App.vue
<template>
  <div id="main">
    <div><p>コメント1</p></div>
    <hoge />
  </div>
</template>
```

```hoge.vue
<template>
  <div>コメント2</div>
  <div>
    <div v-if="true">コメント3</div>
    <div v-else>コメント4</div>
    <div v-if="false">コメント5</div>
  </div>
</template>
```

domツリーは↓こんな感じになります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150607/38f072a9-f0c5-489b-24ff-ac0a1b712290.png)



コンポーネントが1つのインスタンス
インスタンスの中に、htmlタグの通りのvnode
各vnodeでpatchが実行されると思ってください（patchが実行される順番を番号にしました）

流れで書くと以下のような感じ

1. `import App from './App'`の`App`をvnodeに変換して最初のpatch実行（番号1）
2. `先頭のvnode(AppのSubTree)`と子タグの`vnode（div）`と`vnode(hoge)`を生成して、`先頭のvnode`に対してpatch実行（番号2）  
~~コンポーネントじゃないhtmlタグのVNode生成は事前に行ってるみたいな細けぇコメント禁止🖕~~
3. `先頭のvnode.children(divとhoge)`をそれぞれpatch実行する、html順で先に`vnode（div）`のpatch実行（番号3）  
4. `vnode（div）`の`vnode.children(p)`のpatch実行（番号4）
5. `vnode(hoge)`のpatch実行（番号5）
6. `先頭のvnode(HogeのSubTree)`と子タグの`vnode（div)`と`vnode(div)`を生成して、`先頭のvnode`に対してpatch実行（番号6）
7. 番号7~10は同じ流れなので省略  
*`v-if/v-else`はどっちか1つしかvnodeとして扱わないです  
*`v-if="false"`はコメントアウトのvnode（`Symbol(Comment)`）として扱われるので、`div`ではなく`Comment`って書いてあります

# vue3の仮想DOM生成のソースコードを軽く説明
図と箇条書きでザッと説明しましたが、同じ内容をmethod名で追っかけます
`NODE_ENV !== 'production'`の時の処理とか、`hydrate`や`suspense`や`compositionAPI`用の分岐とかめっちゃありますが`// 略`で飛ばします

## app.mount()から最初のpatch()まで

最初に`app.mount('#main')`みたいに実行すると思いますが、
以下のmountの関数をラップしたmountが呼ばれます（基本的に以下のmountが呼ばれると思ってください）

```js
mount(rootContainer, isHydrate) {
  if (!isMounted) {
      // rootComponentは import App from './App' のApp（厳密にはちょっと違います）
      const vnode = createVNode(rootComponent, rootProps);
      // 略
      // rootContainerはindex.htmlのelement
      render(vnode, rootContainer);
      // 略
  }
}

const render = (vnode, container) => {
  // 略
　patch(container._vnode || null, vnode, container);
  flushPostFlushCbs(); // ここでmounted()がまとめて実行されます(道中でmountedを登録してる)
  container._vnode = vnode;
}
```

patch関数は、そのvnodeのtypeを判定してるだけです
今回使うvnodeの種類は、`ELEMENT(divとp), COMPONENT(AppとHoge), Fragment, Comment(コメントアウト)`です
関係ないやつは`//略`にしました

```js
// n1は更新時に使うので、初回レンダリングは n1 = null です
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {
  // 略
  const { type, ref, shapeFlag } = n2;
  switch (type) {
      case Text:
          // 略
      case Comment:
          processCommentNode(n1, n2, container, anchor);
          break;
      case Static:
          // 略
      case Fragment:
          processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
          break;
      default:
          if (shapeFlag & 1 /* ELEMENT */) {
              processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
          }
          else if (shapeFlag & 6 /* COMPONENT */) {
              processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
          }
          // 略(TELEPORTとSUSPENSEとエラー制御)
  }
  // 略
};
```

## コンポーネントのpatch（processComponent）
番号1のpatch処理は`processComponent`です

```js
const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  // 略
  mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
  // 略
}
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  // コンポーネントのインスタンス生成はここ、instanceから先頭のvnodeを生成して、patch処理を実行し始めます
  const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));
  // 略
  setupComponent(instance); // propsやslotの処理,compositionAPIのsetup実行,created実行など、今回とは関係ないのでスルー
  // 略
  setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
}
```

先頭のvnodeは`subTree`って呼ばれてます
この`subTree`のpatch処理実行が番号2です

```js
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
  // create reactive effect for rendering
  instance.update = effect(function componentEffect() {
      if (!instance.isMounted) {
          // 略(beforeMount実行などしてる)
          const subTree = (instance.subTree = renderComponentRoot(instance));
          // 略
          patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
          // 略(mountedを後で実行するように登録などしてる)
          instance.isMounted = true;
      }
      else {
        // 略
      }
  }, prodEffectOptions);
};
```

## HTMLElementのpatch（processElement）
番号2のpatch処理は`processElement`です（先頭のhtmlタグがdivだから）
`mountChildren`の中のpatch処理実行が番号3です

```js
const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  // 略
  mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
  // 略
}
const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  let el;
  let vnodeHook;
  const { type, props, shapeFlag, transition, scopeId, patchFlag, dirs } = vnode;
  // 略
  el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is); // createElementはここ
  mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG && type !== 'foreignObject', optimized || !!vnode.dynamicChildren);
  // 略(transitionのbeforeEntry実行などしてる)
  hostInsert(el, container, anchor); // insertBeforeはここ
  // 略
}
const mountChildren = (children, container, anchor, parentComponent, parentSuspense, isSVG, optimized, start = 0) => {
  for (let i = start; i < children.length; i++) {
      const child = (children[i] = optimized
          ? cloneIfMounted(children[i])
          : normalizeVNode(children[i]));
      patch(null, child, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
  }
}
```

## その他のvnodeのpatch（番号3~10）
- 番号3は、`div`のpatch処理なので`processElement`
- 番号4は、`p`のpatch処理なので`processElement`
- 番号5は、`Hoge`のpatch処理なので`processComponent`
- 番号6は、`Fragment`のpatch処理なので`processFragment`
- 番号7は、`div`のpatch処理なので`processElement`
- 番号8は、`div`のpatch処理なので`processElement`
- 番号9は、`div`のpatch処理なので`processElement`
- 番号10は、`Comment`のpatch処理なので`processCommentNode`

*`processFragment`では`mountChildren`を呼ぶ等、`processElement`を簡素化した感じ
*`processCommentNode`では`hostInsert`しかしてないです
*どれも`mountChildren`が終了してやっと親のpatch処理が終了するので、番号1と番号2のpatch処理は最後に終わります

# 所感
ざっくり説明ですみません（`//略`で終わらせてますが、もっといっっっぱい分岐と実行があります）
[Vue.jsの仮想DOMと差分レンダリングの仕組み②](https://blog.engineer.adways.net/entry/2018/02/02/183000)に書いてある`patch関数のフローチャート`とか死んでも作りたくないと思いました🙀
`vue-next`にプルリク作れるぐらい理解しないと作れないと思います

全体的に思ったのですが、ソースコードに分岐が多すぎるなあって感じ
更新と初回レンダリングの分岐や本番と開発環境の分岐は仕方ないとして、`compositionAPI`とか`suspense`とか、もうちょっと整理して書く設計しなかったのかと、やや疑問
というか何で全部1ファイルにソースコードまとめてるんだろう？現代っ子だから？