React 编码挑战:TypeHead

发布: (2026年1月17日 GMT+8 22:39)
4 min read
原文: Dev.to

Source: Dev.to

抱歉,我无法直接访问外部链接获取文章内容。请您提供需要翻译的文本,我将为您翻译成简体中文。

自动补全

import { useState, useEffect, useRef } from "react";

const Typeahead = ({ placeholder = "Search..." }) => {
  const [query, setQuery] = useState("");
  const [suggestions, setSuggestions] = useState([]);
  const [allUsers, setAllUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [activeSuggestion, setActiveSuggestion] = useState(-1);
  const inputRef = useRef(null);

  // Fetch all users once on component mount
  useEffect(() => {
    const fetchAllUsers = async () => {
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/posts`
        );
        if (!response.ok) throw new Error("Failed to fetch users");
        const data = await response.json();
        console.log(data);
        setAllUsers(data.map((user) => user.title));
      } catch (err) {
        setError(err.message);
        setAllUsers([]);
      } finally {
        setIsLoading(false);
      }
    };

    fetchAllUsers();
  }, []);

  // Filter suggestions locally based on query
  useEffect(() => {
    if (query.length 
      title.toLowerCase().includes(query.toLowerCase())
    );
    setSuggestions(filteredSuggestions);
    setActiveSuggestion(-1);
  }, [query, allUsers]);

  // Handle keyboard navigation for accessibility
  const handleKeyDown = (e) => {
    if (e.key === "ArrowDown") {
      e.preventDefault();
      setActiveSuggestion((prev) =>
        prev 
        prev > 0 ? prev - 1 : suggestions.length - 1
      );
    } else if (e.key === "Enter" && activeSuggestion >= 0) {
      e.preventDefault();
      setQuery(suggestions[activeSuggestion]);
      setSuggestions([]);
      setActiveSuggestion(-1);
    }
  };

  // Handle suggestion click
  const handleSuggestionClick = (suggestion) => {
    setQuery(suggestion);
    setSuggestions([]);
    setActiveSuggestion(-1);
    inputRef.current.focus();
  };

  return (
    
      
        Search for users
      
       setQuery(e.target.value)}
        placeholder={placeholder}
        aria-autocomplete="list"
        aria-controls="suggestions-list"
        aria-expanded={suggestions.length > 0}
        role="combobox"
        className="typeahead-input"
      />
      {isLoading && Loading...}
      {error && (
        
          {error}
        
      )}
      {suggestions.length > 0 && (
        

          {suggestions.map((suggestion, index) => (
             handleSuggestionClick(suggestion)}
              tabIndex={-1}
            >
              {suggestion}
            
          ))}
        

      )}
    
  );
};

export default Typeahead;

CSS

.typeahead-container {
  position: relative;
  width: 300px;
  margin: 20px auto;
}

.typeahead-input {
  width: 100%;
  padding: 8px;
  font-size: 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.suggestions-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: white;
  border: 1px solid #ccc;
  border-radius: 4px;
  max-height: 200px;
  overflow-y: auto;
  list-style: none;
  padding: 0;
  margin: 4px 0 0 0;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  z-index: 1000;
}

.suggestion-item {
  padding: 8px;
  cursor: pointer;
}

.suggestion-item:hover,
.suggestion-item.active {
  background-color: #f0f0f0;
}

.loading,
.error {
  padding: 8px;
  color: #666;
  font-size: 14px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

关键更改和说明

  • 一次性获取所有用户 – 组件在挂载时从 https://jsonplaceholder.typicode.com/posts 获取完整的帖子列表,并将标题存入 allUsers。(如果想要真实用户,请将 URL 替换为 /users。)

  • 本地过滤 – 不再在每次键入时发起请求,组件使用 Array.filter 在已获取的标题中本地过滤,匹配查询时不区分大小写。

  • 性能 – 对于小数据集(例如 JSONPlaceholder 返回的 100 条帖子),这种方式更快且减少网络流量。对于非常大的数据集,可以加入记忆化(useMemo)或服务器端分页。

  • 可访问性与 UI – 键盘导航、ARIA 属性和样式保持不变。可以继续使用上面展示的 styles.css

arlier.

Notes:

- The local filtering is case‑insensitive for better user experience (`toLowerCase()`).
- If the API fetch fails, an error message is displayed, and the typeahead will not function until the data is loaded.
- For real‑world applications with larger datasets, consider implementing pagination or a more efficient search algorithm if performance becomes an issue.
Back to Blog

相关文章

阅读更多 »