brunch

You can make anything
by writing

C.S.Lewis

by 레드윗러 Jun 30. 2020

레드윗러의 React 잘 써보기

React를 입문하고자 하는 험난한 여정

  우리는 개발언어로 JavaScript(지금은 TypeScript로 바꾸었다)를 채택하면서 웹 서비스를 제작할 때 React를 사용하기로 결정했다. 사실, 요즘 핫한 라이브러리인지라 우리도 그 트렌드에 탑승하고자 하는 마음도 컸다. 아무튼 React를 사용하기로 했으니, React에 대해서 알아보아야 했고 이 과정에서 우리가 얻은 내용을 React 입문을 두려워하는 사람들에게 나누면 좋겠다 싶었다. 한 가지 덧붙이자면, 이 글은 JavaScript에 대한 기본적인 지식이 있다는 전제 하에 작성되었기 때문에 만약, JavaScript에 대해서 모른다면 JavaScript를 우선 써보고 읽어보는 것을 권장한다. 자세한 내용은 공식 홈페이지를 참고하면 매우 좋다. 또한, 이렇게 글만 읽고 마는 것이 아닌 직접 찾아보고 코딩을 통해 확인해보면서 읽으면 더 재밌다! (아마도)


React를 사용하기 전에 참고하면 좋은 내용

동기 vs 비동기 그리고 JavaScript

  동기식 처리 과정은 직렬적으로 Task를 수행하는 것을 말하고, 비동기식 처리는 병렬적으로 Task를 수행하는 것을 말한다. 만약, 어떤 Task 1에서 서버에게 데이터를 요청하여 서버로부터 데이터를 받아오는 과정이 포함되어 이 과정이 끝날 때까지 시간이 걸린다고 가정하자. 동기 과정에서는 모든 Task가 직렬적으로 이루어지기 때문에 Task 1의 서버와의 통신이 끝날 때까지 다음 Task2는 실행되지 않고 대기 상태가 된다. 쉽게 말해 순차적으로 Task가 실행이 된다. 반면 비동기식 처리는 순차적으로 실행되지 않는다. Task 1이 종료되지 않은 상태라고 하더라도 대기하지 않고 다음 Task를 실행한다. 아래의 그림을 참고하면 이해가 조금 더 빠를 것이다.

이해를 돕기 위해 그린 그림

  JavaScript는 비동기식 처리를 한다. 즉, 우리가 짠 코드가 순차적으로 흘러가지 않을 수 있다. 따라서 우리는 자바스크립트를 사용하면서 비동기 처리를 순차적으로 진행해야 하는 부분에 있어서 이를 반드시 생각하고 코드를 작성하여야 한다. 이는 가독성을 높일 뿐 아니라 우리가 의도한 흐름으로 진행될 수 있기 때문이다.


JavaScript의 Call Stack 원리

  자바 스크립트는 자바 스크립트 엔진 위에서 동작한다. 자바 스크립트는 single thread 프로그래밍 언어(하나만 실행한다)이므로 call stack 역시 하나만 존재한다. 자바 스크립트의 runtime을 보자면 아래와 같다.

자바스크립트의 대략적인 동작 구조

  메모리 힙은 말 그대로 메모리를 말한다. 여기서 그려진  Call Stack은 Javascript의  function call들이 쌓이는 stack으로 Stack 구조 특성상 아래부터 쌓이고 위부터 실행된다. Callback Queue는 Task Queue라고도 지칭되며, 다음에 처리할  function call를 임시적으로 저장하는 Queue라고 생각하면 된다. Event Loop은 Call Stack이 비워져 있는지를 지속적으로 검사하여 Call Stack이 empty이면 Callback Queue에 있는  function call를 Call Stack으로 옮긴다. (간혹, Event Loop과 Callback queue를 합쳐서 Event Queue로 기재하기도 하는 것 같다.)

  비동기 과정은 브라우저의 Background Job으로 옮겨져 실행이 된다. 예를 들어 아래와 같은 코드가 있다고 가정해보자.

