"디바운스 효과"로 검색 엔진을 만드는 방법은 무엇입니까? 🔎

이 게시물의 목적은 디바운스 효과가 있는 작은 검색 엔진을 만드는 방법에 대한 간단한 방법을 보여주는 것입니다.
그러한 프로젝트는 여러 가지 방법으로 확장될 수 있지만 기본적이면서도 효율적인 것으로 만들려고 노력할 것입니다.

🚨 Note: This post requires you to know the basics of React with TypeScript (basic hooks and fetch requests).



모든 종류의 피드백을 환영합니다. 감사합니다. 기사를 즐기시기 바랍니다.🤗

목차.

📌 Technologies to be used.

📌 What is the "Debounce" effect?

📌 Creating the project.

📌 First steps.

📌 Creating the input.

📌 Handling the input status.

📌 Creating the function for the API request.

📌 Creating the Debounce effect.

📌 Making the API call.

📌 Creating the Pokemon.tsx component.

📌 Using our Pokemon component.

📌 Cleaning the logic of our component.

📌 1. Handling the logic to control the input.

📌 2. Handling the logic for the API call.

📌 3. Handling the logic for the Debounce effect.



📌 Conclusion.

📌 Source code.



🎈 사용할 기술.

  • ▶️ React JS (version 18)
  • ▶️ Vite JS
  • ▶️ TypeScript
  • ▶️ Pokemon API
  • ▶️ Vanilla CSS (스타일은 이 게시물의 끝에 있는 저장소에서 찾을 수 있습니다)

  • 🎈 "디바운스" 효과란?

    The debounce effect is when they are not executed at the time of their invocation. Instead, their execution is delayed for a predetermined period of time. If the same function is invoked again, the previous execution is cancelled and the timeout is restarted.

     

    🎈 프로젝트 생성.

    We will name the project: search-debounce (optional, you can name it whatever you like).

    npm init vite@latest
    

    We create the project with Vite JS and select React with TypeScript.

    Then we execute the following command to navigate to the directory just created.

    cd search-debounce
    

    Then we install the dependencies.

    npm install
    

    Then we open the project in a code editor (in my case VS code).

    code .
    

     

    🎈첫걸음.

    Inside the folder src/App.tsx we delete all the contents of the file and place a functional component that displays a title.

    const App = () => {
      return (
        <div className="container">
          <h1> <span>Search Engine</span> whit <span>Debounce Effect</span> </h1>
        </div>
      )
    }
    export default App
    

    It should look like this 👀:



    🎈 입력 만들기.

    Now we create the folder src/components and inside the folder we create the file Input.tsx and inside we add the following:

    export const Input = () => {
      return (
        <>
            <label htmlFor="pokemon">Name or ID of a Pokemon</label>
            <input type="text" id="pokemon" placeholder="Example: Pikachu" />
        </>
      )
    }
    

    Once done, we import it into the App.tsx file.

    import { Input } from "./components/Input"
    
    const App = () => {
    
      return (
        <div className="container">
          <h1> <span>Search Engine</span> whit <span>Debounce Effect</span> </h1>
    
          <Input/>
        </div>
      )
    }
    export default App
    

    It should look like this 👀:



    🎈 입력 상태 처리.

    🚨 Note: This is NOT the only way to perform this exercise, it is only one option! If you have a better way, I would like you to share it in the comments please. 😌

    In this case I am going to handle the input status at a higher level, i.e. the App component of the App.tsx file.

    We will do this, because we need the value of the input available in App.tsx , since the request to the API and the debounce effect will be made there.

    1 - First we create the state to handle the value of the input.

    const [value, setValue] = useState('');
    

    2 - We create a function to update the state of the input when the input makes a change.

    This function receives as parameter the event that emits the input, of this event we will obtain the property target and then the property value, which is the one that we will send to our state.

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value); 
    

    3 - Therefore, it is time to send the function and the value of the status to the input.

    const App = () => {
    
      return (
        <div className="container">
          <h1> <span>Search Engine</span> whit <span>Debounce Effect</span> </h1>
    
          <Input {...{value, onChange}}/>
        </div>
      )
    }
    export default App
    

    4 - In the Input component we add an interface to receive the properties by parameter in the Input.tsx file.

    interface Props {
       value: string;
       onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    }
    

    5 - We unstructure the properties and add them to the input.

    The onChange function, we place it in the onChange property of the input and the same with the value property value.

    interface Props {
       value: string;
       onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    }
    
    export const Form = ({ onChange, value }:Props) => {
    
      return (
        <>
            <label htmlFor="pokemon">Name of a Pokemon</label>
            <input 
              type="text" 
              id="pokemon" 
              placeholder="Example: Pikachu" 
              value={value}
              onChange={onChange}
            />
        </>
      )
    }
    

    And so we already have the status of our input under control. 🥳

     

    🎈 API 요청을 위한 함수 생성.

    Now we create the src/utils folder and inside we place a file called searchPokemon.ts and add the following function to make the request, and search for a pokemon by its name or ID.

    🚨 Note: The API response has more properties than what is represented in the ResponseAPI interface.

    This function receives two parameters:

    • pokemon: is the name or ID of the pokemon.
    • signal**: allows to set event listeners. In other words, it will help us to cancel the HTTP request when the component is unmounted or makes a change in the state.

    This function returns the pokemon data if everything goes well or null if something goes wrong.

    export interface ResponseAPI {
        name: string;
        sprites: { front_default: string }
    }
    
    export const searchPokemon = async (pokemon: string, signal?: AbortSignal): Promise<ResponseAPI | null> => {
        try {
    
            const url = `https://pokeapi.co/api/v2/pokemon/${pokemon.toLowerCase().trim()}`
            const res = await fetch(url, { signal });
    
            if(res.status === 404) return null
    
            const data: ResponseAPI = await res.json();
            return data
    
        } catch (error) {
            console.log((error as Error).message);
            return null
        }
    }
    

     

    🎈 디바운스 효과 만들기.

    In the App.tsx file we create a state, which will be used to store the value of the input.

    const [debouncedValue, setDebouncedValue] = useState();
    

    As initial state we send the value of the input state (value).

    const [value, setValue] = useState('');
    
    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value);
    
    const [debouncedValue, setDebouncedValue] = useState(value);
    

    Now, we create an effect so that when the value of the input changes, we execute the setTimeout function that will update the state of the debouncedValue sending the new value of the input, after 1 second, and thus we will obtain the keyword or the pokemon, to make the request to the API.

    At the end of the effect, we execute the cleaning method, which consists of cleaning the setTimeout function, that is why we store it in a constant called timer .

    useEffect(() => {
        const timer = setTimeout(() => setDebouncedValue(value), 1000)
    
        return () => clearTimeout(timer)
    }, [value]);
    

    So for the moment our App.tsx file would look like this:

    import { useEffect, useState } from 'react';
    import { Input } from "./components/Input"
    
    const App = () => {
    
      const [value, setValue] = useState('');
      const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value);
    
      const [debouncedValue, setDebouncedValue] = useState(value);
    
      useEffect(() => {
    
        const timer = setTimeout(() => setDebouncedValue(value), delay || 500)
    
        return () => clearTimeout(timer)
      }, [value, delay]);
    
      return (
        <div className="container">
          <h1> <span>Search Engine</span> whit <span>Debounce Effect</span> </h1>
    
          <Input {...{ value, onChange }} />
        </div>
      )
    }
    export default App
    

     

    🎈 API 호출하기.

    Once we have the value of the input already with the debounce effect, it is time to make the API call.

    For that we will use the function that we created previously, searchPokemon.tsx .

    For it, we are going to use an effect.
    First we create the controller which is the one that will help us to cancel the HTTP request, as we mentioned before.
    Inside the controller we have two properties that interest us:

    • abort(): when executed, cancels the request.
    • signal**: maintains the connection between the controller and the request to know which one to cancel.

    The abort() is executed at the end, when the component is unmounted.

    useEffect(() => {
    
        const controller = new AbortController();
    
        return () => controller.abort();
    
      }, []);
    

    The dependency of this effect will be the value of the debouncedValue, since every time this value changes, we must make a new request to search for the new pokemon.

    useEffect(() => {
        const controller = new AbortController();
    
        return () => controller.abort();
    
      }, [debouncedValue])
    

    We make a condition, in which only if the debouncedValue exists and has some word or number, we will make the request.

    useEffect(() => {
        const controller = new AbortController();
    
        if (debouncedValue) {
    
        }
    
        return () => controller.abort();
      }, [debouncedValue])
    

    Inside the if we call the searchPokemon function and send it the value of debouncedValue and also the signal property of the controller.

    useEffect(() => {
        const controller = new AbortController();
    
        if (debouncedValue) {
            searchPokemon(debouncedValue, controller.signal)
        }
    
        return () => controller.abort();
      }, [debouncedValue])
    

    And since the searchPokemon function returns a promise and within the effect it is not allowed to use async/await, we will use .then to resolve the promise and get the value it returns.

    useEffect(() => {
        const controller = new AbortController();
    
        if (debouncedValue) {
            searchPokemon(debouncedValue, controller.signal)
                .then(data => {
                console.log(data) //pokemon | null
            })
        }
    
        return () => controller.abort();
      }, [debouncedValue])
    

    In the end it should look like this. 👀

    import { useEffect, useState } from 'react';
    import { Input } from "./components/Input"
    import { searchPokemon } from "./utils/searchPokemon";
    
    const App = () => {
    
      const [value, setValue] = useState('');
      const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value);
    
      const [debouncedValue, setDebouncedValue] = useState(value);
    
      useEffect(() => {
    
        const timer = setTimeout(() => setDebouncedValue(value), delay || 500)
    
        return () => clearTimeout(timer)
      }, [value, delay]);
    
    
      useEffect(() => {
    
        const controller = new AbortController();
    
        if (debouncedValue) {
          searchPokemon(debouncedValue, controller.signal)
            .then(data => {
                console.log(data) //pokemon | null
            })
        }
    
        return () => controller.abort();
    
      }, [debouncedValue])
    
    
      return (
        <div className="container">
          <h1> <span>Search Engine</span> whit <span>Debounce Effect</span> </h1>
          <Input {...{ value, onChange }} />
    
        </div>
      )
    }
    export default App
    

     

    🎈 Pokemon.tsx 구성 요소 만들기.

    1 - First we create the empty functional component.

    export const Pokemon = () => {
      return (
        <></>
      )
    }
    

    2 - We add the ResponseAPI interface since we are going to receive by props the pokemon, which can contain the pokemon data or a null value.

    import { ResponseAPI } from "../utils/searchPokemon"
    
    export const Pokemon = ({ pokemon }: { pokemon: ResponseAPI | null }) => {
    
      return (
        <></>
      )
    }
    

    3 - We make an evaluation where:

    • If the pokemon property is null, we show the "No results" message.
    • If the pokemon property contains the pokemon data, we show its name and an image.
    import { ResponseAPI } from "../utils/searchPokemon"
    
    export const Pokemon = ({ pokemon }: { pokemon: ResponseAPI | null }) => {
    
      return (
        <>
          {
            !pokemon
              ? <span>No results</span>
              : <div>
                <h3>{pokemon.name}</h3>
                <img src={pokemon.sprites.front_default} alt={pokemon.name} />
              </div>
          }
        </>
      )
    }
    
    1061709610 1061906

    결과가 없으면 다음과 같이 표시됩니다 👀:
    It should look like this if it is loading 👀:


    포켓몬이 있는 모습입니다 👀:


    4 - 이제 마지막으로 포켓몬이 존재하는지(즉, null이 아닌지) 평가하고 빈 개체인 경우 조각을 반환하는 마지막 조건을 추가합니다.

    This is because the initial state for storing pokemon will be an empty object. "{}".

    If we don't put that condition, then at the start of our app, even without having typed anything in the input, the "No results" message will appear, and the idea is that it will appear after we have typed something in the input and the API call has been made.



    import { ResponseAPI } from "../utils/searchPokemon"
    
    export const Pokemon = ({ pokemon }: { pokemon: ResponseAPI | null }) => {
    
      if(pokemon && Object.keys(pokemon).length === 0) return <></>;
    
      return (
        <>
          {
            !pokemon
              ? <span>No results</span>
              : <div>
                <h3>{pokemon.name}</h3>
                <img src={pokemon.sprites.front_default} alt={pokemon.name} />
              </div>
          }
        </>
      )
    }
    

    이것은 우리의 포켓몬 구성 요소가 어떻게 생겼는지, 사용할 때입니다. 😌

    🎈 포켓몬 구성 요소 사용.

    App.tsx 파일에서 2개의 새 상태를 추가합니다. 빈 개체의 초기 값을 갖는 발견된 포켓몬을 저장합니다. 초기 값이 false인 API 호출에 대한 로드를 처리합니다. const [포켓몬, setPokemon] = useState<ResponseAPI | null>({} as ResponseAPI); const [isLoading, setIsLoading] = useState(거짓) 이제 searchPokemon 함수를 통해 API를 호출하는 효과 내에서 호출하기 전에 setIsLoading에 true 값을 보내 로딩을 활성화합니다. 그런 다음 .then 내부에 데이터를 가져오면 데이터를 setPokemon(포켓몬 또는 null 값일 수 있음)으로 보냅니다. 마지막으로 setIsLoading에 false 값을 보내 로딩을 제거합니다. useEffect(() => { const 컨트롤러 = new AbortController(); if (debouncedValue) { setIsLoading(참) searchPokemon(debouncedValue, controller.signal) .then(데이터 => { setPokemon(데이터); setIsLoading(거짓); }) } return () => controller.abort(); }, [디바운스된 값]) 포켓몬이 저장되면 JSX에서 다음 조건을 설정합니다. isLoading 상태 값이 true이면 "결과 로드 중..." 메시지가 표시됩니다. isLoading 상태 값이 false이면 포켓몬 구성 요소를 표시하여 포켓몬을 보냅니다. 반품 ( <사업부 클래스 이름="컨테이너"> <h1> <span>검색 엔진</span> 및 <span>디바운스 효과</span> </h1> <입력 {...{ 값, onChange }} /> { isLoading ? <span>결과 로드 중...</span> : <포켓몬 포켓몬={포켓몬}/> } </div> ) 그리고 모든 것을 합치면 다음과 같이 보일 것입니다 👀: import { useEffect, useState } from 'react'; "./components/Input"에서 가져오기 { 입력 } "./components/Pokemon"에서 { 포켓몬 } 가져오기; "./utils/searchPokemon"에서 가져오기 { searchPokemon }; import { ResponseAPI } from "./interface/pokemon"; const 앱 = () => { const [포켓몬, setPokemon] = useState<ResponseAPI | null>({} as ResponseAPI); const [isLoading, setIsLoading] = useState(거짓) const [값, setValue] = useState(''); const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value); const [debouncedValue, setDebouncedValue] = useState(값); useEffect(() => { const 타이머 = setTimeout(() => setDebouncedValue(값), 지연 || 500) return () => clearTimeout(타이머) }, [값, 지연]); useEffect(() => { const 컨트롤러 = new AbortController(); if (debouncedValue) { setIsLoading(참) searchPokemon(debouncedValue, controller.signal) .then(데이터 => { setPokemon(데이터); setIsLoading(거짓); }) } return () => controller.abort(); }, [디바운스된 값]) 반품 ( <사업부 클래스 이름="컨테이너"> <h1> <span>검색 엔진</span> 및 <span>디바운스 효과</span> </h1> <입력 {...{ 값, onChange }} /> { isLoading ? <span>결과 로드 중...</span> : <포켓몬 포켓몬={포켓몬}/> } </div> ) } 기본 앱 내보내기 하나의 구성 요소에 많은 논리가 있습니다. 😱 이제 리팩토링할 차례입니다! 🎈 구성 요소의 논리 청소.

    구성 요소에 많은 논리가 있으므로 여러 파일로 분리해야 합니다. 입력을 제어하는 ​​논리. 디바운스 로직. API를 호출하고 포켓몬을 처리하는 로직입니다. 그리고 이 논리는 useState 및 useEffect와 같은 후크를 사용하므로 이를 사용자 지정 후크에 배치해야 합니다. 첫 번째는 새 폴더 src/hooks 를 만드는 것입니다. 1. 입력을 제어하는 ​​논리 처리.

    src/hooks 폴더 안에 다음 파일 useInput.ts **를 생성합니다. 그리고 입력 처리에 해당하는 논리를 배치합니다. import { useState } from 'react'; export const useInput = (): [string, (e: React.ChangeEvent<HTMLInputElement>) => 무효] => { const [값, setValue] = useState(''); const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value); 반환 [값, onChange] } 그런 다음 App.tsx 파일에서 useInput을 호출합니다. import { useEffect, useState } from 'react'; "./components/Input"에서 가져오기 { 입력 } "./components/Pokemon"에서 { 포켓몬 } 가져오기; import { useInput } from "./hooks/useInput"; "./utils/searchPokemon"에서 가져오기 { searchPokemon }; import { ResponseAPI } from "./interface/pokemon"; const 앱 = () => { const [값, onChange] = useInput(); const [포켓몬, setPokemon] = useState<ResponseAPI | null>({} as ResponseAPI); const [isLoading, setIsLoading] = useState(거짓) const [debouncedValue, setDebouncedValue] = useState(값); useEffect(() => { const 타이머 = setTimeout(() => setDebouncedValue(값), 지연 || 500) return () => clearTimeout(타이머) }, [값, 지연]); useEffect(() => { const 컨트롤러 = new AbortController(); if (debouncedValue) { setIsLoading(참) searchPokemon(debouncedValue, controller.signal) .then(데이터 => { setPokemon(데이터); setIsLoading(거짓); }) } return () => controller.abort(); }, [디바운스된 값]) 반품 ( <사업부 클래스 이름="컨테이너"> <h1> <span>검색 엔진</span> 및 <span>디바운스 효과</span> </h1> <입력 {...{ 값, onChange }} /> { isLoading ? <span>결과 로드 중...</span> : <포켓몬 포켓몬={포켓몬}/> } </div> ) } 기본 앱 내보내기 2. API 호출에 대한 로직 처리.

    src/hooks 폴더 안에 useSearchPokemon.ts 파일을 생성합니다. API에 요청하고 포켓몬을 보여주는 것과 관련된 로직을 배치합니다. 이 사용자 지정 후크는 포켓몬의 이름 또는 ID인 검색이라는 문자열을 매개 변수로 받습니다. 그리고 API 호출 searchPokemon을 만드는 함수에 해당 매개 변수를 보냅니다. 🚨 참고: 효과의 If 부분을 관찰하십시오. 마지막에 우리는 debouncedValue가 비어 있으면 API를 호출하지 않고 빈 객체의 값을 setPokemon으로 보내는 else를 배치합니다. import { useState, useEffect } from 'react'; import { ResponseAPI } from '../interface/pokemon'; import { searchPokemon } from '../utils/searchPokemon'; 내보내기 const useSearchPokemon = (검색: 문자열) => { const [포켓몬, setPokemon] = useState<ResponseAPI | null>({} as ResponseAPI); const [isLoading, setIsLoading] = useState(거짓) useEffect(() => { const 컨트롤러 = new AbortController(); if (검색) { setIsLoading(참); searchPokemon(검색, 컨트롤러.신호) .then(데이터 => { setPokemon(데이터); setIsLoading(거짓); }); }else { setPokemon({}을 ResponseAPI로) } return () => controller.abort(); }, [검색]) 반품 { 포켓몬, isLoading } } 그런 다음 App.tsx 파일에서 useSearchPokemon을 호출합니다. import { useEffect, useState } from 'react'; "./components/Input"에서 가져오기 { 입력 } "./components/Pokemon"에서 { 포켓몬 } 가져오기; import { useInput } from "./hooks/useInput"; import { useSearchPokemon } from "./hooks/useSearchPokemon"; "./utils/searchPokemon"에서 가져오기 { searchPokemon }; import { ResponseAPI } from "./interface/pokemon"; const 앱 = () => { const [값, onChange] = useInput(); const [debouncedValue, setDebouncedValue] = useState(값); const { isLoading, 포켓몬 } = useSearchPokemon(debouncedValue) useEffect(() => { const 타이머 = setTimeout(() => setDebouncedValue(값), 지연 || 500) return () => clearTimeout(타이머) }, [값, 지연]); 반품 ( <사업부 클래스 이름="컨테이너"> <h1> <span>검색 엔진</span> 및 <span>디바운스 효과</span> </h1> <입력 {...{ 값, onChange }} /> { isLoading ? <span>결과 로드 중...</span> : <포켓몬 포켓몬={포켓몬}/> } </div> ) } 기본 앱 내보내기 3. Debounce 효과에 대한 논리 처리.

    src/hooks 폴더 안에 useDebounce.ts 파일을 만들고 디바운스 효과를 처리하기 위한 모든 논리를 배치합니다. 이 사용자 정의 후크는 2개의 매개변수를 수신합니다. 값: 입력 상태의 값입니다. 지연**: 디바운스 실행을 지연하려는 시간이며 선택 사항입니다. 🚨 참고: 지연 속성은 setTimeout 함수의 두 번째 매개변수로 사용되며 지연이 정의되지 않은 경우 기본 시간은 500ms입니다. 또한 효과 종속성 배열에 지연 속성을 추가합니다. import { useState, useEffect } from 'react'; 내보내기 const useDebounce = (값:문자열, 지연?:숫자) => { const [debouncedValue, setDebouncedValue] = useState(값); useEffect(() => { const 타이머 = setTimeout(() => setDebouncedValue(값), 지연 || 500) return () => clearTimeout(타이머) }, [값, 지연]); 디바운스 값 반환 } 그런 다음 App.tsx 파일에서 useDebounce를 호출합니다. import { useEffect, useState } from 'react'; "./components/Input"에서 가져오기 { 입력 } "./components/Pokemon"에서 { 포켓몬 } 가져오기; import { useInput } from "./hooks/useInput"; import { useSearchPokemon } from "./hooks/useSearchPokemon"; import { useDebounce } from "./hooks/useDebounce"; "./utils/searchPokemon"에서 가져오기 { searchPokemon }; import { ResponseAPI } from "./interface/pokemon"; const 앱 = () => { const [값, onChange] = useInput(); const debouncedValue = useDebounce(값, 1000); const { isLoading, 포켓몬 } = useSearchPokemon(debouncedValue) 반품 ( <사업부 클래스 이름="컨테이너"> <h1> <span>검색 엔진</span> 및 <span>디바운스 효과</span> </h1> <입력 {...{ 값, onChange }} /> { isLoading ? <span>결과 로드 중...</span> : <포켓몬 포켓몬={포켓몬}/> } </div> ) } 기본 앱 내보내기 따라서 App.tsx 구성 요소가 더 깨끗하고 읽기 쉬웠습니다. 🥳 🎈결론.

    방금 보여드린 전체 프로세스는 디바운스 효과가 있는 검색 엔진을 만들 수 있는 방법 중 하나입니다. 🔎 이 연습을 수행하는 방법을 이해하는 데 도움이 되었기를 바랍니다. 여기까지 해주셔서 대단히 감사합니다! 🤗 검색 엔진에 대한 디바운스 효과를 만드는 다른 방법이나 더 나은 방법을 알고 있다면 의견을 말하도록 초대합니다. 🙌 🎈 소스 코드.

    /


    React JS로 디바운스 효과가 있는 검색 엔진 만들기 🚀





    검색 엔진 - 디바운스 효과 🔍


    React JS 및 Pokemon API로 디바운스 효과가 있는 검색 엔진 만들기 🚀

    Franklin361

    기술 🧪


  • 반응 JS
  • 타자기
  • Vite JS

  • 설치. 🚀



    1. 리포지토리 복제


     git clone https://github.com/Franklin361/journal-app

    2. 이 명령을 실행하여 종속성을 설치합니다.


     npm install

    3. 이 명령을 실행하여 개발 서버를 올립니다.


     npm run dev

    연결. ⛓️



    앱 데모 🔗 search-engine-debounce-effect

    보고 싶은 경우를 대비하여 튜토리얼에 대한 링크가 있습니다! 👀



  • 🔗 영어 기사 🇺🇸

  • 🔗 스페인어 기사 🇲🇽


  • 좋은 웹페이지 즐겨찾기