Skip to content

Commit 7572069

Browse files
committed
✨feat: 添加14.3Transition组件的实现原理
1 parent 5e763fa commit 7572069

File tree

2 files changed

+171
-30
lines changed

2 files changed

+171
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Document</title>
8+
<style>
9+
.box {
10+
width: 100px;
11+
height: 100px;
12+
background-color: red;
13+
}
14+
15+
.enter-from {
16+
transform: translateX(200px);
17+
}
18+
19+
.enter-to {
20+
transform: translateX(0);
21+
}
22+
23+
.enter-active {
24+
transition: transform 1s ease-in-out;
25+
}
26+
27+
/* 初始状态 */
28+
.leave-from {
29+
transform: translateX(0);
30+
}
31+
32+
/* 结束状态 */
33+
.leave-to {
34+
transform: translateX(200px);
35+
}
36+
37+
/* 过渡过程 */
38+
.leave-active {
39+
transition: transform 2s ease-out;
40+
}
41+
</style>
42+
</head>
43+
44+
<body>
45+
<script> // 创建 class 为 box 的 DOM 元素
46+
// 创建 class 为 box 的 DOM 元素
47+
const el = document.createElement('div')
48+
el.classList.add('box')
49+
50+
// 在 DOM 元素被添加到页面之前,将初始状态和运动过程定义到元素上
51+
el.classList.add('enter-from') // 初始状态
52+
el.classList.add('enter-active') // 运动过程
53+
54+
// 将元素添加到页面
55+
document.body.appendChild(el)
56+
57+
// 嵌套调用 requestAnimationFrame
58+
requestAnimationFrame(() => {
59+
requestAnimationFrame(() => {
60+
el.classList.remove('enter-from') // 移除 enter-from
61+
el.classList.add('enter-to') // 添加 enter-to
62+
63+
// 监听 transitionend 事件完成收尾工作
64+
el.addEventListener('transitionend', () => {
65+
el.classList.remove('enter-to')
66+
el.classList.remove('enter-active')
67+
})
68+
})
69+
})
70+
el.addEventListener('click', () => {
71+
// 将卸载动作封装到 performRemove 函数中
72+
const performRemove = () => el.parentNode.removeChild(el)
73+
74+
// 设置初始状态:添加 leave-from 和 leave-active 类
75+
el.classList.add('leave-from')
76+
el.classList.add('leave-active')
77+
78+
// 强制 reflow:使初始状态生效
79+
document.body.offsetHeight
80+
81+
// 在下一帧切换状态
82+
requestAnimationFrame(() => {
83+
requestAnimationFrame(() => {
84+
// 切换到结束状态
85+
el.classList.remove('leave-from')
86+
el.classList.add('leave-to')
87+
88+
// 监听 transitionend 事件做收尾工作
89+
el.addEventListener('transitionend', () => {
90+
el.classList.remove('leave-to')
91+
el.classList.remove('leave-active')
92+
// 当过渡完成后,记得调用 performRemove 函数将 DOM 元素移除
93+
performRemove()
94+
})
95+
})
96+
})
97+
})
98+
</script>
99+
</body>
100+
101+
</html>

第14章内建组件和模块/14.3_Transition组件的实现原理.html renamed to 第14章内建组件和模块/14.3Transition组件的实现原理/14.3.2实现Transition组件.html

+70-30
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,18 @@
4242
patchProps(el, key, null, vnode.props[key])
4343
}
4444
}
45+
// 判断一个 VNode 是否需要过渡
46+
const needTransition = vnode.transition
47+
if (needTransition) {
48+
// 调用 transition.beforeEnter 钩子,并将 DOM 元素作为参数传递
49+
vnode.transition.beforeEnter(el)
50+
}
4551

4652
insert(el, container, anchor)
53+
if (needTransition) {
54+
// 调用 transition.enter 钩子,并将 DOM 元素作为参数传递
55+
vnode.transition.enter(el)
56+
}
4757
}
4858

4959
const queue = new Set()
@@ -516,16 +526,14 @@
516526
patchChildren(n1, n2, el)
517527
}
518528

