리액트 설계 철학에 대해 알아보자!

윤효준·2024년 8월 10일

React

목록 보기
4/6

프로그래밍 언어를 효과적으로 사용하고 학습하기 위해서는 해당 언어의 설계 철학을 공부하는 것이 좋다고 생각했다.

https://legacy.reactjs.org/docs/design-principles.html

composition

이전 게시글에서도 자주 나온 표현이다. React는 다양한 컴포넌트를 조합하여 복잡한 애플리케이션을 구성할 수 있어야 하며 각각의 컴포넌트는 독립적으로 동작해야 한다. 상태난 라이프사이클 메서드는 강력하기에 적절히 사용해야 하고 이는 React의 유용성을 높이는 필수 요소이다.

Common Abstraction

React 팀은 불필요하게 라이브러리를 비대하게 만드는 것을 피하기 위해 대부분의 기능을 사용자에게 맡긴다. 하지만 특정 기능이 비효율적이거나 일관되지 않게 구현되는 경우에는 예외적으로 React에 포함시키기도 한다. 이러한 결정은 커뮤니티와의 논의를 통해 신중하게 이루어지며 생태계 전체에 이익이 될 때만 기능이 추가된다.

React Hooks와 Context API를 예시로 들 수 있다.

함수형 컴포넌트에서는 상태 관리나 라이프사이클 메서드 사용이 불가능했기에 이들을 사용하기 위해서는 클래스 컴포넌트로 전환해야 했다. 여러 해 동안 다양한 상태 관리 패턴들이 커뮤니티에 등장했지만 일관성과 효율성의 문제로 React 팀이 useState, useEffect 등의 훅을 포함한 React Hooks를 도입했다.

또한 React 앱에서 전역적으로 데이터를 전달해야 할 때 개발자들은 종종 props drilling이라는 문제를 겪는다. 특정 데이터가 여러 컴포넌트를 거쳐야 하는 경우 중간 컴포넌트들이 필요하지 않은 데이터도 props로 전달 받아야 했고 이는 코드가 복잡해지고 유지보수가 어려워졌다. 이전에는 Redux와 같은 외부 라이브러리를 사용하여 전역 상태 관리를 구현했지만 React 팀은 이 문제를 해결하기 위해 React에 자체적인 전역 상태 관리 기능인 Context API를 추가했다.

Escape Hatches

React는 실용주의적 접근(Facebook에서 사용되는 제품들의 요구 사항에 의해 주도)을 기반으로 다양한 개발자들이 사용할 수 있도록 유연한 API를 제공하는 것을 목표로 한다. React 팀은 특정 패턴을 제거하거나 개선할 때 커뮤니티의 사용 사례를 고려하고 대안을 제시하며 필요한 경우 명령형 API를 제공하여 다양한 요구에 대응한다. 완벽한 API를 설계하지 못할 경우에는 임시적으로 덜 완벽한 API를 제공하되 미래의 개선 가능성을 열어두는 방식을 취합한다.

선언형과 명령형의 경우 Diffing Algorithm에 대해 알아보자에서 정리한 바가 있다.
React는 선언적 방식을 지향하지만 필요한 경우 명령형 API도 제공한다는데 이는 ref API를 예시로 들 수 있다.

Stability

React는 API 안정성을 중요시하지만 정체를 피하기 위해 필요할 때는 변화를 감행한다. 변화가 있을 때는 신중하게 계획하고 명확한 마이그레이션 경로를 제공하여 사용자들이 원활하게 적응할 수 있도록 한다. React 팀은 특정 패턴을 폐기하기 전에 내부 사용 사례를 분석하고 커뮤니티와의 소통을 통해 영향을 평가한다. 폐기 경고가 포함된 변경 사항은 다음 주요 버전에서 실제로 적용되며 codemod 스크립트를 통해 마이그레이션을 자동화할 수 있도록 지원한다.

아래는 제공되는 codemod 스크립트의 주소이다!

https://github.com/reactjs/react-codemod

Interoperability