위의 코드가 동작되는 과정

  여기서 setTimeout으로 인해 걸린 third는 Background Job으로 이동한다. third의 작업이 끝나면(cf. setTimeout은 시간 이후 Tick이 발생하여 작업이 끝났음을 알려준다) third는 queue로 들어가 call stack이 비워질 때까지 대기한다. Event loop은 계속해서 Call Stack을 확인하여 Call Stack이 완전히 비워지면 Queue에서 third를 꺼내 call stack에 넣어 third가 실행될 수 있게 한다. 따라서 위의 코드는 "second", "first", "third" 순으로 콘솔 메시지를 확인할 수 있다.


Promise (ES6 ~)

  Promise는 자바 스크립트 비동기 처리에 사용되는 객체이다. 예를 들면, 서버에서 받아오는 데이터를 보여줄 때, 만약 이 데이터가 다 받아오기도 전에 화면을 표시하면 오류가 발생하거나 데이터가 일부 손실되거나 없어진 상태로 화면이 표시될 수 있다. 이를 방지하기 위해서 자바스크립트에서는 프로미스를 많이 사용한다. Promise의 lifeCycle을 이해하기 위해서 우리는 Promise의 syntax 3가지를 먼저 이해하여야 한다.

Executor : 어떤 작업을 실행할 것인가를

Then : Promise가 resolve(작업 실행을 완료)되었을 때 어떤 작업을 할 것인가

Catch: Promise가 reject(작업 실행을 취소, 실패 등)되었을 때 어떤 작업을 할 것인가


  그리고 이 과정에서 promise는 아래와 같은 상태 중 한 개를 갖게 된다.   

Pending(대기) : 프로미스가 호출하면 대기 상태가 된다. 이는 Excutor가 resolve 혹은 reject 함수를 부르기 전까지의 상태이다. 쉽게 말해 비동기 처리 로직이 아직 완료되지 않은 상태라고 생각하면 좋다.

Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값으로 resolve를 반환해준 상태이다.

Rejected(실패): 비동기 처리를 진행하다가 실패 혹은 오류가 발생하여 프로미스가 결과 값으로 rejec를 반환해준 상태이다.

  프로미스의 정확한 실행 과정을 알고 싶으면 아래의 코드를 분석해보고 실행 결과를 예측해보는 것도 도움이 된다.

  Promise를 사용하면서 추가적으로 사용할 수 있는 method에는 all, race, resolve, reject가 있다.   

Promise.all() : Promise의 array를 받고 이를 하나의 Promise로 묶어주는 역할을 한다.

Promise.race() : Promise의 array를 받아 이중 가장 빠르게 처리되는 Promise를 채택한다.

Promise.resolve / Promise.reject : Pending 상태를 거치지 않고 바로 주어진 값을 이행하여 Promise.then혹은 Promise.catch 객체를 바로 반환하는 Promise를 만들어준다.


async - await

  async와 await는 가장 최근에 나온 자바 스크립트의 비동기 처리 패턴이다. 기존의 promise의 단점을 보완하고 가독성을 높여주는 장점이 있다. (JS es8 표준 방식) await 구문을 사용할 때에는 반드시 function 앞에 async 가 붙어야 한다.

  async function 내의 코드는 await를 사용하여 순차적으로 작용하게 작성할 수 있다. await가 걸린 Promise는 async function의 실행을 일시 정지시키고 Promise가 fulfill이나 reject 상태와 같은 결과값을 반환하면 다시 이어서 실행한다. 이에 대한 예외처리는 try-catch 문을 사용할 수 있다.


React 탄생 배경

MVC vs Flux

  기존의 웹들은 MVC 모델이라는 방법을 기반으로 동작하였다. MVC 모델이란, Model, View, Controller를 줄여 의미하는 것이다. 웹이 동작되는 데에 있어서 어떤 데이터(Model)를 처리하여(Controller) 이에 맞춰 보여주는(View) 것을 3가지로 역할을 쪼개어 개발하는 방법론이다. 논리적 연산이 들어가는 부분(데이터 저장 및 처리 등)과 실제로 보이는 부분을 나누어 개발하였기 때문에 서로 영향 없이 개발할 수 있다는 장점이 있다.

  문제는 이러한 MVC의 한계가 웹이 복잡해지면서 뚜렷하게 등장해버린 것이다. MVC에서는 보이는 부분(View)과 데이터(Model)를 다양하게 가질 수 있다. 이런 구조가 만약 한 화면에서 화면 구성이 복잡해지고 데이터 구성이 복잡해질수록 복잡하게 얽혀버린다. 이런 경우 새로운 기능을 추가하려면 어떤 부분이 영향을 받고 어떤 데이터가 영향을 받는지를 일일이 확인해야 하는 번거로움이 있다. 

