Vue 가상 Dom 에서 실제 Dom 으로 의 전환

나무 구조의 자바 script 대상 이 하나 더 있 으 면 우리 가 해 야 할 일 은 이 나무 가 실제 돔 나무 와 매 핑 관 계 를 형성 하 는 것 이다.우 리 는 먼저 이전의 mount Componnet 방법 을 돌 이 켜 보 자.

export function mountComponent(vm, el) {
  vm.$el = el
  ...
  callHook(vm, 'beforeMount')
  ...
  const updateComponent = function () {
    vm._update(vm._render())
  }
  ...
}
우 리 는 이미 vm 를 집행 했다.render 방법 은 VNode 를 가 져 왔 습 니 다.이 제 는 vm. 에 매개 변수 로 전달 합 니 다.update 방법 및 실행.vm._update 이 방법의 역할 은 VNode 를 실제 Dom 으로 바 꾸 는 것 입 니 다.그러나 두 가지 실행 시기 가 있 습 니 다.
첫 번 째 렌 더 링
new Vue 를 실행 할 때 이때 가 첫 렌 더 링 입 니 다.들 어 오 는 Vnode 대상 을 실제 Dom 으로 표시 합 니 다.
페이지 업데이트
데이터 변 화 는 페이지 에 변 화 를 일 으 킬 수 있 습 니 다.이것 도 vue 의 가장 독특한 특성 중 하나 입 니 다.데이터 가 바 뀌 기 전과 그 후에 두 개의 VNode 를 생 성하 여 비교 하 는 것 입 니 다.낡은 VNode 에서 가장 작은 변경 을 해서 페이지 를 렌 더 링 하 는 방법 은 매우 복잡 합 니 다.데이터 응답 식 이 어떻게 된 일 인지 먼저 말 하기 전에 diff 를 vue 의 전체 절 차 를 이해 하 는 데 좋 지 않 습 니 다.그래서 이 장 분석 이 처음으로 렌 더 링 된 후에 다음 장 은 데이터 응답 식 이 고 그 다음 이 diff 비교 입 니 다.
vm 부터 보 겠 습 니 다.update 방법의 정의:

Vue.prototype._update = function(vnode) {
  ...     
  vm.$el = vm.__patch__(vm.$el, vnode)  //      vm.$el
  ...
}
여기 vm.e l 은 이전에=m o u n t C o m p o n e n t==방법 에 마 운 트 된 것 입 니 다.진실 한==D o m==요소 입 니 다.첫 번 째 렌 더 링 은 v m 로 전 송 됩 니 다.el 은 이전에=mount Component==방법 에 마 운 트 되 었 습 니 다.진실 한=Dom==요소 입 니 다.첫 번 째 렌 더 링 은 vm.el 에 전 송 됩 니 다.첫 번 째 렌 더 링 은 vm.el 및 받 은 VNode 로 전 송 됩 니 다.따라서 vm.patch 정 의 를 보십시오.

