본문 바로가기

Web + APP/React

React 소개 with 이벤트 위임

반응형
SMALL

소개

React는 Facebook에서 개발한 오픈소스 UI 프레임워크입니다.

 

이전엔 직접 DOM 조작을 하며 이뤄진 과거 개발 방식(jQuery)이었는데, 이젠 DOM을 직접 다루지 않고, 데이터 상태에 따라 UI를 관리하기 때문에, 개발자는 특정 상태에 대한 뷰의 변화만 구현하면 됩니다.

 

React는 3가지 특징이 있습니다.

  • UI 컴포넌트를 만들기 위한 라이브러리이며 React의 컴포넌트는 트리형태로 구성됩니다.
  • Virtual DOM을 사용하여 변경된 부분에 대한 최소한의 DOM 처리로 UI를 업데이트하여 애플리케이션의 성능을 향상합니다.
  • 부모 컴포넌트에서 하위 컴포넌트로 전달하는 단방향 데이터 흐름을 가지고 있습니다. 그렇기에 데이터 추적 및 디버깅이 쉽습니다.

개발 환경

JSX

React에서는 UI 표현의 편의를 위해 XML 형태의 구문을 JS 구문 사이에 사용할 수 있는 JSX 문법을 제공합니다. JSX를 사용하면 기존의 어려웠던 중첩 엘리먼트 표현도 보다 쉽게 할 수 있습니다.

 

JSX 문법을 사용한 파일은 *.jsx 라는 확장자로 저장되고 배포 전 트랜스파일링을 해야합니다.

 

개발 환경 구성

JSX와 es6 문법을 사용하려면, Babel이 필요하고, 이를 사용하려면 webpack과 같은 번들링 도구가 필요합니다.

 

React에서는 이를 한 번에 처리해주는 Create React App이라는 npm 모듈을 제공하고 있습니다.

 

물론 현업에서는 안쓴다고 하네요.

React의 구조

React 애플리케이션의 구조는 컴포넌트 트리 형태입니다.

트리이

props

부모 컴포넌트는 하위 컴포넌트로 데이터를 전달하기 위해 props를 사용합니다. 부모로부터 변경된 props를 전달 받게 되면 해당 컴포넌트와 모든 자식 컴포넌트들을 다시 렌더링 합니다.

 

state

컴포넌트 자체의 상태를 관리하기 위해 state를 사용합니다. state의 데이터를 이용해 하위 컴포넌트에 props로 전달하기도 합니다.

 

컴포넌트 생명주기 (lifecycle)

컴포넌트가 페이지의 DOM 트리에 실제로 추가될 때 마운트(mount) 된다고 하고, DOM 트리에서 삭제될 때를 언마운트(unmount) 된다고 합니다.

  • componentWillMount() - 마운트 직전에 한 번 발생
  • componentDidMount() - 마운트 직후 한 번 발생
  • componentWillReceiveProps()
  • componentWillUpdate() - props, state 업데이트 직전에 발생
  • componentDidUpdate() - props, state 업데이트 직후에 발생
  • componentWillUnmount() - 언마운트 직전에 발생

 

컴포넌트 간 데이터 전달

React는 단방향 데이터 흐름을 갖고 있기 때문에 부모 컴포넌트에서 하위 컴포넌트로 데이터(props)를 전달 할 수 있습니다.

 

그런데 이게 여간 불편한 게 아닙니다. 컴포넌트 간의 거리가 멀거나 한다면 별로 좋은 방법이 아닙니다.

 

이게 뭐야.. 무서워..

이를 해결하기 위해서 데이터 공유가 필요한 컴퍼넌트가 같은 데이터 모델을 바라볼 수 있도록 하는 개념이 탄생했습니다. 바로 Redux입니다.

Redux와 React

Redux는 Facebook에서 개발한 단방향 흐름 아키텍처인 Flux의 구현체로, JS 앱을 위한 예측 가능한 상태 컨테이너입니다.

 

Redux는 Store, Action creator, Reducer로 구성되어있습니다.

 

아래와 같은 단방향 흐름을 갖습니다.

  1. Store의 데이터로 뷰 렌더링
  2. 뷰에서 발생한 이벤트로 Action(Action creator로 생성)을 생성
  3. Reducer는 생성된 Action으로 Store를 갱신
  4. 갱신된 Store로 뷰 렌더링

이런 느낌

허나 Redux를 사용하더라도 최상단 컴포넌트에만 Redux Store가 연결되면, 이전과 차이가 없는데, 다행히 컴포넌트의 깊이와 관계없이 Redux Store에 접근할 수 있습니다.

Redux는 react-redux라는 npm 모듈을 이용해 React에서 사용할 수 있는데, react-redux 모듈의 connect라는 메소드를 이용하면 React 컴포넌트에서 Redux store에 접근할 수 있는 스마트(smart) 컴포넌트를 만들 수 있습니다.

Redux 없이 부모 컴포넌트로부터 props만 전달받는 컴포넌트를 덤(dumb) 컴포넌트라고 부릅니다.

이 때 connect 메소드의 인자로 mapStateToProps / mapDispatchToProps 개념의 두 가지 인자를 전달하는데 각각의 역할은 다음과 같습니다.

  • mapStateToProps : 그림에서 파란색 화살표 역할을 하는 함수로 컴포넌트에 필요한 값을 store로부터 직접 조회하는 역할을 합니다.
  • mapDispatchToProps : 그림에서 녹색 화살표의 역할을 하는 함수로 사용자의 액션에서 발생하는 store의 변화를 구현합니다.

이벤트 핸들링