React는 기존 시스템과의 상호 운용성과 점진적 도입을 매우 중요하게 여긴다. Facebook과 같은 대규모 코드베이스를 가진 조직에서 모든 코드를 한 번에 React로 전환하지 않고 작은 기능부터 시작하여 점진적으로 React를 도입할 수 있도록 설계된다. 이를 위해 React는 Escape Hatches를 제공하여 변경 가능한 모델과 함께 작업할 수 있고 기존의 명령형 UI와 선언형 컴포넌트를 서로 감쌀 수 있는 기능을 제공한다. 이러한 접근은 React를 기존 코드베이스에 자연스럽게 통합할 수 있게 한다.

Scheduling

React는 컴포넌트의 렌더링을 사용자가 직접 호출하는 것이 아니라 필요에 따라 React가 제어할 수 있도록 설계되었다. 이는 React가 컴포넌트 호출을 지연시켜 성능을 최적화할 수 있게 하며 UI에서 어떤 작업이 우선되어야 하는지 결정할 수 있는 유연성을 제공한다. React는 push 방식 대신 pull 방식을 선호하여 필요할 때만 계산을 수행하며 이러한 접근을 통해 UI 성능을 최적화할 수 있다. setState()의 비동기적 특성도 이러한 스케줄링 제어의 일환이며 React는 이 제어권을 통해 사용자 인터페이스의 효율성을 극대화한다.

Push vs Pull

생산자와 소비자라는 표현 방식에 대해 정리하고 넘어가자!!!

생산자

  • 데이터를 생성하거나 제공하는 주체
  • 웹 어플리케이션의 경우 : 서버
  • 이벤트 기반 시스템의 경우 : 이벤트 생성자(사용자가 버튼을 클릭한다)
  • 실시간 채팅 어플리케이션의 경우 : 메세지를 보내는 사용자

소비자

  • 데이터를 소비하는 주체
  • 웹 어플리케이션의 경우 : 클라이언트
  • 이벤트 기반 시스템의 경우 : 이벤트 리스너
  • 실시간 채팅 어플리케이션의 경우 : 메세지를 수신하여 처리하는 시스템

Push방식은 데이터나 이벤트가 발생할 때 즉시 그 데이터를 소비자에게 전달하는 방식이다.

Push 방식 특징

1. 즉시성

  • 이벤트 발생 시 바로 소비자에게 전달되어 처리함
  • 예를 들어 이벤트 리스너가 특정 이벤트를 감지하면 그 즉시 그 이벤트에 대한 콜백 함수가 실행됨

2. 능동적 전송

  • 생산자가 데이터를 능동적으로 소비자에게 밀어넣는다(push)

장점

  • 실시간으로 데이터나 이벤트에 반응할 수 있다.
  • 단순한 이벤트 기반 시스템에서 구현이 용이하다.

단점

  • 데이터를 처리해야 하는 타이밍을 조절하기 어려울 수 있다.
  • 너무 많은 이벤트가 동시에 발생하면 시스템이 과부하될 수 있다.

pull방식은 데이터를 소비자가 필요할 때 요청하는 방식입니다.

Pull 방식 특징

1. 지연된 실행

  • 데이터가 생산되더라도 소비자가 요청할 때까지 처리되지 않는다.
  • 소비자는 필요할 때만 데이터를 끌어온다(pull)

2. 수정적 전송

  • 생산자는 소비자가 요청할 때 데이터를 제공한다.
  • 소비자는 데이터를 언제 처리할지 제어할 수 있다.

장점

  • 데이터를 처리하는 타이밍을 소비자가 제어할 수 있다.
  • 부하가 많은 이벤트나 데이터가 발생해도 이를 소비자가 적절히 처리할 수 있도록 조절할 수 있다.

단점

  • 실시간 반응이 어려울 수 있다.
  • 복잡한 타이밍 제어가 필요할 수 있다.

Developer Experience

React 팀은 개발자 경험을 향상시키는 것을 중요하게 생각한다. 이를 위해 React DevTools와 같은 도구를 제공하고 개발 과정에서 흔히 발생하는 실수를 예방하기 위한 경고 메시지를 통해 개발자 생산성을 높이고자 한다. Facebook 내부의 사용 패턴을 분석하여 일반적인 실수를 사전에 방지할 수 있도록 노력하며 개발자 경험을 더욱 개선하기 위해 커뮤니티의 제안과 기여를 적극적으로 받아들인다.

