React表单输入组件:聚焦时标签未同步添加active类的问题
问题分析
你当前实现的核心问题在于:const active = document.activeElement === internalRef.current 是组件渲染时的一次性计算值,但这个值的变化不会触发组件重新渲染。只有当组件的 props(比如输入内容变化导致props.value更新)或 state 改变时,组件才会重新计算该变量,所以标签的active类只会在输入内容时更新,无法和输入框的:focus状态同步。
解决方法
用 React 的状态来跟踪输入框的聚焦状态,通过onFocus和onBlur事件更新状态,状态变化会自动触发组件重渲染,从而让标签的active类和输入框的聚焦状态完全同步。
修改后的完整代码
import React, { forwardRef, ReactElement, ReactNode, HTMLProps, useImperativeHandle, useState } from 'react' import styles from './index.module.css' import classNames from 'classnames' import { IconFa } from '../icon-fa' import { useStableId } from '../use-stable-id' export interface Props extends HTMLProps<HTMLInputElement> { // An icon component to show on the left. icon?: ReactNode // A help text tooltip to show on the right. help?: string // A clear text tooltip to show on the right. clear?: string // Pass an onHelp click handler to show a help button. onHelp?: () => void // Pass an onClear click handler to show a clear button. onClear?: () => void errorMessage?: ReactNode label?: string hideLabel?: boolean } export const FormInput = forwardRef<HTMLInputElement, Props>(function FormInput(props, ref): ReactElement { const { id, icon, help, hideLabel, errorMessage, onHelp, onClear, ...rest } = props const internalRef = React.useRef<HTMLInputElement>(null) useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(ref, () => internalRef.current) const stableId = useStableId() const inputId = id ? id : stableId // 用状态跟踪输入框聚焦状态 const [isFocused, setIsFocused] = useState(false) const inputClassName = classNames( styles.input, icon && styles.hasLeftIcon, (help || onHelp || onClear) && styles.hasRightIcon ) const labelClassName = classNames( styles.label, Boolean(props.value) && styles.hasValue, props['aria-invalid'] && styles.hasError, // 使用聚焦状态控制active类 isFocused && styles.active, props.className ) return ( <div className={labelClassName}> {!hideLabel && ( <label className={styles.labelText} htmlFor={inputId}> {props.label} {props.required && '*'} </label> )} <div className="relative"> {icon && <div className={styles.leftIcon}>{icon}</div>} <input {...rest} id={inputId} ref={internalRef} aria-invalid={props['aria-invalid']} aria-label={props.hideLabel ? props.label : undefined} className={inputClassName} // 添加聚焦/失焦事件更新状态 onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} /> {onClear && <ClearIcon {...props} />} {!onClear && (help || onHelp) && <HelpIcon {...props} />} </div> {props['aria-invalid'] && <span className={styles.error}>{errorMessage}</span>} </div> ) }) function HelpIcon(props: Props) { if (props.onHelp) { return ( <button type="button" className={styles.rightIcon} aria-label={props.help} onClick={props.onHelp}> <IconFa icon={['far', 'info-circle']} title={props.help} /> </button> ) } return ( <div className={styles.rightIcon} title={props.help}> <IconFa icon={['far', 'info-circle']} /> </div> ) } function ClearIcon(props: Props) { return ( <button type="button" className={styles.rightIcon} aria-label={props.clear} onClick={props.onClear}> <IconFa icon={['far', 'times']} title={props.clear} /> </button> ) }
关键修改点
- 引入
useState创建isFocused状态变量,专门跟踪输入框的聚焦状态 - 给
<input>元素添加onFocus和onBlur事件,实时更新聚焦状态 - 将标签类名中的
active判断逻辑,从document.activeElement替换为isFocused状态
修改后,输入框聚焦时会立即触发状态更新,组件重渲染并给标签添加active类,和输入框的:focus样式完全同步。
内容的提问来源于stack exchange,提问作者Ludwig
相关产品推荐
相关产品推荐

