Vue 3.0 渲染机制和优化
为了学习如何更好地使用 Vue,不需要阅读本页,但是它提供了更多信息,如果你想知道渲染在背后是如何工作的。
#虚拟 DOM
现在我们知道了侦听器是如何更新组件的,你可能会问这些更改最终是如何应用到 DOM 中的!也许你以前听说过虚拟 DOM,包括 Vue 在内的许多框架都使用这种方式来确保我们的接口能够有效地反映我们在 JavaScript 中更新的更改
完整实例:
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 3.0 渲染机制和优化</title>
<script src="https://unpkg.com/vue@3.0.0/dist/vue.global.js"></script>
<style>
body {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-family: 'Source Sans Pro', sans-serif;
color: #4f5959;
}
* { box-sizing: border-box; }
.container {
position: relative;
margin: 0 80px;
}
.text {
position: absolute;
width: 300px;
font-size: 18px;
padding: 30px;
box-shadow: -5px 18px 40px -12px rgba(0, 0, 0, 0.4);
transform: perspective(800px) rotateY(30deg) rotateX(15deg) rotate(-2deg) scaleX(0.95);
backface-visibility: hidden;
}
.explainer, .vdom, .vdomtext, .gear, .code {
visibility: hidden;
}
.explainer div {
transform-origin: 50% 50%;
}
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap;
}
h2 {
color: #273849;
text-align: center;
margin-top: 40px;
font-size: 28px;
}
h3, .code, pre, code {
font-family: 'Roboto Mono', monospace;
font-size: 15px;
}
h3 {
color: rgb(66, 185, 131);
}
.domtext {
margin-left: 80px;
}
.vdomtext {
position: absolute;
top: 60px;
margin-left: 400px;
}
.explainer {
margin-bottom: 40px;
}
.active {
border: 1px dashed #db443c;
background: #c84f491f;
color: #c40e05;
}
.gear {
position: absolute;
padding: 5px;
top: 150px;
left: 260px;
}
.midgear {
fill: #42b983;
}
.geararrows {
fill: #a7d6c0;
}
//code
.code, pre, code {
font-family: 'Roboto Mono', monospace;
font-size: 15px;
}
.string {
color: #42b983;
}
.keyword {
color: #d63200;
}
.func {
color: #2973b7;
}
.sp {
margin-left: 20px;
}
.comment {
color: #848484;
}
.code {
position: absolute;
top: 80px;
margin-left: 750px;
line-height: 1.3;
}
</style>
</head>
<body>
.container
.explainer
%h2.explain1 Here is the DOM
%h2.explain2.visually-hidden We make a copy in JavaScript called the Virtual DOM
%h2.explain3.visually-hidden We do this because touching the DOM with JavaScript is computationally expensive.
%h2.explain4.visually-hidden While performing updates in JavaScript is cheap,
%h2.explain5.visually-hidden Finding the required DOM nodes and updating them with JS is expensive
%h2.explain6.visually-hidden So we batch calls, and change the DOM all at once.
%h2.explain7.visually-hidden The virtual DOM in is a lightweight JavaScript object, created by this render function
%h2.explain8.visually-hidden It takes three arguments: the element, an object with data, props, attrs and more, and an array
%h2.explain9.visually-hidden The array is where we pass in the children, which have all these arguments too
%h2.explain10.visually-hidden Here’s the text in the div
%h2.explain11.visually-hidden And it’s child, the ul
%h2.explain12.visually-hidden Now in turn it’s children, the lis
%h2.explain13.visually-hidden If we need to update the list items, we do so in javascript
%h2.explain14.visually-hidden And only then do we update the actual DOM
%h2.explain15.visually-hidden The Virtual DOM allows us to make performant update our UIs! ✨
%h3.domtext The DOM
.text.dom
%span This is a webpage. This webpage has some text in a p tag. Below is a list:
%ul
%li.firstli.dom-last-li Thing
%li.dom-last-li Another thing
%span It's fun to make websites with Vue you know? Some more text.
%h3.vdomtext The Virtual DOM
.text.vdom
%span.last-text This is a webpage. This webpage has some text in a p tag. Below is a list:
%ul.last-ul
%li.firstli.last-li.update-li Thing
%li.last-li.update-li Another thing
%span.last-text It's fun to make websites with Vue you know? Some more text.
%svg.gear{:height => "75", :viewbox => "0 0 90 90", :width => "75", :xmlns => "http://www.w3.org/2000/svg"}
%g
%g.geararrows
%path{:d => "M45.5,77.8c-14.8,0-27.8-10-31.6-24.3c-0.1-0.5,0.2-1.1,0.7-1.2c0.5-0.1,1.1,0.2,1.2,0.7c3.6,13.4,15.8,22.8,29.7,22.8 c17,0,30.8-13.8,30.8-30.8c0-0.6,0.4-1,1-1s1,0.4,1,1C78.2,63.1,63.5,77.8,45.5,77.8z"}
%path{:d => "M13.7,46c-0.6,0-1-0.4-1-1c0-18.1,14.7-32.8,32.8-32.8c13.6,0,26,8.6,30.7,21.4c0.2,0.5-0.1,1.1-0.6,1.3 c-0.5,0.2-1.1-0.1-1.3-0.6c-4.5-12-16-20.1-28.8-20.1c-17,0-30.8,13.8-30.8,30.8C14.7,45.6,14.3,46,13.7,46z"}
%path{:d => "M12.8,60c-0.1,0-0.2,0-0.3,0c-0.5-0.2-0.8-0.7-0.7-1.2l1.5-5.2c0.2-0.7,0.7-1.2,1.4-1.4c0.7-0.2,1.4,0,1.9,0.6l3.7,3.9 c0.4,0.4,0.4,1,0,1.4c-0.4,0.4-1,0.4-1.4,0l-3.7-3.9l-1.5,5.2C13.6,59.7,13.2,60,12.8,60z"}
%path{:d => "M75.5,35.9c-0.4,0-0.8-0.1-1.1-0.4l-4.4-3c-0.5-0.3-0.6-0.9-0.3-1.4c0.3-0.5,0.9-0.6,1.4-0.3l4.4,3l0.4-5.4 c0-0.6,0.5-1,1.1-0.9c0.6,0,1,0.5,0.9,1.1L77.5,34c-0.1,0.7-0.5,1.3-1.1,1.6C76.1,35.8,75.8,35.9,75.5,35.9z"}
%g.midgear
%path{:d => "M63.1,41.7c-0.5-2.6-1.5-5.1-3-7.2l2.6-2.6l-4.6-4.6l-2.6,2.6c-2.1-1.5-4.6-2.5-7.2-3v-3.7h-6.6v3.7c-2.6,0.5-5.1,1.5-7.2,3 l-2.6-2.6l-4.6,4.6l2.6,2.6c-1.5,2.1-2.5,4.6-3,7.2h-3.7v6.6h3.7c0.5,2.6,1.5,5.1,3,7.2l-2.6,2.6l4.6,4.6l2.6-2.6 c2.1,1.5,4.6,2.5,7.2,3v3.7h6.6v-3.7c2.6-0.5,5.1-1.5,7.2-3l2.6,2.6l4.6-4.6l-2.6-2.6c1.5-2.1,2.5-4.6,3-7.2h3.7v-6.6H63.1z M45,54 c-5,0-9-4-9-9s4-9,9-9s9,4.1,9,9S50,54,45,54z"}
%path{:d => "M49.3,67.9h-8.6v-3.9c-2.2-0.5-4.2-1.3-6.1-2.5l-2.8,2.8l-6.1-6.1l2.8-2.8c-1.2-1.9-2-3.9-2.5-6.1h-3.9v-8.6h3.9 c0.5-2.2,1.3-4.2,2.5-6.1l-2.8-2.8l6.1-6.1l2.8,2.8c1.9-1.2,3.9-2,6.1-2.5v-3.9h8.6v3.9c2.2,0.5,4.2,1.3,6.1,2.5l2.8-2.8l6.1,6.1 l-2.8,2.8c1.2,1.9,2,3.9,2.5,6.1h3.9v8.6h-3.9c-0.5,2.2-1.3,4.2-2.5,6.1l2.8,2.8l-6.1,6.1l-2.8-2.8c-1.9,1.2-3.9,2-6.1,2.5V67.9z M42.7,65.9h4.6v-3.6l0.8-0.1c2.5-0.4,4.8-1.4,6.8-2.8l0.7-0.5l2.5,2.5l3.2-3.2l-2.5-2.5l0.5-0.7c1.4-2.1,2.4-4.4,2.8-6.8l0.1-0.8 h3.6v-4.6h-3.6l-0.1-0.8c-0.4-2.5-1.4-4.8-2.8-6.8l-0.5-0.7l2.5-2.5l-3.2-3.2l-2.5,2.5l-0.7-0.5c-2.1-1.4-4.3-2.4-6.8-2.8l-0.8-0.1 v-3.6h-4.6v3.6l-0.8,0.1c-2.5,0.4-4.7,1.4-6.8,2.8l-0.7,0.5l-2.5-2.5l-3.2,3.2l2.5,2.5l-0.5,0.7c-1.4,2.1-2.4,4.3-2.8,6.8l-0.1,0.8 h-3.6v4.6h3.6l0.1,0.8c0.4,2.5,1.4,4.8,2.8,6.8l0.5,0.7l-2.5,2.5l3.2,3.2l2.5-2.5l0.7,0.5c2.1,1.4,4.3,2.4,6.8,2.8l0.8,0.1V65.9z M45,55c-5.5,0-10-4.5-10-10s4.5-10,10-10s10,4.5,10,10S50.5,55,45,55z M45,37c-4.4,0-8,3.6-8,8s3.6,8,8,8s8-3.6,8-8S49.4,37,45,37z"}
.code
%span.keyword export default function
= succeed "()" do
%span.func render
%br/
%span.sp.keyword return
(
%span.func> openBlock
(),
= succeed "(" do
%span.func createBlock
%br/
%span.fn-el.visually-hidden
%span.sp
= succeed "," do
%span.sp.string "div"
%span.comment //element
%br/
%span.fn-state.visually-hidden
%span.sp
= succeed "{}," do
%span.sp
%span.comment.sp //state, props, style, attrs
%br/
%span.fn-arr1.visually-hidden
%span.sp
= succeed "[...]" do
%span.sp
%span.comment //children
%br/
%span.fn-arr2.visually-hidden
%span.sp
= succeed "[" do
%span.sp
%br/
%span.last-text
%span.sp
%span.sp
%span.sp
= succeed "(" do
%span.func createTextVNode
= succeed ")," do
%span.string "This is a webpage...."
%br/
%span.last-ul
%span.sp
%span.sp
%span.sp
= succeed "(" do
%span.func createVNode
= succeed "," do
%span.string "ul"
%br/
%span.last-li
%span.sp
%span.sp
%span.sp
%span.sp
= succeed "(" do
%span.func createVNode
= succeed "," do
%span.string "li"
= succeed ")," do
%span.string
"
%span.update-li> Thing One
"
%br/
%span.last-li
%span.sp
%span.sp
%span.sp
%span.sp
= succeed "(" do
%span.func createVNode
= succeed "," do
%span.string "li"
= succeed ")" do
%span.string
"
%span.update-li> Another thing
"
%br/
%span.sp
%span.sp
= succeed "])," do
%span.sp
%br/
%span.last-text
%span.sp
%span.sp
%span.sp
= succeed "(" do
%span.func createTextVNode
= succeed ")" do
%span.string "It's fun to make websites..."
%br/
%span.sp
= succeed "]" do
%span.sp
%br/
= succeed "))" do
%span.sp
%br/
}
%br/
<script>
gsap.set('.explainer, .vdom, .vdomtext, .gear, .code', {
visibility: 'visible'
})
//-------------//
// helpers //
//-------------//
const hideShow = (el1, el2) => {
let elref1 = document.querySelector(el1)
elref1.classList.add('visually-hidden')
let elref2 = document.querySelector(el2)
elref2.classList.remove('visually-hidden')
}
const showElement = (el) => {
let elref = document.querySelector(el)
elref.classList.remove('visually-hidden')
}
const updateText = (el, text) => {
let elref = document.querySelectorAll(el)
elref.forEach(function(el) {
el.innerHTML = text
});
}
const animateHeading = (tl, el1, el2, delay1, delay2) => {
tl.to(`${el1} div`, {
opacity: 0,
scaleX: 0,
duration: 0.1,
stagger: 0.01,
ease: 'sine.in'
}, delay1)
tl.call(hideShow, [el1, el2])
tl.to(`${el2} div`, {
opacity: 1,
scaleX: 1,
duration: 0.4,
delay: delay2,
stagger: 0.02,
ease: 'sine'
})
return tl
}
//------------------------//
// scene one, plus text //
//------------------------//
const explainText = new SplitText('.explainer', { type: 'words, chars' });
gsap.set('.explainer h2 div', {
opacity: 0,
scaleX: 0
})
gsap.set('.vdom, .vdomtext, .gear, .code, .fn-el, .fn-state, .fn-arr1, .fn-arr2', {
opacity: 0
})
const scene1 = () => {
const tl1 = gsap.timeline({
delay: 0.7
})
tl1.to('.explain1 div', {
opacity: 1,
scaleX: 1,
duration: 0.4,
delay: 0.5,
stagger: 0.03,
ease: 'sine'
})
tl1.call(animateHeading, [tl1, '.explain1', '.explain2', '-=1.5', 0])
tl1.add('vdomcreate', '+=1')
tl1.to('.vdom', {
opacity: 1,
duration: 0.3,
ease: 'sine'
}, 'vdomcreate')
tl1.to('.vdom', {
duration: 1.25,
x: 350,
perspective: 1200,
rotateY: 20,
ease: 'sine'
}, 'vdomcreate')
tl1.to('.vdomtext', {
opacity: 1,
duration: 0.35,
ease: 'sine'
})
tl1.call(animateHeading, [tl1, '.explain2', '.explain3', '+=2', 2])
tl1.call(animateHeading, [tl1, '.explain3', '.explain4', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain4', '.explain5', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain5', '.explain6', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain6', '.explain7', '+=2', 2])
tl1.call(animateHeading, [tl1, '.explain7', '.explain8', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain8', '.explain9', '+=4', 0])
tl1.call(animateHeading, [tl1, '.explain9', '.explain10', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain10', '.explain11', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain11', '.explain12', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain12', '.explain13', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain13', '.explain14', '+=2', 0])
tl1.call(animateHeading, [tl1, '.explain14', '.explain15', '+=2', 0])
return tl1
}
//------------------//
// scene two //
//------------------//
const scene2 = () => {
const tl2 = gsap.timeline({
delay: 16,
ease: 'sine'
})
tl2.to('.vdom .firstli', {
background: '#eee',
duration: 0.4
})
tl2.call(updateText, ['.vdom .firstli', 'Thing One'])
tl2.to('.vdom .firstli', {
background: 'none',
ease: 'sine.easeIn',
delay: 0.2,
duration: 0.3
})
tl2.add('geardom', '+=3.5')
//gear stuff
tl2.to('.gear', {
opacity: 1,
duration: 0.4
}, 'geardom')
tl2.to('.midgear', {
rotation: -720,
duration: 1.5,
transformOrigin: '50% 50%'
}, 'geardom')
tl2.to('.geararrows', {
rotation: 720,
duration: 1.5,
transformOrigin: '50% 50%'
}, 'geardom')
//dom update
tl2.to('.dom .firstli', {
background: '#eee',
duration: 0.4
}, 'geardom')
tl2.call(updateText, ['.dom .firstli', 'Thing One'], 'geardom')
tl2.to('.dom .firstli', {
background: 'none',
ease: 'sine.easeIn',
delay: 0.2,
duration: 0.3
}, 'geardom+=0.5')
tl2.to('.gear', {
opacity: 0,
duration: 0.4,
ease: 'sine.easeIn',
}, 'geardom+=1.75')
return tl2
}
//------------------//
// scene three //
//------------------//
const scene3 = () => {
const tl3 = gsap.timeline({
delay: 29,
ease: 'sine'
})
tl3.to('.code', {
opacity: 1,
duration: 0.4
})
tl3.add('children', '+=5')
tl3.call(showElement, ['.fn-el'], 'children')
tl3.call(showElement, ['.fn-state'], 'children')
tl3.call(showElement, ['.fn-arr1'], 'children')
tl3.to('.fn-el', {
opacity: 1,
duration: 0.6
}, 'children+=0.2')
tl3.to('.fn-state', {
opacity: 1,
duration: 0.6
}, 'children+=0.7')
tl3.to('.fn-arr1', {
opacity: 1,
duration: 0.6
}, 'children+=1.2')
tl3.call(hideShow, ['.fn-arr1', '.fn-arr2'], 'children+=7')
tl3.to('.fn-arr2', {
opacity: 1,
duration: 0.6
}, 'children+=7')
return tl3
}
//------------------//
// scene four //
//------------------//
const scene4 = () => {
const tl4 = gsap.timeline({
delay: 45,
ease: 'sine'
})
tl4.add('ending')
tl4.to('.last-text', {
background: '#eee',
duration: 0.75
}, 'ending')
tl4.to('.last-text', {
background: 'none',
ease: 'sine.easeIn',
duration: 0.25
}, 'ending+=0.75')
tl4.to('.last-ul', {
background: '#eee',
duration: 0.75
}, 'ending+=3')
tl4.to('.last-ul', {
background: 'none',
ease: 'sine.easeIn',
duration: 0.25
}, 'ending+=3.75')
tl4.to('.last-li', {
background: '#eee',
duration: 0.75
}, 'ending+=6')
tl4.to('.last-li', {
background: 'none',
ease: 'sine.easeIn',
duration: 0.25
}, 'ending+=6.75')
//update the list items
tl4.to('.last-li', {
background: '#eee',
duration: 0.75
}, 'ending+=10')
tl4.call(updateText, ['.update-li', 'New List Items!'], 'ending+=10')
tl4.to('.last-li', {
background: 'none',
ease: 'sine.easeIn',
duration: 0.25
}, 'ending+=10.75')
//update the list items in the dom
tl4.to('.dom-last-li', {
background: '#eee',
duration: 0.75
}, 'ending+=14')
tl4.call(updateText, ['.dom-last-li', 'New List Items!'], 'ending+=14')
tl4.to('.dom-last-li', {
background: 'none',
ease: 'sine.easeIn',
duration: 0.25
}, 'ending+=14.75')
//gear stuff
tl4.to('.gear', {
opacity: 1,
duration: 0.4
}, 'ending+=14')
tl4.to('.midgear', {
rotation: -1440,
duration: 1.5,
transformOrigin: '50% 50%'
}, 'ending+=14')
tl4.to('.geararrows', {
rotation: 1440,
duration: 1.5,
transformOrigin: '50% 50%'
}, 'ending+=14')
tl4.to('.gear', {
opacity: 0,
duration: 0.4,
ease: 'sine.easeIn',
}, 'ending+=15.75')
return tl4
}
//------------------//
// master //
//------------------//
window.onload = () => {
const sceneOne = scene1()
const sceneTwo = scene2()
const sceneThree = scene3()
const sceneFour = scene4()
const master = gsap.timeline()
master.add('sceneOne')
master.add('sceneTwo')
master.add('sceneThree')
master.add('sceneFour')
};
</script>
</body>
</html>
可以使用本站在线JavaScript测试工具测试上述代码运行效果:http://www.phpcodeweb.com/runjs.html
我们用 JavaScript 复制了一个名为 Virtual Dom 的 DOM,我们这样做是因为用 JavaScript 接触 DOM 的计算成本很高。虽然用 JavaScript 执行更新很廉价,但是找到所需的 DOM 节点并用 JS 更新它们的成本很高。所以我们批处理调用,同时更改 DOM。
虚拟 DOM 是轻量级的 JavaScript 对象,由渲染函数创建。它包含三个参数:元素,带有数据的对象,prop,attr 以及更多,和一个数组。数组是我们传递子级的地方,子级也具有所有这些参数,然后它们可以具有子级,依此类推,直到我们构建完整的元素树为止。
如果需要更新列表项,可以使用前面提到的响应性在 JavaScript 中进行。然后,我们对 JavaScript 副本,虚拟 DOM 进行所有更改,并在此与实际 DOM 之间进行区分。只有这样,我们才能对已更改的内容进行更新。虚拟 DOM 允许我们对 UI 进行高效的更新!