fix(components): [loading] inherit app context (#20371)

* fix(components): [loading] inherit app context in ssr mode

* chore: get appContext inherit in childApp

* docs: add loading inheritance

* Update docs/en-US/component/loading.md

Co-authored-by: qiang <qw13131wang@gmail.com>

---------

Co-authored-by: qiang <qw13131wang@gmail.com>
This commit is contained in:
Noblet Ouways
2025-05-09 03:17:06 +02:00
committed by GitHub
parent afd36831f7
commit 41d6351a3d
6 changed files with 61 additions and 19 deletions

View File

@@ -79,6 +79,27 @@ Calling the `close` method on any one of them can close this full screen Loading
If Element Plus is imported entirely, a globally method `$loading` will be registered to `app.config.globalProperties`. You can invoke it like this: `this.$loading(options)`, and it also returns a Loading instance.
## App context inheritance ^(2.9.10)
Now loading accepts a `context` as second parameter of the loading constructor which allows you to inject current app's context to loading which allows you to inherit all the properties of the app.
You can use it like this:
:::tip
If you globally registered ElLoading component, it will automatically inherit your app context.
:::
```ts
import { getCurrentInstance } from 'vue'
import { ElLoading } from 'element-plus'
// in your setup method
const { appContext } = getCurrentInstance()!
ElLoading.service({}, appContext)
```
## API
### Options

View File

@@ -1,8 +1,8 @@
import { nextTick, ref } from 'vue'
import { mount } from '@vue/test-utils'
import { afterEach, describe, expect, test, vi } from 'vitest'
import { Loading } from '../src/service'
import { vLoading } from '../src/directive'
import Loading from '../src/service'
import vLoading from '../src/directive'
import ElInput from '../../input'
import type { VNode } from 'vue'

View File

@@ -1,11 +1,18 @@
import { Loading } from './src/service'
import { vLoading } from './src/directive'
import Loading from './src/service'
import vLoading from './src/directive'
import type { App } from 'vue'
import type { App, AppContext, Directive } from 'vue'
import type { ElementLoading, LoadingBinding } from './src/directive'
// installer and everything in all
export const ElLoading = {
install(app: App) {
Loading._context = app._context
;(
vLoading as Directive<ElementLoading, LoadingBinding> & {
_context: AppContext | null
}
)._context = app._context
app.directive('loading', vLoading)
app.config.globalProperties.$loading = Loading
},

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { isRef, ref } from 'vue'
import { hyphenate, isObject, isString } from '@element-plus/utils'
import { Loading } from './service'
import Loading from './service'
import type { Directive, DirectiveBinding, UnwrapRef } from 'vue'
import type { LoadingOptions } from './types'
import type { LoadingInstance } from './loading'
@@ -54,9 +54,11 @@ const createInstance = (
body: getBindingProp('body') ?? binding.modifiers.body,
lock: getBindingProp('lock') ?? binding.modifiers.lock,
}
const instance = Loading(options)
instance._context = vLoading._context
el[INSTANCE_KEY] = {
options,
instance: Loading(options),
instance,
}
}
@@ -70,7 +72,7 @@ const updateOptions = (
}
}
export const vLoading: Directive<ElementLoading, LoadingBinding> = {
const vLoading: Directive<ElementLoading, LoadingBinding> = {
mounted(el, binding) {
if (binding.value) {
createInstance(el, binding)
@@ -94,3 +96,6 @@ export const vLoading: Directive<ElementLoading, LoadingBinding> = {
el[INSTANCE_KEY] = null
},
}
vLoading._context = null
export default vLoading

View File

@@ -14,10 +14,14 @@ import {
import { removeClass } from '@element-plus/utils'
import { useGlobalComponentSettings } from '@element-plus/components/config-provider'
import type { AppContext } from 'vue'
import type { UseNamespaceReturn } from '@element-plus/hooks'
import type { LoadingOptionsResolved } from './types'
export function createLoadingComponent(options: LoadingOptionsResolved) {
export function createLoadingComponent(
options: LoadingOptionsResolved,
appContext: AppContext | null
) {
let afterLeaveTimer: ReturnType<typeof setTimeout>
// IMPORTANT NOTE: this is only a hacking way to expose the injections on an
// instance, DO NOT FOLLOW this pattern in your own code.
@@ -149,6 +153,7 @@ export function createLoadingComponent(options: LoadingOptionsResolved) {
})
const loadingInstance = createApp(elLoadingComponent)
Object.assign(loadingInstance._context, appContext ?? {})
const vm = loadingInstance.mount(document.createElement('div'))
return {

View File

@@ -13,13 +13,11 @@ import type { UseNamespaceReturn, UseZIndexReturn } from '@element-plus/hooks'
import type { LoadingInstance } from './loading'
import type { LoadingOptionsResolved } from '..'
import type { LoadingOptions } from './types'
import type { CSSProperties } from 'vue'
import type { AppContext, CSSProperties } from 'vue'
let fullscreenInstance: LoadingInstance | undefined = undefined
export const Loading = function (
options: LoadingOptions = {}
): LoadingInstance {
const Loading = function (options: LoadingOptions = {}): LoadingInstance {
if (!isClient) return undefined as any
const resolved = resolveOptions(options)
@@ -28,13 +26,16 @@ export const Loading = function (
return fullscreenInstance
}
const instance = createLoadingComponent({
...resolved,
closed: () => {
resolved.closed?.()
if (resolved.fullscreen) fullscreenInstance = undefined
const instance = createLoadingComponent(
{
...resolved,
closed: () => {
resolved.closed?.()
if (resolved.fullscreen) fullscreenInstance = undefined
},
},
})
Loading._context
)
addStyle(resolved, resolved.parent, instance)
addClassList(resolved, resolved.parent, instance)
@@ -163,3 +164,6 @@ const addClassList = (
removeClass(parent, ns.bm('parent', 'hidden'))
}
}
Loading._context = null as AppContext | null
export default Loading