코드스테이츠/코드스테이츠 @ 개발 복습

[코드 스테이츠] 55일차, 8주차 복습(1) - React 데이터 흐름, Effect Hook"

Je-chan 2021. 9. 11. 22:07


[ 오늘의 TODO ]

  1. 코드 스테이츠) 월~수 내용 복습
    // React 데이터 흐름 
    // useEffect
  2. 패스트 캠퍼스) 인강 3개 이상 듣기 // optional
  3. 스터디 그룹) 프로그래머스 문제 풀기
  4. 생활) 물 1L 이상 마시기
  5. 생활) 수-토-일 운동 

 


[ 오늘의 복습 ]

1. React에서의 데이터 흐름

  React 개발의 큰 특징은 컴포넌트 단위라는 점이다. 디자인을 받으면 UI를 쪼개서 컴포넌트를 만들고 페이지를 조립해 나가는 상향식 방식이 일반적이다. 더불어, React 공식문서에서 React를 소개할 때 붙는 키워드가 있으니, 바로 "단방향 데이터 흐름"이다. 단방향 데이터 흐름이란, 한쪽 방향으로만 데이터가 흐른다는 것인데 데이터의 흐름은 하향식이다.

 

1) 컴포넌트

  앱을 만들 때는 상향식(bottom-up) 구조로 만든다. 물론, 규모가 매우 큰 프로젝트에서는 상향식으로 만든 후 테스트를 하면서 만들기도 한다. 하지만 기본적으로는 상향식으로 만드는데 이렇게 만들면 테스트가 쉽고 확장성이 좋다는 장점이 있다. 그래서 처음 언급했던 것처럼 디자인을 받았을 때 컴포넌트를 나누는 작업을 한다. 이때, 컴포넌트를 나누는 기준은 "관심사 분리"가 된다. 단일 책임 원칙(Single Responsibility Principle)에 의해 하나의 컴포넌트는 한 가지 일만 하도록 작성하는 것이 좋다. 그래야 React 를 충분히 잘 활용하는 것이다. 밑에는 React로 사고하기라는 공식 문서의 내용이다.

 

https://ko.reactjs.org/docs/thinking-in-react.html

 

React로 사고하기 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

  공식 문서의 내용을 정리하면 다음과 같이 할 수 있을 것이다. 

 

1. UI를 컴포넌트 계층으로 나누기

  단일 책임 원칙에 따라 컴포넌트가 커지면 한 가지의 기능만을 할 수 있도록 작은 하위 컴포넌트들로 쪼개야할 것이다.

 

2. React로 정적인 버전 만들기

  일단, UI 는 렌더링 되지만 아무런 동작을 하지 않는 버전을 만든다. 정적인 버전은 생각은 적게 필요하지만 타이핑은 많이 필요한 작업이다. 이때, 동적인 부분이라고 했으므로 값이 계속해서 변화하는 State 는 사용하지 않는 것이 좋다

 

