Javascript 反应:如何通过箭头键在列表中导航

Javascript 反应:如何通过箭头键在列表中导航,javascript,reactjs,Javascript,Reactjs,我用一个文本输入构建了一个简单的组件,下面是一个列表(使用语义ui) 现在我想使用箭头键在列表中导航 首先,我必须选择第一个元素。但是如何访问特定的列表元素呢 其次,我将获得当前选定元素的信息并选择下一个元素。如何获取所选元素的信息 选择意味着将类活动添加到项目中,或者有更好的方法吗 export default class Example extends Component { constructor(props) { super(props) th

我用一个文本输入构建了一个简单的组件,下面是一个列表(使用语义ui)

现在我想使用箭头键在列表中导航

  • 首先,我必须选择第一个元素。但是如何访问特定的列表元素呢
  • 其次,我将获得当前选定元素的信息并选择下一个元素。如何获取所选元素的信息
选择意味着将类
活动
添加到项目中,或者有更好的方法吗

export default class Example extends Component {
    constructor(props) {
        super(props)
        this.handleChange = this.handleChange.bind(this)
        this.state = { result: [] }
    }
    handleChange(event) {
        // arrow up/down button should select next/previous list element
    }
    render() {
        return (
            <Container>
                <Input onChange={ this.handleChange }/>
                <List>
                    {
                        result.map(i => {
                            return (
                                <List.Item key={ i._id } >
                                    <span>{ i.title }</span>
                                </List.Item>
                            )
                        })
                    }
                </List>
            </Container>
        )
    }
}
导出默认类示例扩展组件{
建造师(道具){
超级(道具)
this.handleChange=this.handleChange.bind(this)
this.state={result:[]}
}
手变(活动){
//向上/向下箭头按钮应选择下一个/上一个列表元素
}
render(){
返回(
{
result.map(i=>{
返回(
{i.title}
)
})
}
)
}
}

尝试以下方法:

export default class Example extends Component {
  constructor(props) {
    super(props)
    this.handleKeyDown = this.handleKeyDown.bind(this)
    this.state = {
      cursor: 0,
      result: []
    }
  }

  handleKeyDown(e) {
    const { cursor, result } = this.state
    // arrow up/down button should select next/previous list element
    if (e.keyCode === 38 && cursor > 0) {
      this.setState( prevState => ({
        cursor: prevState.cursor - 1
      }))
    } else if (e.keyCode === 40 && cursor < result.length - 1) {
      this.setState( prevState => ({
        cursor: prevState.cursor + 1
      }))
    }
  }

  render() {
    const { cursor } = this.state

    return (
      <Container>
        <Input onKeyDown={ this.handleKeyDown }/>
        <List>
          {
            result.map((item, i) => (
              <List.Item
                key={ item._id }
                className={cursor === i ? 'active' : null}
              >
                <span>{ item.title }</span>
              </List.Item>
            ))
          }
        </List>
      </Container>
    )
  }
}
导出默认类示例扩展组件{
建造师(道具){
超级(道具)
this.handleKeyDown=this.handleKeyDown.bind(this)
此.state={
光标:0,
结果:[]
}
}
handleKeyDown(e){
const{cursor,result}=this.state
//向上/向下箭头按钮应选择下一个/上一个列表元素
如果(e.keyCode===38&&cursor>0){
this.setState(prevState=>({
游标:prevState.cursor-1
}))
}else if(e.keyCode===40&&cursor({
游标:prevState.cursor+1
}))
}
}
render(){
const{cursor}=this.state
返回(
{
结果.map((项目,i)=>(
{item.title}
))
}
)
}
}
光标会跟踪您在列表中的位置,因此当用户按下向上或向下箭头键时,您会相应地减少/增加光标。光标应与数组索引一致

您可能希望使用
onKeyDown
来查看箭头键,而不是
onChange
,这样您就不会延迟或干扰标准的输入编辑行为

在渲染循环中,只需对照光标检查索引,以查看哪个索引处于活动状态


如果您正在根据字段的输入筛选结果集,您可以随时将光标重置为零,以便始终保持行为一致。

接受的答案对我非常有用,谢谢!我对该解决方案进行了调整,并提出了一个建议,也许它会对某些人有用:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const useKeyPress = function(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false);

  function downHandler({ key }) {
    if (key === targetKey) {
      setKeyPressed(true);
    }
  }

  const upHandler = ({ key }) => {
    if (key === targetKey) {
      setKeyPressed(false);
    }
  };

  React.useEffect(() => {
    window.addEventListener("keydown", downHandler);
    window.addEventListener("keyup", upHandler);

    return () => {
      window.removeEventListener("keydown", downHandler);
      window.removeEventListener("keyup", upHandler);
    };
  });

  return keyPressed;
};

