Skip to content

refactor(components): [upload] use type-based definitions#23442

Merged
btea merged 6 commits into
element-plus:devfrom
wjp980108:refactor/upload-type
Jan 19, 2026
Merged

refactor(components): [upload] use type-based definitions#23442
btea merged 6 commits into
element-plus:devfrom
wjp980108:refactor/upload-type

Conversation

@wjp980108
Copy link
Copy Markdown
Contributor

@wjp980108 wjp980108 commented Jan 18, 2026

Please make sure these boxes are checked before submitting your PR, thank you!
rel #23399

  • Make sure you follow contributing guide English | (中文 | Español | Français).
  • Make sure you are merging your commits to dev branch.
  • Add some descriptions and refer to relative issues for your PR.

Summary by CodeRabbit

  • New Features

    • Upload components expose typed, public prop interfaces with default values for lifecycle callbacks, file handling, and upload options.
  • Bug Fixes

    • Improved defaulting and non-null handling to reduce runtime errors when props or handlers are missing.
  • Deprecations

    • Old public prop/type aliases are deprecated; migrate to the new typed interfaces and provided defaults.

✏️ Tip: You can customize this high-level summary in your review settings.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 18, 2026

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 18, 2026

📝 Walkthrough

Walkthrough

Replaces 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

Cohort / File(s) Summary
Core types & props
packages/components/upload/src/upload.ts
Added public types (ListType, Crossorigin, UploadBaseProps, UploadProps), deprecated prop builders (uploadBaseProps, uploadProps) and public alias UploadPropsPublic via ExtractPublicPropTypes; added uploadBasePropsDefaults/uploadPropsDefaults; removed ExtractPropTypes import.
Upload content
packages/components/upload/src/upload-content.ts, packages/components/upload/src/upload-content.vue
Introduced UploadContentProps interface and UploadContentPropsPublic alias; added uploadContentPropsDefaults; component switched to defineProps<UploadContentProps>() + withDefaults(uploadContentPropsDefaults) and changed resolveData to return data!.
Dragger
packages/components/upload/src/upload-dragger.ts, packages/components/upload/src/upload-dragger.vue
Added UploadDraggerProps interface and UploadDraggerPropsPublic; removed runtime uploadDraggerProps import and switched component to typed defineProps<UploadDraggerProps>() withDefaults (default disabled).
List
packages/components/upload/src/upload-list.ts, packages/components/upload/src/upload-list.vue
Added UploadListProps interface and UploadListPropsPublic; uploadListProps extended with defaults (files, disabled, handlePreview, listType); exported UploadListEmits/UploadListInstance; component uses typed props + withDefaults and mutable/NOOP defaults.
Upload root component
packages/components/upload/src/upload.vue
Replaced defineProps(uploadProps) with withDefaults(defineProps<UploadProps>(), uploadPropsDefaults); imports adjusted to use UploadProps and uploadPropsDefaults (many default values including ajaxUpload, NOOP callbacks, mutable lists).
Handlers typing
packages/components/upload/src/use-handlers.ts
Tightened useHandlers parameter to `UploadProps & Required<Pick<UploadProps, 'listType'
Public defaults & exports
packages/components/upload/src/*.ts
Added multiple ...Defaults export constants (e.g., uploadContentPropsDefaults, uploadBasePropsDefaults) providing NOOP/mutable defaults for newly typed props; added/updated deprecated documentation comments for old runtime prop builders.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • btea
  • keeplearning66

Poem

🐇 I hop through props with whiskers bright,
I stitch interfaces by moonlit light,
NOOP crumbs and mutable trails I sow,
Defaulted ears and types in a row,
A carrot-coded cheer — onward we go! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor(components): [upload] use type-based definitions' clearly and concisely summarizes the main change: refactoring the upload component to use type-based definitions instead of the previous prop object approach.
Description check ✅ Passed The PR description includes the contributing guide checklist, references a related issue (#23399), and confirms merging to the dev branch, meeting the template requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jan 18, 2026

Open in StackBlitz

pnpm add https://pkg.pr.new/element-plus/element-plus@23442
npm i https://pkg.pr.new/element-plus/element-plus@23442
yarn add https://pkg.pr.new/element-plus/[email protected]

commit: a2dd4f6

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and onRemove assume these optional callbacks are always defined. While the component's current implementation provides NOOP defaults via withDefaults(), using optional chaining makes the code more defensive and safer if useHandlers is 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: Fix fileList default to include the as const type assertion for parity with uploadBaseProps.

The inline default for fileList is missing the as const assertion. In uploadBaseProps, it's defined as () => mutable([] as const), but in the component's withDefaults it'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 uploadListTypes tuple so the union can’t drift.

♻️ Suggested tweak
-export type ListType = 'text' | 'picture' | 'picture-card'
+export type ListType = (typeof uploadListTypes)[number]

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 18, 2026

🧪 Playground Preview: https://element-plus.run/?pr=23442
Please comment the example via this playground if needed.

@wjp980108
Copy link
Copy Markdown
Contributor Author

image @btea @rzzf It works fine on my local machine. How do I re-enable the check?

@rzzf
Copy link
Copy Markdown
Member

rzzf commented Jan 18, 2026

It works fine on my local machine. How do I re-enable the check?

Don't worry, CI is often unstable.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 for disabled.

Line 136 sets disabled: undefined, but Vue boolean props already default to false when 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, ...

Comment thread packages/components/upload/src/use-handlers.ts Outdated
@btea btea merged commit 53134da into element-plus:dev Jan 19, 2026
17 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

@wjp980108 Thanks for your contribution! ❤️

@wjp980108 wjp980108 deleted the refactor/upload-type branch January 20, 2026 07:17
@element-bot element-bot mentioned this pull request Jan 30, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants