feat(components): [message] add vue context for message component (#6259)

This commit is contained in:
JeremyWuuuuu 2022-02-24 11:24:34 +08:00 committed by GitHub
parent cd0f01c034
commit 6aa69126b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 169 additions and 108 deletions

View File

@ -85,6 +85,24 @@ import { ElMessage } from 'element-plus'
In this case you should call `ElMessage(options)`. We have also registered methods for different types, e.g. `ElMessage.success(options)`. You can call `ElMessage.closeAll()` to manually close all the instances.
## App context inheritance <el-tag>> 2.0.2</el-tag>
Now message accepts a `context` as second parameter of the message constructor which allows you to inject current app's context to message which allows you to inherit all the properties of the app.
You can use it like this:
:::tip
If you globally registered ElMessage component, it will automatically inherit your app context.
:::
```ts
import { ElMessage } from 'element-plus'
ElMessage({}, app._context)
```
## Options
| Attribute | Description | Type | Accepted Values | Default |

View File

@ -1,6 +1,7 @@
import { nextTick } from 'vue'
import { getStyle } from '@element-plus/utils'
import { rAF } from '@element-plus/test-utils/tick'
import { ElMessage } from '..'
import Message from '../src/message-method'
jest.useFakeTimers()
@ -115,4 +116,21 @@ describe('Message on command', () => {
await nextTick()
expect(htmlElement.querySelector(selector)).toBeFalsy()
})
describe('context inheritance', () => {
it('should globally inherit context correctly', () => {
expect(ElMessage._context).toBe(null)
const testContext = {
config: {
globalProperties: {},
},
_context: {},
}
ElMessage.install?.(testContext as any)
expect(ElMessage._context).not.toBe(null)
expect(ElMessage._context).toBe(testContext._context)
// clean up
ElMessage._context = null
})
})
})

View File

@ -1,20 +1,27 @@
import { createVNode, render } from 'vue'
import { isClient } from '@vueuse/core'
import { isVNode, isNumber, debugWarn } from '@element-plus/utils'
import {
isVNode,
isNumber,
isObject,
isString,
debugWarn,
} from '@element-plus/utils'
import { useZIndex } from '@element-plus/hooks'
import { messageConfig } from '@element-plus/components/config-provider/src/config-provider'
import MessageConstructor from './message.vue'
import { messageTypes } from './message'
import type { AppContext, ComponentPublicInstance, VNode } from 'vue'
import type { Message, MessageFn, MessageQueue, MessageProps } from './message'
import type { ComponentPublicInstance, VNode } from 'vue'
const instances: MessageQueue = []
let seed = 1
// TODO: Since Notify.ts is basically the same like this file. So we could do some encapsulation against them to reduce code duplication.
const message: MessageFn & Partial<Message> = function (options = {}) {
const message: MessageFn & Partial<Message> & { _context: AppContext | null } =
function (options = {}, context?: AppContext | null) {
if (!isClient) return { close: () => undefined }
if (isNumber(messageConfig.max) && instances.length >= messageConfig.max) {
return { close: () => undefined }
@ -22,7 +29,7 @@ const message: MessageFn & Partial<Message> = function (options = {}) {
if (
!isVNode(options) &&
typeof options === 'object' &&
isObject(options) &&
options.grouping &&
!isVNode(options.message) &&
instances.length
@ -38,13 +45,15 @@ const message: MessageFn & Partial<Message> = function (options = {}) {
return {
close: () =>
((
vm.component!.proxy as ComponentPublicInstance<{ visible: boolean }>
vm.component!.proxy as ComponentPublicInstance<{
visible: boolean
}>
).visible = false),
}
}
}
if (typeof options === 'string' || isVNode(options)) {
if (isString(options) || isVNode(options)) {
options = { message: options }
}
@ -71,7 +80,7 @@ const message: MessageFn & Partial<Message> = function (options = {}) {
let appendTo: HTMLElement | null = document.body
if (options.appendTo instanceof HTMLElement) {
appendTo = options.appendTo
} else if (typeof options.appendTo === 'string') {
} else if (isString(options.appendTo)) {
appendTo = document.querySelector(options.appendTo)
}
// should fallback to default value with a warning
@ -87,13 +96,15 @@ const message: MessageFn & Partial<Message> = function (options = {}) {
container.className = `container_${id}`
const message = props.message
const messageContent = props.message
const vm = createVNode(
MessageConstructor,
props,
isVNode(props.message) ? { default: () => message } : null
isVNode(messageContent) ? { default: () => messageContent } : null
)
vm.appContext = context || message._context
// clean message element preventing mem leak
vm.props!.onDestroy = () => {
render(null, container)
@ -115,11 +126,11 @@ const message: MessageFn & Partial<Message> = function (options = {}) {
vm.component!.proxy as ComponentPublicInstance<{ visible: boolean }>
).visible = false),
}
}
}
messageTypes.forEach((type) => {
message[type] = (options = {}) => {
if (typeof options === 'string' || isVNode(options)) {
if (isString(options) || isVNode(options)) {
options = {
message: options,
}
@ -161,5 +172,6 @@ export function closeAll(): void {
}
message.closeAll = closeAll
message._context = null
export default message as Message

View File

@ -1,5 +1,5 @@
import { buildProps, definePropType, iconPropType } from '@element-plus/utils'
import type { VNode, ExtractPropTypes } from 'vue'
import type { VNode, ExtractPropTypes, AppContext } from 'vue'
export const messageTypes = ['success', 'info', 'warning', 'error'] as const
@ -85,10 +85,16 @@ export interface MessageHandle {
export type MessageParams = Partial<MessageOptions> | string | VNode
export type MessageParamsTyped = Partial<MessageOptionsTyped> | string | VNode
export type MessageFn = ((options?: MessageParams) => MessageHandle) & {
export type MessageFn = ((
options?: MessageParams,
appContext?: null | AppContext
) => MessageHandle) & {
closeAll(): void
}
export type MessageTypedFn = (options?: MessageParamsTyped) => MessageHandle
export type MessageTypedFn = (
options?: MessageParamsTyped,
appContext?: null | AppContext
) => MessageHandle
export interface Message extends MessageFn {
success: MessageTypedFn

View File

@ -1,5 +1,7 @@
import { NOOP } from '@vue/shared'
import type { SFCWithInstall } from './typescript'
import type { App } from 'vue'
import type { SFCWithInstall, SFCInstallWithContext } from './typescript'
export const withInstall = <T, E extends Record<string, any>>(
main: T,
@ -20,11 +22,12 @@ export const withInstall = <T, E extends Record<string, any>>(
}
export const withInstallFunction = <T>(fn: T, name: string) => {
;(fn as SFCWithInstall<T>).install = (app) => {
;(fn as SFCWithInstall<T>).install = (app: App) => {
;(fn as SFCInstallWithContext<T>)._context = app._context
app.config.globalProperties[name] = fn
}
return fn as SFCWithInstall<T>
return fn as SFCInstallWithContext<T>
}
export const withNoopInstall = <T>(component: T) => {

View File

@ -1,3 +1,7 @@
import type { Plugin } from 'vue'
import type { AppContext, Plugin } from 'vue'
export type SFCWithInstall<T> = T & Plugin
export type SFCInstallWithContext<T> = SFCWithInstall<T> & {
_context: AppContext | null
}