#react

리액트로 생각하기

리액트로 생각하기

제목이 약간은 어색해 보인다. ‘리액트에 대해서 생각하기’가 더 자연스러울 것 같은데 리액트로 생각하자는 말은 어떤 의미일까? 리액트를 공부하며 그 방대한 생태계에서 끊임없이 헤매는 중이지만, 최근에 그나마 알게 된 사실이 있다면 이것은 그저 자바스크립트 앱의 구조화를 돕기 위한 ‘도구’라는 점이다.

도구를 잘 사용하기 위해서는 그 철학을 이해하는 것 못지않게 시의적절하게 사용할 수 있는 방법에도 초점을 맞추어야 한다고 생각한다. 그래서 좀 더 리액트의 관점으로 프로그래밍 방법론을 바라보고자 위와 같은 워딩을 제목으로 선정하게 되었다.

리액트를 이해하기

A JavaScript library for building user interfaces

리액트 공식문서 초기 화면에서 확인할 수 있는 문구다. 여기서 눈에 띄는 표현은 라이브러리UI를 설계하는 부분이다. 대개 UI는 기존의 MVC 따위의 디자인 패턴에서 V(View) 레이어를 처리하는 마크업, 로직을 담당하고 있다. 즉 리액트는 이 View를 설계하는 방식에 어떠한 문제의식을 느낀 개발자들의 새로운 대안으로 출현하게 된 셈이다. 여기서 강조하는 장점을 나열해보면 다음과 같다.

  1. 데이터 변경이 발생하면 리액트가 변경 지점을 어떻게 그릴 것인지 알아서 처리해주기 때문에, 선언적인 UI를 작성할 수 있다.
  2. 컴포넌트 기반으로 애플리케이션을 운용할 수 있어 재사용성이 높아진다.

표면적인 이해에서 그치지 않도록 각 문장에서 중요한 표현을 좀 더 풀어보고자 한다.

명령형 vs 선언형

  • 명령형 프로그래밍: 무엇을 어떻게 할 것인가
  • 선언형 프로그래밍: 무엇을 할 것인가
<div id="value">0</div>
<button id="plus-button">+</button>
<button id="minus-button">-</button>
<script>
  const value = document.querySelector('#value');
  const plusButton = document.querySelector('#plus-button');
  const minusButton = document.querySelector('#minus-button');

  plusButton.addEventListener('click', () => {
    value.innerText = Number(value.innerText) + 1;
  });
  minusButton.addEventListener('click', () => {
    value.innerText = Number(value.innerText) - 1;
  });
</script>

기존의 View를 다루는 방식은 DOM 요소를 불러오는 것부터 데이터를 수정하고 적용하는 것까지 일일이 신경 써야 한다. 즉 원하는 목적을 이루기 위해 컴퓨터에 모든 과정에 대한 명령을 내려야 하기 때문에 이는 명령형 프로그래밍에 가깝다고 할 수 있다.

그에 반해 선언형 프로그래밍의 핵심은 내가 원하는 관심사만 선언하면 되도록 상세한 로직은 추상화되어 감추어져 있다는 특징에 있다. 이 관점으로 리액트를 사용하는 코드를 살펴보자.

class Counter extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value: 0,
    };

    this.handleIncrement = this.handleIncrement.bind(this);
    this.handleDecrement = this.handleDecrement.bind(this);
  }

  handleIncrement() {
    this.setState({ value: this.state.value + 1 });
  }

  handleDecrement() {
    this.setState({ value: this.state.value - 1 });
  }

  render() {
    return (
      <>
        <div>{this.state.value}</div>
        <button onClick={this.handleIncrement}>+</button>
        <button onClick={this.handleDecrement}>-</button>
      </>
    );
  }
}

DOM을 바인딩하거나 데이터를 변경하는 방법은 오롯이 리액트에 맡기면 되기 때문에 개발자는 코드를 짤 때 UI가 어떤 식으로 화면에 그려질지만 설계하면 된다. 결과적으로 이는 개발자가 고민 해야 하는 양을 확연히 덜어줄 수 있다.

컴포넌트

하나의 독립적인 요소이다. 단순하게 보자면 자바스크립트의 함수나 클래스라고 볼 수 있다. 다만 리액트 내부에서 컴포넌트는 외부의 프로퍼티(props)를 인자로 받아 리액트 엘리먼트를 반환하는 구조로 이루어져 있으며 반환된 엘리먼트는 어떤 식으로 UI를 구성할지에 대한 정보를 가지고 있다. 컴포넌트는 그 자체로 완결성을 가지고 있기 때문에 재사용이 쉽다.

리액트를 사용하기

어떤 앱을 개발할 때 리액트를 사용하기로 했다면, 우선은 상태와 컴포넌트를 정의하는 것부터 출발 해볼 수 있다. 반드시 이러한 규칙을 따라야 하는 건 아니지만, 리액트를 이용하여 개발을 시도해보는 입장에서는 한번 쯤 참고해보는 것도 괜찮다고 생각한다. 자세한 사항은 리액트의 공식문서를 확인하자.

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

컴포넌트를 어디까지 분리해야 하는지에 대해서는 다양한 견해가 생길 수 있다. 하지만 자바스크립트에서 함수가 하나의 역할만 가지는 걸 지향했던 것처럼 컴포넌트를 작성할 때에도 단일 책임 원칙을 매 순간 염두에 둘 수 있어야 한다.

2. 상태를 정의하기

상태는 앱에서 동적으로 변경되는 값이다. 상태는 가급적 최소한으로 정말 필요한 정도로만 가지고 있는 것이 좋다. 무언가를 상태로 만들기 전에 다음과 같은 항목을 고려해 볼 수 있으며 아래의 경우에 하나라도 해당한다면 그것은 상태가 아니다.

  • 다른 상태로 충분히 계산 가능한 값인지
  • 시간이 지나도 변하지 않는 값인지
  • 부모로부터 props로 전달될 수 있는 값인지

이와 같은 검증 단계를 거쳐 완전한 상태를 정의했다면, 해당 상태를 공유하는 컴포넌트를 찾는다. 이 컴포넌트들의 위치에서 가장 가까운 부모 컴포넌트가 상태를 위치시켜야 하는 계층일 확률이 높다.

3. 단방향의 데이터 흐름을 추가하기

간단히 말해 자식 컴포넌트가 상태를 변경하는 경우를 설정해주는 것이다. 부모 컴포넌트에서 setState()를 자식 컴포넌트에 props로 내려 주어 자식 컴포넌트의 태그에 이벤트를 설정해주는 식으로 처리할 수 있겠다. 여기서 리액트의 단방향 데이터 바인딩은, 양방향 데이터 바인딩 방식보다 코드 작성량이 더 많아 보일 수 있다. 하지만 이러한 방식은 앱 내에서의 데이터 흐름을 더 명시적으로 확인할 수 있고 앱이 어떻게 동작하는지 쉽게 파악할 수 있게 해준다.

정리

리액트는 선언적인 UI 설계 방식을 취하며, 변경된 데이터를 View로 쉽게 적용할 수 있도록 도와주는 라이브러리이다. 또한 컴포넌트라는 패러다임을 도입하여 프로젝트의 확장성과 유지보수성을 크게 높였다. 어떠한 도구를 사용하기로 했다면 도구가 추구하는 방식을 이해하고 도구의 관점으로 프로그래밍을 시도해보자.

Reference

React Tutorial for Beginners