diff --git a/components/_util/PortalWrapper.tsx b/components/_util/PortalWrapper.tsx index 5c02f3ba38..e1f6e05b13 100644 --- a/components/_util/PortalWrapper.tsx +++ b/components/_util/PortalWrapper.tsx @@ -4,13 +4,12 @@ import setStyle from './setStyle'; import Portal from './Portal'; import { defineComponent, - ref, watch, onMounted, onBeforeUnmount, onUpdated, - getCurrentInstance, nextTick, + shallowRef, } from 'vue'; import canUseDom from './canUseDom'; import ScrollLocker from '../vc-util/Dom/scrollLocker'; @@ -60,9 +59,10 @@ export default defineComponent({ }, setup(props, { slots }) { - const container = ref(); - const componentRef = ref(); - const rafId = ref(); + const container = shallowRef(); + const componentRef = shallowRef(); + const rafId = shallowRef(); + const triggerUpdate = shallowRef(1); const scrollLocker = new ScrollLocker({ container: getParent(props.getContainer) as HTMLElement, }); @@ -131,7 +131,7 @@ export default defineComponent({ switchScrollingEffect(true); } }; - const instance = getCurrentInstance(); + onMounted(() => { let init = false; watch( @@ -177,7 +177,7 @@ export default defineComponent({ nextTick(() => { if (!attachToParent()) { rafId.value = raf(() => { - instance.update(); + triggerUpdate.value += 1; }); } }); @@ -203,7 +203,7 @@ export default defineComponent({ scrollLocker, }; - if (forceRender || visible || componentRef.value) { + if (triggerUpdate.value && (forceRender || visible || componentRef.value)) { portal = ( & @@ -51,3 +51,32 @@ export function deepCloneElement( return cloned; } } + +export function triggerVNodeUpdate(vm: VNode, attrs: Record, dom: any) { + VueRender(cloneVNode(vm, { ...attrs }), dom); +} + +const ensureValidVNode = (slot: VNodeArrayChildren | null) => { + return (slot || []).some(child => { + if (!isVNode(child)) return true; + if (child.type === Comment) return false; + if (child.type === Fragment && !ensureValidVNode(child.children as VNodeArrayChildren)) + return false; + return true; + }) + ? slot + : null; +}; + +export function customRenderSlot( + slots: Slots, + name: string, + props: Record, + fallback?: () => VNodeArrayChildren, +) { + const slot = slots[name]?.(props); + if (ensureValidVNode(slot)) { + return slot; + } + return fallback?.(); +} diff --git a/components/affix/index.tsx b/components/affix/index.tsx index 414a2bffdd..4730664b06 100644 --- a/components/affix/index.tsx +++ b/components/affix/index.tsx @@ -167,7 +167,6 @@ const Affix = defineComponent({ affixStyle: undefined, placeholderStyle: undefined, }); - currentInstance.update(); // Test if `updatePosition` called if (process.env.NODE_ENV === 'test') { emit('testUpdatePosition'); diff --git a/components/card/Card.tsx b/components/card/Card.tsx index 82925226dc..e3765c775b 100644 --- a/components/card/Card.tsx +++ b/components/card/Card.tsx @@ -1,5 +1,5 @@ import type { VNodeTypes, PropType, VNode, ExtractPropTypes, CSSProperties } from 'vue'; -import { isVNode, defineComponent, renderSlot } from 'vue'; +import { isVNode, defineComponent } from 'vue'; import Tabs from '../tabs'; import Row from '../row'; import Col from '../col'; @@ -10,6 +10,7 @@ import isPlainObject from 'lodash-es/isPlainObject'; import useConfigInject from '../_util/hooks/useConfigInject'; import devWarning from '../vc-util/devWarning'; import type { CustomSlotsType } from '../_util/type'; +import { customRenderSlot } from '../_util/vnode'; export interface CardTabListType { key: string; tab: any; @@ -173,7 +174,7 @@ const Card = defineComponent({ `tabList slots is deprecated, Please use \`customTab\` instead.`, ); let tab = temp !== undefined ? temp : slots[name] ? slots[name](item) : null; - tab = renderSlot(slots, 'customTab', item as any, () => [tab]); + tab = customRenderSlot(slots, 'customTab', item as any, () => [tab]); return ; })} diff --git a/components/modal/confirm.tsx b/components/modal/confirm.tsx index 8781a070b0..0017e94aca 100644 --- a/components/modal/confirm.tsx +++ b/components/modal/confirm.tsx @@ -4,6 +4,7 @@ import type { ModalFuncProps } from './Modal'; import { destroyFns } from './Modal'; import ConfigProvider, { globalConfigForApi } from '../config-provider'; import omit from '../_util/omit'; +import { triggerVNodeUpdate } from '../_util/vnode'; import InfoCircleOutlined from '@ant-design/icons-vue/InfoCircleOutlined'; import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined'; import CloseCircleOutlined from '@ant-design/icons-vue/CloseCircleOutlined'; @@ -28,7 +29,6 @@ const confirm = (config: ModalFuncProps) => { if (confirmDialogInstance) { // destroy vueRender(null, container as any); - confirmDialogInstance.component.update(); confirmDialogInstance = null; } const triggerCancel = args.some(param => param && param.triggerCancel); @@ -67,8 +67,7 @@ const confirm = (config: ModalFuncProps) => { }; } if (confirmDialogInstance) { - Object.assign(confirmDialogInstance.component.props, currentConfig); - confirmDialogInstance.component.update(); + triggerVNodeUpdate(confirmDialogInstance, currentConfig, container); } } diff --git a/components/table/hooks/useColumns.tsx b/components/table/hooks/useColumns.tsx index aab022e250..3f1a5304bd 100644 --- a/components/table/hooks/useColumns.tsx +++ b/components/table/hooks/useColumns.tsx @@ -1,10 +1,10 @@ import devWarning from '../../vc-util/devWarning'; -import { renderSlot } from 'vue'; import type { Ref } from 'vue'; import type { ContextSlots } from '../context'; import type { TransformColumns, ColumnsType } from '../interface'; import { SELECTION_COLUMN } from './useSelection'; import { EXPAND_COLUMN } from '../../vc-table'; +import { customRenderSlot } from '../../_util/vnode'; function fillSlots(columns: ColumnsType, contextSlots: Ref) { const $slots = contextSlots.value; @@ -27,7 +27,7 @@ function fillSlots(columns: ColumnsType, contextSlots: R }); if (contextSlots.value.headerCell && !column.slots?.title) { - cloneColumn.title = renderSlot( + cloneColumn.title = customRenderSlot( contextSlots.value, 'headerCell', { diff --git a/components/upload/UploadList/index.tsx b/components/upload/UploadList/index.tsx index 2834d48182..07343c52ca 100644 --- a/components/upload/UploadList/index.tsx +++ b/components/upload/UploadList/index.tsx @@ -9,7 +9,16 @@ import type { ButtonProps } from '../../button'; import Button from '../../button'; import ListItem from './ListItem'; import type { HTMLAttributes } from 'vue'; -import { computed, defineComponent, getCurrentInstance, onMounted, ref, watchEffect } from 'vue'; +import { + triggerRef, + watch, + shallowRef, + computed, + defineComponent, + onMounted, + ref, + watchEffect, +} from 'vue'; import { filterEmpty, initDefaultProps, isValidElement } from '../../_util/props-util'; import type { VueNode } from '../../_util/type'; import useConfigInject from '../../_util/hooks/useConfigInject'; @@ -39,15 +48,26 @@ export default defineComponent({ }), setup(props, { slots, expose }) { const motionAppear = ref(false); - const instance = getCurrentInstance(); onMounted(() => { motionAppear.value == true; }); + const mergedItems = shallowRef([]); + watch( + () => props.items, + (val = []) => { + mergedItems.value = val.slice(); + }, + { + immediate: true, + deep: true, + }, + ); watchEffect(() => { if (props.listType !== 'picture' && props.listType !== 'picture-card') { return; } - (props.items || []).forEach((file: InternalUploadFile) => { + let hasUpdate = false; + (props.items || []).forEach((file: InternalUploadFile, index) => { if ( typeof document === 'undefined' || typeof window === 'undefined' || @@ -62,11 +82,17 @@ export default defineComponent({ if (props.previewFile) { props.previewFile(file.originFileObj as File).then((previewDataUrl: string) => { // Need append '' to avoid dead loop - file.thumbUrl = previewDataUrl || ''; - instance.update(); + const thumbUrl = previewDataUrl || ''; + if (thumbUrl !== file.thumbUrl) { + mergedItems.value[index].thumbUrl = thumbUrl; + hasUpdate = true; + } }); } }); + if (hasUpdate) { + triggerRef(mergedItems); + } }); // ============================= Events ============================= @@ -160,7 +186,6 @@ export default defineComponent({ listType, locale, isImageUrl: isImgUrl, - items = [], showPreviewIcon, showRemoveIcon, showDownloadIcon, @@ -173,6 +198,7 @@ export default defineComponent({ appendActionVisible, } = props; const appendActionDom = appendAction?.(); + const items = mergedItems.value; return ( {items.map(file => { diff --git a/components/vc-select/BaseSelect.tsx b/components/vc-select/BaseSelect.tsx index 0b7ed9d300..fa098e93e5 100644 --- a/components/vc-select/BaseSelect.tsx +++ b/components/vc-select/BaseSelect.tsx @@ -19,7 +19,6 @@ import type { ScrollConfig, ScrollTo } from '../vc-virtual-list/List'; import { computed, defineComponent, - getCurrentInstance, onBeforeUnmount, onMounted, provide, @@ -593,10 +592,10 @@ export default defineComponent({ // ============================= Dropdown ============================== const containerWidth = ref(null); - const instance = getCurrentInstance(); + // const instance = getCurrentInstance(); const onPopupMouseEnter = () => { // We need force update here since popup dom is render async - instance.update(); + // instance.update(); }; onMounted(() => { watch( diff --git a/components/vc-table/Cell/index.tsx b/components/vc-table/Cell/index.tsx index 13bd6f3751..bb5601c805 100644 --- a/components/vc-table/Cell/index.tsx +++ b/components/vc-table/Cell/index.tsx @@ -6,7 +6,7 @@ import { parseStyleText, } from '../../_util/props-util'; import type { CSSProperties, VNodeArrayChildren } from 'vue'; -import { Text, computed, defineComponent, isVNode, renderSlot } from 'vue'; +import { Text, computed, defineComponent, isVNode } from 'vue'; import type { DataIndex, @@ -28,6 +28,7 @@ import { useInjectSticky } from '../context/StickyContext'; import { warning } from '../../vc-util/warning'; import type { MouseEventHandler } from '../../_util/EventInterface'; import eagerComputed from '../../_util/eagerComputed'; +import { customRenderSlot } from '../../_util/vnode'; /** Check if cell is in hover range */ function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: number, endRow: number) { @@ -228,7 +229,7 @@ export default defineComponent({ contextSlots.value.bodyCell && !column.slots?.customRender ) { - const child = renderSlot( + const child = customRenderSlot( contextSlots.value, 'bodyCell', {