[리액트] 리액트적 사고하기

invisibleVoice·2025년 1월 22일

리액트

목록 보기
1/14
post-thumbnail

프로그래밍 과정을 나만의 언어로 표현할 수 있도록 노력하자!

리액트를 배워야 하는 이유?

왜 React를 사용하게 되었을까? 웹이 발전하면서 어째서 React가 생겨나게 되었고 인기를 얻게 되었는지 생각해보자.

리액트 등장배경

  • 초기 웹
    정적인 페이지의 향연. 인터넷은 정적인 정보 공유를 위한 곳.

  • 웹 2.0
    웹이 보급이 되면서 사용자들이 참여할 수 있는 기능이 생겨남. 블로그, 싸이월드, 게시판 등등의 전성시대.
    노드의 등장 : 구글의 V8엔진에 팔다리를 붙여 마개조를 한 node.js가 탄생했다! 브라우저 바깥에서도 JS를 실행하고 디버깅이 가능해져 개발의 난이도가 쉬워지고 프론트뿐만 아니라 백엔드에서도 사용 가능해지는 등 활용도가 급격히 늘어나게 되었다.

  • 모던 웹(2010년대 이후)
    모바일 인터넷 사용 급증하면서 반응형 웹 디자인의 중요성이 증가했고, 웹에서 앱처럼 동작하는 SPA가 등장하게 된다.

  • 리액트의 탄생 (2013년)
    프론트엔드 프레임워크는 이전부터 존재해왔지만, 프로젝트 규모가 커지면서 브라우저 안에 여러 요소들을 표시하는 것이 너무 느려지게 되었다. 페이스북에서는 JS만을 위한 가상 돔을 만들어서 이를 이용해 필요한 부분만 렌더링을 하면 어떨까? 라는 생각에 도달하게 되었고, 이렇게 리액트가 탄생했다. 예전의 렌더링 복잡도와 이슈를 해결하고 사용자는 상태 변화에만 집중하도록 만들어주었다.

리액트의 특징

기술적 관점

  • 가상 DOM
    가상 DOM은 메모리에 존재하는 in memory UI이기에 조작이 편리하고 속도가 빠르다! 업데이트를 효율적으로 할 수 있게 된 것.
  • 라우팅의 이점
    전통적인 라우팅은 페이지에서 다른 페이지로 넘어가는 것을 말한다. 그래서 다른 HTML문서를 받고 파싱하고 페인팅하는 시간까지 걸리는 시간 때문에 깜빡 거리는 현상이 발생한다. 리액트의 라우팅은 변경이 필요한 부분만 업데이트 하도록 하여 변경이 필요없는 부분은 깜빡거리지 않게 된다.

개발 경험 관점

  • 컴포넌트 기반 개발
    UI를 독립적이면서도 재사용이 가능한 컴포넌트라는 레고 조각을 활용한 개발.
    대규모 애플리케이션의 개발과 유지보수를 편리하게 해준다.
  • 선언적 접근
    선언적 접근을 통해 컴포넌트 생성이 가능하다! 직접 DOM을 관리하는 것이 아니라, 여기에 이런 화면이 들어갈거야~ 하고 선언만 해주면 알아서 리액트가 처리한다.

🤪트럭 지수 Truck Factor
버스 팩터, 복권 팩터 등 여러 이름으로 불리는 이 용어는 팀원 중 몇명이 제대로 된 인수인계 등의 절차 없이 갑작스럽게 빠지게 되었을 때 프로젝트에 심각한 문제가 생기는지를 나타내는 지표다. 몇명의 직원이 갑자기 트럭에 치여서 회사에 못 나오게 되면 프로젝트가 망할까(?) 에서 나온 말이다.

트럭 지수가 높을수록 프로젝트의 안전성이 높아진다.
트럭 지수가 1이면 한 명만 빠져도 프로젝트에 문제가 생기지만
트럭 지수가 100이면 100명이 빠져야 프로젝트에 문제가 생긴다.

리액트적 사고

리액트 공식 문서에서 권장하는 리액트적 사고 순서다.

React로 화면을 구성하고자 할 때, 먼저 이를 컴포넌트로 나눈다. 그리고 각 컴포넌트의 다양한 시각적 상태들을 정의한다. 마지막으로 컴포넌트들을 연결하여 데이터가 그 사이를 흘러가게 한다. 이를 단계별로 나누어 살펴보자!

1. UI를 컴포넌트 계층으로 쪼개기

UI를 기능에 따라 여러 컴포넌트로 나누고, 이들의 부모-자식 관계를 생각해봐야 한다. 여기엔 정답이 없다! 다만 기능을 살펴보고 어떻게 나누는 것이 효율적일지 고민해봐야 한다.

위 그림의 경우, UI를 다섯 컴포넌트로 나누었다. 저기에서 더 쪼갤지 덜 쪼갤지는 본인의 판단이다. 이 컴포넌트들의 상하 관계를 나누어보면 다음과 같다.

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

2. 정적인 버전 구현하기

상호작용 기능이 없는 UI만 우선 만들어본다. props를 적절히 활용해야한다. 다만 이 과정에서는 state를 쓰지 말자! state는 상호작용을 위해 아껴두자.

