MUI CustomSelect移动端无限渲染,如何传递event参数?
基于MUI实现的CustomSelect组件在桌面端运行正常,onChange绑定的handleChange函数仅打印一次日志;但在移动端(含开发模式)会触发51次渲染后崩溃,报错:
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
崩溃后会再打印一次日志。
尝试用e.preventDefault()解决,但无法在onChange中传入event,出现类型错误:
"Type '(e: any, newValue: any) => void' is not assignable to type '(value: number | null) => void'."
请问如何为CustomSelect传递event参数?
相关代码:
const CustomSelect = forwardRef(function CustomSelect( props: SelectUnstyledProps<number>, ref: React.ForwardedRef<any>, ) { const components: SelectUnstyledProps<number>['components'] = { Root: StyledButton, Listbox: StyledListbox, Popper: StyledPopper, ...props.components, }; return <SelectUnstyled {...props} ref={ref} components={components}/>; });
JSX使用代码:
<CustomSelect value={value} onChange={(e: any, newValue: any) => handleChange(e, newValue)}> {$languageOptions.map((c, i) => ( <StyledOption key={i} value={c.value}> <> .... </> </StyledOption> ))} </CustomSelect>
曾尝试扩展props类型:props: SelectUnstyledProps<number> & {event: any},,并修改返回部分:
return <SelectUnstyled {...props} ref={ref} components={components} onChange={(event) => props.onChange(event, props.event)} />;
但出现更多错误。
1. 修正CustomSelect的props类型,支持带event的onChange
首先得自定义符合需求的props类型,覆盖SelectUnstyledProps里onChange的类型,让它能接收event参数:
import { SelectUnstyledProps } from '@mui/base/SelectUnstyled'; // 自定义props类型,修改onChange的签名 type CustomSelectProps = Omit<SelectUnstyledProps<number>, 'onChange'> & { onChange?: (event: React.MouseEvent | React.KeyboardEvent, value: number | null) => void; }; const CustomSelect = forwardRef(function CustomSelect( props: CustomSelectProps, ref: React.ForwardedRef<any>, ) { const components: SelectUnstyledProps<number>['components'] = { Root: StyledButton, Listbox: StyledListbox, Popper: StyledPopper, ...props.components, }; // 包装onChange,把SelectUnstyled的事件和值传递给父组件 const handleSelectChange = (value: number | null, event: React.MouseEvent | React.KeyboardEvent) => { props.onChange?.(event, value); }; return ( <SelectUnstyled {...props} ref={ref} components={components} onChange={handleSelectChange} // 使用包装后的处理函数 /> ); });
2. 排查无限循环的根本原因
注意:e.preventDefault()大概率解决不了无限循环问题,得先找到触发多次渲染的根源:
- 检查
handleChange里是不是直接或间接调用了setState,导致组件重渲染后又触发onChange,形成循环 - 确认
value状态的更新逻辑有没有问题,是不是每次渲染都生成了新的引用(比如直接赋值对象/数组而不是用稳定引用) - 看看移动端有没有特殊的触摸事件触发逻辑,导致
SelectUnstyled的onChange被多次调用
3. 正确使用修改后的组件
现在父组件里就能正常接收event参数了:
const handleChange = (e: React.MouseEvent | React.KeyboardEvent, newValue: number | null) => { // 按需用e.preventDefault(),但先确认是不是真的需要 // e.preventDefault(); console.log(newValue); // 你的状态更新逻辑 }; <CustomSelect value={value} onChange={handleChange}> {/* 选项内容 */} </CustomSelect>
内容的提问来源于stack exchange,提问作者codmitu