ReactDOM에서 이벤트 바인딩

ReactDOM에서 DOM에 할당할 수 있는 속성을 정의할 때 JSX 엘리먼트에 카멜 케이스로 작성하면 됩니다.

onClick과 같은 속성이요.

 

이벤트 위임(delegation)

JSX 엘리먼트에 이벤트를 할당하면 React는 이벤트를 해당 엘리먼트에 바인딩 하지 않고, document.body에 위임된 형태로 동작하게 됩니다. 그 이유는 무엇일까요?

 

엘리먼트에 기능을 추가하고 이벤트 리스너를 추가하는거까지는 그렇다 칩시다. 허나 엘리먼트를 삭제시엔 엘리먼트 만을 삭제하기 때문에 이벤트 리스너가 삭제되지 않아 메모리 누수가 발생됩니다.

 

허나 매번 해제하는 것도 정말 웃긴 일이고, 동적으로 이벤트 리스너를 등록하는 날이라도 온다면, 너무 비효율적으로 리스너를 등록해야할 순간이 올 수 있기 때문에, 이벤트 위임을 사용합니다.

 

아래에서는 이벤트 리스너 캐시를 사용하기도 했죠.

 

ggodong.tistory.com/260

 

이벤트 리스너 캐시

제가 최근 과제를 진행하며, render 함수에 이벤트 리스너를 추가하는 코드를 많이 짰는데, 이러한 코드는 성능을 떨어뜨린다는 피드백을 많이 받았습니다. 도대체 왜 그런 것일까가 궁금해서 찾

ggodong.tistory.com

사용자의 액션에 의해 이벤트 발생 시 이벤트는 document 레벨까지 버블링되어 올라갑니다. 이 때문에 자식 엘리먼트에서 발생하는 이벤트를 부모 엘리먼트에서도 감지가 가능합니다.

<ul id="menu">
  <li><button id="file">파일</button></li>
  <li><button id="edit">편집</button></li>
  <li><button id="view">보기</button></li>
</ul>
document.getElementById("file").addEventListener("click", function(e) {
  // 파일 메뉴 동작
});
document.getElementById("edit").addEventListener("click", function(e) {
  // 편집 메뉴 동작
});
document.getElementById("view").addEventListener("click", function(e) {
  // 보기 메뉴 동작
});

이렇게 하면 매번 ... 끔찍한 일을 반복해야합니다. 허나 위임을 사용하면 ?

document.getElementById("menu").addEventListener("click", function(e) {
  var target = e.target;
  if (target.id === "file") {
    // 파일 메뉴 동작
  } else if (target.id === "edit") {
    // 편집 메뉴 동작
  } else if (target.id === "view") {
    // 보기 메뉴 동작
  }
});

상위 엘리먼트에만 이벤트 리스너를 추가하고 분기만 잘해주면 끝 !

 

그나저나 무슨 장점이 있길래 이벤트 위임을 사용하는 걸까요?

  1. 동적인 엘리먼트에 대한 이벤트 처리가 수월합니다.
  2. 상위 엘리먼트에서만 이벤트 리스너를 관리하기 때문에 하위 엘리먼트는 자유롭게 추가 삭제할 수 있습니다.
  3. 이벤트 핸들러 관리가 쉽습니다.
  4. 동일한 이벤트에 대해 한 곳에서 관리하기 때문에 수월합니다.
  5. 메모리 사용량이 줄어듭니다. 동적으로 추가되는 이벤트가 없어지기 때문이죠. 등록 핸들러 자체가 줄어들기 때문에 메모리 누수 가능성도 줄어들고요.

그러니 이벤트 위임을 습관적으로 사용하는 것이 좋습니다.

성능 - 렌더링

React 애플리케이션은 여러 컴포넌트들이 트리 형태로 구성되어 있는데, 앞서 말했듯이 컴포넌트의 상태가 변경될 때 하위 컴포넌트들이 연쇄적으로 렌더링 되며 운영되는 구조입니다.

 

공식적으로 React에서는 O(n)의 시간 복잡도로 최적화된 렌더링 엔진을 가지고 있습니다. 허나 이를 어설프게 남발하다간 디버깅이 어려울 수 있으니 꼭 필요한 곳에만 최적화를 하라고 가이드하고있는데, 어떤 경우가 있을까요?

 

부모 컴포넌트의 상태가 변경될 때 모든 하위 컴포넌트가 렌더링 되는데, 몇몇 컴포넌트가 렌더링 되지 않아도 되거나 (A), 어떤 조건에서만 렌더링 되면 되는 경우(B)가 최적화 포인트입니다.

 

(A)의 경우엔 마크업만 포함되는 컴포넌트를 들 수 있겠죠.

 

(B)의 경우 3가지 목록 중 2가지만의 변경 DOM 조작만 하면 성능 상 좋을 수 있겠죠.

결론

React의 기본 내용부터 성능 최적화까지 다뤘습니다. React는 웹 앱 개발의 가장 큰 성능 문제를 Virtual DOM을 통해 해결했습니다. JSX 문법을 이용하면 기존 HTML 마크업을 작성하듯 컴포넌트를 만들 수 있다는 점도 장점이죠.

 

이런 리액트 사용해보는게 어떤가요?


이상 React 소개였습니다. ^_^

 

반응형
LIST

'Web + APP > React' 카테고리의 다른 글

Flux 패턴  (0) 2021.03.19
이벤트 리스너 캐시  (0) 2021.03.14
React Form과 bind 개념  (4) 2020.06.10
React Project에 Tailwind 적용하기  (1) 2020.06.07