Vue.prototype.__patch__ = createPatchFunction({ nodeOps, modules }) 
patch 는 createPatch Function 방법 내부 에서 되 돌아 오 는 방법 입 니 다.대상 을 받 아들 입 니 다.
nodeOps 속성:네 이 티 브 Dom 을 조작 하 는 방법 들 의 집합 을 봉 했 습 니 다.예 를 들 어 생 성,삽입,제거 등 우리 가 사용 하 는 곳 에 가서 어떻게 자세히 설명 합 니까?
modules 속성:실제 Dom 을 만 드 는 데 도 class/attrs/style 등 속성 을 생 성 해 야 합 니 다.modules 는 하나의 배열 집합 입 니 다.배열 의 모든 항목 은 이러한 속성 에 대응 하 는 갈고리 방법 입 니 다.이러한 속성의 생 성,업데이트,소각 등 은 모두 해당 하 는 갈고리 방법 이 있 습 니 다.어느 순간 어떤 일 을 해 야 할 때 대응 하 는 갈 고 리 를 집행 하면 된다.예 를 들 어 create 라 는 갈고리 방법 이 있 습 니 다.예 를 들 어 이 create 갈 고 리 를 하나의 배열 에 수집 하려 면 실제 Dom 에서 이 속성 을 만들어 야 할 때 배열 의 모든 항목 을 순서대로 실행 하 는 것 입 니 다.즉,순서대로 만 드 는 것 입 니 다.
PS:여기 modules 속성 에 있 는 갈고리 방법 은 플랫폼 을 구분 하 는 것 입 니 다.웹,weex 와 SSR 은 VNode 를 호출 하 는 방법 이 다 르 기 때문에 vue 는 여기 서 함수 코 리 화 라 는 소 리 를 사 용 했 습 니 다.createPatch Function 에서 플랫폼 의 차 이 를 평평 하 게 만 들 고 patch 방법 은 신 구 node 만 받 으 면 됩 니 다.
돔 생 성
여기 서 한 마디 만 기억 하면 됩 니 다.VNode 가 어떤 유형의 노드 든 세 가지 유형의 노드 만 Dom 에 생 성 되 고 삽 입 됩 니 다.요소 노드,주석 노드,텍스트 노드 입 니 다.
이 어 createPatch Function 이 어떤 방법 으로 돌아 가 는 지 살 펴 보 겠 습 니 다.

export function createPatchFunction(backend) {
  ...
  const { modules, nodeOps } = backend  //         
  
  return function (oldVnode, vnode) {  //     vnode
    ...
    const isRealElement = isDef(oldVnode.nodeType) //      Dom
    if(isRealElement) {  // $el   Dom
      oldVnode = emptyNodeAt(oldVnode)  //   VNode      
    }
    ...
  }
}
첫 번 째 렌 더 링 시 oldVnode 가 없습니다.oldVnode 는$el 입 니 다.진실 한 dom 입 니 다.empty NodeAt(odVnode)방법 으로 포장 합 니 다.

function emptyNodeAt(elm) {
  return new VNode(
    nodeOps.tagName(elm).toLowerCase(), //   tag  
    {},  //   data
    [],   //   children
    undefined,  //  text
    elm  //   dom    elm  
  )
}

    :
{
  tag: 'div',
  elm: '<div id="app"></div>' //   dom
}

-------------------------------------------------------

nodeOps:
export function tagName (node) {  //         
  return node.tagName  
}
들 어 오 는==$el==속성 을 VNode 형식 으로 변환 한 후 계속 진행 합 니 다:

export function createPatchFunction(backend) { 
  ...
  
  return function (oldVnode, vnode) {  //     vnode
  
    const insertedVnodeQueue = []
    ...
    const oldElm = oldVnode.elm  //      Dom <div id='app'></div>
    const parentElm = nodeOps.parentNode(oldElm)  //       <body></body>
  	
    createElm(  //     Dom
      vnode, //      
      insertedVnodeQueue,  //    
      parentElm,  // <body></body>
      nodeOps.nextSibling(oldElm)  //      
    )
    
    return vnode.elm //     Dom  vm.$el
  }
}
                                              
------------------------------------------------------

nodeOps:
export function parentNode (node) {  //      
  return node.parentNode 
}

export function nextSibling(node) {  //        
  return node.nextSibing  
}
createElm 방법 은 실제 Dom 을 생 성하 기 시 작 했 습 니 다.VNode 가 실제 Dom 을 생 성 하 는 방식 은 요소 노드 와 구성 요소 두 가지 방식 으로 나 뉘 기 때문에 우 리 는 지난 장 에서 생 성 된 VNode 를 사용 하여 각각 설명 합 니 다.
1.원소 노드 생 성 돔

