API 호출 횟수 단축을 통한 성능 개선

성찬홍·2023년 6월 8일

Code Refactoring

목록 보기
2/5

API 호출 최소화를 통한 성능 개선!

기능 구현을 위해 구현에만 초점을 맞춰서 만들다가, 성능 측면에서 더 좋은 방법을 조언받아 개선해 보았습니다.

수정 전/후 로직

수정 전

  1. 페이지 입장 시 SELECT API를 실행해서 댓글을 불러와 클라이언트에 보여 준다.
  2. 댓글 등록을 위해 댓글을 입력하고 버튼을 누른다.
  3. INSERT API 실행을 통해 MySQL에 저장한다.
  4. useEffect를 통해 댓글 버튼을 클릭할 때마다 SELECT API를 실행하여 다시 댓글 리스트를 불러온다.
  5. 댓글 등록 완료

수정 후 로직

  1. 페이지 입장 시 SELECT API를 실행해서 댓글을 불러와 클라이언트에 보여 준다.
  2. 댓글 등록을 위해 댓글을 입력하고 버튼을 누른다.
  3. INSERT API 결과를 통해 방금 입력한 댓글의 MySQL INSERT_ID를 받아온다.
    (INSERT_ID는 임시 댓글창에도 댓글 삭제 기능을 넣어 주기 위함임)
  4. 추출된 댓글과 INSERT_ID를 useState에 저장한다.
  5. useEffect를 이용하여 useState에 저장될 때마다 map을 이용해 임시 댓글을 클라이언트에 보여 준다.
  6. 댓글 등록 완료

개선 내용

-> 수정 전에는 댓글 생성 시마다 SELECT API를 호출하여 새로운 댓글을 보여 주었다.
수정 후에는 useState에 댓글 정보를 담아서 임시로 보여 주는 형태로, API 호출을 한 번 줄일 수 있게 됐다.

수정 전 코드

// 새로운 댓글 등록하기 함수(댓글 추출 -> API 실행 -> DB에 댓글 저장)
const handleSubmit = (event: React.MouseEvent<HTMLButtonElement>) => {
  const data = insertText.current?.value;
  event.preventDefault();
  const axiosData = {
    CO_CONTEXT: data,
    S_IDX,
  };
  try {
    const result = axiosInstance.post("/clubDetail/insertContext", axiosData);
  } catch (error) {
    console.log(error);
  }
  if (insertText.current) {
    insertText.current.value = "";
  }
};

// 새로운 댓글이 등록될 때마다 getContext 함수가 실행되며 리렌더링된다.
useEffect(() => {
  getContext(); // 댓글 불러오기 함수(SELECT 댓글)
}, [handleSubmit]);

// JSX 코드
return (
  // 댓글 입력 박스
  <div className="">
    <div className="my-2">
      <form>
        <div className="flex flex-start">
          <input
            type="text"
            placeholder="댓글을 입력해 주세요"
            className="w-4/5 ml-1 border-y-neutral-800"
            ref={insertText}
          />
          <button
            type="button"
            className="mx-1 border-2 px-2 rounded-xl"
            onClick={handleSubmit}
          >
            등록
          </button>
        </div>
      </form>
    </div>
    <div className="overflow-y-auto h-[10rem]">
      // 댓글 불러오기 박스
      {context?.map((item, index) => (
        <div
          key={index}
          className="flex flex-start border-2 py-2 pl-3 my-1 text-[14px] border-y-gray-200 border-x-slate-100"
        >
          <p>{item.U_NAME} : </p>
          <p>&nbsp; {item.CO_CONTEXT}</p>
        </div>
      ))}
    </div>
  </div>
);

-> INSERT API 실행 후 SELECT API 실행으로 두 번의 API 호출이 이루어진다.

수정 후 코드

// 댓글 추출용 useRef
const insertText = useRef<HTMLInputElement>(null);

// 댓글 입력 함수
const handleSubmit = async (event: React.MouseEvent<HTMLButtonElement>) => {
  // 댓글 추출
  const data = insertText.current?.value;
  event.preventDefault();

  // INSERT API에 보낼 데이터
  const axiosData = {
    CO_CONTEXT: data,
    S_IDX,
  };

  try {
    // INSERT API 실행
    const result = await axiosInstance.post(
      "/clubDetail/insertContext",
      axiosData
    );

    // 유저 ID와 받아온 INSERT_ID 저장
    const newAddComment = { S_IDX: S_IDX.S_IDX, data, id: result.data.id };

    // SELECT API 재호출이 아닌, 임시로 보여 줄 댓글 박스 데이터 저장
    setTemporaryContext((prevComment) => [...prevComment, newAddComment]);
  } catch (error) {
    console.log(error);
  }

  if (insertText.current) {
    insertText.current.value = "";
  }
};

// 댓글 입력이 완료될 때마다 임시 댓글창을 보여 주기 위해 리렌더링 발생
useEffect(() => {}, [temporaryContext]);

// JSX 코드

<div className="my-2">
  <form>
    <div className="flex border-2 border-x-white border-t-white flex-start">
      <input
        type="text"
        placeholder="댓글을 입력해 주세요"
        className="w-full outline-none ml-1 border-y-neutral-800 pl-2 py-1"
        ref={insertText}
      />
      <button
        type="button"
        className="mx-1 px-2 rounded-xl"
        onClick={handleSubmit}
      >
        <MdSend />
      </button>
    </div>
  </form>
</div>

<div>
  <div>
    // 새로운 API 호출을 통한 댓글 불러오기가 아닌, 리렌더링만으로 댓글을 임시로 보여 주기 위한 코드
    {temporaryContext.map((item, index) => (
      <div
        key={index}
        ref={(ref) => (contextRef.current[String(item.id)] = ref)}
      >
        <div
          className="flex justify-between border-2 py-2 pl-3 my-1
            text-[14px] border-y-gray-100 border-x-white border-t-white"
        >
          <p>
            {userName} :&nbsp; {item.data}
          </p>
          <p>
            <button
              onClick={() => deleteTemporaryContext(Number(item.id))}
            >
              <GrFormClose />
            </button>
          </p>
        </div>
      </div>
    ))}
  </div>
</div>

-> INSERT API 실행 후 정보를 useState에 담아 보여 줌으로써 백엔드와 한 번 더 통신하는 것이 아니라 클라이언트에서 처리할 수 있게 됐다.
(다른 페이지에 갔다가 다시 들어오면 useState는 초기화되고 SELECT API를 통해 댓글을 불러온다.)

정리

사용자가 많은 서비스에서는 API 호출 한 번이 성능에 영향을 줄 수 있다.
그래서 기능을 구현할 때 렌더링을 최소화하고, API를 호출하지 않고 클라이언트에서 처리할 수 있는 방법을 고민할 필요가 있다.

느낀 점

React 환경에서 작업할 때는 렌더링을 어떻게 최소화할 수 있을지에 대해서만 생각하며 만들어 왔어서, API 호출을 줄이는 방향은 생각하지 못했었습니다. 앞으로는 기능을 구현할 때 조금 더 나은 방향을 생각해 보고 구현할 수 있을 것 같습니다.

profile
꾸준한 개발자

0개의 댓글