-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(vue): add unmount hook to unmount application #2849
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@qiankunjs/vue": patch | ||
--- | ||
|
||
fix(vue): add unmount hook to unmount application |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,5 +1,16 @@ | ||||
import type { PropType } from 'vue-demi'; | ||||
import { computed, defineComponent, h, onMounted, reactive, ref, toRefs, watch, isVue2 } from 'vue-demi'; | ||||
import { | ||||
computed, | ||||
defineComponent, | ||||
h, | ||||
onMounted, | ||||
ref, | ||||
onBeforeUnmount, | ||||
shallowRef, | ||||
toRefs, | ||||
watch, | ||||
isVue2, | ||||
} from 'vue-demi'; | ||||
import type { AppConfiguration, LifeCycles } from 'qiankun'; | ||||
import type { MicroAppType } from '@qiankunjs/ui-shared'; | ||||
import { mountMicroApp, omitSharedProps, unmountMicroApp, updateMicroApp } from '@qiankunjs/ui-shared'; | ||||
|
@@ -43,23 +54,23 @@ export const MicroApp = defineComponent({ | |||
type: String, | ||||
default: undefined, | ||||
}, | ||||
appProps: { | ||||
type: Object, | ||||
default: undefined, | ||||
}, | ||||
}, | ||||
setup(props, { slots }) { | ||||
const originProps = props; | ||||
const { name, wrapperClassName, className, ...propsFromParams } = toRefs(originProps); | ||||
const { name, wrapperClassName, className, appProps, autoCaptureError } = toRefs(originProps); | ||||
|
||||
const loading = ref(false); | ||||
const error = ref<Error>(); | ||||
|
||||
const containerRef = ref(null); | ||||
const microAppRef = ref<MicroAppType>(); | ||||
|
||||
const reactivePropsFromParams = computed(() => { | ||||
return omitSharedProps(reactive(propsFromParams)); | ||||
}); | ||||
const microAppRef = shallowRef<MicroAppType>(); | ||||
|
||||
const isNeedShowError = computed(() => { | ||||
return slots.errorBoundary || reactivePropsFromParams.value.autoCaptureError; | ||||
return slots.errorBoundary || autoCaptureError.value; | ||||
}); | ||||
|
||||
// 配置了 errorBoundary 才改 error 状态,否则直接往上抛异常 | ||||
|
@@ -77,31 +88,38 @@ export const MicroApp = defineComponent({ | |||
|
||||
const rootRef = ref(null); | ||||
|
||||
onMounted(() => { | ||||
console.log(rootRef.value); | ||||
const unmount = () => { | ||||
const microApp = microAppRef.value; | ||||
|
||||
console.log(containerRef.value); | ||||
if (microApp) { | ||||
microApp._unmounting = true; | ||||
|
||||
unmountMicroApp(microApp).catch((err: Error) => { | ||||
setComponentError(err); | ||||
loading.value = false; | ||||
}); | ||||
|
||||
microAppRef.value = undefined; | ||||
} | ||||
}; | ||||
|
||||
onMounted(() => { | ||||
// watch name 变更切换子应用 | ||||
watch( | ||||
name, | ||||
() => { | ||||
const microApp = microAppRef.value; | ||||
|
||||
if (microApp) { | ||||
microApp._unmounting = true; | ||||
|
||||
unmountMicroApp(microApp).catch((err: Error) => { | ||||
setComponentError(err); | ||||
loading.value = false; | ||||
}); | ||||
|
||||
microAppRef.value = undefined; | ||||
} | ||||
const prevApp = microAppRef.value; | ||||
// 销毁上一个子应用 | ||||
unmount(); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里为啥要调 unmount ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 利用 name 切换子应用时,先将前一个子应用 unmount There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里应该可以去掉, 我看 最新的 mountMicroApp 已经有对上一个应用进行卸载的操作。 然后最好判断下 name 是否存在再调用mountMicroApp。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 最新的 pr 应该没有在 mountMicroApp 进行卸载,只是做了如果 上一个 应用正在卸载,使用 await 等待卸载完毕再执行 mount 操作 |
||||
|
||||
// 初始化下一个子应用 | ||||
void mountMicroApp({ | ||||
prevMicroApp: microAppRef.value, | ||||
prevMicroApp: prevApp, | ||||
container: containerRef.value!, | ||||
componentProps: originProps, | ||||
componentProps: { | ||||
...originProps, | ||||
...appProps.value, | ||||
}, | ||||
setLoading: (l) => { | ||||
loading.value = l; | ||||
}, | ||||
|
@@ -118,13 +136,17 @@ export const MicroApp = defineComponent({ | |||
); | ||||
|
||||
watch( | ||||
reactivePropsFromParams, | ||||
appProps, | ||||
() => { | ||||
updateMicroApp({ | ||||
microApp: microAppRef.value, | ||||
setLoading: (l) => { | ||||
loading.value = l; | ||||
}, | ||||
microAppProps: { | ||||
...omitSharedProps(originProps), | ||||
...appProps.value, | ||||
}, | ||||
}); | ||||
}, | ||||
{ | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里不应该不用深度监听,甚至 ref 都不用, shallowRef 就好。因为可能传递各种实例,或者组件啥的下去没必要 observe 一次。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. react 的版本现在就是 deepCompare 的
vue 里有更合理的方案实现类似的效果吗? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不使用深度监听的话,以目前暴露的接口来说,是没有主动更新 vue 组件里的子应用的能力的,是否需要把子应用实例或者是更新方法暴露给使用者 此外,目前 vue 里如果使用 shallowRef 或者仅仅浅层 watch props 中的 appProps ,这种方式仅能检测到引用的变化,对于 appProps 内部的变化是无法检测到的,可能不太符合预期? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 一般来说 react 要改变 componentProps 里面的状态一般也是 {...componentProps} 这样吧,如果是这样 vue 这边可以用 shallowRef 然后 lodash.isEqual 比较新旧值来判断是否要更新 micro。 我是感觉 deep watch appProps 虽然简单但是有点耗性能。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 跟react版本保持一致吧,还是深度监听吧 |
||||
|
@@ -133,6 +155,10 @@ export const MicroApp = defineComponent({ | |||
); | ||||
}); | ||||
|
||||
onBeforeUnmount(() => { | ||||
unmount(); | ||||
}); | ||||
|
||||
const microAppWrapperClassName = computed(() => | ||||
wrapperClassName.value ? `${wrapperClassName.value} qiankun-micro-app-wrapper` : 'qiankun-micro-app-wrapper', | ||||
); | ||||
|
@@ -149,16 +175,12 @@ export const MicroApp = defineComponent({ | |||
microAppWrapperClassName, | ||||
microAppClassName, | ||||
rootRef, | ||||
reactivePropsFromParams, | ||||
microApp: microAppRef, | ||||
}; | ||||
}, | ||||
|
||||
render() { | ||||
return this.reactivePropsFromParams.autoSetLoading || | ||||
this.reactivePropsFromParams.autoCaptureError || | ||||
this.$slots.loader || | ||||
this.$slots.errorBoundary | ||||
return this.autoSetLoading || this.autoCaptureError || this.$slots.loader || this.$slots.errorBoundary | ||||
? h( | ||||
'div', | ||||
{ | ||||
|
@@ -169,7 +191,7 @@ export const MicroApp = defineComponent({ | |||
? typeof this.$slots.loader === 'function' | ||||
? this.$slots.loader(this.loading) | ||||
: this.$slots.loader | ||||
: this.reactivePropsFromParams.autoSetLoading && | ||||
: this.autoSetLoading && | ||||
h(MicroAppLoader, { | ||||
...(isVue2 | ||||
? { | ||||
|
@@ -186,7 +208,7 @@ export const MicroApp = defineComponent({ | |||
? typeof this.$slots.errorBoundary === 'function' | ||||
? this.$slots.errorBoundary(this.error) | ||||
: this.$slots.errorBoundary | ||||
: this.reactivePropsFromParams.autoCaptureError && | ||||
: this.autoCaptureError && | ||||
h(ErrorBoundary, { | ||||
...(isVue2 | ||||
? { | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kuitos 卸载这一块要不要 await 下,不等上一个应用A卸载完就加载应用B并跳转路由。有时候会匹配到A应用的404路由
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
你是指通过同一个组件切 name 的方式切换子应用的场景的吗?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
是的, 改变name跟entry ,再push路由。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
那需要存一下前一个的 unmountPromise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里 await 好像没啥意义。
在你说的场景下,await 也解决不了的,切换本身也是一个异步行为。
目前已经通过 _unmounting 这个同步标志位来处理了。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个标志位好像作用也不大,按理来说应该是等上一个子应用完全卸载完再进行 路由跳转