본문 바로가기

프로젝트 회고 🌊

React 기능 구현 스터디 - 2. 검색 기능 (리팩토링 회고) ☀️

1. 주제 소개

1) 완성 조건

STEP 1 : 검색어를 입력하여 데이터를 가져와서 뿌려주기 

STEP 2 : 검색어를 입력하면 자동완성창 혹은 연관검색어 검색창완성 

STEP 3 : option 드롭다운으로 옵션선택할수있게하기

2) 시간

- 07.01 토요일 오후 3시~ 오후 9시 (1시간동안 밥 먹음. 그리고 후회함)

3) 사용기술과 라이브러리

- React, react-router-dom, styled-components, material-ui icons

 

4) 폴더 구조

 

5) 전체 회고

이번 코드에서도 Flux 패턴을 적용해 각 컴포넌트와 컨테이너의 기능들을 명확히 하려고 노력했다. 짧은 시간이었어서 아쉽게도 렌더링 최적화와와 env 파일을 활용하지 못해서 아쉬웠지만, 정해진 시간이 지나고 스터디 팀원들의 코드를 다 보면서 아쉬웠던 부분이 더 많아졌다.

interceptor와 instance, deboucing, URL 수정(book 카테고리) 등등

 

 

9시간이라는 짧다면 짧고 길다면 긴 시간동안 약 10명의 팀원들은 각각 다른 코드를 짜 왔고 리덕스, 리덕스 툴킷, 디바운싱 등등 각각 사용한 기술들이 많이 다르다는 점이 매우 신기했다. 다양한 기술들을 배울 수 있었고 특히나 내가 알고있기로는 routing을 통해 parameter로 데이터를 전달한건 나밖에 없었다.

 

그래서 chat gpt한테

1. 라우터 파라미터를 통해 데이터를 전달하는것

2. 리덕스를 도입해 store에 접근하여 데이터를 가져오는 것

 

이 둘 중 어느것이 더 효율적이라고 볼 수 있는지에 대해 물어봤다.

 

 

그랬더니 프로젝트의 규모와 복잡성에 따라 특히 다르고 이번 프로젝트 같은 경우 매우 작고 페이지가 2개밖에 되지않아 문제될 것은 없으나, 서로 컴포넌트간에 직접적인 의존성이 생기고 상위 컴포넌트에서 중간 단계를 거쳐 데이터를 전달하기 때문에 프로젝트의 규모가 커지면 문제될 것처럼 보인다고 했다.

 

redux 를 사용하면 초기 설정과 관리에 많은 작업이 필요하긴 하지만, 여러 컴포넌트에서 데이터를 공유하기 때문에 컴포넌트간의 불필요한 의존성을 없앤다는 단점이 있다.

 

 

이번에 책과 강의를 통해서 리덕스 미들웨어를 통한 비동기 작업 처리에 대해 공부하고 있는데, 강의가 끝나고 나면 어떤 식으로 리팩토링 하는게 좋을지 생각해봐야겠다.

 

 

 

 

 

 


2. ☀️ 코드 리팩토링 ☀️

 

목록

  1. .env 파일 활성화 (URL, API_KEY)
  2. 렌더링 최적화 (useCallback)
  3. debouncing (lodash)

(issues: https://github.com/nayoung3669/ReactStudy/issues/1)

 

 

리팩토링 글에는 왠지 이 이모티콘이 어울린다 ☀️

 

 

1. root 폴더에 .env 정의

//.env
VITE_REACT_API_KEY=api_key

vite로 만든 앱은 VITE_ 가 맨 앞에 같이 정의되어있어야 한다.

 

키 사용하기!

try {
    const response = await axios(`${SEARCH_URL}/${category}`, {
      headers: {
        //import key
        Authorization: `KakaoAK ${import.meta.env.VITE_REACT_API_KEY}`,
      },
      params: {
        query: query,
        sort: "accuracy",
        size: size,
      },
    });
    setResolved(response.data.documents);
}

CRA는 process.env로 접근하면 된다.

 

 

이 중요한 API Key는 git에 올려지면 안되니 .gitignore에도 추가하자

# ignore .env
.env

맨 아래 추가했다. 그럼 이제 git으로 부터 무시되고 커밋 이력에도 남지 않는다. 키는 항상 조심 또 조심

 

 

 

2. 렌더링 최적화

 

useCallback을 통해 함수 렌더링 시 같은 메모리 주소를 참조하도록했다.

const SearchInputContainer = () => {
  const [text, setText] = useState("");
  const [category, setCategory] = useState(categories[0].category);
  //custom hook으로 선택한 category와 text를 넘겨주고, loading, data, error 변수 디스트럭쳐링
  const size = 3;
  const [loading, data, error] = usePromise(category, text, size);
  const [autoComplete, setAutoComplete] = useState([]);

  useEffect(() => {
    data && setAutoComplete(data.map((a) => a.title));
  }, [data]);

  const onChangeText = useCallback((e) => {
    setText(e.target.value);
  }, []);

  const onChangeCategory = useCallback((e) => {
    setCategory(e.target.value);
  }, []);

  const navigate = useNavigate();

  const onClickHandler = useCallback(() => {
    navigate(`${category}/${text}`);
  }, [category, navigate, text]);

  const onKeyPressHandler = useCallback(
    (e) => {
      if (e.key === "Enter") {
        navigate(`${category}/${text}`);
      }
    },
    [category, navigate, text],
  );

 

 

여기서 props를 받는 SearchInput component또한 props가 변경되지 않는다면 리렌더링하지 않도록 했다.

 

/* eslint-disable react/prop-types */
import { CancelRounded } from "@material-ui/icons";
import { SearchOutlined } from "@material-ui/icons";
import React from "react";

const SearchInput = ({
  text,
  categories,
  onChangeText,
  onChangeCategory,
  onKeyPressHandler,
}) => {
  return (
    <>
	   ...
    </>
  );
};

export default React.memo(SearchInput);

 

렌더링 시간 비교

 

렌더링 최적화 전

 

 

 

 

 

렌더링 최적화 후

 

단축성공 👻

 

 

 

3. 디바운싱

디바운스는 반복적인 특정 동작을 반복되는 과정에서 강제적으로 대기하는 것을 말한다. 아주 빠르게 같은 동작을 처리하는 함수가 있을 때, 그 중간 과정을 모두 거치지 않고 결과를 모아서 보여줘도 문제가 되지 않을 경우 사용한다. (연이어 호출되는 함수들 중 마지막 함수만 호출하도록 함)

 

(vs throttling : 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것)

 

특히 검색창에서 사용자가 검색어를 입력할 때, 매 입력 시 api 호출을 할 경우 중간 과정을 생략하고 사용자가 입력을 멈췄을 때 debounce를 통해 보여줄 수 있다.

 

lodash를 사용하면 이 디바운스를 간단하게 작성할 수 있다. lodash는 순수 자바스크립트로 작성된 유틸리티를 제공하는 라이브러리이며 빠르고 직관적이라 많이 사용되고 있다.

 

 

useEffect(() => {
    const autoCompleteHandler = debounce(() => {
      console.log("debounced");
      if (data) {
        setAutoComplete(data.map((a) => a.title));
      }
    }, 500);
    autoCompleteHandler();

    return () => {
      autoCompleteHandler.cancel();
    };
  }, [data]);

console.log("debounced")가 찍히는 콘솔을 확인하려면 5000밀리초 정도로 설정해서 속으로 5초를 세면서.. 확인하면 된다 ㅎ하하

 

이제 마지막 입력 기준 500 밀리초 이후 api 가 호출되므로 불필요한 호출을 막을 수 있다.

 

 

 

 

 

 

 


https://www.mrlatte.net/code/2020/12/15/lodash-debounce

https://snupi.tistory.com/189