{  //     VNode
  tag: 'div',
  children: [{
      tag: 'h1',
      children: [
        {text: 'title h1'}
      ]
    }, {
      tag: 'h2',
      children: [
        {text: 'title h2'}
      ]
    }, {
      tag: 'h3',
      children: [
        {text: 'title h3'}
      ]
    }
  ]
}
여러분 은 먼저 이 흐름 도 를 보고 인상 을 남기 면 됩 니 다.그 다음 에 구체 적 으로 실현 할 때 생각 이 많이 뚜렷 해 집 니 다.(여기 서 먼저 인터넷 의 그림 을 빌려 씁 니 다)
在这里插入图片描述
Dom 을 시작 하여 정의 보기:

function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) { 
  ...
  const children = vnode.children  // [VNode, VNode, VNode]
  const tag = vnode.tag  // div
  
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return  //          true,    ,    createComponent
  }
  
  if(isDef(tag)) {  //     
    vnode.elm = nodeOps.createElement(tag)  //      
    createChildren(vnode, children, insertedVnodeQueue)  //      
    insert(parentElm, vnode.elm, refElm)  //   
    
  } else if(isTrue(vnode.isComment)) {  //     
    vnode.elm = nodeOps.createComment(vnode.text)  //       
    insert(parentElm, vnode.elm, refElm); //       
    
  } else {  //     
    vnode.elm = nodeOps.createTextNode(vnode.text)  //       
    insert(parentElm, vnode.elm, refElm)  //       
  }
  
  ...
}

------------------------------------------------------------------

nodeOps:
export function createElement(tagName) {  //     
  return document.createElement(tagName)
}

export function createComment(text) {  //      
  return document.createComment(text)
}

export function createTextNode(text) {  //       
  return document.createTextNode(text)
}

function insert (parent, elm, ref) {  //  dom  
  if (isDef(parent)) {  //     
    if (isDef(ref)) { //      
      if (ref.parentNode === parent) {  //                 
        nodeOps.insertBefore(parent, elm, ref)  //               elm
      }
    } else {
      nodeOps.appendChild(parent, elm)  //    elm parent 
    }
  }  //           
}
           ,         。
요소 노드 인지,주석 노드,텍스트 노드 인지 순서대로 판단 하고 각각 만 든 다음 에 부모 노드 에 삽입 합 니 다.여 기 는 주로 요소 노드 를 만 드 는 것 을 소개 합 니 다.다른 두 개 는 복잡 한 논리 가 없습니다.다음 을 살 펴 보 겠 습 니 다:create Child 방법 정의:

function createChild(vnode, children, insertedVnodeQueue) {
  if(Array.isArray(children)) {  //    
    for(let i = 0; i < children.length; ++i) {  //   vnode   
      createElm(  //     
        children[i], 
        insertedVnodeQueue, 
        vnode.elm, 
        null, 
        true, //        
        children, 
        i
      )
    }
  } else if(isPrimitive(vnode.text)) {  //typeof string/number/symbol/boolean  
    nodeOps.appendChild(  //          
      vnode.elm, 
      nodeOps.createTextNode(String(vnode.text))
    )
  }
}

-------------------------------------------------------------------------------

nodeOps:
export default appendChild(node, child) {  //      
  node.appendChild(child)
}
하위 노드 를 만 들 기 시작 합 니 다.VNode 의 모든 항목 을 옮 겨 다 니 며,모든 항목 은 이전의 createElm 방법 으로 Dom 을 만 듭 니 다.만약 에 한 항목 이 또 배열 이 라면 create Child 를 계속 호출 하여 특정한 하위 노드 를 만 듭 니 다.배열 이 아니라면 텍스트 노드 를 만 들 고 부모 노드 에 추가 합 니 다.이렇게 재 귀적 인 형식 으로 포 함 된 VNode 를 모두 실제 Dom 으로 만 듭 니 다.
절차 도 를 한 번 보면 많은 의혹 을 줄 일 수 있 을 것 이다.
在这里插入图片描述
쉽게 말 하면 안에서 밖으로 진실 한 Dom 을 만 든 다음 에 부모 노드 에 삽입 하 는 것 입 니 다.마지막 으로 만 든 Dom 을 body 에 삽입 하여 만 드 는 과정 을 완성 합 니 다.요소 노드 의 생 성 은 비교적 간단 합 니 다.다음은 구성 요소 식 이 어떻게 만 드 는 지 보 겠 습 니 다.
구성 요소 VNode 생 성 돔

