Act99 기술블로그

[Nextjs] 주식사이트 만들기-7 채팅 프론트엔드 Apollo client - polling & refetch 를 이용한 실시간 채팅 구현 본문

개발팁저장소/nextjs

[Nextjs] 주식사이트 만들기-7 채팅 프론트엔드 Apollo client - polling & refetch 를 이용한 실시간 채팅 구현

Act99 2021. 12. 15. 10:07

실시간 채팅구현을 Apollo client useQuery 를 이용해 만들 방법을 생각하다가 apollo client docs 에서 polling 과 refetch 개념을 찾았다.

 

 

https://www.apollographql.com/docs/react/data/queries/

 

Queries

Fetch data with the useQuery hook

www.apollographql.com

 

polling의 경우 타이머 설정 후 타이머 설정 시간마다 실시간으로 refetch를 해주는 기능으로, 쉽게 말해 settimeout 같은 느낌으로 데이터 refetch를 시켜준다.

 

코드는 다음과 같다.

 

  const { data } = useQuery<frontChatQuery>(GET_CHAT, {
    variables: {},
    pollInterval: 500,
  });

 

그 결과 실시간 채팅 구현이 잘 된다.

 

 

하지만 이것 역시 문제가 있다고 판단되었다.

그건 백엔드에 데이터를 요청하는 수가 너무 많다는 것이다.

 

가령, 다음 이미지를 보면 이해할 수 있다.

 

 

보다시피 채팅 데이터가 업데이트 되지 않았음에도 계속 쿼리를 요청하는 것을 볼 수 있다.

따라서 refetch 를 이용해 사용자가 mutation 을 했을 때, refetch 를 하도록 설정하려고 한다.

 

useMutation 사용시 query를 refetch 하는 코드는 다음과 같다.

 

  const [createChatMutation, { data: createChatMutationResult, loading }] =
    useMutation<frontCreateChatMutation, frontCreateChatMutationVariables>(
      CREATE_CHAT,
      {
        onCompleted,
        refetchQueries: [
          {
            query: GET_CHAT,
          },
        ],
      }
    );

 

그 결과

 

 

mutation 시 실시간으로 refetch 가 되면서

 

 

데이터를 무리하게 요구하지 않고 mutation 시에만 요구하게 된다.

 

 

하지만 이 역시 단편적인 면만 봤을 때이다.

 

 

이 코드 구현은 결과적으로 사용자 본인이 mutation 을 진행했을 때 그 값을 실시간으로 받아볼 수 있을 뿐이며,

다른 사람들은 새로고침을 하지 않는 이상 이 채팅 내용을 볼 수 없다. 

 

polling과 refetch에 대한 문서들을 보던 중 나와 비슷한 상황을 겪는 사람들을 볼 수 있었다.

 

polling 의 경우 주기를 짧게하면 서버 과부하가 걸릴 위험이 있고, refetch는 데이터 수신을 할 때 결국 refresh 형태로 이루어져야 하기 때문에 사실상 실시간 구현이 아니라고 한다고 한다.

 

그리고 대부분 실시간 기능을 구현하는 웹 사이트들은 polling 으로 구현한다고 들었다.

 

따라서 polling 기능을 이용할 예정이며, 다른 방법을 사용할 수 있는지 꾸준히 찾아볼 예정이다.

 

 

- 완성코드

 

const GET_CHAT = gql`
  query frontChatQuery {
    chats {
      id
      createdAt
      updatedAt
      user
      text
    }
  }
`;

const Chat: React.FC<Props> = ({ width, height }) => {
  const [nickname, setNickname] = useState("따끈한 메밀호빵");
  const { data } = useQuery<frontChatQuery>(GET_CHAT, {
    variables: {},
    pollInterval: 500,
  });

  const { register, handleSubmit, getValues, formState, reset } =
    useForm<Inputs>({
      defaultValues: { user: nickname },
    });
  const onCompleted = (data: frontCreateChatMutation) => {
    const {
      createChat: { ok },
    } = data;
    if (ok) {
      console.log(data);
      console.log("Okay! Chat!");
    }
  };

  const [createChatMutation, { data: createChatMutationResult, loading }] =
    useMutation<frontCreateChatMutation, frontCreateChatMutationVariables>(
      CREATE_CHAT,
      {
        onCompleted,
        // refetchQueries: [
        //   {
        //     query: GET_CHAT,
        //   },
        // ],
      }
    );

  const onSubmit: SubmitHandler<Inputs> = () => {
    if (!loading) {
      const { user, text } = getValues();
      createChatMutation({ variables: { createChatDto: { user, text } } });
      setNickname(user);
    }
  };
  useEffect(() => {
    if (formState.isSubmitSuccessful) {
      reset({ user: nickname, text: "" });
    }
  }, [formState, reset]);

  return (
    <div
      style={{
        width: 300,
        height: height == undefined ? undefined : height * 0.9,
      }}
      className=" bg-black flex flex-col justify-center items-center"
    >
      <div className=" w-72 h-5/6  bg-chartGray-default">
        (
        <ul>
          {data?.chats.map((item: any, index: number) => {
            return (
              <li key={index} className="flex flex-row">
                <h3 className=" mx-2 text-white">{item.id}: </h3>
                <h3 className=" mx-2 text-white">{item.user}: </h3>
                <h3 className=" text-white">{item.text}</h3>
              </li>
            );
          })}
          <li></li>
        </ul>
        )
      </div>
      <form className="flex flex-col my-5" onSubmit={handleSubmit(onSubmit)}>
        <input
          className=" bg-gray-300 mb-2"
          {...register("user", { required: true })}
        ></input>
        <input
          className=" bg-gray-300 mb-2"
          {...register("text", { required: true })}
        ></input>
        <button className=" bg-gray-400" type="submit">
          {loading ? "로딩중..." : "전송"}
        </button>
      </form>
    </div>
  );
};

export default Chat;

 

 

결과

 

 

채팅 완료

 

채팅까지 구현되었다.