MVC의 한계를 그린 그림

  이런 MVC의 한계를 극복하고자 Flux 방법이 제안되었다. Flux는 React의 창시자 Facebook이 MVC 모델 대신에 설계한 모델이다. 예측 가능하도록 코드를 구조화하고 데이터 흐름을 단방향으로 설계하였다. 이는 React의 기본 철학으로 자리 잡는다.

Flux의 구조

Store: 모든 데이터를 저장한다 (MVC의 Model 역할)

Dispatcher: 모든 데이터를 관리하는 control의 역할이며 Action을 받아 Store의 어떤 부분이 어떻게 업데이트되어야 하는지를 결정한다.

View: Store가 변경된 경우 View는 다시 변경되어 나타나 진다.


DOM을 다루는 새로운 방식 - Virtual DOM

  DOM은 웹의 핵심이자 브라우저가 화면을 그리기 위한 정보들이 담겨있는 문서 객체(Document Object)이다. 더 자세하게 설명하자면 자바 스크립트에서 DOM(The Document Object Model, 문서 객체 모델)은 프로그래밍 인터페이스이다. 우리가 웹 페이지를 어떤 언어로 작성했다면, 이 작성된 텍스트(코드)가 웹 브라우저에 보이는 것이 아니다. 이것은 문서 객체로 변환되어 웹 브라우저에 보이게 되는데 이때 웹 브라우저에 보이는 것이 DOM이다.

  예를 들면, 우리가 어떤 HTML 코드를 작성했다고 하자. 브라우저는 전달받은 HTML을 렌더 엔진을 이용해 파싱 하고 DOM 노드로 이루어진 tree를 만든다. 여기에 스타일이 붙어서 render tree가 완성되면 브라우저는 레이아웃 과정(위치를 잡는 과정)과 페인팅 과정을 거쳐 뷰를 띄운다.

  DOM은 브라우저에 기록되는 모든 것이며, Javascript는 이를 조작할 수 있게 API를 제공한다. 쉽게 말해 자바스크립트가 문법이고 이 문법이 작용되어 브라우저에 뷰되는 것은 DOM이다. 문제는 이 과정 때문에 DOM을 다루는 것이 힘들다는 것이다. (이 DOM을 효과적으로 다루기 위해 Jquery라는 것이 React 이전에 존재했다.)

DOM 조작이 발생할 때마다 브라우저는 많은 연산을 하게 되고, 전체적인 프로세스 비용이 비효율적으로 흘러간다. 심지어는 브라우저 별로 DOM을 가지고 화면을 만드는 방식 역시 다르다. 이런 한계를 극복하는 방법으로 브라우저의 연산을 줄이고 실제 DOM에게는 최종 결과 DOM만을 전달해주기 위해 React는 Virtual DOM 구조를 사용하였다. 사실 React의 가장 큰 특징이라고 보이는 부분이다. 

  React는 이전의 jquery와 다르게 DOM을 컨트롤한다. Jquery의 경우 DOM의 정보를 자르는(직접적으로 접근하여 DOM을 수정하는) 방식을 기반으로 컨트롤한다면, React는 Virtual DOM을 생성하여 DOM을 컨트롤한다. 간단하게 설명하자면, react의 render를 통해 호출되어 나오는 return 값(View)은 바로 DOM에 반영되지 않고, 이를 토대로 React는 virtual DOM을 제작한다. 그리고 실제 DOM과의 달라진 부분을 비교하여 바뀐 부분만을 실제 DOM에 적용한다. 그리고 브라우저는 이 수정된 DOM을 해석하여 유저에게 새로운 화면을 보여준다. Virtual DOM은 자동으로 기존 값에서 어떤 값이 바뀌었는지를 연산하여 DOM에게 최종 결과물들만을 전달하기 때문에 브라우저의 연산 의존도를 낮춰 성능을 높이며 각 변화에 대한 동기화 작업을 거치지 않으면서도 모든 작업을 하나로 묶어주는 강력한 장점을 지닌다. React는 Virtual DOM을 이용해 DOM을 조작하기 때문에 Component 단위 별로 개발을 구현할 수 있는 것이다.


