React MUI TextField使用sx正常但styled封装后异常原因
问题原因
你遇到的输入失焦、样式错乱问题,核心原因是将styled封装的自定义组件定义在了组件渲染函数内部:
- 每次输入内容触发
textValue状态更新时,组件会重渲染,渲染函数内部会重新生成一个引用完全不同的ModalTextField组件 - React对比虚拟DOM时,会判定这是一个全新的组件类型,直接卸载旧的输入框组件、挂载新的组件,直接导致输入框焦点丢失,同时样式重新计算触发渲染错乱
- 你在组件内部定义的
ClearButton、CheckButton也存在同样的隐患,只是当前触发问题的核心是styled生成的输入框组件。 - 之前使用
styled没遇到问题,是因为之前的写法大概率是把styled组件定义在组件外部,不会随重渲染重新生成。
sx属性写法正常的原因是:sx是挂载在现有组件实例上的样式配置,不会生成新的组件类型,重渲染时React只会更新现有组件的属性,不会触发卸载重挂载流程。
修复方案
- 所有通过
styled生成的自定义组件、以及独立渲染的子组件,全部移到当前组件函数的外部定义,保证组件引用在整个生命周期内稳定,不会随重渲染变化。 - 如果styled组件需要用到动态值(比如当前代码里依赖
nameRect计算宽高),通过props给styled组件传参,不要在组件内部闭包读取动态值。
修复后完整代码
import React, { useState } from 'react'; import { Box, IconButton, Modal, TextField } from '@mui/material'; import { styled } from '@mui/material/styles'; import CheckIcon from '@mui/icons-material/Check' import ClearIcon from '@mui/icons-material/Clear' // styled组件移到外部定义,通过props接收动态配置 const ModalTextField = styled(TextField)(({ sizeConfig }) => ({ backgroundColor: 'white', width: `calc(${sizeConfig.width}px - 2px)`, height: `calc(${sizeConfig.height}px - 2px)`, '& .MuiOutlinedInput-root': { borderRadius: '0rem', height: 'inherit', paddingRight: '0.25rem', fontSize: '0.875rem', } })); // 内部按钮组件也移到外部,通过props传入点击回调 const ClearButton = ({ onClick }) => ( <IconButton onClick={onClick} style={{ color: 'red'}}> <ClearIcon /> </IconButton> ); const CheckButton = ({ onClick }) => ( <IconButton onClick={onClick} style={{ color: 'green'}}> <CheckIcon /> </IconButton> ); export default function TextFieldModal(props) { const { nameRect, initialValue, handleModalClose } = props; const [openModal, setOpenModal] = useState(true); const [textValue, setTextValue] = useState(initialValue); const cancelEdit = () => { closeModal(null); }; const saveNewName = () => { closeModal(textValue); }; const closeModal = (newName) => { setOpenModal(false); console.log('closing value = ', textValue); handleModalClose(newName); }; return ( <Modal open={openModal}> <Box sx={{ position: 'absolute', top: `${nameRect.top}px`, left: `${nameRect.left}px`, width: `calc(${nameRect.width}px - 2px)`, height: `calc(${nameRect.height}px - 2px)` }}> <ModalTextField id='modal-textfield' value={textValue} onChange={(event) => setTextValue(event.target.value)} sizeConfig={nameRect} InputProps={{ endAdornment: <> <CheckButton onClick={saveNewName} /> <ClearButton onClick={cancelEdit} /> </> }} /> </Box> </Modal> ); }
内容的提问来源于stack exchange,提问作者KenPx
相关产品推荐
相关产品推荐