Debugging

React의 디자인 철학 중 하나는 문제가 발생했을 때 그 문제를 쉽게 추적하고 원인을 찾아 수정할 수 있도록 하는 것이다. 이 과정에서 props와 state가 중요한 역할을 한다. React DevTools를 사용하면 문제가 발생한 컴포넌트를 찾고 해당 컴포넌트의 props와 state가 올바른지 확인할 수 있다. 잘못된 state나 props가 문제의 원인일 경우 setState() 호출이나 잘못된 props를 전달한 상위 컴포넌트를 추적하여 문제를 해결할 수 있다. React는 UI와 데이터를 연결하여 디버깅을 쉽게 만들도록 설계되었으며 이를 통해 디버깅이 명확하고 체계적인 과정이 된다.

Configuration

React 팀은 전역 런타임 구성이 컴포지션을 방해할 수 있기 때문에 이를 지원하지 않기로 결정했다. 전역 구성이 충돌을 일으키거나 예기치 않은 동작을 초래할 수 있기 때문이다. 대신 React는 빌드 수준에서의 전역 구성을 제공하며 개발과 프로덕션 빌드의 구분이 그 예이다. 앞으로도 빌드 플래그를 통해 특정한 요구를 충족시킬 수 있는 방안을 모색할 예정이다.(현재는 일부 구현하고 있다)

process.env.NODE_ENV 플래그는 개발 환경에서 추가적인 검사와 경고를 활성화 하고 프로덕션 환경에서는 성능 최적화를 적용한다.

프로파일링 빌드는 성능을 분석하고 최적화하는 데 사용된다.

Implemetation

React 팀은 API를 우아하게 설계하려고 노력하지만 내부 구현에서는 실용성 성능 정확성을 더 중시한다. 지루하지만 명확하고 이해하기 쉬운 코드가 똑똑하지만 복잡한 코드보다 더 선호된다. 이는 코드를 쉽게 변경하고 이동할 수 있도록 하여 불필요한 추상화나 복잡성을 피하려는 접근이다. React 팀은 사용자에게 복잡한 코드를 강요하지 않기 위해 라이브러리 내부에 필요한 복잡성을 감추는 것을 목표로 한다.

지루하지만 명확하고 이해하기 쉬운 코드

function abs(x) {
  if (x >= 0)
    return x;
  else
    return -x;

똑똑하지만 복잡한 코드

function abs(x) {
	return ((x >= 0) - (x < 0)) * x;
}

지루하지만 명확하고 이해하기 쉬운 코드는 유지보수성과 가독성이 뛰어나며 다른 개발자들이 쉽게 이해하고 수정할 수 있다.

반면 똑똑하지만 복잡한 코드는 간결함을 추구하지만 그 결과로 인해 이해하기 어려워지고 유지보수에 어려움을 겪을 수 있다.

Optimized for Tooling

React 팀은 도구와의 최적화를 위해 API 이름을 의도적으로 길고 구체적으로 만든다. 이는 대규모 코드베이스에서 특정 API를 쉽게 검색하고 자동화 도구를 활용하여 안전하게 대규모 변경을 적용할 수 있도록 한다. componentDidMount()와 dangerouslySetInnerHTML 같은 명확한 이름은 코드 가독성을 높이고 코드 리뷰와 린트 도구에서의 오탐지를 줄인다. 또한 JSX는 React 요소 트리를 명확하게 표현하여 빌드 타임 최적화와 도구 통합을 돕는다.

Dogfooding

React 팀은 Dogfooding을 통해 Facebook 내부에서 React를 광범위하게 사용하면서 React가 신뢰할 수 있고 지속 가능한 기술임을 입증한다. 이러한 접근은 커뮤니티가 React에 신뢰를 가지게 하는 주요 이유가 된다. Facebook 내부의 문제를 우선적으로 해결하지만 커뮤니티의 요구도 적극적으로 반영하려고 노력한다.

profile
작은 문제를 하나하나 해결하며, 누군가의 하루에 선물이 되는 코드를 작성해 갑니다.

0개의 댓글