前两天在写一个修改头像的需求,点击用户的头像之后弹出一个Modal,用户可以选择不同的默认头像,也可以自己上传。
需求:点击用户头像之后出来的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)
遇到的问题:在第一次点击自定义头像的时候没有触发beforeUpload和customRequest函数,但是能够打开图片选择框,在第二次点击的时候才有执行beforeUpload和customRequest函数
出现问题的原因:点击的时候改变了
useState,所以会造成SetAvatarModal组件重新渲染,<upload/>是子组件,只要父组件重新渲染,子组件也会重新渲染。
注意:在这里我一直有一个错误的理解:react的 dom diff 算法会自动对比哪些dom发生了变化,之后只重新渲染有变化的部分。这个理解是错误的,虽然 dom diff做了优化,但是如果父组件重新渲染,那么子组件也会重新渲染,这是react的设置.
不好的解决方式:把Upload下面的div绑定的点击函数取消,然后在customRequest函数中设置nowFocusAvatar的值为-1,可以解决问题,但是又出现了新的现象。
新的问题:第一次点击自定义头像没有展示进度条,但是第二次点击自定义头像却显示了进度条。不过这个问题不是很大,第一次点击的时候就能够拿到File文件信息
新问题的原因: 本质上还是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