为何同样式Container与原生div包裹单选按钮时键盘导航表现不同
问题描述
- 预期效果:给单选按钮组添加宽度为200px的外层包裹容器,同时保留默认键盘导航行为:通过左右方向键即可在同组单选按钮间切换选项。
- 异常表现:
- 使用内部定义的自定义Container组件包裹单选组时,仅能完成一次选项切换,之后键盘导航完全失效,无法继续切换。自定义组件代码如下:
const Container = ({ children }) => ( <div style={{ width: "200px" }}>{children}</div> ); - 直接使用原生
<div style={{ width: "200px"}}>作为包裹元素时,键盘导航可正常运行。
- 使用内部定义的自定义Container组件包裹单选组时,仅能完成一次选项切换,之后键盘导航完全失效,无法继续切换。自定义组件代码如下:
- 排查疑点:通过开发者工具检查两种写法最终渲染的DOM结构、样式、语义完全一致,无法定位自定义组件导致功能异常的原因。
问题根因
核心问题是自定义Container组件的定义位置错误:
你将Container组件写在了RadioButtonExample函数组件的内部,每次组件因状态更新触发重渲染时,Container都会生成全新的函数引用。React的diff逻辑判定前后两次渲染的组件类型完全不同,会直接卸载Container下的所有旧DOM节点(包含全部单选按钮),重新挂载一整套全新的DOM元素。
第一次按方向键切换选项时,setValue触发组件重渲染,旧的单选按钮DOM被销毁,新挂载的单选按钮丢失了原有的焦点状态、单选组键盘导航上下文,因此无法继续响应方向键操作。
而直接书写原生<div>时,div是React内置的原生DOM类型,引用永远保持稳定,React只会更新DOM属性,不会卸载重挂整个子树,单选按钮的焦点和导航状态会完整保留,因此功能正常。
修复方案
二选一即可:
- 方案1:将Container组件移到
RadioButtonExample组件外部定义,保证组件引用在每次渲染时保持稳定,避免React误判为新组件触发整棵子树重挂。参考代码:import { Stack, RadioButton } from "@shopify/polaris"; import { useState, useCallback } from "react"; // 移到组件外层定义,引用稳定 const Container = ({ children }) => ( <div style={{ width: "200px" }}>{children}</div> ); function RadioButtonExample() { const [value, setValue] = useState("disabled"); const handleChange = useCallback( (_checked, newValue) => setValue(newValue), [] ); return ( <Container> <Stack vertical> <RadioButton label="Accounts are disabled" helpText="Customers will only be able to check out as guests." checked={value === "disabled"} id="disabled" name="accounts" onChange={handleChange} /> <RadioButton label="Accounts are optional" helpText="Customers will be able to check out with a customer account or as a guest." id="optional" name="accounts" checked={value === "optional"} onChange={handleChange} /> </Stack> </Container> ); } export default RadioButtonExample; - 方案2:如果Container组件没有复用需求,直接使用原生div包裹单选组即可,减少不必要的组件嵌套。
内容的提问来源于stack exchange,提问作者Judoboy Alex
相关产品推荐
相关产品推荐

