Redux Toolkit 알아보기 -RTK에서 네트워크 통신하기-

김민규·2023년 1월 8일
0
post-thumbnail

클라이언트의 데이터 통신

리액트로 어플리케이션을 만들면서 서버와 통신할 경우를 생각해보자. 예를 들어 쇼핑몰의 장바구니를 예시로 들어보자.

프로그램의 처리 과정을 생각해본다면,

대략 이렇게 진행된다고 예상 할 수 있다.
하지만 우리가 여기서 생각해봐야할 단계가 하나 존재한다.

바로 상품 데이터의 데이터 관련 검증 수행 & 상품 데이터 전송, 이 부분이다.

서버에서 데이터 관련 검증 로직을 수행한다면 클라이언트에선 단순히 이벤트가 발생한 데이터를 전송해주기만 하면 된다.

하지만 만약 백엔드가 요청 & 응답만 실행하는 단순 프로그램(ex.파이어베이스)을 사용한 경우라면 얘기가 달라진다.

위 같은 경우에는 기존의 장바구니 데이터를 가지고 클라이언트 차원에서 검증 로직을 실행한 이후에 서버로 데이터를 보내주어야한다.

그럼 그냥 무지성 fetch, POST 하면 되는거 아닌가?

어플리케이션이 redux를 사용한다고 가정해보자.

데이터 검증을 클라이언트에서 실행한다는 가정 하에 생각해보면 선택지는 두가지가 나온다.

첫번째, 이벤트가 발생한 컴포넌트에서 검증을 실행하고 상태를 최신화, 이후 서버로 전송

두번째, redux에서 dispatch로 데이터를 받을때 리듀서 내부에서 검증을 실행하고 서버로 전송

하지만 이 두가지 방법 모두 문제점이 존재한다.

이벤트가 발생한 컴포넌트에서 검증을 실행

첫번째 케이스 같은 경우 검증 로직의 재사용성이 문제점이다.
데이터 검증 로직이 꼭 한 개의 컴포넌트에서만 필요하진 않을 것이기 때문이다.

이벤트가 발생한 컴포넌트에서 데이터 검증을 실행할 시, 만약 다른 컴포넌트에서도 동일한 검증 로직을 사용할 경우 검증 로직을 반복해서 적어야하는 문제점이 발생한다.

리듀서 내부에서 상태를 변경할 때 검증을 실행

두번째 케이스를 적용할 경우 검증 로직의 재사용성을 보장 할 수 있다. 하지만 이 케이스의 문제점은 바로 redux의 기본 원칙을 무시한다는 점이다.

바로 상태의 변화는 항상 순수한 함수이며 네트워크 처리 등 비동기가 존재해선 안된다는 점이다.

따라서 단순히 redux만 사용한다면 위와 같은 문제에 빠지게 된다.

첫번째 해결책, 네트워크 통신 시점 변경하기

첫번째 해결책으로는 네트워크 통신을 하는 시점을 변경하는 것이다.

기존에 redux의 action 함수 내부에서 네트워크 요청을 계획하는 것이 아닌 컴포넌트에서 네트워크 통신을 보내는 것이다.

첫번째 케이스와 다른점은 데이터 검증은 action 함수 내부에서 실행하고 action으로 인해 변화한 상태를 구독한다.

그리고 구독한 상태를 useEffect에 의존성으로 할당한 다음 상태가 업데이트 될 때마다 useEffect 내부에서 fetch를 통한 네트워크 통신이 일어나게 된다면 redux를 적극 사용하면서 네트워크 통신을 할 수 있게 된다.

하지만 이또한 문제점이 존재하는데 useEffect의 특성인 컴포넌트가 최초 렌더링 시 실행된다는 특징으로 인해 최초 렌더링 시 기존 장바구니를 덮어쓴다는 점이다.

해결법 - 최초 렌더링 판단 전역변수 선언

