본문 바로가기

프론트엔드/리액트

[리액트] 실습 - 함수형 컴포넌트에서 데이터 통신하기

간단한 클래스형->함수형 리팩토링은 했는데 서버쪽 세팅이 늦어지고 있어서 함수형 컴포넌트에서 데이터 통신을 해보는 실습을 못 하고 있었음. 그러다 좋은 강좌를 발견해서 공부한 내용을 정리한다.

www.youtube.com/watch?v=DtLhiMxgsm0

 

목표는

1. hook 사용하기

2. API 통신하기 

 


[1차]

영상에서 다룬 부분만 완료해봤다.

uesEffect 훅에서 API 통신을 하고, 데이터를 파싱해서 각각의 state를 세팅한 뒤 차트를 그려주고 끝.

 

[내용정리]

 

1. UseEffect란?

결론부터 말하자면 함수형 컴포넌트에서 사용되는 hook의 일종이다.

기존의 클래스형 컴포넌트가 가지던 라이프사이클들 (mount, update, unmount) 과 같은 목적으로 탄생했다.

 

리액트 공식 문서는 클래스형 컴포넌트의 라이프사이클과 함수형 컴포넌트의 hook에 대해 다음과 같이 평한다.

함께 변경되는 상호 관련 코드는 분리되지만 이와 연관 없는 코드들은 단일 메서드로 결합합니다.

이같은 문제를 해결하기 위해, 생명주기 메서드를 기반으로 쪼개는 것 보다는, 
Hook을 통해 서로 비슷한 기능하는 작은 함수의 묶음으로 컴포넌트를 나누는 방법을 사용할 수 있습니다.

하나의 useEffect 훅에서 처리할 수 있는 동작이 기존에는 분리되어있는 라이프사이클에 따라 각각의 생명주기에 중복된 코드를 작성해줘야 했으며, 또는 연계된 동작이 mount에는 등록하는 코드를, unmount에 해제하는 코드를 조각조각 나눠서 넣어야 하기도 했다. 여기에 컴포넌트에 정의된 동작이 두 개 이상이 되면 서로 같은 동작은 각각의 라이프사이클에 쪼개져야 하고 다른 동작은 라이프사이클이에 묶여있고... 아주 정신없는 컴포넌트가 탄생하는 불상사가 발생한다.

 

이 외에 클래스형 컴포넌트와 함수형 컴포넌트에 대한 논의는 다음 세션의 영상(11:10~15:10 부분)을 참고하면 실제 코드와 함께 잘 설명되어있으니 꼭 한 번 보길 추천함.

if.kakao.com/2019/program?sessionId=54f8e1d3-ed6e-4020-b9da-5bfa43432c94

 

if(kakao)2020

오늘도 카카오는 일상을 바꾸는 중

if.kakao.com

 

 

2. hook과 API 통신

hook에서 API통신은 useEffect 훅에서 하면 된다.

초기 렌더링이든 후에 state변경에 의한 재렌더링이든 상관없이 컴포넌트가 렌더링 될 때 마다 이 useEffect를 타 줄 것이기 때문. 

구조를 단순화하면 다음과 같다.

const [state, setState] = useState({});

useEffect(() => {
  const fetchEvents = async () => {
    const response = await axios.get("...some url...");
    makeData(response);
  };
  const makeData = (data) => {
    // parsing data...
    setState( ...parsedData... );
  };

  fetchEvents();
}, []);

 

이 때 주의사항은 

useEffect 자체는 async function이 될 수 없다!

useEffect는 함수만 반환해야 하기 때문에 Promise 객체가 반환되는 asynce await를 직접 useEffect에 붙여버리면 안된다.

따라서 fetchEvent라는 비동기 통신 function을 먼저 정의하고 이를 부르는 형식으로 작성해줘야 함.

 

 

3. useEffect 무한루프

위 코드에서 어딘가 이상한 부분을 발견했는가? 

useEffect는 렌더링 이후에 매번 수행되는 걸까요?
 네, 기본적으로 첫번째 렌더링과 이후의 모든 업데이트에서 수행됩니다!

