【问题标题】:How do I implement a function to navigate through my react table using arrow keys?如何实现使用箭头键浏览我的反应表的功能?
【发布时间】:2021-05-17 15:25:40
【问题描述】:

我在 react 中有一个 15 x 15 的表格,每个单元格都包含输入字段。我想实现一个功能,这样每次我按下箭头键时,它都会将焦点移到那个方向。到目前为止,这是我的董事会。 任何帮助表示赞赏!

 let rows = [];
 for (var i = 0; i < 15; i++){
   let rowID = `row${i}`
   let cell = []
   for (var idx = 0; idx < 15; idx++){
     let cellID = `cell${i}-${idx}`
     let row = parseInt(`${i}`)
     let col = parseInt(`${idx}`)
     cell.push(
      <td key={cellID} id={cellID}>
         <div className={"tile"}>
              <input>
              </input>
           </div>
        </td>)
      }
   rows.push(<tr key={i} id={rowID}>{cell}</tr>)
 }
 return (
  <div className="board">
   <table>
    {rows}
   </table>
  </div>
  );
}```

【问题讨论】:

    标签: javascript html reactjs


    【解决方案1】:

    要添加键盘控件,需要处理以下内容:

    • 了解哪个单元格处于活动状态 (useState)
    • 了解用户是在编辑、导航还是两者都没有 (useState)
    • 存储输入值 (useState)
    • 对板的引用 (useRef)
    • 对输入元素的引用 (useRef)
    • 处理 mousedown 和 keydown 事件(事件侦听器,useEffect)
    • 处理导航、编辑、索引等更改的副作用(useEffect、useCallback)
    • 为用户提供他们正在导航与编辑 (CSS) 的可视化效果

    这是我添加用户控件的方法,如果您愿意的话。我敢打赌,您会更喜欢实施自己的解决方案。我确信这段代码可以被清理,但这是第一次通过。

    你可以试试演示here

    import React, { useCallback, useEffect, useRef, useState } from "react";
    
    const SimpleTable = () => {
      const [numRows, numCols] = [3, 3]; // No magic numbers
      const [activeIndex, setActiveIndex] = useState(-1); // Track which cell to highlight
      const [isNavigating, setIsNavigating] = useState(false); // Track navigation
      const [isEditing, setIsEditing] = useState(false); // Track editing
      const [values, setValues] = useState([]); // Track input values
      const boardRef = useRef(); // For setting/ unsetting navigation
      const inputRefs = useRef([]); // For setting / unsetting input focus
    
      // Handle input changes to store the new value
      const handleChange = (e) => {
        const { value } = e;
        const newValues = Array.from(values);
        newValues[activeIndex] = value;
        setValues(newValues);
      };
    
      // Handle mouse down inside or outside the board
      const handleMouseDown = useCallback(
        (e) => {
          if (boardRef.current && boardRef.current.contains(e.target)) {
            if (e.target.className === "cell-input") {
                setIsNavigating(true);
                setIsEditing(true);
            }
          } else {
                setIsNavigating(false);
          }
        },
        [boardRef, setIsNavigating]
      );
    
      // Handle key presses: 
      // arrows to navigate, escape to back out, enter to start / end editing
      const handleKeyDown = useCallback(
        (e) => {
          if (isNavigating) {
            const { key } = e;
            switch (key) {
                case "ArrowUp":
                    // Move up a row, subtract num cols from index
                    if (!isEditing && activeIndex >= numRows)
                        setActiveIndex(activeIndex - numCols);
                    break;
                case "ArrowDown":
                    // Move down a row, add num cols to index
                    if (!isEditing && activeIndex < numRows * numCols - numCols)
                        setActiveIndex(activeIndex + numCols);
                    break;
                case "ArrowRight":
                    // Move one col right, add one
                    if (!isEditing && activeIndex < numRows * numCols - 1)
                        setActiveIndex(activeIndex + 1);
                    break;
                case "ArrowLeft":
                    // Move one col left, subtract one
                    if (!isEditing && activeIndex > 0) setActiveIndex(activeIndex - 1);
                    break;
                case "Enter":
                    if (isEditing) setIsEditing(false);
                    else if (isNavigating) setIsEditing(true);
                    else if (!isEditing) setIsNavigating(true);
                    break;
                case "Escape":
                    // Stop navigating
                    if (isEditing) setIsEditing(false);
                    else if (isNavigating) setIsNavigating(false);
                    break;
                default:
                    break;
            }
          }
        },
        [activeIndex, isNavigating, isEditing, numRows, numCols]
      );
    
      // Add listeners on mount, remove on unmount
      useEffect(() => {
        window.addEventListener("mousedown", handleMouseDown);
        window.addEventListener("keydown", handleKeyDown);
    
        return () => {
            window.removeEventListener("mousedown", handleMouseDown);
            window.removeEventListener("keydown", handleKeyDown);
        };
      }, [handleMouseDown, handleKeyDown]);
    
      // When the index changes, determine if we should focus or blur the current input
      const onIndexChange = useCallback(() => {
        if (activeIndex >= 0 && activeIndex < numRows * numCols) {
            const inputRef = inputRefs.current[activeIndex];
            if (inputRef) {
                if (isEditing) {
                    inputRef.focus();
                } else {
                    inputRef.blur();
                }
            }
        }
      }, [activeIndex, isEditing, inputRefs, numRows, numCols]);
      useEffect(onIndexChange, [activeIndex, onIndexChange]);
    
      // When isEditing changes focus or blur the current input
      const onIsEditingChange = useCallback(() => {
        const inputRef = inputRefs.current[activeIndex];
        if (!inputRef) return;
    
        if (isNavigating && isEditing) {
            inputRef.focus();
        } else if (!isEditing) {
            inputRef.blur();
        }
      }, [inputRefs, isEditing, activeIndex, isNavigating]);
      useEffect(onIsEditingChange, [isEditing, onIsEditingChange]);
    
      // When isNavigating changes, set the index to 0 or -1 (if not navigating)      
      const onIsNavigatingChange = useCallback(() => {
        if (!isNavigating) {
            setActiveIndex(-1);
        } else if (activeIndex < 0) {
            setActiveIndex(0);
        }
      }, [isNavigating, setActiveIndex, activeIndex]);
      useEffect(onIsNavigatingChange, [isNavigating, onIsNavigatingChange]);
      
      // Your original code with minor changes
      let rows = [];
      for (var i = 0; i < numRows; i++) {
        let rowID = `row${i}`;
        let cell = [];
        for (var idx = 0; idx < numCols; idx++) {
            let cellID = `cell${i}-${idx}`;
            const index = i * numCols + idx;
            cell.push(
                <td key={cellID} id={cellID}>
                <div className={`tile ${activeIndex === index ? "active" : ""}`}>
                    <input
                    value={values[activeIndex]}
                    onChange={handleChange}
                    className="cell-input"
                    onFocus={() => setActiveIndex(index)}
                    ref={(el) => (inputRefs.current[index] = el)}
                    />
                </div>
                </td>
            );
        }
        rows.push(
            <tr key={i} id={rowID}>
                {cell}
            </tr>
        );
      }
    
      return (
        <div className="board" ref={boardRef}>
            <table>
                <tbody>{rows}</tbody>
            </table>
        </div>
      );
    };
    
    export default SimpleTable;
    

    还有我用来显示哪个单元格处于活动状态的小 CSS:

    .tile.active {
      border: 1px solid rgb(0, 225, 255);
    }
    
    .tile {
      border: 1px solid black;
    }
    
    .cell-input {
      border: none;
      outline: none !important;
    }
    

    如果您对具体细节有任何疑问,请告诉我!

    【讨论】:

    • 嘿伙计,虽然你的代码不起作用,但你给了我解决这个问题的关键,它是使用 useRef。谢谢你!我设法解决了它
    • 很奇怪,它对我有用,但我很高兴你明白了!
    猜你喜欢
    • 2020-02-15
    • 2017-07-23
    • 2019-06-25
    • 2011-10-27
    • 2012-02-12
    • 1970-01-01
    • 2021-05-06
    • 1970-01-01
    相关资源
    最近更新 更多