3. UI state 에 대한 최소한의 (하지만 완전한) 표현 찾아내기

  애플리케이션을 올바르게 사용하기 위해 애플리케이션에서 필요로 하는 변경 가능한 state의 최소 집합을 생각해야 한다. 여기서 핵심은 중복 배제 원칙(Don't repeat yourself") 다. 중복 배제 원칙은 React 뿐만 아니라 컴퓨터 프로그래밍에서 종종 사용되는 용어인데, 모든 형태의 중복을 지양한다는 의미다. React와 같이 다층 구조에서는 이 원칙이 굉장히 중요하며 실제로 React는 코드의 재사용성이 매우 용이하기에 중복을 최대한으로 지양하기 위해 State를 최소한으로 정해야 한다. 일단 최소한으로 설정한 후, 필요한 건 그때마다 추가하도록 한다.

 

4. State가 어디에 있어야 할지 찾기

  이게 가장 어려운 부분 중 하나다. React는 다층 구조고 컴포넌트 계층 구조에 따라서 데이터는 위에서 아래로 흐른다. 그렇기에 이 데이터를 어디에서부터 지정해줘야 할지 찾기가 어렵다. 하지만 만약 사용해야 하는 state가 여러 컴포넌트에서 사용되고 있다면 그 컴포넌트들의 공통 조상인 컴포넌트에 state를 넣어주면 된다. 

 

5. 역방향 데이터 흐름 추가하기

  단방향 데이터 흐름이라면서 역방향이라니 이게 무슨소리야? 할 것이다. 이렇게 생각해보자. 나는 지금 티스토리 블로그에 글을 쓰고 있다. 글을 써서 파일을 업로드하면 사람들은 이 글을 볼 수 있다. 하지만, 이 글은 지금 게시물로써도 볼 수 있고 오른쪽에 사이드 바에서 "인기 게시물" 이라는 목록에서도 볼 수 있고, 블로그 홈 화면에서도 볼 수 있다. 이렇게 작성한 글을 데이터라고 했을 때 이 데이터는 게시물, 사이드 바, 홈 화면 총 세 곳에서 사용되고 있다. 그렇기에 전달되는 데이터의 흐름은 이 세 컴포넌트들의 조상에서 내려다 줄 것이다. 이 부분을 뿌리 데이터라고 하자.(임시로 가독성 좋게 뿌리 데이터라고 얘기를 했지만 공식 문서에서 제시하는 용어는 진리의 원천(Source of truth)이다. )하지만 만약에 내가 이 글을 업로드한다면, 혹은 각각의 컴포넌트에서 삭제 버튼을 누른다면 어떻게 될까? 사용자로부터 CRUD(Create, Read, Update, Delete) 의 이벤트를 받으면 여기서 받은 데이터는 뿌리 데이터로 거슬러 올라가서 뿌리 데이터에서 CRUD를 행해야 할 것이다. 이런 걸 역방향 데이터 흐름 추가하기라고 한다. 그러면, 단방향 데이터 흐름이 아니라 양방향 데이터잖아? 라고 생각할 수 있다. 하지만, 실질적으로는 단방향 데이터로 사용할 수 있다. 그 문제와 왜 그렇게 단방향 데이터 흐름에 집착하는 건지 밑에서 한 번 다뤄보도록 하자.

 

 

2) 데이터의 흐름

  컴포넌트는 컴포넌트 밖의 props를 마치 인자나 속성처럼 전달받을 수 있다. 즉, 데이터를 전달하는 주체는 부모 컴포는트고 데이터 흐름은 하향식 구조를 띤다. 그렇기에 컴포넌트는 데이터가 어디에서 왔는지 알 수 없다. 이 원칙을 React를 대표하는 설명으로 "단방향 데이터 흐름(one-way data flow)라고 한다. 모든 것을 모듈화하고 빠르게 만들어준다는 장점이 있다. 

 

  데이터는 변하는 값과 변하지 않을 값으로 나뉠 수 있다. 얼마든지 변할 수 있는 값들을 상태, State라고 하다. State는 많아질수록 애플리케이션이 복잡해지기에 최소화하는 것이 가장 좋다. 부모로부터 전달되거나, 시간이 지나도 변하지 않거나, 컴포넌트 안의 다른 state나 props를 가지고 계산이 가능하다면 state가 아니다. 

 

  앞서 언급했듯이 state는 여러 컴포넌트에서 사용되는 경우가 종종 있다. 특정 컴포넌트에서만 사용하는 state라면 그 컴포넌트 안에만 넣어주면 되지만, 여러 컴포넌트에서 사용하는 경우 그 컴포넌트들의 공통 조상을 찾아서 거기에 state를 넣어줘야 한다. 

 

  state 의 위치를 정해주고 나면 부모 컴포넌트가 하위 컴포넌트에 넣은 state 가 하위 컴포넌트의 이벤트 발생에 의해서 변화되는 경우가 있다.(방금 위에서 설명한 게시물, 사이드 바, 홈에서의 글 목록 등) 단방향 데이터 흐름 원칙에 의해 후손 컴포넌트들은 props로 전달받은 데이터의 형태와 타입까지만 알 수 있고 어디서부터 왔고, 그 데이터가 어떤 입력의 형태를 가지고 있는지 내용을 알지 못한다. 그래서 하위 컴포넌트의 이벤트로 상위 컴포넌트의 상태가 바뀌는 역방향 데이터 흐름은 그 목적지가 어디인지도 모르고 일단 던지고 실행하는 것과 비슷하다.

 

 이런 경우에는 그걸 실행시키는 함수 자체도 부모 컴포넌트에서 하위 컴포넌트로 넘겨줄 수 있다. 업로드 상황만을 가정했을 때 업로드 버튼이 눌리면 state를 변화시키는 handler 함수를 뿌리 state가 있는 컴포넌트에서 작성한 후, 그 함수를 업로드 버튼이 있는 컴포넌트에 props로 넘겨주면 된다. 그렇게 함으로써 단방향 데이터 흐름을 유지할 수 있다.

 

 

 

2. useEffect Hook

  리액츠에서 Effect Hook 은 보통 서버를 가져올 때 종종 사용한다. 이 개념을 명확하게 이해하기 위해 다음의 내용을 살펴보자

 

1) Side Effect

   함수 내에 어떤 구현이 외부 함수에 영향을 끼치는 경우 해당 함수는 Side Effect가 있다고 한다.

let arr = []

function pushing () {
  for(let i = 0 ; i < 5 ; i++) {
    arr.push(i)
  }
}

pushing();

// 이렇게 함수 밖에 있는 변수에 영향을 끼치는 경우

 