const items = [
  { id: 1, name: "Josh Weir" },
  { id: 2, name: "Sarah Weir" },
  { id: 3, name: "Alicia Weir" },
  { id: 4, name: "Doo Weir" },
  { id: 5, name: "Grooft Weir" }
];

const ListItem = ({ item, active, setSelected, setHovered }) => (
  <div
    className={`item ${active ? "active" : ""}`}
    onClick={() => setSelected(item)}
    onMouseEnter={() => setHovered(item)}
    onMouseLeave={() => setHovered(undefined)}
  >
    {item.name}
  </div>
);

const ListExample = () => {
  const [selected, setSelected] = useState(undefined);
  const downPress = useKeyPress("ArrowDown");
  const upPress = useKeyPress("ArrowUp");
  const enterPress = useKeyPress("Enter");
  const [cursor, setCursor] = useState(0);
  const [hovered, setHovered] = useState(undefined);

  useEffect(() => {
    if (items.length && downPress) {
      setCursor(prevState =>
        prevState < items.length - 1 ? prevState + 1 : prevState
      );
    }
  }, [downPress]);
  useEffect(() => {
    if (items.length && upPress) {
      setCursor(prevState => (prevState > 0 ? prevState - 1 : prevState));
    }
  }, [upPress]);
  useEffect(() => {
    if (items.length && enterPress) {
      setSelected(items[cursor]);
    }
  }, [cursor, enterPress]);
  useEffect(() => {
    if (items.length && hovered) {
      setCursor(items.indexOf(hovered));
    }
  }, [hovered]);

  return (
    <div>
      <p>
        <small>
          Use up down keys and hit enter to select, or use the mouse
        </small>
      </p>
      <span>Selected: {selected ? selected.name : "none"}</span>
      {items.map((item, i) => (
        <ListItem
          key={item.id}
          active={i === cursor}
          item={item}
          setSelected={setSelected}
          setHovered={setHovered}
        />
      ))}
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<ListExample />, rootElement);
import React,{useState,useffect}来自“React”;
从“react dom”导入react dom;
const useKeyPress=功能(targetKey){
const[keyPressed,setKeyPressed]=使用状态(false);
函数下处理程序({key}){
如果(键===目标键){
setKeyPressed(真);
}
}
常量upHandler=({key})=>{
如果(键===目标键){
setKeyPressed(假);
}
};
React.useffect(()=>{
addEventListener(“keydown”,downHandler);
addEventListener(“keyup”,upHandler);
return()=>{
removeEventListener(“keydown”,downHandler);
removeEventListener(“keyup”,upHandler);
};
});
按下返回键;
};
常数项=[
{id:1,名字:“Josh Weir”},
{id:2,姓名:“莎拉·威尔”},
{id:3,姓名:“Alicia Weir”},
{id:4,名字:“杜威尔”},
{id:5,姓名:“格罗奥特威尔”}
];
常量列表项=({item,active,setSelected,setHovered})=>(
(项目)}
onMouseCenter={()=>setHovered(项)}
onMouseLeave={()=>setHovered(未定义)}
>
{item.name}
);
常量ListExample=()=>{
const[selected,setSelected]=使用状态(未定义);
const downPress=useKeyPress(“箭头向下”);
const upPress=使用按键(“箭头向上”);
const enterPress=useKeyPress(“回车”);
const[cursor,setCursor]=useState(0);
常量[悬停,设置悬停]=使用状态(未定义);
useffect(()=>{
如果(items.length&向下按){
setCursor(prevState=>
prevState{
如果(项目.长度和加高){
setCursor(prevState=>(prevState>0?prevState-1:prevState));
}
},[upPress]);
useffect(()=>{
如果(items.length&&enterPress){
设置选定项(项目[光标]);
}
},[cursor,enterPress]);
useffect(()=>{
如果(items.length&悬停){
setCursor(items.indexOf(悬停));
}
},[悬停];
返回(

使用上下键并按enter键进行选择,或使用鼠标

选定:{选定?选定。名称:“无”} {items.map((item,i)=>( ))} ); }; const rootElement=document.getElementById(“根”); render(,rootElement);
与@joshweir提供的解决方案几乎相同,但使用的是Typescript。我还使用了“ref”来代替“window”对象,并仅将事件侦听器添加到输入文本框中

import React, { useState, useEffect, Dispatch, SetStateAction, createRef, RefObject } from "react";

const useKeyPress = function (targetKey: string, ref: RefObject<HTMLInputElement>) {
    const [keyPressed, setKeyPressed] = useState(false);


    function downHandler({ key }: { key: string }) {
        if (key === targetKey) {
            setKeyPressed(true);
        }
    }

    const upHandler = ({ key }: { key: string }) => {
        if (key === targetKey) {
            setKeyPressed(false);
        }
    };

    React.useEffect(() => {
        ref.current?.addEventListener("keydown", downHandler);
        ref.current?.addEventListener("keyup", upHandler);

        return () => {
            ref.current?.removeEventListener("keydown", downHandler);
            ref.current?.removeEventListener("keyup", upHandler);
        };
    });

    return keyPressed;
};

const items = [
    { id: 1, name: "Josh Weir" },
    { id: 2, name: "Sarah Weir" },
    { id: 3, name: "Alicia Weir" },
    { id: 4, name: "Doo Weir" },
    { id: 5, name: "Grooft Weir" }
];

const i = items[0]
type itemType = { id: number, name: string }

type ListItemType = {
    item: itemType
    , active: boolean
    , setSelected: Dispatch<SetStateAction<SetStateAction<itemType | undefined>>>
    , setHovered: Dispatch<SetStateAction<itemType | undefined>>
}

const ListItem = ({ item, active, setSelected, setHovered }: ListItemType) => (
    <div
        className={`item ${active ? "active" : ""}`}
        onClick={() => setSelected(item)}
        onMouseEnter={() => setHovered(item)}
        onMouseLeave={() => setHovered(undefined)}
    >
        {item.name}
    </div>
);

const ListExample = () => {
    const searchBox = createRef<HTMLInputElement>()
    const [selected, setSelected] = useState<React.SetStateAction<itemType | undefined>>(undefined);
    const downPress = useKeyPress("ArrowDown", searchBox);
    const upPress = useKeyPress("ArrowUp", searchBox);
    const enterPress = useKeyPress("Enter", searchBox);
    const [cursor, setCursor] = useState<number>(0);
    const [hovered, setHovered] = useState<itemType | undefined>(undefined);
    const [searchItem, setSearchItem] = useState<string>("")


    const handelChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
        setSelected(undefined)
        setSearchItem(e.currentTarget.value)
    }

    useEffect(() => {
        if (items.length && downPress) {
            setCursor(prevState =>
                prevState < items.length - 1 ? prevState + 1 : prevState
            );
        }
    }, [downPress]);
    useEffect(() => {
        if (items.length && upPress) {
            setCursor(prevState => (prevState > 0 ? prevState - 1 : prevState));
        }
    }, [upPress]);
    useEffect(() => {
        if (items.length && enterPress || items.length && hovered) {
            setSelected(items[cursor]);
        }
    }, [cursor, enterPress]);
    useEffect(() => {
        if (items.length && hovered) {
            setCursor(items.indexOf(hovered));
        }
    }, [hovered]);

    return (
        <div>
            <p>
                <small>
                    Use up down keys and hit enter to select, or use the mouse
        </small>
            </p>
            <div>
                <input ref={searchBox} type="text" onChange={handelChange} value={selected ? selected.name : searchItem} />
                {items.map((item, i) => (
                    <ListItem

                        key={item.id}
                        active={i === cursor}
                        item={item}
                        setSelected={setSelected}
                        setHovered={setHovered}
                    />
                ))}
            </div>
        </div>
    );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<ListExample />, rootElement);
import React,{useState,useffect,Dispatch,SetStateAction,createRef,reobject}来自“React”;
const useKeyPress=函数(targetKey:string,ref:ReObject){
const[keyPressed,setKeyPressed]=使用状态(false);
函数下处理程序({key}:{key:string}){
如果(键===目标键){
setKeyPressed(真);
}
}
常量upHandler=({key}:{key:string})=>{
如果(