일반적으로는 하향식(top-down)으로 컴포넌트를 만들지만, 큰 프로젝트의 경우 상향식(bottom-up)으로 만들면서 테스트를 작성하면서 개발하기도 한다. 상향식의 경우 만든 컴포넌트가 상위 컴포넌트가 없더라도 에러 없이 동작하기 때문이다. (물론 props를 받지 못하지만, 컴포넌트는 기본적으로 함수이기에 props는 파라미터일 뿐이므로 에러를 일으키지 않는다.)

3. state 결정하기

state는 앱이 기억해야 하면서도 변경할 수 있는 데이터의 최소 집합이다. 이외의 데이터는 필요에 따라 실시간으로 계산해야한다.

state를 결정하는 3가지 질문 :

  • 시간이 지나도 변하지 않는다? 음.. 확실히 state가 아니군.
  • 부모로부터 props를 통해 전달된다? 음.. 확실히 state가 아니군.
  • 컴포넌트 내 다른 state 또는 props를 가지고 계산이 가능한가? 오, 이건 절대로 state가 아니군

이외의 것들은 아마 state일 것이다.

state를 최소화하는 이유

왜 상태를 최소화하는 것이 좋을까? 여러 이유가 있겠지만, 가장 중요한 이유는
상태의 복잡성 관리와 일관성 유지를 통해 상태를 효율적으로 관리하고 유지보수를 용이하게 만들기 위해서다.

  • 복잡성 증가

여러 상태가 서로 의존하거나 중복될 경우, 이를 동기화하는 로직이 복잡해진다. 동일한 데이터를 두 곳의 상태로 관리하면 한 곳에서 상태를 업데이트했을 때 다른 곳도 함께 업데이트해야 하기 때문이다.
불필요한 상태가 많을수록 상태 간의 관계를 이해하기 어려워지고, 예상치 못한 버그가 발생할 가능성이 높아지게 된다. 그래서 계산이 가능한 데이터의 경우 상태로 관리하지 않는다!

  • 불필요한 렌더링 증가
    리액트는 상태가 변경될 때마다 해당 상태를 사용하는 컴포넌트를 리렌더링한다. 불필요한 상태를 줄이면 렌더링 비용도 줄일 수 있다!

4. state위치 결정하기

최소한의 state를 결정했다면 이제 어떤 컴포넌트가 이 state를 책임질지 정해야한다. React는 위에서 아래로 내려가는 단방향 데이터 바인딩을 사용하기 때문에 일반적으로 state는 공통 부모 컴포넌트, 또는 공통 부모의 상위 컴포넌트에 둔다. 적절한 컴포넌트를 찾지 못하겠다면 아예 state 전용의 컴포넌트를 만들어서 상위 계층에 추가하면 된다.

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

지금까지의 단계는 데이터가 아래로만 흐르도록 만들었다면, 마지막 단계는 반대 방향의 데이터 흐름을 만드는 것이다. 하위 컴포넌트에서 공통 부모 컴포넌트에 있는 state를 변경해볼 것이다!

React는 양방향 데이터 바인딩을 지원하지 않는다. 그래서 React에서 state를 변경하기 위해서는 setState를 사용해야 한다.
state를 변경한다고 해서 state를 조작하는 것이 아니다. state는 직접 변경할 수 없으며, 불변성을 유지하는 것이 React의 권장 사항이기에 React의 철학에 따라 읽기 전용으로 취급해야 한다.
state를 변경한다는 것은 setState를 통해 기존 상태를 교체하거나 병합하여 새로운 상태를 저장하는 것이다.

주의할 점

setState로 바뀐 값은 다음 렌더링 때 바뀐다. 그 전엔 변하지 않는다.

...
console.log(state)
setState(변경된state)
console.log(state) 
...

두 출력 값은 같다! 아직 다음 렌더링이 발생하지 않았기 때문이다.

리액트를 사용한 프로젝트를 어떻게 만들어야 하는지에 대한 전체적인 흐름을 파악하면 앞으로의 개발에 많은 힘이 될 것이다. 프로젝트 시작하기 전에 전체적인 컴포넌트 구조를 어떻게 만들지와 데이터를 어떻게 써야할지 구상을 먼저 해보도록 하자!

함수적 사고

현재 리액트의 트렌드인 함수형 컴포넌트를 사용할 때 함수적으로 생각하는 것이 중요하다. 결국 컴포넌트와 훅은 함수다.

함수와 컴포넌트

// App.jsx
import { Child } from './Child'

function App() {
  return (
    <div>
      <Child/>
    </div>
  );
}

// Child.jsx
function Child() {
  return (
    <p>Hello!</p>
  );
}

App.jsx<Child/>에는 Child컴포넌트의 결과값(jsx)이 들어간다. 어떻게 보면 Child()의 반환값이 곧 <Child/>인 것이다.

함수 시그니쳐

  1. 함수 이름

  2. 파라미터

  3. 반환값

간단한 3개의 요소지만, 원함수와 콜백함수가 섞여있을 경우 반환값이 어떤 함수의 반환값인지 햇갈릴 때가 있다. 어떤 함수가 실행되고, 어떤 함수가 실행되지 않는지 잘 판단해야한다.

profile
게임 QA 이직 준비중

0개의 댓글