实习小记4.25(antd中upload问题)

131 阅读1分钟

前两天在写一个修改头像的需求,点击用户的头像之后弹出一个Modal,用户可以选择不同的默认头像,也可以自己上传。

image.png
需求:点击用户头像之后出来的Modal默认选中第一个默认头像,点击框内哪一个默认头像,哪个默认头像就被选中(选中后的样式:外面显示一个圆形橙色边框),也可以自定义上传头像,然后点击OK按钮之后进行同步用户头像信息
实现方法:用useState维护选中的头像的index,自定义头像设置为-1 代码展示:

import {  Avatar, Modal, Upload } from 'antd'
import NiceModal, { useModal } from '@ebay/nice-modal-react'
import {useState } from 'react'
import IconFont from '../../utils/icont'
import './index.css'
const SetAvatarModal = () => {
  console.log('刷新---SetAvatarModal')
  const [nowFocusAvatar, setNowFocusAvatar] = useState(0)
  const modal = useModal()
  const handleOnOk = () => {
    //这里要用到nowFocusAvatar
    console.log("nowFocusAvatar")
    console.log('ok')
    modal.remove()
  }
  const handleOnCancel = () => {
    console.log('cancel')
    modal.remove()
  }

  const changeDefaultAvatar = (index) => {
    console.log(index)
    setNowFocusAvatar(index)
  }

  const beforeUpload = () => {
    console.log("beforeUpload")
    return true
  }
  const customRequest = () => {
    console.log("customRequest")
  }

  const ModalContent = () => {
    console.log('刷新-----ModalContent')
  
    return (
      <>
        <div>
          <div>默认头像</div>
          <div className="w-[100%] h-[1px] mt-[10px] mb-[7px] bg-[#ECEEF2]"></div>
          <div className="flex justify-between mb-[17px]">
            {new Array(5).fill(0).map((_, index) => (
              <div
              key={index}
              className={`${index === nowFocusAvatar ? 'showborder' : ''} w-[66px] h-[66px] flex justify-center items-center rounded-[33px] cursor-pointer`}
              onClick={() => changeDefaultAvatar(index)}
              >
                <Avatar size={60} src={`/logo192.png`} />
              </div>
            ))}
          </div>
          <div>自定义头像</div>
            <Upload
            beforeUpload={beforeUpload}
            customRequest={customRequest}
           
            >
               <div className='w-[60px] h-[60px] flex justify-center items-center rounded-[30px] cursor-pointer border border-[#bab9b7] '
                onClick={() => changeDefaultAvatar(-1)}>
               <IconFont  type="icon-baobiao-"/>
        </div>
            </Upload>
          </div>
      </>
    )
  }
  return (
    <Modal open={modal.visible} width={404} onOk={handleOnOk} onCancel={handleOnCancel} destroyOnClose={true}>
      <ModalContent />
    </Modal>
  )
}

export default NiceModal.create(SetAvatarModal)

遇到的问题:在第一次点击自定义头像的时候没有触发beforeUploadcustomRequest函数,但是能够打开图片选择框,在第二次点击的时候才有执行beforeUploadcustomRequest函数

screen_recording_2025-04-25_11-13-58.gif 出现问题的原因:点击的时候改变了useState,所以会造成SetAvatarModal组件重新渲染,<upload/>是子组件,只要父组件重新渲染,子组件也会重新渲染。
注意:在这里我一直有一个错误的理解:reactdom diff 算法会自动对比哪些dom发生了变化,之后只重新渲染有变化的部分。这个理解是错误的,虽然 dom diff做了优化,但是如果父组件重新渲染,那么子组件也会重新渲染,这是react的设置.

不好的解决方式:把Upload下面的div绑定的点击函数取消,然后在customRequest函数中设置nowFocusAvatar的值为-1,可以解决问题,但是又出现了新的现象。
新的问题:第一次点击自定义头像没有展示进度条,但是第二次点击自定义头像却显示了进度条。不过这个问题不是很大,第一次点击的时候就能够拿到File文件信息

screen_recording_2025-04-25_11-15-43.gif

新问题的原因: 本质上还是upload组件重新渲染导致的

那么怎么解决呢???

使用React.memo进行包裹子组件,如果子组件需要给父组件传值,那么给子组件传入一个回调函数,但是这个回调函数要用useCallback包裹,不然子组件因为每次父组件选然后都重新创建了回调函数,导致传入给子组件的props变化,子组件一样会发生变化。
代码展示:

import {  Avatar, Modal, Upload } from 'antd'
import NiceModal, { useModal } from '@ebay/nice-modal-react'
import React, { useState, useCallback } from 'react'
import IconFont from '../../utils/icont'
import './index.css'

const UploadCom = React.memo((props) => {
  const {onCustomRequestResult} = props
  console.log("UploadCom")
  const beforeUpload = (file) => {
    console.log("beforeUpload")
    console.log(file)
    return true
  }
  const customRequest = (file) => {
    console.log("customRequest")
    onCustomRequestResult(file)
    // setNowFocusAvatar(-1)
  }
  return (
    <Upload
    beforeUpload={beforeUpload}
    customRequest={customRequest}
    >
       <div className={ `w-[60px] h-[60px] flex justify-center items-center rounded-[30px] cursor-pointer border border-[#bab9b7] `}
     >
       <IconFont  type="icon-baobiao-"/>
</div>
     </Upload>
  )
})

const SetAvatarModal = () => {
  console.log('刷新---SetAvatarModal')
  const [nowFocusAvatar, setNowFocusAvatar] = useState(0)
  const modal = useModal()
  const handleOnOk = () => {
    //这里要用到nowFocusAvatar
    console.log("nowFocusAvatar")
    console.log('ok')
    modal.remove()
  }
  const handleOnCancel = () => {
    console.log('cancel')
    modal.remove()
  }

  const handleCustomRequestResult = useCallback((e) => {
    console.log("我是upload传过来的值", e)
  }, []) 

  const changeDefaultAvatar = (index) => {
    console.log(index)
    setNowFocusAvatar(index)
  }



  return (
    <Modal open={modal.visible} width={404} onOk={handleOnOk} onCancel={handleOnCancel} destroyOnClose={true}>
       <div>
          <div>默认头像</div>
          <div className="w-[100%] h-[1px] mt-[10px] mb-[7px] bg-[#ECEEF2]"></div>
          <div className="flex justify-between mb-[17px]">
            {new Array(5).fill(0).map((_, index) => (
              <div
              key={index}
              className={`${index === nowFocusAvatar ? 'showborder' : ''} w-[66px] h-[66px] flex justify-center items-center rounded-[33px] cursor-pointer`}
              onClick={() => changeDefaultAvatar(index)}
              >
                <Avatar size={60} src={`/logo192.png`} />
              </div>
            ))}
          </div>
          <div>自定义头像</div>
          <div className={ `${-1 === nowFocusAvatar ? 'showborder' : ''} w-[66px] h-[66px] flex justify-center items-center rounded-[30px] cursor-pointer border border-[#bab9b7] `}
               onClick={() => changeDefaultAvatar(-1)}
             >
           <UploadCom onCustomRequestResult={handleCustomRequestResult}/>
            </div>
          </div>
    </Modal>
  )
}

export default NiceModal.create(SetAvatarModal)

我写的demo: git仓库链接
以上问题对应这个文件:my-react-app\src\components\indModal