vueでのVirtual DOMソース学習

7529 ワード

vueが2.0にアップグレードされた後にVirtual DOMが加わったが,Virtual DOMの概念についてはここではあまり説明しない.本稿では,vueにおけるVirtual DOMレンダリングが実際のDOMにどのように実現されるかを主に解析する.
最近私はmarkdown解析に関することを研究していますが、Githubで関連する解析器を見つけるのは基本的にmarkdownを文字列に解析する方法です.複数のオープンソースライブラリを参照した後、markedを解析ライブラリとして選択してmarkdownを解析します.markedを選択したのは、markedが他のライブラリに比べてコード量が相対的に少なく、一人でやりやすく、解析能力も効率もよく、star数も高いため、コード品質も非常に高いからです.
markdown解析を作成するとき、最初の目的はmarkdownをvnodeに解析し、実際のDOMにレンダリングすることです.したがって,自己実現解析markdown部分はmarkedに基づいて実現され,解析が完了するとvnodeの木構造が得られる.
現在vnodeとして解析することは基本的に実現されているが,多くの点で最適化が必要である.現在最も解決しなければならないのはvnodeを実際のDOMにレンダリングすることであるため,vueのVirtual DOMを基礎として検討する.実はこの文章を書くのも自分のためにvnodeのrenderの方法を書くために考えを整理しているので、これもなぜタイトルが分析ではなく勉強と呼ばれているのかということです.文章の中の間違いに対して私に指摘してほしい.
vueのvnodeのデータ構造
vueのvnodeコード部分はプロジェクトのsrc/core/vdomフォルダの下にあり、vueのVirtual DOMはsnabdomに基づいて修正され、vnodeクラスのデータ構造は以下の通りである.
export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  functionalContext: Component | void; // only for functional component root nodes
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.functionalContext = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}

vnodeがReal DOMをレンダリングする方法
vueでvnodeをReal DOMにレンダリングする主な仕事はsrc/core/vdom/patch.jsファイルにあるコードが完成したことです.patchを見てみましょう.jsではどんな仕事をしていますか.ここではソースコードと結びつけて見ることができます.https://github.com/vuejs/vue/blob/dev/src/core/vdom/patch.js
ソースを読むときの一般的な順序は、このファイルがどのファイルに依存しているのかを見ることです.次に、最も重要なのは、ファイルが何をエクスポートしているのか、エクスポートしているのはファイルの核心部分です.つまり、ソースを読む入り口です.
ファイルの上部にファイルの依存性が表示されます.ここで導入したファイルは次のようになります(重要なモジュールの役割のみが書かれています).
import VNode from './vnode' // vnode 
import config from '../config' // vue     ,         
import { SSR_ATTR } from 'shared/constants'
import { registerRef } from './modules/ref'
import { activeInstance } from '../instance/lifecycle' 

import {
  warn, //       
  isDef, //      undefined null
  isUndef, //     undefined null
  isTrue,
  makeMap, //      ``,``     
  isPrimitive //       string、number boolean
} from '../util/index' //       

