无法重写JavaScript变量

无法重写JavaScript变量,javascript,reactjs,Javascript,Reactjs,我有一个React组件,当用户在搜索栏中键入时,它会显示一个“自动完成”建议列表。我试图允许用户也通过使用箭头键和enter键来浏览列表 当用户第一次按下向下键时,focus变量增加1,HTML焦点转到第一个推荐。如果他们再次按下该键,它将转到focus变量,该变量将再次增加1,HTML焦点将移至第二个建议,依此类推。类似地,当用户按下向上键时,它会将焦点减少1,并将HTML焦点移动到上一个推荐位置。此外,还有一些措施可以确保焦点索引不超出可能的范围,以及在搜索栏的值更改时将焦点索引重置为零

我有一个React组件,当用户在搜索栏中键入时,它会显示一个“自动完成”建议列表。我试图允许用户也通过使用箭头键和enter键来浏览列表

当用户第一次按下向下键时,focus变量增加1,HTML焦点转到第一个推荐。如果他们再次按下该键,它将转到focus变量,该变量将再次增加1,HTML焦点将移至第二个建议,依此类推。类似地,当用户按下向上键时,它会将焦点减少1,并将HTML焦点移动到上一个推荐位置。此外,还有一些措施可以确保焦点索引不超出可能的范围,以及在搜索栏的值更改时将焦点索引重置为零

我遇到了这样一个奇怪的错误:当覆盖焦点索引值时,它只有在使用向下键时才能正常工作,然后当我按下向上键时,它将更改为覆盖之前的值形式。在某些情况下,这可能会导致焦点索引超出范围,然后崩溃,因为当您键入更长的搜索时,它会缩小建议列表

以下是仅搜索一个字母时焦点索引控制台输出的一些屏幕截图:

现在,我将展示缩小搜索范围,然后浏览建议时发生的情况:

下面是一个工作代码沙盒链接:

问题 好的,这正是我所怀疑的。在“keyup”事件处理程序中包含了一些
focus
值,该值独立于在主组件主体中的每个渲染周期重置的
focus
以及在依赖项更新时的
useffect
钩子而变化

您的codesandbox中甚至有一个react lint警告:

从内部分配到“焦点”变量的效果 将在每次渲染后丢失。要随时间保留该值,请存储 将其保存在useRef挂钩中,并将可变值保留在“.current”中 财产

解决方案
  • 将“焦点”值存储在React ref中,以便所有回调实例和挂钩都可以引用相同的稳定值
  • 我还建议使用一个单独的
    useffect
    钩子来管理“keyup”事件侦听器,并在组件卸载时清除它。这也将防止每次效果回调触发时都添加一个新的侦听器(您的代码在每次渲染时都添加了一个侦听器!!)
  • 我还建议使用React ref引用
    “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>
      );
    }