クエリー自動補完キーボードコントロール(with:react)


その位置決めは、以前の位置決めに関連付けられています.
リンク
今日のポジショニング内容は,タイトルのように,自動完了した検索語データにキーボードでアクセスして選択する機能である.

->前回の位置決め結果から開始します.

useRef


まずuseref hooksを使います.
userefについて簡単に説明します.通常のJavaScriptではgetElementById、querySelectorなどのDOMセレクタ関数を使用してDOMを選択し、reactではuseref hookを使用してdomにアクセスできます.
userefの詳細については、リンクを参照してください.
では、なぜDomにアクセスしてキーボードを制御するのでしょうか.写真を見て

この画像は私たちが自動的に完成した検索語html形式です.
div->ul->liでアクセスする必要があります.私たちの自動完成キーワードに達することができます.
スキップコードは、以前の転送最終コードで見つけることができます.

userefの適用

function Header() {
...생략
const autoRef = useRef<HTMLUListElement>(null);
}
console.log(autoRef);
return (
...생략
	<AutoSearchWrap ref={autoRef}>
    {keyItems.map((search, idx) => (
     <AutoSearchData
       isFocus={index === idx ? true : false}
       key={search.city}
        onClick={() => {
        setKeyword(search.city);
       }}
)
ref値はstyled-componentと宣言されたulTagに渡されます.
コンソールも表示できます.

写真などの属性を確認できます.
私たちが見たい属性はタグのchildrenです.
childrenプロパティには、先に説明したli tagのリストがあります.
すなわち.refでサブ要素にアクセスするとdivタグをクリックせず、liタグの値にアクセスすれば終了します.

キーボードを使用した索引の選択

function Header() {

const AutoSearchData = styled.li<{isFocus?: boolean}>`
  padding: 10px 8px;
  width: 100%;
  font-size: 14px;
  font-weight: bold;
  z-index: 4;
  letter-spacing: 2px;
  &:hover {
    background-color: #edf5f5;
    cursor: pointer;
  }
  background-color: ${props => props.isFocus? "#edf5f5" : "#fff"};
  position: relative;
  img {
    position: absolute;
    right: 5px;
    width: 18px;
    top: 50%;
    transform: translateY(-50%);
  }
`;

const [index,setIndex] = useState<number>(-1);
return (
<AutoSearchWrap ref={autoRef}>
    {keyItems.map((search, idx) => (
     <AutoSearchData
       isFocus={index === idx ? true : false}
       key={search.city}
        onClick={() => {
        setKeyword(search.city);
       }}
 )}
コードを見てみましょう.index変数の初期値が-1の点を深く理解する必要があります.
自動完了データが受信された場合、配列では最初のインデックス値が0で始まるため、-1値として指定します.
次に、isFocusというpropsをAutoSearchDataコンポーネントに渡し、キーボードでもキーワードhoverスタイルを自動的に完了するようにindex値とES 6 mapパラメータにインデックスを取得し、trueとfalseを返すことができます.

keydown関数の作成

const ArrowDown = "ArrowDown";
const ArrowUp = "ArrowUp";
const Escape = "Escape";
const handleKeyArrow = (e:React.KeyboardEvent) => {
    if (keyItems.length > 0) {
      switch (e.key) {
        case ArrowDown: //키보드 아래 키
          setIndex(index + 1);
          if (autoRef.current?.childElementCount === index + 1) setIndex(0);
          break;
        case ArrowUp: //키보드 위에 키
          setIndex(index - 1);
          if (index <= 0) {
            setKeyItems([]);
            setIndex(-1);
          }
          break;
        case Escape: // esc key를 눌렀을때,
          setKeyItems([]);
          setIndex(-1);
          break;
      }  
    } 
  }
  return (
  	<Search
     value={keyword}
     onChange={onChangeData}
     onKeyDown={handleKeyArrow}
    />
    ...생략
  )
まず、keyが押されているためeventプロパティを使用する必要があります.
上矢印キー「ArrowDown」下矢印キー「ArrowUp」
つまり、e.key=="ArrowDown"エンクロージャは、ダウンキーを押したときに動作します.
e.key===「文字列」はオタワエラーを防止するために定数処理を提供します.
if (autoRef.current?.childElementCount === index + 1) setIndex(0); このセクションでは、最後のインデックスキーで下矢印キーを押すと、最初のインデックスキーに戻ります.
Ex:childElementCount->liタグの個数.

結果画面



最終コード

import React, { useRef } from 'react';
import { useEffect } from 'react';
import { useState } from 'react';
import styled from 'styled-components';

const SearchContainer = styled.div`
  width: 400px;
  height: 45px;
  position: relative;
  border: 0;
  img {
    position: absolute;
    right: 10px;
    top: 10px;
  }
`;

const Search = styled.input`
  border: 0;
  padding-left: 10px;
  background-color: #eaeaea;
  width: 100%;
  height: 100%;
  outline: none;
`;


const AutoSearchContainer = styled.div`
  z-index: 3;
  height: 50vh;
  width: 400px;
  background-color: #fff;
  position: absolute;
  top: 45px;
  border: 2px solid;
  padding: 15px;
`;

const AutoSearchWrap = styled.ul`

`;

const AutoSearchData = styled.li<{isFocus?: boolean}>`
  padding: 10px 8px;
  width: 100%;
  font-size: 14px;
  font-weight: bold;
  z-index: 4;
  letter-spacing: 2px;
  &:hover {
    background-color: #edf5f5;
    cursor: pointer;
  }
  background-color: ${props => props.isFocus? "#edf5f5" : "#fff"};
  position: relative;
  img {
    position: absolute;
    right: 5px;
    width: 18px;
    top: 50%;
    transform: translateY(-50%);
  }
`;
interface autoDatas {
  city: string;
  growth_from_2000_to_2013: string;
  latitude:number;
  longitude:number;
  population:string;
  rank:string;
  state:string;
}
function Header() {
	const [keyword, setKeyword] = useState<string>("");
    const [index,setIndex] = useState<number>(-1);
    const [keyItems, setKeyItems] = useState<autoDatas[]>([]);
    const autoRef = useRef<HTMLUListElement>(null);
    const onChangeData = (e:React.FormEvent<HTMLInputElement>) => {
    setKeyword(e.currentTarget.value);
  };
  const handleKeyArrow = (e:React.KeyboardEvent) => {
    if (keyItems.length > 0) {
      switch (e.key) {
        case ArrowDown:
          setIndex(index + 1);
          if (autoRef.current?.childElementCount === index + 1) setIndex(0);
          break;
        case ArrowUp:
          setIndex(index - 1);
          if (index <= 0) {
            setKeyItems([]);
            setIndex(-1);
          }
          break;
        case Escape:
          setKeyItems([]);
          setIndex(-1);
          break;
      }  
    } 
  }
  const fetchData = ()  =>{
    return fetch(
      `https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json`
    )
      .then((res) => res.json())
      .then((data) => data.slice(0,100))
  }
  interface ICity {
    includes(data:string): boolean;
    city?: any;
  }
  const updateData = async() => {
    const res = await fetchData();
    let b = res.filter((list: ICity) => list.city.includes(keyword) === true)
                .slice(0,10);
    // console.log(b);
    setKeyItems(b);
  }
  useEffect(() => {
    updateData();
    },[keyword])
    return (
    <SearchContainer>
     <Search value={keyword} onChange={onChangeData} onKeyDown={handleKeyArrow}/>
      <img src="assets/imgs/search.svg" alt="searchIcon" />
       {keyItems.length > 0 && keyword && (
        <AutoSearchContainer>
         <AutoSearchWrap ref={autoRef}>
          {keyItems.map((search, idx) => (
           <AutoSearchData
           	isFocus={index === idx ? true : false}
            key={search.city}
            onClick={() => {
            setKeyword(search.city);
           }}
            >
            <a href="#">{search.city}</a>
            <img src="assets/imgs/north_west.svg" alt="arrowIcon" />
           </AutoSearchData>
          ))}
         </AutoSearchWrap>
        </AutoSearchContainer>
       )}
      </SearchContainer>
     );
}
export default Header;
ありがとうございます.