빠릿빠릿한 useEffect는 모든 렌더링에 칼같이 반응한다.

state가 변경된다면 기본적으로 재렌더링이 일어난다.

그리고 내 코드는 useEffect 내부에서 state를 변경하고 있다.

 

즉 useEffect의 신조에 따르면

렌더링 > useEffect 동작! > state 변경 > 재렌더링 > useEffect 동작! > state 변경 > 재렌더링 > ...어?

이런 무한루프에 빠져버린다!

항상 그런 건 아니고 effect 훅 내에서 재렌더링을 야기하는 동작을 하는 경우에 한해서 그러하다

 

그런데 어떻게 내 코드는 멀쩡하게 동작했을까?

정답은 코드 끝자락에 보이는 useEffect의 두번째 인자로 전달된 빈 배열 []에 있다.

 

리액트 공식 문서는 과하게 부지런한 effect를 진정시키기 위한 방법을 소개하고 있다.

ko.reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

 

Using the Effect Hook – React

A JavaScript library for building user interfaces

ko.reactjs.org

모든 렌더링에 effect를 발생시키는 대신, 특정 값의 변경을 감지해서 기준값이 변하지 않는 한 effect를 건너뛰게 하는 방식이다. 

 

이 때 effect 가 변화를 감지할 기준값이 바로 두번째 인자에 배열 형태로 넘어가게 된다. 

예를 들어 다음 코드에서는 count가 바뀔 때만 effect를 재실행한다.

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); 

 

이를 활용해 내 코드에서는 두번째 인자에 빈 배열을 넘겨서 아예 기준값을 없애버렸다.

즉, 초기 렌더링 이후의 재렌더링에 있어서 이 이펙트는 모두 무시된다!

 

현재는 처음 화면 로딩 시에만 데이터 통신을 하면 되므로 이런 식으로 처리가 가능하다. 

이후 사용자의 요청에 따라 데이터 재요청이 필요한 경우는 어떻게 해야 할까? 2차 실습 내용에서 마저 살펴보자.

 

[기타 내용]

 

1) 중복 코드 없이 컴포넌트 만들기

본 컴포넌트는 총 세 종류의 차트를 생성해서 붙인다. 

컴포넌트를 정의하는 부분들이 미묘하게 조금씩 다른데 중복되는 내용이 많았음.

그래서 차트 정보를 저장한 오브젝트를 하나 만들어서 컴포넌트를 따로 생성해서 붙여줬다. 

좌 : 변경 전 / 우 :변경 후

하지만 이렇게까지 하는 게 더 나은게 맞는지, 걍 좀 중복되는 코드가 있더라도 깔쌈하게 넣는게 맞는지는 모르겠다.

 

 

2) grid template과 canvas (feat. chart.js)

다음과 같이 grid 속성을 가지는 contents 태그 하위에 div로 감싼 Chart 컴포넌트를 넣어줬다. 

이 때 chart.js가 생성한 canvas가 grid가 정의해준 범위를 넘어 크기가 마구 뛰쳐나오거나, 창의 크기를 줄일 경우 resize가 되지 않는 불상사가 발생.

div 태그로 감싸줬던 걸 빼보거나, canvas의 height를 100% !important로 고정하거나 chartjs의 resize option을 추가하는 등 별 짓을 다해보다 겨우 해결책을 찾았다.

우선

grid-template-columns: 1fr 1fr;  이렇게 간단히 정의해줬던 colunms의 크기를

grid-template-columns: repeat(2, minmax(200px, 1fr));  이처럼 minmax로 바꿔줌.

그리고

chart options에

maintainAspectRatio: false,

를 추가해주면 됨.

 

 


 

[2차]

1차 실습 내용에 초기 렌더링 이후에도 사용자의 동작에 따라 추가적인 통신 기능이 가능하도록 해줄 거임. 

셀렉트 박스와 라디오 버튼을 넣어 국가, 단위(월,일), 기간을 선택 가능하게 함.   

 

내용은 추가 예정!