无法重写JavaScript变量
我有一个React组件,当用户在搜索栏中键入时,它会显示一个“自动完成”建议列表。我试图允许用户也通过使用箭头键和enter键来浏览列表 当用户第一次按下向下键时,focus变量增加1,HTML焦点转到第一个推荐。如果他们再次按下该键,它将转到focus变量,该变量将再次增加1,HTML焦点将移至第二个建议,依此类推。类似地,当用户按下向上键时,它会将焦点减少1,并将HTML焦点移动到上一个推荐位置。此外,还有一些措施可以确保焦点索引不超出可能的范围,以及在搜索栏的值更改时将焦点索引重置为零 我遇到了这样一个奇怪的错误:当覆盖焦点索引值时,它只有在使用向下键时才能正常工作,然后当我按下向上键时,它将更改为覆盖之前的值形式。在某些情况下,这可能会导致焦点索引超出范围,然后崩溃,因为当您键入更长的搜索时,它会缩小建议列表 以下是仅搜索一个字母时焦点索引控制台输出的一些屏幕截图: 现在,我将展示缩小搜索范围,然后浏览建议时发生的情况: 下面是一个工作代码沙盒链接: 问题 好的,这正是我所怀疑的。在“keyup”事件处理程序中包含了一些无法重写JavaScript变量,javascript,reactjs,Javascript,Reactjs,我有一个React组件,当用户在搜索栏中键入时,它会显示一个“自动完成”建议列表。我试图允许用户也通过使用箭头键和enter键来浏览列表 当用户第一次按下向下键时,focus变量增加1,HTML焦点转到第一个推荐。如果他们再次按下该键,它将转到focus变量,该变量将再次增加1,HTML焦点将移至第二个建议,依此类推。类似地,当用户按下向上键时,它会将焦点减少1,并将HTML焦点移动到上一个推荐位置。此外,还有一些措施可以确保焦点索引不超出可能的范围,以及在搜索栏的值更改时将焦点索引重置为零
focus
值,该值独立于在主组件主体中的每个渲染周期重置的focus
以及在依赖项更新时的useffect
钩子而变化
您的codesandbox中甚至有一个react lint警告:
从内部分配到“焦点”变量的效果
将在每次渲染后丢失。要随时间保留该值,请存储
将其保存在useRef挂钩中,并将可变值保留在“.current”中
财产
解决方案
useffect
钩子来管理“keyup”事件侦听器,并在组件卸载时清除它。这也将防止每次效果回调触发时都添加一个新的侦听器(您的代码在每次渲染时都添加了一个侦听器!!)“autocomplete\uu container”
容器,因为直接DOM查询和操作在React中被视为反模式函数应用程序(){
const[value,setValue]=useState(“”);
const[items,setItems]=useState([]);
const[query,setQuery]=useState([]);
const[formSubmit,setFormSubmit]=useState(false);
常量[完成]=使用自动完成(值,自动完成值);
const autocompleteContainer=useRef();//{
如果(event.keyCode===38&&focusRef.current>=1){/focus
可能真的应该存储在React ref中,这样它可以在渲染中保持,而不是在每个渲染中重置为-1
。每次触发效果回调时,您也会添加一个新的事件侦听器,但您永远不会删除它们(从代码段中删除AFAIK)。您是否可以将此代码放入运行的代码沙盒中,以便我们进行实时调试?@DrewReese是的,我会立即将其取出。但是,我不希望它在渲染过程中保持不变,因为例如,您在列表底部附近导航,然后决定输入另一个键。然后列表会变短,因此焦点索引会变短是否应重置为第一个建议?如果您的组件因任何原因重新加载,焦点索引将重置。您可能希望它保持不变,除非您的列表大小更新,此时您应该检查边界、重置等。我还想知道这是什么类型的选择输入,因为我所知道的大多数选择输入都应该是键盘accessible.@Drewrese我不相信它会以任何理由呈现,因为依赖项只是自动完成容器、值和项。所有这些值都可以以某种方式更改建议。整个程序中还有很多变量,但我没有将它们包含在这个代码段中。您有一个让焦点=-1;
装备ht在组件的函数体中,因此,如果有什么原因导致组件重新运行,比如父组件重新运行,那么焦点
将被设置回-1
。可能实际上没有任何东西触发组件重新运行,但您不应该指望。再次感谢您提供的伟大解决方案。我看到它在然而,当我在我的机器上更改代码时,我得到“TypeError:无法读取未定义的'childElementCount'属性”带着对其他人的暗示if@JSON_Derulo如果选择将自动完成容器
转换为React ref,则它不再是先前由document.getElementsByClassName
返回的数组。它应该是自动完成容器.current.childElementCount
,
function App() {
const [value, setValue] = useState("");
const [items, setItems] = useState([]);
const [query, setQuery] = useState([]);
const [formSubmit, setFormSubmit] = useState(false);
const [completions] = useAutocomplete(value, autocompleteValues);
const autocompleteContainer = useRef(); // <-- ref to get DOMNode
const focusRef = useRef(-1); // <-- ref to store stable focus value
...
// Effect hook to manage keyup event handler and cleanup
useEffect(() => {
const handleKeypress = (event) => {
if (event.keyCode === 38 && focusRef.current >= 1) { // <-- focus.current
focusRef.current -= 1;
autocompleteContainer.current.childNodes[focusRef.current].focus(); // <-- autocompleteContainer.current
} else if (
event.keyCode === 40 &&
focusRef.current < autocompleteContainer.current.childElementCount - 1
) {
focusRef.current += 1;
autocompleteContainer.current.childNodes[focusRef.current].focus();
} else if (
event.keyCode === 13 &&
document.activeElement.className !== "form-control"
) {
setValue(document.activeElement.innerHTML);
setQuery(document.activeElement.innerHTML);
document.getElementsByClassName(
"autocomplete__container"
)[0].style.display = "none";
setFormSubmit(true);
}
};
window.addEventListener("keyup", handleKeypress); // <-- add listener
return () => window.removeEventListener("keyup", handleKeypress); // <-- return cleanup function to remove listener
}, []);
// Effect hook to reset focus value when list updates
useEffect(() => {
focusRef.current = -1;
console.log("focus reset", focusRef.current);
}, [value, items]);
return (
<div className="App">
<Form id="search__form" onSubmit={handleSubmit}>
<Form.Group>
<InputGroup className="mb-3">
...
<div
ref={autocompleteContainer} // <-- attach DOM ref here
className="autocomplete__container"
>
{completions.map((val, index) => (
<p
tabIndex={index + 10}
key={index}
onClick={() => updateInput({ val })}
>
{val}
</p>
))}
</div>
</InputGroup>
</Form.Group>
</Form>
<p>{focusRef.current}</p>
</div>
);
}