Skip to content

Commit 9c21b95

Browse files
fix: 🐛 修复Transition被打断时出现显示异常的问题 (#368)
1 parent 7b44765 commit 9c21b95

File tree

4 files changed

+124
-44
lines changed

4 files changed

+124
-44
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export class AbortablePromise<T> {
2+
promise: Promise<T>
3+
private _reject: ((res?: any) => void) | null = null
4+
5+
constructor(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) {
6+
this.promise = new Promise<T>((resolve, reject) => {
7+
executor(resolve, reject)
8+
this._reject = reject // 保存reject方法的引用,以便在abort时调用
9+
})
10+
}
11+
// 提供abort方法来中止Promise
12+
abort(error?: any) {
13+
if (this._reject) {
14+
this._reject(error) // 调用reject方法来中止Promise
15+
}
16+
}
17+
18+
then<TResult1 = T, TResult2 = never>(
19+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
20+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
21+
): Promise<TResult1 | TResult2> {
22+
return this.promise.then(onfulfilled, onrejected)
23+
}
24+
25+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult> {
26+
return this.promise.catch(onrejected)
27+
}
28+
}

src/uni_modules/wot-design-uni/components/common/util.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { AbortablePromise } from './AbortablePromise'
2+
13
/**
24
* 生成uuid
35
* @returns string
@@ -330,7 +332,7 @@ export function isNumber(value: any): value is number {
330332
*/
331333
export function isPromise(value: unknown): value is Promise<any> {
332334
// 先将 value 断言为 object 类型
333-
if (isObj(value)) {
335+
if (isObj(value) && isDef(value)) {
334336
// 然后进一步检查 value 是否具有 then 和 catch 方法,并且它们是函数类型
335337
return isFunction((value as Promise<any>).then) && isFunction((value as Promise<any>).catch)
336338
}
@@ -417,7 +419,7 @@ export function objToStyle(styles: Record<string, any> | Record<string, any>[]):
417419
}
418420

419421
export const requestAnimationFrame = (cb = () => {}) => {
420-
return new Promise((resolve) => {
422+
return new AbortablePromise((resolve) => {
421423
const timer = setInterval(() => {
422424
clearInterval(timer)
423425
resolve(true)

src/uni_modules/wot-design-uni/components/wd-toast/wd-toast.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ function buildSvg() {
145145
*/
146146
function reset(option: ToastOptions) {
147147
if (option) {
148-
show.value = isDef(option.show!) ? option.show! : false
148+
show.value = isDef(option.show) ? option.show : false
149+
149150
if (show.value) {
150151
iconName.value = isDef(option.iconName!) ? option.iconName! : ''
151152
customIcon.value = isDef(option.customIcon!) ? option.customIcon! : false

src/uni_modules/wot-design-uni/components/wd-transition/wd-transition.vue

Lines changed: 90 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ export default {
1717

1818
<script lang="ts" setup>
1919
import { computed, onBeforeMount, ref, watch } from 'vue'
20-
import { isObj, requestAnimationFrame } from '../common/util'
20+
import { isObj, isPromise, requestAnimationFrame } from '../common/util'
2121
import { transitionProps } from './types'
22+
import { AbortablePromise } from '../common/AbortablePromise'
2223
2324
const getClassNames = (name?: string) => {
2425
if (!name) {
@@ -54,7 +55,13 @@ const currentDuration = ref<number>(300)
5455
// 类名
5556
const classes = ref<string>('')
5657
// 用于控制enter和leave的顺序执行
57-
const enterPromise = ref<Promise<void> | null>(null)
58+
const enterPromise = ref<AbortablePromise<void> | null>(null)
59+
60+
// 动画进入的生命周期
61+
const enterLifeCyclePromises = ref<AbortablePromise<unknown> | null>(null)
62+
63+
// 动画离开的生命周期
64+
const leaveLifeCyclePromises = ref<AbortablePromise<unknown> | null>(null)
5865
5966
const style = computed(() => {
6067
return `-webkit-transition-duration:${currentDuration.value}ms;transition-duration:${currentDuration.value}ms;${
@@ -75,66 +82,107 @@ onBeforeMount(() => {
7582
watch(
7683
() => props.show,
7784
(newVal) => {
78-
observerShow(newVal)
85+
handleShow(newVal)
7986
},
80-
{ deep: true, immediate: true }
87+
{ deep: true }
8188
)
8289
8390
function handleClick() {
8491
emit('click')
8592
}
8693
87-
function observerShow(value: boolean) {
88-
value ? enter() : leave()
94+
function handleShow(value: boolean) {
95+
if (value) {
96+
handleAbortPromise()
97+
enter()
98+
} else {
99+
leave()
100+
}
101+
}
102+
/**
103+
* 取消所有的promise
104+
*/
105+
function handleAbortPromise() {
106+
isPromise(enterPromise.value) && enterPromise.value.abort()
107+
isPromise(enterLifeCyclePromises.value) && enterLifeCyclePromises.value.abort()
108+
isPromise(leaveLifeCyclePromises.value) && leaveLifeCyclePromises.value.abort()
109+
enterPromise.value = null
110+
enterLifeCyclePromises.value = null
111+
leaveLifeCyclePromises.value = null
89112
}
90113
91114
function enter() {
92-
if (enterPromise.value) return
93-
enterPromise.value = new Promise((resolve) => {
94-
const classNames = getClassNames(props.name)
95-
const duration = isObj(props.duration) ? (props.duration as any).enter : props.duration
96-
status.value = 'enter'
97-
emit('before-enter')
98-
99-
requestAnimationFrame(() => {
115+
enterPromise.value = new AbortablePromise(async (resolve) => {
116+
try {
117+
const classNames = getClassNames(props.name)
118+
const duration = isObj(props.duration) ? (props.duration as any).enter : props.duration
119+
status.value = 'enter'
120+
emit('before-enter')
121+
enterLifeCyclePromises.value = requestAnimationFrame()
122+
await enterLifeCyclePromises.value
100123
emit('enter')
101124
classes.value = classNames.enter
102125
currentDuration.value = duration
103-
requestAnimationFrame(() => {
104-
inited.value = true
105-
display.value = true
106-
requestAnimationFrame(() => {
107-
transitionEnded.value = false
108-
classes.value = classNames['enter-to']
109-
resolve()
110-
})
111-
})
112-
})
126+
enterLifeCyclePromises.value = requestAnimationFrame()
127+
await enterLifeCyclePromises.value
128+
inited.value = true
129+
display.value = true
130+
enterLifeCyclePromises.value = requestAnimationFrame()
131+
await enterLifeCyclePromises.value
132+
enterLifeCyclePromises.value = null
133+
transitionEnded.value = false
134+
classes.value = classNames['enter-to']
135+
resolve()
136+
} catch (error) {
137+
/**
138+
*
139+
*/
140+
}
113141
})
114142
}
115-
function leave() {
116-
if (!enterPromise.value) return
117-
enterPromise.value.then(() => {
143+
async function leave() {
144+
if (!enterPromise.value) {
145+
transitionEnded.value = false
146+
return onTransitionEnd()
147+
}
148+
try {
149+
await enterPromise.value
118150
if (!display.value) return
119151
const classNames = getClassNames(props.name)
120152
const duration = isObj(props.duration) ? (props.duration as any).leave : props.duration
121153
status.value = 'leave'
122154
emit('before-leave')
155+
currentDuration.value = duration
156+
leaveLifeCyclePromises.value = requestAnimationFrame()
157+
await leaveLifeCyclePromises.value
158+
emit('leave')
159+
classes.value = classNames.leave
160+
leaveLifeCyclePromises.value = requestAnimationFrame()
161+
await leaveLifeCyclePromises.value
162+
transitionEnded.value = false
163+
classes.value = classNames['leave-to']
164+
leaveLifeCyclePromises.value = setPromise(currentDuration.value)
165+
await leaveLifeCyclePromises.value
166+
leaveLifeCyclePromises.value = null
167+
onTransitionEnd()
168+
enterPromise.value = null
169+
} catch (error) {
170+
/**
171+
*
172+
*/
173+
}
174+
}
123175
124-
requestAnimationFrame(() => {
125-
emit('leave')
126-
classes.value = classNames.leave
127-
currentDuration.value = duration
128-
129-
requestAnimationFrame(() => {
130-
transitionEnded.value = false
131-
setTimeout(() => {
132-
onTransitionEnd()
133-
enterPromise.value = null
134-
}, currentDuration.value)
135-
classes.value = classNames['leave-to']
136-
})
137-
})
176+
/**
177+
* 定时器promise化
178+
* @param duration 持续时间ms
179+
*/
180+
function setPromise(duration: number) {
181+
return new AbortablePromise<void>((resolve) => {
182+
const timer = setTimeout(() => {
183+
clearTimeout(timer)
184+
resolve()
185+
}, duration)
138186
})
139187
}
140188
function onTransitionEnd() {
@@ -148,6 +196,7 @@ function onTransitionEnd() {
148196
// 进入后触发
149197
emit('after-enter')
150198
}
199+
151200
if (!props.show && display.value) {
152201
display.value = false
153202
}

0 commit comments

Comments
 (0)