モジュールのエクスポートには2つの場所があります
  • は[30行]にあります(https://github.com/vuejs/vue/blob/dev/src/core/vdom/patch.js#L30)を使用して、空のvnodeオブジェクト
  • をエクスポートします.
  • が70行に位置する主patch関数を生成する関数
  • 最も重要な関数はcreatePatchFunctionであり、createPatchFunctionはパラメータを受け入れ、関数patchを返していることがわかります.patch関数は6つのパラメータを受け入れます.
  • oldVnode:古い仮想ノードまたは古いリアルdomノード
  • vnode:新しい仮想ノード
  • hydrating:本物のdomと混合するかどうか
  • removeOnly:コンポーネント
  • 用特殊flag
  • parentElm:親ノード
  • refElm:新しいノードがrefElmより前の
  • に挿入されます.
    patch関数の主な考え方:
  • vnodeが存在しないがoldVnodeが存在する場合、古いnodeを除去することを示す場合、
  • を破棄するためにinvokeDestroyHook(oldVnode)を呼び出す.
  • oldVnodeが存在しないがvnodeが存在する場合、新しいノードを作成することを示す場合、createElmを呼び出して新しいノード
  • を作成する.
  • vnodeとoldVnodeの両方が存在する場合:
  • oldVnodeとVnodeが同一ノードである場合patchVnode処理を呼び出して両ノードの相違を比較する
  • .
  • vnodeとoldVnodeが同一ノードでない場合、oldVnodeが真のDOMノードまたはhydratingがtrueに設定されている場合、hydrate関数で仮想DOMと真のDOMをマッピングし、oldVnodeを対応する仮想domに設定してoldVnodeを見つける必要がある.Elmの親ノードは、vnodeに基づいて実際のdomノードを作成し、その親ノードに挿入するoldVnodeである.Elmの位置

  • patchVnode関数の主な考え方:
  • vnode==oldVnodeの場合は直接戻り、何も実行しない
  • oldVnodeとvnodeが共に静的ノードであり、同じkeyを有する場合、vnodeがクローンノードまたはv-once指令制御ノードである場合、oldVnodeのみが必要となる.ElmとoldVnode.childはvnodeにコピーされ、他の操作も必要ありません
  • vnodeがtextノードでない場合
  • oldVnodeとvnodeの両方にサブノードがあり、サブノードが等しくない場合、updateChildrenを呼び出して更新サブノード操作
  • を実行する.
  • oldVnodeにサブノードがなく、vnodeにサブノードがある場合、作成ノード
  • oldVnodeにはサブノードがあり、vnodeにはサブノードがないので、古いノード
  • を除去する.
  • oldVnodeがtextノードである場合、テキストノード
  • が除去される.
  • vnodeがtextノードであるノードテキスト内容
  • を設定する.
    updateChildren関数実装機能:
  • は、oldVnodeとvnodeのそれぞれの最初のノードと最後のノードを取得し、oldStartVnode、oldEndVnode、newStartVnode、newEndVnode、oldStartIdx、newStartIdx、oldEndIdx、newEndIdxにそれぞれoldVnodeとvnodeのサブノードの下付き、最後のサブノードの下付きを割り当て、whileサイクルで比較を実行します.さらに、oldStartIdxおよびnewStartIdxを、oldStartIdxがoldEndIdxより大きいか、newStartIdxがnewEndIdx
  • より大きいまで移動する
  • oldStartVnodeがなければoldStartIdxを1加算し、oldStartVnodeを再求め、次のサイクル
  • に進む.
  • oldEndVnodeがなければoldEndIdxを1減算し、oldEndVnodeを再求めて次のサイクル
  • に進む.
  • oldStartVnodeとnewStartVnodeが同じタイプのノードである場合、patchVnodeを呼び出して2つのノードを比較し、oldStartIdxとnewStartIdxの両方に1を加え、同時に開始ノードも対応する下付きノード
  • を更新する
  • oldEndVnodeとnewEndVnodeが同じタイプのノードである場合、patchVnodeを呼び出して2つのノードを比較し、oldEndIdxとnewEndIdxの両方から1を減算するとともに、開始ノードも対応する下付きノード
  • を更新する
  • oldStartVnodeとnewEndVnodeが同じタイプのノードである場合、patchVnodeを呼び出して2つのノードを比較し、removeOnlyがfalseである場合、oldStartVnodeを比較することができる.ElmはoldEndVnodeに移動する.Elmの後、oldStartIdxに1を加算し、newEndIdxから1を減算するとともに、対応するノードが最新のノードである
  • を更新する.
  • oldEndVnodeとnewStartVnodeが同じタイプのノードである場合、patchVnodeを呼び出して2つのノードを比較し、removeOnlyがfalseである場合、oldEndVnodeを比較することができる.ElmはoldStartVnodeに移動します.Elmの前に、oldEndIdxを1に減らし、newStartIdxを1にし、対応するノードを最新のノード
  • に更新する.
  • 上記の条件が一致しない場合、oldVnodeのvnodeと同じkeyを持つノードを検索し、検索結果をelmToMoveに割り当てます.
  • 同じkeyのノードが見つからない場合は、新しく作成されたノード
  • であることを示す.
  • 見つかったら、この2つのノードが同じタイプのノードであるか否かを判断する
  • 同じタイプでpatchVnodeを呼び出すと、対応する下付きのoldVnodeをundefinedに設定し、removeOnlyがfalseであればelmToMoveとする.ElmをoldStartVnodeに挿入します.Elmの前に、newStartIdxに1を加え、newStartVnodeを次のノード
  • に設定する.
  • 見つからない場合は新しいノードを直接作成し、newStartVnode=newCh[++newStartIdx]-
  • を実行します.

  • サイクル終了後、oldStartIdx>oldEndIdxの場合、vnodeの間にループがないノードを新しいDOMに
  • 追加する.
  • newStartIdx>newEndIdxの場合、oldVnodeに遍歴するノードをDOMから
  • 削除する.
    これで、vueでVCOMからリアルDOMを実現するための基本的な説明が完了し、ライフサイクルについては、ここでは言及せず、対応する場所にライフサイクルコールバックを加えればOKとなる
    最終成果
    vnodeを実現した後に、自分で1つの比較的に簡単なdiff-renderのクラスを編纂して、基本原理と上で述べた差は多くなくて、実現効果も多くなくて、ただ新しい要素を追加して削除する時に再び同級の後ろの兄弟node、住所をレンダリングしますhttps://github.com/markdown365/markdown365-parserあ、興味のある方はダウンロードして効果を見ることができます.最后に本文を书く时にVue原理解析のVirtual Dom部分の内容を参考にして、しかもこの文章は非常に详しくて、同じく推荐して见に行きます
    レベルが限られているので、文章の中には正しくないところがありますので、許してください.ありがとうございます.