From 12bfa07ffccd86ff5f81e15688bd32627e68c068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90?= Date: Tue, 21 Jun 2022 04:41:33 +0800 Subject: [PATCH] refactor(components): [message] fix typings and switch to script setup --- internal/build/src/type-unsafe-stricter.json | 1 - .../components/message/src/message-method.ts | 269 +++++++++--------- packages/components/message/src/message.ts | 91 ++++-- packages/components/message/src/message.vue | 164 +++++------ 4 files changed, 275 insertions(+), 250 deletions(-) diff --git a/internal/build/src/type-unsafe-stricter.json b/internal/build/src/type-unsafe-stricter.json index 5922df6cde9f8..ffe35540ad8fc 100644 --- a/internal/build/src/type-unsafe-stricter.json +++ b/internal/build/src/type-unsafe-stricter.json @@ -15,7 +15,6 @@ "packages/components/loading/", "packages/components/menu/", "packages/components/message-box/", - "packages/components/message/", "packages/components/notification/", "packages/components/option-group/", "packages/components/option/", diff --git a/packages/components/message/src/message-method.ts b/packages/components/message/src/message-method.ts index 00569abdb8c39..088fd5797f94b 100644 --- a/packages/components/message/src/message-method.ts +++ b/packages/components/message/src/message-method.ts @@ -5,86 +5,46 @@ import { isElement, isFunction, isNumber, - isObject, isString, isVNode, } 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, MessageProps, MessageQueue } from './message' +import { messageDefaults, messageTypes } from './message' + +import type { AppContext } from 'vue' +import type { + Message, + MessageFn, + MessageHandler, + MessageInstance, + MessageOptions, + MessageParams, + MessageParamsNormalized, + MessageQueue, + MessageQueueItem, +} from './message' 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 & { _context: AppContext | null } = - function (options = {}, context?: AppContext | null) { - if (!isClient) return { close: () => undefined } - if (isNumber(messageConfig.max) && instances.length >= messageConfig.max) { - return { close: () => undefined } - } +const normalizeOptions = (params?: MessageParams) => { + const options: MessageOptions = + !params || isString(params) || isVNode(params) || isFunction(params) + ? { message: params } + : params - if ( - !isVNode(options) && - isObject(options) && - options.grouping && - !isVNode(options.message) && - instances.length - ) { - const tempVm: any = instances.find( - (item) => - `${item.vm.props?.message ?? ''}` === - `${(options as any).message ?? ''}` - ) - if (tempVm) { - tempVm.vm.component!.props.repeatNum += 1 - tempVm.vm.component!.props.type = options?.type || 'info' - return { - close: () => - (( - vm.component!.proxy as ComponentPublicInstance<{ - visible: boolean - }> - ).visible = false), - } - } - } + const normalized = { + ...messageDefaults, + ...options, + } - if (isString(options) || isVNode(options)) { - options = { message: options } - } + if (isString(normalized.appendTo)) { + let appendTo = document.querySelector(normalized.appendTo) - let verticalOffset = options.offset || 20 - instances.forEach(({ vm }) => { - verticalOffset += (vm.el?.offsetHeight || 0) + 16 - }) - verticalOffset += 16 - - const { nextZIndex } = useZIndex() - - const id = `message_${seed++}` - const userOnClose = options.onClose - const props: Partial = { - zIndex: nextZIndex(), - ...options, - offset: verticalOffset, - id, - onClose: () => { - close(id, userOnClose) - }, - } - - let appendTo: HTMLElement | null = document.body - if (isElement(options.appendTo)) { - appendTo = options.appendTo - } else if (isString(options.appendTo)) { - appendTo = document.querySelector(options.appendTo) - } // should fallback to default value with a warning if (!isElement(appendTo)) { debugWarn( @@ -94,89 +54,142 @@ const message: MessageFn & Partial & { _context: AppContext | null } = appendTo = document.body } - const container = document.createElement('div') + normalized.appendTo = appendTo + } - container.className = `container_${id}` + return normalized as MessageParamsNormalized +} - const messageContent = props.message - const vm = createVNode( - MessageConstructor, - props, - isFunction(messageContent) - ? { default: messageContent } - : isVNode(messageContent) - ? { default: () => messageContent } - : null - ) +const closeMessage = (instance: MessageQueueItem) => { + const idx = instances.indexOf(instance) + if (idx === -1) return - vm.appContext = context || message._context + instances.splice(idx, 1) + const { vnode, handler } = instance + handler.close() + + const removedHeight = vnode.el!.offsetHeight + // adjust other instances vertical offset + const len = instances.length + if (len < 1) return + for (let i = idx; i < len; i++) { + const pos = + Number.parseInt(instances[i].vnode.el!.style['top'], 10) - + removedHeight - + 16 + + instances[i].vnode.component!.props.offset = pos + } +} + +const createMessage = ( + { appendTo, ...options }: MessageParamsNormalized, + context?: AppContext | null +): MessageQueueItem => { + const { nextZIndex } = useZIndex() + + const id = `message_${seed++}` + const userOnClose = options.onClose + + let verticalOffset = options.offset + instances.forEach(({ vnode: vm }) => { + verticalOffset += (vm.el?.offsetHeight || 0) + 16 + }) + verticalOffset += 16 + + const container = document.createElement('div') + + const props = { + ...options, + zIndex: options.zIndex ?? nextZIndex(), + offset: verticalOffset, + id, + onClose: () => { + userOnClose?.() + closeMessage(instance) + }, // clean message element preventing mem leak - vm.props!.onDestroy = () => { - render(null, container) + onDestroy: () => { // since the element is destroy, then the VNode should be collected by GC as well // we do not want cause any mem leak because we have returned vm as a reference to users // so that we manually set it to false. - } - - render(vm, container) - // instances will remove this item when close function gets called. So we do not need to worry about it. - instances.push({ vm }) - appendTo.appendChild(container.firstElementChild!) - - return { - // instead of calling the onClose function directly, setting this value so that we can have the full lifecycle - // for out component, so that all closing steps will not be skipped. - close: () => - (( - vm.component!.proxy as ComponentPublicInstance<{ visible: boolean }> - ).visible = false), - } + render(null, container) + }, + } + const vnode = createVNode( + MessageConstructor, + props, + isFunction(props.message) || isVNode(props.message) + ? { default: props.message } + : null + ) + vnode.appContext = context || message._context + + render(vnode, container) + // instances will remove this item when close function gets called. So we do not need to worry about it. + appendTo.appendChild(container.firstElementChild!) + + const vm = vnode.component!.proxy as MessageInstance + const handler: MessageHandler = { + // instead of calling the onClose function directly, setting this value so that we can have the full lifecycle + // for out component, so that all closing steps will not be skipped. + close: () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore `visible` from defineExpose + vm.visible = false + }, } -messageTypes.forEach((type) => { - message[type] = (options = {}, appContext?: AppContext | null) => { - if (isString(options) || isVNode(options)) { - options = { - message: options, - } - } - return message( - { - ...options, - type, - }, - appContext - ) + const instance = { + vnode, + vm, + handler, } -}) -export function close(id: string, userOnClose?: (vm: VNode) => void): void { - const idx = instances.findIndex(({ vm }) => id === vm.component!.props.id) - if (idx === -1) return + return instance +} - const { vm } = instances[idx] - if (!vm) return - userOnClose?.(vm) +const message: MessageFn & + Partial & { _context: AppContext | null } = ( + options = {}, + context +) => { + if (!isClient) return { close: () => undefined } - const removedHeight = vm.el!.offsetHeight - instances.splice(idx, 1) + if (isNumber(messageConfig.max) && instances.length >= messageConfig.max) { + return { close: () => undefined } + } - // adjust other instances vertical offset - const len = instances.length - if (len < 1) return - for (let i = idx; i < len; i++) { - const pos = - Number.parseInt(instances[i].vm.el!.style['top'], 10) - removedHeight - 16 + const normalized = normalizeOptions(options) - instances[i].vm.component!.props.offset = pos + if (normalized.grouping && instances.length) { + const instance = instances.find( + ({ vnode: vm }) => vm.props?.message === normalized.message + ) + if (instance) { + ;(instance.vnode.component as any).props.repeatNum += 1 + ;(instance.vnode.component as any).props.type = normalized.type + return instance.handler + } } + + const instance = createMessage(normalized, context) + + instances.push(instance) + return instance.handler } +messageTypes.forEach((type) => { + message[type] = (options = {}, appContext) => { + const normalized = normalizeOptions(options) + return message({ ...normalized, type }, appContext) + } +}) + export function closeAll(): void { - for (let i = instances.length - 1; i >= 0; i--) { - const instance = instances[i].vm.component - ;(instance?.proxy as any)?.close() + for (const instance of instances) { + closeMessage(instance) } } diff --git a/packages/components/message/src/message.ts b/packages/components/message/src/message.ts index b4f90ec84a97e..50d6f85014131 100644 --- a/packages/components/message/src/message.ts +++ b/packages/components/message/src/message.ts @@ -1,5 +1,12 @@ -import { buildProps, definePropType, iconPropType } from '@element-plus/utils' +import { + buildProps, + definePropType, + iconPropType, + mutable, +} from '@element-plus/utils' import type { AppContext, ExtractPropTypes, VNode } from 'vue' +import type { Mutable } from '@element-plus/utils' +import type MessageConstructor from './message.vue' export const messageTypes = ['success', 'info', 'warning', 'error'] as const @@ -7,30 +14,47 @@ export interface MessageConfigContext { max?: number } +export const messageDefaults = mutable({ + customClass: '', + center: false, + dangerouslyUseHTMLString: false, + duration: 3000, + icon: '', + id: '', + message: '', + onClose: undefined, + showClose: false, + type: 'info', + offset: 20, + grouping: false, + repeatNum: 1, + appendTo: document.body, +} as const) + export const messageProps = buildProps({ customClass: { type: String, - default: '', + default: messageDefaults.customClass, }, center: { type: Boolean, - default: false, + default: messageDefaults.center, }, dangerouslyUseHTMLString: { type: Boolean, - default: false, + default: messageDefaults.dangerouslyUseHTMLString, }, duration: { type: Number, - default: 3000, + default: messageDefaults.duration, }, icon: { type: iconPropType, - default: '', + default: messageDefaults.icon, }, id: { type: String, - default: '', + default: messageDefaults.id, }, message: { type: definePropType VNode)>([ @@ -38,7 +62,7 @@ export const messageProps = buildProps({ Object, Function, ]), - default: '', + default: messageDefaults.message, }, onClose: { type: definePropType<() => void>(Function), @@ -46,28 +70,27 @@ export const messageProps = buildProps({ }, showClose: { type: Boolean, - default: false, + default: messageDefaults.showClose, }, type: { type: String, values: messageTypes, - default: 'info', + default: messageDefaults.type, }, offset: { type: Number, - default: 20, + default: messageDefaults.offset, }, zIndex: { type: Number, - default: 0, }, grouping: { type: Boolean, - default: false, + default: messageDefaults.grouping, }, repeatNum: { type: Number, - default: 1, + default: messageDefaults.repeatNum, }, } as const) export type MessageProps = ExtractPropTypes @@ -77,28 +100,36 @@ export const messageEmits = { } export type MessageEmits = typeof messageEmits -export type MessageOptions = Omit & { - appendTo?: HTMLElement | string +export type MessageInstance = InstanceType + +export type MessageOptions = Partial< + Mutable< + Omit & { + appendTo?: HTMLElement | string + } + > +> +export type MessageParams = MessageOptions | MessageOptions['message'] +export type MessageParamsNormalized = Omit & { + appendTo: HTMLElement } -export type MessageOptionsTyped = Omit +export type MessageOptionsWithType = Omit +export type MessageParamsWithType = + | MessageOptionsWithType + | MessageOptions['message'] -export interface MessageHandle { +export interface MessageHandler { close: () => void } -export type MessageParams = Partial | string | VNode -export type MessageParamsTyped = Partial | string | VNode - -export type MessageFn = (( - options?: MessageParams, - appContext?: null | AppContext -) => MessageHandle) & { +export type MessageFn = { + (options?: MessageParams, appContext?: null | AppContext): MessageHandler closeAll(): void } export type MessageTypedFn = ( - options?: MessageParamsTyped, + options?: MessageParamsWithType, appContext?: null | AppContext -) => MessageHandle +) => MessageHandler export interface Message extends MessageFn { success: MessageTypedFn @@ -107,8 +138,10 @@ export interface Message extends MessageFn { error: MessageTypedFn } -type MessageQueueItem = { - vm: VNode +export type MessageQueueItem = { + vnode: VNode + handler: MessageHandler + vm: MessageInstance } export type MessageQueue = MessageQueueItem[] diff --git a/packages/components/message/src/message.vue b/packages/components/message/src/message.vue index 750d1510cb43a..4e025481c1c24 100644 --- a/packages/components/message/src/message.vue +++ b/packages/components/message/src/message.vue @@ -36,113 +36,93 @@

- + -