{  //   VNode
  tag: 'vue-component-1-app',
  context: {...},
  componentOptions: {
    Ctor: function(){...},  //        
    propsData: undefined,
    children: undefined,
    tag: undefined
  },
  data: {
    on: undefined,  //     
    hook: {  //     
      init: function(){...},
      insert: function(){...},
      prepatch: function(){...},
      destroy: function(){...}
    }
  }
}

-------------------------------------------

<template>  // app     
  <div>app text</div>
</template>
먼저 간단 한 절차 도 를 보고 영향 을 남기 면 됩 니 다.정리 후의 논리 적 순 서 를 편리 하 게 정리 할 수 있 습 니 다.
在这里插入图片描述
이전 구성 요소 로 VNode 를 생 성 합 니 다.createElm 에서 구성 요소 Dom 분기 논 리 를 만 드 는 것 이 어떤 지 보 세 요.

function createElm(vnode, insertedVnodeQueue, parentElm, refElm) { 
  ...
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { //     
    return  
  }
  ...
createComponent 방법 을 실행 합 니 다.요소 노드 라면 아무것도 되 돌려 주지 않 기 때문에 undefined 입 니 다.다음 에 원 노드 를 만 드 는 논 리 를 계속 진행 할 것 입 니 다.현재 구성 요소 입 니 다.createComponent 의 실현 을 보 겠 습 니 다.

function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if(isDef(i)) {
    if(isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode)  //   init  
    }
    ...
  }
}
우선 구성 요소 의 vnode.data 를 i 에 할당 합 니 다.이 속성 이 있 는 지 없 는 지 는 구성 요소 vnode 인지 판단 할 수 있 습 니 다.다음 if(isDef(i=i.hook)&&isDef(i=i.init)집합 판단 과 할당 을 하나 로 합 쳤 습 니 다.if 내 i(vnode)는 실 행 된 구성 요소 init(vnode)방법 입 니 다.이 럴 때 구성 요소 의 init 갈고리 방법 이 무엇 을 했 는 지 살 펴 보 겠 습 니 다.

import activeInstance  //     

const init = vnode => {
  const child = vnode.componentInstance = 
    createComponentInstanceForVnode(vnode, activeInstance)
  ...
}
activeInstance 는 전역 적 인 변수 입 니 다.update 방법 에서 현재 인 스 턴 스 를 할당 하고 현재 인 스 턴 스 를 patch 로 하 는 과정 에서 구성 요소 의 부모 인 스 턴 스 로 들 어 와 하위 구성 요소 의 initLifecycle 에서 구성 요소 관 계 를 구축 합 니 다.createComponent InsanceForVnode 에서 실 행 된 결 과 를 vnode.componentInstance 에 할당 하 였 으 므 로 되 돌아 오 는 결과 가 무엇 인지 보십시오.

export  createComponentInstanceForVnode(vnode, parent) {  // parent     activeInstance
  const options = {  //    options
    _isComponent: true,  //        ,     
    _parentVnode: vnode, 
    parent  //      vm  ,    initLifecycle        
  }
  
  return new vnode.componentOptions.Ctor(options)  //            Ctor
}
재 구성 요소 의 init 방법 에서 먼저 craeete Component Instance ForVnode 방법 을 실행 합 니 다.이 방법 은 내부 에서 하위 구성 요소 의 구조 함 수 를 예화 합 니 다.하위 구성 요소 의 구조 함 수 는 기본 Vue 의 모든 능력 을 계승 하기 때 문 입 니 다.이 때 는 new Vue({...})를 실행 하 는 것 과 같 고 그 다음 에 실 행 됩 니 다=init 방법 은 일련의 하위 구성 요소 의 초기 화 논 리 를 진행 하여 로 돌아 갑 니 다.init==방법 내 에서 그들 사이 에는 아직 다른 점 이 있 기 때문이다.

Vue.prototype._init = function(options) {
  if(options && options._isComponent) {  //      options,_isComponent         
    initInternalComponent(this, options)  //                 
  }
  
  initLifecycle(vm)  //       
  ...
  callHook(vm, 'created')
  
  if (vm.$options.el) { //      el   ,         
    vm.$mount(vm.$options.el)
  }
}

----------------------------------------------------------------------------------------

function initInternalComponent(vm, options) {  //      options
  const opts = vm.$options = Object.create(vm.constructor.options)
  opts.parent = options.parent  //   init  ,    activeInstance
  opts._parentVnode = options._parentVnode  //   init  ,   vnode 
  ...
}
앞 에 서 는 잘 실행 되 었 지만 마지막 으로 엘 속성 이 없어 서 마 운 트 하지 않 았 습 니 다.createComponent Instance ForVnode 방법 이 실행 되 었 습 니 다.이 때 우 리 는 구성 요소 의 init 방법 으로 돌아 가 남 은 논 리 를 보완 합 니 다.

const init = vnode => {
  const child = vnode.componentInstance = //        
    createComponentInstanceForVnode(vnode, activeInstance)
    
  child.$mount(undefined)  //        
}
init 방법 에서 이 구성 요 소 를 수 동 으로 마 운 트 한 다음 에 구성 요 소 를 실행 합 니 다.=render()=방법 은 구성 요소 내 요소 노드 VNode 를 얻 은 다음 vm. 를 실행 합 니 다.update(),구성 요 소 를 실행 하 는 patch 방법 입 니 다.$mount 방법 은 undefined,oldVnode 도 undefinned 로 들 어 오기 때문에 를 실행 합 니 다.patch_ 내 논리:

return function patch(oldVnode, vnode) {
  ...
  if (isUndef(oldVnode)) {
    createElm(vnode, insertedVnodeQueue)
  }
  ...
}
이번 createElm 을 실행 하 는 것 은 세 번 째 인자 부모 노드 에 들 어 오지 않 았 습 니 다.그 구성 요소 가 만 든 Dom 을 어디 에 두 었 습 니까?부모 노드 페이지 가 없 으 면 Dom 을 생 성 합 니 다.이 때 는 구성 요소 의 patch 를 실행 하기 때문에 매개 변수 vnode 는 구성 요소 내 요소 노드 의 vnode 입 니 다.

<template> // app     
  <div>app text</div>
</template>

-------------------------

{  // app   vnode
  tag: 'div',
  children: [
    {text: app text}
  ],
  parent: {  //    _init   initLifecycle     
    tag: 'vue-component-1-app',
    componentOptions: {...}
  }
}
이 때 는 구성 요소 가 아 닌 것 이 분명 합 니 다.구성 요소 라 도 괜 찮 습 니 다.createComponent 가 구성 요 소 를 만 드 는 논 리 를 한 번 실행 하 는 것 이 좋 습 니 다.구성 요소 가 요소 노드 로 구성 되 어 있 기 때 문 입 니 다.이 럴 때 우 리 는 요소 노드 를 만 드 는 논 리 를 한 번 실행 합 니 다.세 번 째 매개 변수 인 부모 노드 가 없 기 때문에 구성 요소 의 Dom 은 만 들 었 지만 여기에 삽입 되 지 않 습 니 다.이 때 구성 요소 의 init 가 완성 되 었 지만 구성 요소 의 createComponent 방법 이 완성 되 지 않 았 습 니 다.논 리 를 보완 합 니 다.

function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data;
  if (isDef(i)) {
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode)  // init    
    }
    
    if (isDef(vnode.componentInstance)) {  //     init    
    
      initComponent(vnode)  //     dom vnode.elm
      
      insert(parentElm, vnode.elm, refElm)  //   Dom     
      ...
      return true  //      return
    }
  }
}

