如何让屏幕阅读器聚焦回前一个元素(React场景)
问题描述
我需要实现屏幕阅读器友好的交互:点击Return按钮后,该按钮隐藏、Submit按钮显示;点击Submit按钮后,Submit按钮隐藏、Return按钮重新显示并自动获得焦点。尝试用useRef存储Return按钮的引用,但点击Submit后无法让Return按钮聚焦,推测是因为Return按钮被卸载后,存储的是旧的DOM节点引用。
问题原因
当showBucketItem设为false时,BucketItem组件(包含Return按钮)会被完全卸载,对应的DOM节点被销毁。此时保存的ref指向的是已失效的旧DOM节点,而当showBucketItem重新设为true时,React会渲染一个全新的Return按钮DOM节点,旧ref自然无法聚焦到新节点上。
解决方案
方案一:使用useEffect监听组件渲染状态,完成后聚焦
通过useEffect监听showBucketItem的变化,当Return按钮重新渲染完成后,执行聚焦操作:
import BucketItem from "./BucketItem"; import OrderReturn from "./OrderReturn"; import { useRef, useState, useEffect } from "react"; const ParentComp = () => { const bucketItemTitle = "Item: ABC"; const returnButtonTitle = "information for return"; const returnButtonText = "Return"; const returnBtnRef = useRef(null); const [showBucketItem, setShowBucketItem] = useState(true); const [showOrderReturn, setShowOrderReturn] = useState(false); const handleClickReturnButton = () => { setShowBucketItem(false); setShowOrderReturn(true); }; const handleClickSubmitButton = () => { setShowBucketItem(true); setShowOrderReturn(false); }; // 监听showBucketItem状态,组件重新渲染后聚焦Return按钮 useEffect(() => { if (showBucketItem && returnBtnRef.current) { returnBtnRef.current.focus(); } }, [showBucketItem]); return ( <div> <h1>Order Details Page</h1> {showBucketItem && ( <BucketItem returnBtnRef={returnBtnRef} bucketItemTitle={bucketItemTitle} returnButtonTitle={returnButtonTitle} returnButtonText={returnButtonText} handleClickReturnButton={handleClickReturnButton} /> )} {showOrderReturn && ( <OrderReturn handleClickSubmitButton={handleClickSubmitButton} /> )} </div> ); }; export default ParentComp;
说明:当showBucketItem变为true时,React完成BucketItem和Return按钮的重新渲染,此时returnBtnRef会指向新的DOM节点。useEffect依赖showBucketItem,确保在组件更新完成后执行聚焦操作,保证DOM节点已存在。
方案二:通过CSS控制显示隐藏,避免组件卸载
如果不想让组件卸载,可以用display属性控制显示隐藏,保持DOM节点始终存在,ref引用不会失效:
- 修改ParentComp的返回部分:
return ( <div> <h1>Order Details Page</h1> <BucketItem returnBtnRef={returnBtnRef} bucketItemTitle={bucketItemTitle} returnButtonTitle={returnButtonTitle} returnButtonText={returnButtonText} handleClickReturnButton={handleClickReturnButton} style={{ display: showBucketItem ? 'block' : 'none' }} /> <OrderReturn handleClickSubmitButton={handleClickSubmitButton} style={{ display: showOrderReturn ? 'block' : 'none' }} /> </div> );
- 子组件接收并应用style属性:
BucketItem组件:
const BucketItem = ({ bucketItemTitle, returnButtonTitle, returnButtonText, handleClickReturnButton, returnBtnRef, style }) => { return ( <div style={style}> <div>{bucketItemTitle}</div> <ReturnButton ref={returnBtnRef} returnButtonText={returnButtonText} returnButtonTitle={returnButtonTitle} handleClickReturnButton={handleClickReturnButton} /> </div> ); };
OrderReturn组件:
const OrderReturn = ({ handleClickSubmitButton, style }) => { return ( <div style={style}> <label htmlFor="return">Ship Back</label> <input type="radio" id="return" /> <SubmitButton handleClickSubmitButton={handleClickSubmitButton} /> </div> ); };
说明:组件始终存在于DOM中,仅通过CSS控制显示/隐藏,ref引用始终有效,点击Submit后直接调用returnBtnRef.current.focus()即可完成聚焦。
额外修正
注意到原代码中BucketItem传递给ReturnButton的props存在拼写错误:returnButtonTitl应改为returnButtonTitle,修正后才能正确传递标题属性。
内容的提问来源于stack exchange,提问作者A.K