드디어 React 입문!

  React는 쉽게 말해 Javascript의 라이브러리이다. 사실, React는 JS와 매우 흡사한 문법인 JSX를 사용한다. JSX로 작성한 코드를 JS로 한 번 변환시킨 뒤에 JS 엔진을 실행한다. 이 과정 때문에 React를 사용할 때마다  babel과 같은 툴을 사용해야 하는 것이다. (React 처음에 babel 같은 게 왜 있나 했다) 복잡한 UI를 구성하는 데에 도움이 되기 위해 개발되었다. React는 "컴포넌트"를 이용하여 복잡한 UI를 구성하도록 돕는다.


Component?

  Component(컴포넌트)란 작고 고립된 코드의 파편, 복잡한 UI의 구성의 단위를 지칭하며 일종의 함수와 같다고 볼 수 있다. 함수에 빗대어 표현하자면 input 값을 props를 받고, UI(View)를 return 한다.

Component : props ⇒ UI ( React Element ) = View(state)   

State와 props의 변화에 따라 UI (결과)가 달라진다.

react element = react 앱의 가장 작은 단위, 화면에 표시할 내용   

함수의 형식으로 작성하는 컴포넌트와 클래스의 형식으로 작성하는 컴포넌트가 존재한다.

클래스 컴포넌트의 경우 render 함수를 사용하여 React Element를 보여준다.

React의 관점에서는 두 유형 모두 같은 컴포넌트의 개념이다.

  사설로 함수 형식으로 작성되는 함수형 컴포넌트가 더 낫다, 혹은 클래스 형식으로 작성하는 클래스형 컴포넌트가 더 낫다는 의견은 분분한데, 내가 읽어보았을 때에는 서로의 장단점이 있고 이를 보완하려고 Facebook에서도 지속적으로 업데이트하려는 것 같다. 우리의 경우에는 클래스 형식을 채택하였다. 특별한 이유보다 우리끼리 읽고 해석하기 편한 방식이라 채택하였다.


Props & State

  리액트 컴포넌트에서 다루는 데이터는 props, state로 나뉜다. 쉽게 props는 부모 컴포넌트가 자식 컴포넌트에 주는 값이고 state는 컴포넌트 내에서 사용하는 값이다. props는 부모 컴포넌트에서 받아오기만 하고 직접 수정할 수 없다. 반면에 state는 컴포넌트 내부에서 수정하며 사용할 수 있다. 또한 리액트의 데이터는 부모 컴포넌트에서 자식 컴포넌트로 데이터 흐름이 이루어지는 단방향 데이터 흐름이 특징이다. 

  Props는 앞서 설명한 것처럼 컴포넌트의 input 값이라고 생각하면 된다. props는 개발에서 많이 사용되는 용어인 프로퍼티(properties)의 줄임말로, 리액트에서 사용자가 컴포넌트에 전달해서 보관하길 원하는 데이터이다. 컴포넌트 내에서 데이터가 보관되면, 이 데이터는 수정되지 않고 보존되어야 한다. 만약 props의 값을 변경하고자 한다면, 부모 컴포넌트에서 이에 대한 변경이 이루어져야 한다. 이러한 props에는 몇 가지 특징이 있다.

1) Props는 기본적으로 readonly(읽기 전용)이다.   

     모든 React 컴포넌트는 자신의 Props를 다룰 때 Pure function처럼 동작해야 한다     (즉, props의 값을 변경하는 행위는 일어나서는 안된다)

2) props를 사용한다면 클래스 컴포넌트에서 반드시 constructor(생성자)를 호출하여야 한다. 객체 지향형 프로그래밍을 많이 해본 개발자라면 이런 생성자 호출이 익숙할 것이다.

