Vue 가상 Dom 에서 실제 Dom 으로 의 전환
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 까지 의 내용 은 저희 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 부탁드립니다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Vue Render 함수로 DOM 노드 코드 인스턴스 만들기render에서createElement 함수를 사용하여 DOM 노드를 만드는 것은 직관적이지 않지만 일부 독립 구성 요소의 디자인에서 특수한 수요를 충족시킬 수 있습니다.간단한 렌더링 예는 다음과 같습니다. 또한 v...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.