refactor(components): [upload] use type-based definitions#23442
Conversation
📝 WalkthroughWalkthroughReplaces runtime Vue prop objects and ExtractPropTypes with explicit TypeScript public interfaces and ExtractPublicPropTypes across the upload package; components now use typed defineProps() with withDefaults and provided defaults, add deprecation wrappers for old prop builders, and narrow handler typings. (≤50 words) Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/components/upload/src/use-handlers.ts (1)
61-114: Use optional chaining for optional callback invocations to avoid runtime errors.The non-null assertions on
onChange,onError,onProgress,onSuccess, andonRemoveassume these optional callbacks are always defined. While the component's current implementation provides NOOP defaults viawithDefaults(), using optional chaining makes the code more defensive and safer ifuseHandlersis called elsewhere without guaranteed defaults.Safer pattern with optional chaining
const emitChange = (file: UploadFile) => { - nextTick(() => props.onChange!(file, uploadFiles.value)) + nextTick(() => props.onChange?.(file, uploadFiles.value)) } const handleError: UploadContentProps['onError'] = (err, rawFile) => { const file = getFile(rawFile) if (!file) return console.error(err) file.status = 'fail' removeFile(file) - props.onError!(err, file, uploadFiles.value) + props.onError?.(err, file, uploadFiles.value) emitChange(file) } const handleProgress: UploadContentProps['onProgress'] = (evt, rawFile) => { const file = getFile(rawFile) if (!file) return - props.onProgress!(evt, file, uploadFiles.value) + props.onProgress?.(evt, file, uploadFiles.value) file.status = 'uploading' file.percentage = Math.round(evt.percent) } const handleSuccess: UploadContentProps['onSuccess'] = (response, rawFile) => { const file = getFile(rawFile) if (!file) return file.status = 'success' file.response = response - props.onSuccess!(response, file, uploadFiles.value) + props.onSuccess?.(response, file, uploadFiles.value) emitChange(file) } const handleStart: UploadContentProps['onStart'] = (file) => { if (isNil(file.uid)) file.uid = genFileId() const uploadFile: UploadFile = { name: file.name, percentage: 0, status: 'ready', size: file.size, raw: file, uid: file.uid, } if (props.listType === 'picture-card' || props.listType === 'picture') { try { uploadFile.url = URL.createObjectURL(file) } catch (err: unknown) { debugWarn(SCOPE, (err as Error).message) - props.onError!(err as Error, uploadFile, uploadFiles.value) + props.onError?.(err as Error, uploadFile, uploadFiles.value) } } uploadFiles.value = [...uploadFiles.value, uploadFile] emitChange(uploadFile) } const handleRemove: UploadContentProps['onRemove'] = async (file): Promise<void> => { const uploadFile = file instanceof File ? getFile(file) : file if (!uploadFile) throwError(SCOPE, 'file to be removed not found') const doRemove = (file: UploadFile) => { abort(file) removeFile(file) - props.onRemove!(file, uploadFiles.value) + props.onRemove?.(file, uploadFiles.value) revokeFileObjectURL(file) } if (props.beforeRemove) { const before = await props.beforeRemove(uploadFile, uploadFiles.value) if (before !== false) doRemove(uploadFile) } else { doRemove(uploadFile) } } watch( () => props.listType, (val) => { if (val !== 'picture-card' && val !== 'picture') { return } uploadFiles.value = uploadFiles.value.map((file) => { const { raw, url } = file if (!url && raw) { try { file.url = URL.createObjectURL(raw) } catch (err: unknown) { - props.onError!(err as Error, file, uploadFiles.value) + props.onError?.(err as Error, file, uploadFiles.value) } } return file }) } )packages/components/upload/src/upload-content.vue (1)
45-89: FixfileListdefault to include theas consttype assertion for parity withuploadBaseProps.The inline default for
fileListis missing theas constassertion. InuploadBaseProps, it's defined as() => mutable([] as const), but in the component'swithDefaultsit's() => mutable([]). Align the two to maintain type consistency.Current mismatch
uploadBaseProps (upload.ts:277):
fileList: { type: definePropType<UploadUserFile[]>(Array), default: () => mutable([] as const), }upload-content.vue (withDefaults):
fileList: () => mutable([]),Should be:
fileList: () => mutable([] as const),
🧹 Nitpick comments (1)
packages/components/upload/src/upload.ts (1)
86-86: Avoid duplicated list-type unions.Derive the type from the existing
uploadListTypestuple so the union can’t drift.♻️ Suggested tweak
-export type ListType = 'text' | 'picture' | 'picture-card' +export type ListType = (typeof uploadListTypes)[number]
|
🧪 Playground Preview: https://element-plus.run/?pr=23442 |
Don't worry, CI is often unstable. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/components/upload/src/use-handlers.ts`:
- Around line 28-41: The props contract for useHandlers doesn't require fileList
but useHandlers (and useVModel inside it) expects fileList as an array; update
the Required<Pick<...>> type passed into useHandlers to include 'fileList' from
UploadProps so callers must provide it (e.g., add 'fileList' to the Pick union),
then remove any unsafe casts relying on fileList being present; ensure the code
paths in useHandlers and any useVModel usage now treat props.fileList as the
required array type.
🧹 Nitpick comments (1)
packages/components/upload/src/upload-list.vue (1)
134-138: Remove redundant boolean default fordisabled.Line 136 sets
disabled: undefined, but Vue boolean props already default tofalsewhen not provided, so this default adds noise without changing behavior. Consider dropping it for clarity.♻️ Suggested simplification
const props = withDefaults(defineProps<UploadListProps>(), { files: () => mutable([]), - disabled: undefined, handlePreview: NOOP, listType: 'text', })Based on learnings, ...
|
@wjp980108 Thanks for your contribution! ❤️ |


Please make sure these boxes are checked before submitting your PR, thank you!
rel #23399
devbranch.Summary by CodeRabbit
New Features
Bug Fixes
Deprecations
✏️ Tip: You can customize this high-level summary in your review settings.