constructor 내부에서 this.props를 사용하여 props에 접근하고 싶다면 반드시 super(props)를 불러야 한다.

super( )은 constructor 반드시 함께 하여야 한다. 왜냐하면 우리는 this를 사용할 것이기 때문이다. (사용하지 않는 경우가 거의 발생할 일이 없을 것이다)

3) 실수로 props를 빠트리는 경우나, 일부러 props를 안 넣는 경우를 생각하고 싶다면 우리는 기본값을 설정해놓는 defaultProps를 사용할 수 있다.


defaultProps 적용 예제


  Props와 다르게 State는 컴포넌트의 내부에서 사용되는 Local-Data (로컬 데이터)이다. 직접적인 변화를 일으키지 않고 setState를 통해서만 변화가 가능하다. (사실 직접적으로 변화할 수야 있지만, 권하지 않는다. 그 이유에 대해서는 차차 설명하고자 한다.)

  Props와 State의 가장 큰 특징은 우리가 이들을 변화시켰다고 해서 이 변화가 동기적으로 일어나지 않을 수 있다. 즉, props와 state의 변화는 비동기적으로 일어난다. 우리는 React를 이용하여 코딩을 진행할 때 이를 꼭 기억해야 한다.


React.Component vs React.PureComponent

  Component와 PureComponent의 차이는 React LifeCycle에서 shouldComponentUpdate가 이미 구현이 되어 있는가 아닌가의 차이이다. PureComponent는 shoudComponentUpdate가 이미 구현되어 있어 props와 state 변화에 대한 이미 얕은 비교를 선수 진행하여 렌더를 결정한다. 얕은 비교란 값에 대한 비교만 이루어지거나 Object나 Array의 경우에는 참조하고 있는 객체가 같은지를 확인하는 과정이다.

예를 들어, 만약 setState가 불렸지만 실제로 값이 변하지 않았다면 Component에서는 render가 다시 진행되지만 PureComponent에서는 렌더가 진행되지 않는다. 따라서 React의 성능을 높이고 싶다면 PureComponent를 적절하게 사용하는 것을 권장한다.


React LifeCycle

  React의 lifeCycle은 사실 아래의 그림보다 훨씬 복잡하지만, 웬만한 문제들은 아래의 그림에 나온 부분들로 충분히 해결 가능하다. 사실 React 자체에서도 아래의 부분 외의 lifeCycle 요소들의 사용을 권하지 않는다. 만약, 사용해야 하는 경우가 발생한다면 React 공식 문서를 참고해서 주의 사항을 꼼꼼하게 살피고 사용을 하는 방향으로 진행하는 것이 좋다. 각 LifeCycle의 요소에 대해 자세히 알아보고 싶다면 React 공식 문서를 참고하자. 여기서는 우리가 주로 요긴하게 사용했던 componentDidMount와 componentDidUpdate에 대해서만 설명하고자 한다. (글이 너무 길어질까 봐의 문제도 있다)

출처 - 리액트 공식 문서 홈페이지

componentDidMount

  컴포넌트가 만들어지고 첫 렌더링을 다 마친 후 실행되는 함수이다. 주로 초기 세팅에서 사용되고, 이미 렌더링 된 컴포넌트에서 두 번 불리지 않는다. 주로 외부 라이브러리 연동 초기 세팅을 해주거나 미리 설정하여야 할 값들에 대한 설정을 해준다. 


componentDidUpdate

  compoenetDidUpdate는 shouldComponentUpdate에서 true를 반환했을 때 호출된다. shouldComponentUpdate는 props 혹은 state의 변화를 감지하여 변화가 발생되면 true, 변화 감지가 없다면 false를 반환한다. 따라서, componentDidUpdate는 변화된 상태를 비교하여 render를 바꿀 수 있게 도움을 줄 수 있다. componentDidUpdate에서는 파라미터를 통해 prevProps(변화 전 props), prevState(변화 전 state) 등을 받아올 수 있다. 이전과 어떻게 변했는지를 비교해서 우리는 다음 render를 결정할 수 있다.


React를 잘 쓰려면

좋은 코드 작성하기

   좋은 코드에 대한 논의는 많지만, 우리는 좋은 코드에 대해서 아래와 같이 정의를 내렸다