-----------------------------------------------------------------------

function initComponent(vnode) {
  ...
  vnode.elm = vnode.componentInstance.$el  // __patch__     dom
  ...
}
아무리 깊 은 구성 요소 라 도 구성 요 소 를 만나면 init 를 실행 합 니 다.init 의 patch 과정 에서 새 겨 진 구성 요 소 를 만 나 면 새 겨 진 구성 요소 의 init 를 실행 합 니 다.새 겨 진 구성 요 소 는 를 완성 합 니 다.patch__그 다음 에 진짜 Dom 을 부모 노드 에 삽입 한 다음 에 외부 구성 요소 의 patch 를 실행 하고 아버지 몇 시 에 삽입 한 다음 에 body 에 삽입 하여 구성 요소 의 생 성 과정 을 완성 합 니 다.어쨌든 내부 와 외부 과정 입 니 다.
고 개 를 돌려 이 그림 을 보면 이해 하기 쉬 울 거 라 고 믿 습 니 다.
在这里插入图片描述
이 장의 최초의 mountComponent 이후 의 논 리 를 보완 합 니 다.

export function mountComponent(vm, el) {
  ...
  const updateComponent = () => {
    vm._update(vm._render())
  }
  
  new Watcher(vm, updateComponent, noop, {
    before() {
      if(vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }   
  }, true)
  
  ...
  callHook(vm, 'mounted')
  
  return vm
}
다음은 updateComponent 를 하나의 Watcher 클래스 에 전송 할 것 입 니 다.이 종 류 는 무엇 입 니까?다음 장 에서 소개 하 겠 습 니 다.다음은 mounted 갈고리 방법 을 실행 합 니 다.이로써 new vue 의 전체 절 차 는 모두 끝났다.new Vue 에서 시작 하 는 순 서 를 되 짚 어 보 겠 습 니 다.

new Vue ==> vm._init() ==> vm.$mount(el) ==> vm._render()  ==> vm.update(vnode) 
마지막 으로 우 리 는 한 가지 문제 로 이 장의 내용 을 끝 냈 다.
부자 두 구성 요 소 는 동시에 beforeCreate,created,beforeMounte,mounted 네 개의 갈 고 리 를 정 의 했 습 니 다.그들의 실행 순 서 는 어떻게 됩 니까?
해답:
우선 부모 구성 요소 의 초기 화 과정 을 실행 하기 때문에 beforeCreate,created 를 순서대로 실행 합 니 다.마 운 트 를 실행 하기 전에 beforeMount 갈 고 리 를 실행 하지만 실제 dom 을 생 성 하 는patch__이 과정 에서 하위 구성 요 소 를 만 나 면 하위 구성 요 소 를 실행 하 는 초기 화 갈고리 beforeCreate,created 로 전 환 됩 니 다.하위 구성 요 소 는 마 운 트 하기 전에 beforeMounte 를 실행 하고 하위 구성 요소 의 Dom 생 성 을 완료 한 후 mounted 를 실행 합 니 다.이 부모 구성 요소 의 patch 과정 이 완성 되 었 습 니 다.마지막 으로 부모 구성 요 소 를 실행 하 는 mounted 갈고리 입 니 다.이것 이 바로 실행 순서 입 니 다.다음 과 같다.

parent beforeCreate
parent created
parent beforeMounte
    child beforeCreate
    child created
    child beforeMounte
    child mounted
parent mounted
Vue 가상 Dom 에서 실제 Dom 으로 의 전환 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 Vue 가상 Dom 에서 실제 Dom 까지 의 내용 은 저희 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 부탁드립니다!

좋은 웹페이지 즐겨찾기