519-
// 卸载操作
520529
function unmount (vnode) {
530+
// 判断 VNode 是否需要过渡处理
531+
const needTransition = vnode.transition
521532
if (vnode.type === Fragment) {
522533
vnode.children.forEach(c => unmount(c))
523534
return
524535
} else if (typeof vnode.type === 'object') {
525-
// vnode.shouldKeepAlive 是一个布尔值,用来标识该组件是否应该被 KeepAlive
526536
if (vnode.shouldKeepAlive) {
527-
// 对于需要被 KeepAlive 的组件,我们不应该真的卸载它,而应调用该组件的父组件,
528-
// 即 KeepAlive 组件的 _deActivate 函数使其失活
529537
vnode.keepAliveInstance._deActivate(vnode)
530538
} else {
531539
unmount(vnode.component.subTree)
@@ -534,7 +542,16 @@
534542
}
535543
const parent = vnode.el.parentNode
536544
if (parent) {
537-
parent.removeChild(vnode.el)
545+
// 将卸载动作封装到 performRemove 函数中
546+
const performRemove = () => parent.removeChild(vnode.el)
547+
if (needTransition) {
548+
// 如果需要过渡处理,则调用 transition.leave 钩子,
549+
// 同时将 DOM 元素和 performRemove 函数作为参数传递
550+
vnode.transition.leave(vnode.el, performRemove)
551+
} else {
552+
// 如果不需要过渡处理,则直接执行卸载操作
553+
performRemove()
554+
}
538555
}
539556
}
540557

@@ -677,32 +694,55 @@
677694
const Text = Symbol()
678695
const Fragment = Symbol()
679696

680-
const Teleport = {
681-
__isTeleport: true,
682-
process (n1, n2, container, anchor, internals) {
683-
// 通过 internals 参数取得渲染器的内部方法
684-
const { patch, patchChildren, move } = internals
685-
// 如果旧 VNode n1 不存在,则是全新的挂载,否则执行更新
686-
if (!n1) {
687-
// 挂载
688-
// 获取容器,即挂载点
689-
const target = typeof n2.props.to === 'string'
690-
? document.querySelector(n2.props.to)
691-
: n2.props.to
692-
// 将 n2.children 渲染到指定挂载点即可
693-
n2.children.forEach(c => patch(null, c, target, anchor))
694-
} else {
695-
// 更新
696-
patchChildren(n1, n2, container)
697-
// 如果新旧 to 参数的值不同,则需要对内容进行移动
698-
if (n2.props.to !== n1.props.to) {
699-
// 获取新的容器
700-
const newTarget = typeof n2.props.to === 'string'
701-
? document.querySelector(n2.props.to)
702-
: n2.props.to
703-
// 移动到新的容器
704-
n2.children.forEach(c => move(c, newTarget))
697+
const Transition = {
698+
name: 'Transition',
699+
setup (props, { slots }) {
700+
return () => {
701+
const innerVNode = slots.default()
702+
703+
innerVNode.transition = {
704+
beforeEnter (el) {
705+
// 设置初始状态:添加 enter-from 和 enter-active 类
706+
el.classList.add('enter-from')
707+
el.classList.add('enter-active')
708+
},
709+
enter (el) {
710+
// 在下一帧切换到结束状态
711+
nextFrame(() => {
712+
// 移除 enter-from 类,添加 enter-to 类
713+
el.classList.remove('enter-from')
714+
el.classList.add('enter-to')
715+
// 监听 transitionend 事件完成收尾工作
716+
el.addEventListener('transitionend', () => {
717+
el.classList.remove('enter-to')
718+
el.classList.remove('enter-active')
719+
})
720+
})
721+
},
722+
leave (el, performRemove) {
723+
// 设置离场过渡的初始状态:添加 leave-from 和 leave-active 类
724+
el.classList.add('leave-from')
725+
el.classList.add('leave-active')
726+
// 强制 reflow,使得初始状态生效
727+
document.body.offsetHeight
728+
// 在下一帧修改状态
729+
nextFrame(() => {
730+
// 移除 leave-from 类,添加 leave-to 类
731+
el.classList.remove('leave-from')
732+
el.classList.add('leave-to')
733+
734+
// 监听 transitionend 事件完成收尾工作
735+
el.addEventListener('transitionend', () => {
736+
el.classList.remove('leave-to')
737+
el.classList.remove('leave-active')
738+
// 调用 transition.leave 钩子函数的第二个参数,完成 DOM 元素的卸载
739+
performRemove()
740+
})
741+
})
742+
}
705743
}
744+
745+
return innerVNode
706746
}
707747
}
708748
}

0 commit comments

Comments
 (0)