좋은 코드 = 
목적에 맞게 실행되며,
적절한 구조화와 최적화가 이루어졌고,
가독성이 높은 코드



  이건 레드윗러끼리의 약속과 같다. 이를 조금 더 설명해보자면 코드를 보았을 때 우리는 이 코드가 어떤 역할을 하는 코드인지 쉽게 알아차릴 수 있고, 앞으로 일어날 알 수 없는 (기획팀의) 요구 사항 변화에 대응하기 쉬운 코드를 지향하자, 라는 의미이다. 좋은 코드를 작성하기 위해서, 이에 맞춰 우리가 react를 잘 사용하기 위해서 우리는 React 공식 문서의 제안과 덧붙여 우리끼리의 몇 가지 규칙을 정했다.

(물론, 좋은 코드의 기준에 따라 이 규칙은 변하게 될 것이다. 어떤 코드가 좋은 코드인지를 생각하며 아래의 규칙 외에도 여러 가지를 제안해보는 것이 더 좋을 것 같다.)


컴포넌트 합성

상속 vs 합성

  객체 지향 프로그래밍에서 클래스를 상속하는 방법과 합성하는 방법으로 코딩을 할 수 있는데, React에서는 상속(class를 extends 하여 부모를 상속받는 형태)이 아닌 합성(component의 render에서 다른 component를 호출하는 형태)을 권한다. 이는 코드 재사용을 할 수 있고, 명시적이고 안전한 방법으로 컴포넌트 모양과 동작을 커스터마이징 하는데 매우 유연하고 적합하다.

// 상속의 형태
class Page extends Button {... }

// 합성의 형태
class Page extends React.Component {  
    ...  
    render()
    {  
        return <Button />  
    }

컴포넌트 합성을 위한 컴포넌트 추출

  컴포넌트 합성을 매우 잘 활용하려면 특정 컴포넌트 내에서 특징적인 코드를 추출할 수 있어야 한다.

추출의 기준은,   

넓은 범위로 재사용이 가능한 코드인가

코드가 반복적으로 등장하는가

특수한 성질을 가진 코드인가

위의 3가지 기준 중 하나라도 부합한다면 특정 컴포넌트로 추출하는 방향으로 진행하기로 했다.


불변성을 조심하기

불변성이란?

  말 그대로 변하지 않는 성질을 의미한다. JS에서는 기존의 값을 유지하면서 새로운 값을 추가하는 것을 말한다. 좀 더 쉽게 말하면, 어떤 변수를 복사했을 때, 그 값만 복사하였는가 아니면 통째로 복사하였는가의 차이라고도 볼 수 있다. 아래의 코드를 생각하면 조금 더 쉽게 생각할 수 있다.

위의 a===b의 경우 true가 나온다 하지만, a === c의 경우 false가 나온다

  실제로 a와 b와 c가 가리키는 주소가 어딘가? 이 주소까지 같은 값인가? 이를 생각해보면 a와 b는 그렇다고 할 수 있지만, c는 그렇지 않다. 


불변성이 중요한 이유

  우리는 데이터가 변했고, 이를 반영해서 렌더 결과가 달라짐을 의도하고 싶다면, 기존 값을 복사해서 "사본"을 만들어 이를 알려야 한다. props 값이 달라졌음에도 component가 렌더를 다시 하지 않는다는 것은 데이터가 같다는 것이다. 우리가 불변성을 생각해서 값을 "직접" 변경하는 것이 아닌 사본을 통해 변경한다면, 몇 가지 장점이 있다.   

이전 버전을 기억할 수 있다.

React가 변화를 감지할 수 있다.

React가 다시 렌더하는 시기를 결정할 수 있다.


props를 state에 카피하지 않는다