2) Pure Function (순수 함수)

 순수 함수란 오직 함수 입력만이 함수의 결과에 영향을 주는 함수를 의미한다. 함수의 입력이 아닌 다른 값이 함수의 결과에 영향을 미칠 때도 순수한 함수라고 할 수 없다. 또, 순수 함수는 입력으로 전달된 값을 수정하지 않는다.

 

let arr = [0, 1, 2, 3, 4]

function mapedArr (arr) {
  return arr.map(el => el + 1) // 원본 배열에 영향을 주지 않음
}

mapedArr(arr);

 

  순수 함수는 네트워크 요청과 같은 Side Effect 가 없다. 순수 함수의 큰 특징 중 하나는 어떤 인자가 주어질 때 항상 똑같은 값이 리턴됨을 보장해야 한다는 것. Side Effect 가 없어야 한다는 것. 입력 값에 아무런 변화를 주지 말아야 할 것. 이렇게 세 가지다

 

 

3) useEffect

  React 의 함수 컴포넌트는 props 가 입력으로, JSX Element 가 출력으로 나간다. 여기에는 어떤 Side Effect도 없는 순수 함수다. Props는 변형할 수 없는 Read-only 값이다. 그래서 변형되는 값인 state는 동적이며, props들은 정적이라고 하는 것이다.  하지만, React 애플리케이션에서 AJAX 요청이나 LocalStorage, 타이머 API 등을 사용할 경우 React의 입장에서는 전부 Side Effect다. React에서는 이런 Side Effect를 다루기 위한 훅으로 Effect Hook 가 있다.

 

  첫 번째 인자(함수)

  useEffect는 컴포넌트 내에서 Side Effect를 실행할 수 있게 해주는 Hook 이다. useEffect의 첫 번째 인자는 함수다. 해당 함수 내에서 Side Effect를 실행하면 된다. 

 

  두 번째 인자(공백 or 종속성 배열)

  useEffect의 두 번째 인자는 함수를 실행하는 조건이라 생각하면 된다. 인자에는 공백으로 비우던가 종속성 배열(그냥 일단 배열이라고 받아들이면 된다)을 받으면 된다. 여기서 종속성 배열을 넣지 않고 넣고의 차이는 매우 크다. 

 

넣지 않은 경우

두 번째 인자에 아무 값도 들어있지 않은 경우 useEffect 첫 번째 인자의 함수가 실행되는 조건은 다음과 같다 

  1. 컴포넌트 생성 후 나타나는 처음 화면에 렌더링
  2. 컴포넌트에 새로운 Props 가 전달되면서 렌더링
  3. 컴포넌트에 state가 바뀌면 렌더링

 

종속성 배열을 넣은 경우

  종속성 배열이란, 배열 안에 종속성의 값들이 들어가는 것을 의미한다. 종속성의 값이란 쉽게 말해 변화되는 값들을 의미한다. state로 관리되는 변화되는 값이 될 수도 있고 event에 의해서 변화되는 값일 수도 있다. 이렇게 변화가 되는 값이 종속성 배열의 요소로 들어간다. 그러면 useEffect 안에 있는 함수의 실행은 종속성 배열 안에 있는 요소들의 값이 변화할 때 실행된다.  

 

  그렇다면 종속성 배열에 빈 배열이 들어간 경우는 어떨까? useEffct 함수는 종속성 배열이 존재하면 그 배열 안에 존재하는 값들이 변할 때마다 실행하게 된다. 하지만, 참조해야할 요소가 존재하지 않으므로 실행할 수 없게 된다. 즉, 맨 처음에만 실행되고 이후에는 실행되지 않게 된다. 

 

 

4) AJAX 요청 필터링

  네트워크 요청은 Side Effect 를 발생시키는 대표적인 이벤트다. AJAX 로 데이터를 요청하고 useEffext를 사용하여 그 데이터 내에서 필터링을 거쳐 내가 원하는 데이터만을 가져오고자 할 때의 방법은 두 가지가 있다. 첫 번째는 컴포넌트 내부에서 필터링을 하는 것이고, 두 번째는 컴포넌트 외부에서 필터링을 하는 것이다

 

컴포넌트 내부에서 필터링

  서버에 요청으로 전체 데이터를 다 불러오고, 목록을 검색어로 필터링을 한다. 장점으로는 HTTP 요청의 빈도를 줄일 수 있다는 점이다. 단점으로는 클라이언트인 브라우저가 메모리 상으로 필터링을 하는 것까지 추가하게 되어 클라이언트에서 짊어야 할 부담감이 커진다는 것이다.

 

컴포넌트 외부에서 필터링

  대부분 서버에서 알아서 필터링을 하고 필터링된 목록을 들여와달라고 요청할 수 있다. 장점으로는 클라이언트가 필터링을 생각하지 않아도 된다는 점이다. 단점으로는 빈번히 HTTP 요청이 일어나고 서버가 필터링을 하면서 서버에 부담이 갈 수 있다는 점이다.