useEffect가 최초 렌더링시 실행되면서 장바구니를 뒤집어 쓰는 문제를 해결하기 위해서 최초 렌더링인지를 판단하는 변수를 선언한다.

이때 변수는 컴포넌트 바깥let 형태의 true 상태의 불리언으로 선언한다.

그리고 useEffect 내부에서 통신이 이루어지기 전에 최초 렌더링 판단 변수에 대한 조건문을 작성하여 최초 렌더링 판단 변수의 값이 true 일시 값을 false로 재할당하여 해제하고 ealrt return하여 통신을 방지한다.

이후부턴 의존성에 의한 useEffect 실행시 최초 렌더링 판단 변수의 값이 false이므로 통신히 정상적으로 진행된다.

// 컴포넌트 바깥 환경


// 최초 렌더링을 판단하는 변수
let isInitial = true;


// 컴포넌트 내부 환경
function App() {
  const cart = useSelector((state) => state.cart);
  const dispatch = useDispatch();

  useEffect(() => {
    const sendCartData = async () => {
      // ...네트워크 통신 로직
    };
	
    // 최초 렌더링일시 최초 렌더링 상태를 해제하고 useEffect를 탈출한다
    if (isInitial) {
      isInitial = false;
      return;
    }

    // 최초 렌더링일시 통신 코드까지 도달하지 않지만 아닐시 정상적으로 동작한다
    sendCartData();
  }, [cart, dispatch]);
  
  //....

두번째 해결책, Thunk를 사용하여 통신 로직 독립시키기

첫번째 해결책으로도 네트워크 통신이 가능하다. 하지만 새로운 문제가 생기는데 네트워크 통신 로직이 한 컴포넌트에 귀속된다는 것과 컴포넌트가 무거워진다는 점이다.

이를 해결 할 수 있는 방법이 있는데 바로 Thunk를 사용하는 것이다. Thunk란 다른 작업이 완료될 때까지 작업을 지연시키는 단순한 함수를 의미한다.


// App.js

function App() {
  //....
  
  useEffect(() => {
   	  // dispatch의 인자로 thunk 함수를 호출
      // ⚠️ 메모리에 로그된 함수를 지정하는것이 아닌 함수를 호출
    
      dispatch(sendData(data));
  }, [data]);
  
  //...
}

// thunk 존재 파일.js

export const sendData = (data) => {
  // thunk 함수는 비동기 함수를 반환함
  // 반환되는 함수는 dispatch가 동작하는 동안 RTK 내부에서 실행되면 인자로 dispatch가 부여되므로 내부에서 dispatch를 사용할 수 있음
  // RTK의 action creator 함수의 원리와 같다.
  return async (dispatch) => {

    const sendRequest = async () => {
      const response = await fetch(
        "url",
        {
          method: "PUT",
          body: JSON.stringify(data),
        }
      );

      if (!response.ok) {
        throw new Error("Sending data failed.");
      }
    };
    
    dispatch(someAction.someFunction()) // ui 변경, GET 통신일시 상태 변경 등 dispatch로 상태 업데이트 가능

    try {
      await sendRequest();
    } catch (error) {
      // error 로직
      );
    }
  };
};

위와 같이 네트워크를 통신하는 로직을 분리하여 비동기 작업을 처리할 수도 있다.

즉 로직을 정리하자면

  1. 상품 추가 이벤트 발생 =>

  2. 리듀서에서 상태 업데이트 =>

  3. useEffect에서 의존성 변경으로 인한 useEffect 내부 함수 실행 =>

  4. 네트워크 통신 함수 호출 =>

  5. 비동기 thunk 함수 반환 =>

  6. dispatch 내부에서 thunk 함수를 실행 (이때 인자로 dispatch가 부여된다) =>

  7. 네트워크 통신

이렇게 볼 수 있다.

referenced by React Complete Guide - udemy

profile
Error Driven Development

0개의 댓글