  react에서 가장 기피해야 하는 코드는 state에 props를 복사하는 방향이다. 이는 우리가 알아채기 꽤 힘들 수도 있는 "버그"를 양산할 가능성을 매우 높인다. props값을 사용해야 하는 경우 반드시 직접적으로 props를 참고하는 this.props를 이용해 사용하고, 만약, props에 따라 state값이 변해야 하는 경우에는 비교문을 이용하여 componentDidUpdate를 활용한다. 이러한 생각들을 바탕으로 state를 쓰지 말아야 하는 경우에 대해서 몇 가지 생각을 하였다.

부모로부터 값을 전달받아야 한다면 반드시 props이다.

시간이 지나도 값을 유지하고 싶다면 이는 state가 아닌 props로 전달받아야 한다.

컴포넌트 내에서 독립적으로 관리되며, 한 가지의 기능을 수행한다면 state를 사용할 수 있으나 이 같은 경우가 다른 state와 props를 통해 충분히 계산된다면 state를 사용하지 않는다.


하향식 데이터 흐름

  앞서 여러 차례 설명했던 것처럼 React 자체적으로 하향식 데이터 흐름을 권장한다. 되도록이면 import와 컴포넌트화를 통해 데이터 흐름이 하향식으로 전달되는 것이 가장 실수를 줄일 수 있으며 효율적인 방법이다. 이를 생각해서 import가 cycle 되지 않도록 주의해야 한다. (우리가 가장 많이 겪었던 warning 중 하나였다) 또한, 이러한 데이터 흐름에 맞춰 각 컴포넌트의 렌더 시기를 생각하여야 한다. 


하향식 데이터 흐름의 한계

  하지만 실제로 하향식 데이터 흐름을 사용하려고 보니 몇 가지 한계가 있었다. 첫째로는 가장 상위에 있는 컴포넌트가 관여하는 어떤 데이터에 대해서 가장 아랫단의 컴포넌트가 사용한다면, 그전에 수많은 컴포넌트들을 거쳐야 한다는 것이다. 즉, 컴포넌트 내에서 사용하지 않을 props임에도 단지 넘겨주기 위해서 남발되는 props가 많다는 점이었다. 또한, 만약에 이렇게 전달받은 props를 반드시 수정해야 하는 불가피한 상황에서 속수무책으로 할 도리가 없다는 점이 두 번째 한계였다. 자식이 부모의 데이터에 관여하고 싶어 지는 이른바 폐륜 사태(우리끼리는 이렇게 칭하였다)가 발생하는 안타까운 상황에서 이를 어떻게 해결할 수 있을까, 라는 질문에 나타난 것이 Redux이다. React에서는 데이터를 따로 관리하며 모든 컴포넌트가 접근 가능한 데이터 저장소로 사용할 수 있는 Redux를 함께 쓰기를 권장하고 있다. 


React를 겪어본 레드윗러로서

  React는 쓰기 편하고 (특히 JavaScript를 많이 다뤄본 개발자일수록) 초심자에게도 접근하기 쉬운 라이브러리 중 하나임은 틀림이 없다. 뭣도 몰랐던(?) 초보 개발자인 나도 처음 React를 접하고 사용하면서 이보다 더 습득력 있게 배웠던 라이브러리는 없었다. 물론, 나의 설명이 부족한 부분도 많고 내 나름의 이해를 가지고 작성하다 보니 말 자체가 이해가 안 되는 부분도 많겠지만 확실한 건 React를 사용하기 전에 React에 대한 이해는 필수적이라는 것이다. 이 이해가 제대로 안 되었다면, 우리가 예측하지 못했던 현상(버그)들이 발생하는 경우도 많고 심지어는 잘 동작하는 코드도 다시 뜯어고치는 대참사(현재 나의 상태가 그렇다)가 발생할 수도 있다. 

  사람들이 React를 사용하는 이유에 대해서 내가 말하기는 힘들지만, 내가 React를 선호하는 이유는 이전 html과 css로 UI를 구성했 듯이 손쉽게 UI 구성이 가능하다는 점과 (특히 가독성 부분에서) 좋은 코드를 만들기 매우 편리한 점이다. 웹 서비스 프론트엔드 개발에 관심이 많다면 한 번은 접해야 하는 필수 라이브러리임은 틀림이 없다.



참고 및 레퍼런스

[1] https://ko.reactjs.org/

[2] https://velopert.com/



작성자 : Terra (kimsenbeer@redwit.io)

React는 매력적이지만 잘쓰려면 어렵더라구요.
앞으로도 React 경험들을 나누어가고자 합니다.


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari