티스토리 뷰

반응형

 

 

React – A JavaScript library for building user interfaces

A JavaScript library for building user interfaces

reactjs.org

 

왜 만들었는가?

리액트는 자바스크립트의 라이브러리이다.

그렇다면, 라이브러리는 왜 만드는가? 에 대한 질문도 된다.

우선, 반복적인 작업을 줄이고, 한정되어 있는 자원을 효율적으로 사용하기 위함이라고 일반적으로 말할 수 있겠다. 여기서 자원이라고 함은 컴퓨팅 자원뿐만 아니라 사람(개발자)도 포함된다.

이건 일반적인 내용이고, 리액트 자체에 국한해서 이야기하려면 공식 페이지에서 소개하고 있는 아래의 핵심가치들을 살펴보면 이해할 수 있다.

 

리액트의 핵심가치

1. Declarative (선언적 View)
    1. Interactive UI 제작의 고통을 줄이자!
        1. 각 state에 따른 심플한 디자인
        2. 데이터 변화에 따른 효율적인 업데이트와 렌더링!
        3. 코드를 더욱 예측 가능하고, 디버깅 쉽게!
2. Component-Based
    1. 각 컴포넌트의 state는 잘 캡슐화하자
    2. 그리고 이 컴포넌트를 잘 조합하여 복잡한 UI까지 뚝딱!
        1. 템플릿은 어렵고 복잡해... 그니깐 컴포넌트 로직!
3. Learn Once, Write Anywhere
    1. 웹은 라이브러리/프레임워크가 너무 많아! 이걸로 다 해버리자!
        1. WebBrowser, Node, Native 다 하자!

 

 

핵심 가치를 주장하는 부분이라서, 그렇게 디테일하진 않지만, 설계 방향은 명확하게 보여주고 있다.

각각의 가치들에 대해 어떤 콘셉트들을 가지고 풀어낼 것인지는 이어지는 내용에서 확인해보자.

 


메인 컨셉

Main Copcepts, 즉 이것들이 리액트에 있어서 제일 중요한 개념들이다.

https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction

 

JSX (XML-like syntax): 컴포넌트 작성은 심플하게!

"어차피 웹페이지 만들건대, HTML 작성할 때처럼, 직관적이면 좋잖아?

풀어쓴 이름에서 알 수 있듯이, XML처럼 문법을 쓰자는 건데, 이것은 메인 콘셉트이지만 optional이라고 한다. 즉 굳이 쓰기 싫으면 쓰지 말라는...? 근데 이거 안 쓰면 리액트 쓰는 장점이 많이 사라진다 (그러니까 쓰라는 말)

아무튼, 이러한 JSX로 작성된 것은 raw 한 JS코드로 컴파일해야 하는데 (사람한테 편한 건 기계한테 편하지 않다), 이러한 것을 해주는 컴파일러가 바로 Babel이다. (이름이 그 특징을 잘 보여준다)

// 아래의 HTML 요소를 만들려면
<h1>Hello, world!</h1>

// pure하게는 이렇게 사용해야하는데
const element = React.createElement("h1", null, "Hello, world!");

// 아래처럼 XML 스타일로 사용할 수 있게 해주겠다는거다 (즉, 이게 JSX)
// 반대로 말하면, 이렇게 작성한 코드를 Babel이 위 코드처럼 pure하게 바꿔준다는 거다.
const element = <h1>Hello, world!</h1>;
 

Babel · The compiler for next generation JavaScript

The compiler for next generation JavaScript

babeljs.io


Elements와 Rendering

Elements는 React에서 가장 작은 블록을 뜻한다 → 이거 가져다가 애플리케이션이라는 집을 짓는 것

아래 코드에서 볼 수 있듯이 이 요소들을 렌더링 하는 것 자체는 심플하다

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

그런데, 보통 하나의 페이지에는 수많은 요소들이 있다. 그럼 페이지 자체를 렌더링 할 때마다 이 요소들 하나하나를 렌더링 한다면? 요소가 많을수록, 각각의 렌더링이 오래 걸릴수록 페이지가 엄청나게 느려질 것이다. 그럼 변경된 요소만 렌더링 해주면 되는 거 아닌가?라고 생각할 수 있는데, 바로 그거다. 그걸 리액트에서 해준다. 그럼 리액트 이전에는 그렇게 비효율적으로 렌더링 했냐고? 그렇다고 한다. 왜 그랬었는지는...... 넘어가자

 

아무튼! 여기서의 핵심을 다시 정리하자면 아래와 같다.

리액트는 변화가 필요한 요소만 업데이트해서 렌더링 해준다

애초에 위의 코드처럼 element 하나에 대해서 렌더링 해주는 함수를 만든 이유가 무엇일까? 바로 이런 거 하려고 그런 거라고 유추해볼 수 있다.

즉, element 단위로, 이전 상태와 이후 상태를 비교하여, 변경사항만 렌더링 하는 것이다.

각 컴포넌트가 state를 캡슐화해서 들고 있게 한다는 React의 핵심가치를 떠올리자.

state가 뭔지는 조금 뒤에서 다루겠다.

아래는 이러한 특성들을 활용한 간단한 예제이다. (공식 매뉴얼에 있는 예제)

 

Hello World in React

...

codepen.io


컴포넌트와 props (properties): 쉬운 데이터 바인딩

React에서 element는 가장 작은 '블록'이라고 했다.

결국 이 블록을 모아 HTML 페이지를 만드는 건데, 원래 JS에서 HTML의 특정 값을 변경하기 위해서는 getElementId() 같은 메서드를 사용해서 해야만 했다.

그리고, 이렇게 element의 데이터를 가져오거나 수정할 수 있게 하는 것을 '데이터 바인딩'이라고 부른다

아래 코드와 같이 말 그대로 data를 binding 해서 root 변수를 통해 데이터 핸들링이 가능한 것이다.

let root = document.getElementById('root')

그런데, 리액트는 컴포넌트 작성 시에 JSX를 사용한다.

즉, Babel을 통해 pure JS로 컴파일할 때, 데이터가 결정되므로 element의 데이터를 동적으로 변경할 수 있다.

이것을 위해 중괄호( {} )를 사용하는데, 이것은 꽤 많은 언어에서 동일한 역할로 발견할 수 있다.

즉 아래처럼 Welcome이라는 function의 파라미터 props를 받아 리턴 값을 바꿔줄 수 있는 것이다.

여기서 props는 property의 약자이며, 컴포넌트의 속성이라는 개념으로 이해하면 된다.

이 property 처리에 대해서는 여러 가지 규칙이 있지만, 여기서는 "Read Only"처럼 사용해라 + SOLID 원칙이나 일반적으로 장려되는 프로그래밍 원칙들을 잘 지키면 문제없다고만 언급하고 넘아가겠다. (추후 필요하다면 포스팅...)

// 요게 하나의 Welcome이라는 이름을 가진 컴포넌트(모듈)가 되는 것이다
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// React 프로젝트의 App.js가 이런 스타일
function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

// React 프로젝트의 index.js에 보면 유사한 코드를 발견할 수 있다
ReactDOM.render(
  <App />,
  document.getElementById('root')
);

그리고 여기서 나온 props는 상속관계에서도 중요하다.

클래스형이든 함수형 구현이든 관계없이, 리액트에서 컴포넌트들의 데이터 관계가 완전 비의존적일 수 없다. 즉, 상위 컴포넌트로부터 어떠한 속성 데이터를 전달받아 사용하는 경우가 많다.

부모로부터 전달받아야 할 데이터가 존재한다면, 이 props를 꼭 기억해 내자


State

리액트의 데이터 저장공간

리액트에서 가장 중요한 개념 중 하나다. 위에 써둔 거처럼 '데이터 저장공간'이라고 생각하면 된다.

즉, 변수 대신 쓸 수 있는 데이터 저장공간이라는 것이다.

방법은 두 가지가 있는데, 클래스형과 함수형으로 나뉜다.

아래는 그 예시들이다.

// Class 스타일
// 생성자에 state 객체를 만들고,
// setState() 메서드를 사용하여 state 값을 변경하자
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  
  Render() { ... 생략 }
}


// 함수 컴포넌트 스타일
// 함수엔 this가 없다
// 그러니 hook 기능을 사용하자. useState
import React, { useState } from 'react';
 
function Example() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
	   Click me
      </button>
    </div>
  );
}​

위에서 hook이라는 개념이 나왔는데, 우리가 알고 있는 hook과 동일한데, 즉, 여기에서는 onClick 이벤트가 발생했을 때, state 저장소를 가로채서(hooking) setCount를 call 하여 해당 count를 증가시키게 해주는 것이다.

hook은 리액트 릴리즈 5년 뒤에 추가된 기능인만큼, 이것의 사용을 장려한다.
그 동기와 이유에 대해서는 공식 매뉴얼에 잘 설명해두었으니 읽어보면 좋겠다.

 

Introducing Hooks – React

A JavaScript library for building user interfaces

reactjs.org

여담으로, 이러한 state (저장공간)에 대한 관리를 보다 스마트하게 하고 싶어 만든 라이브러리가 Redux이다


ReactDOM

위에서 리액트 코드의 JSX를 보다 보면, HTML는 다른 이질감을 느낄 수 있다.

예를 들어 아래와 같은 것들이다

HTML JSX 비고
class className JS에는 이미 class라는 키워드가 있다. 그래서 다르게 네이밍했다.
font-size fontSize JS에서 - 는 빼기(sub)다
onclick onClick  
onsubmit onSubmit  

요약하면,

  • JS문법과 충돌 날 수 있는 것들 역시 새로운 네이밍을 해서 camelCase를 적용
  • 겸사겸사 snake_case도 camelCase로 바꾼 것!

아무튼, 피치 못할 사정으로 네이밍이 다르니, 기존 HTML과 헷갈리지 않게 주의하도록 하자.
다른 건 몰라도 camelCase 이거 하나만큼은 꼭 기억하자.


그 외... 그리고, React 답게 생각하기

그 외에도 몇 가지 컨셉들이 공식홈페이지에 나열되어 있지만, 위 내용들로도 핵심가치를 설명하기엔 충분하니, 필요하다면 공식 문서를 다 읽어보길 권장한다. (포스팅 최상단 링크)

 

마무리 짓기 전에, 매우 중요한 컨셉 설명이 매뉴얼에 있어서 이거까지만 이야기하고 정리하려고 한다.

바로 "React 답게 생각하기"이다. (공식 매뉴얼에 이런 내용이 있는 게 너무 마음에 든다)

리액트를 쓴다면! 그리고 리액트를 효율적으로 쓰고 싶다면, 설계 의도에 맞게 사용하면 된다. (설계가 잘못된 경우가 아니라면)

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

  • SOLID의 "단일 책임 원칙"을 기억하며,
  • UI를 컴포넌트의 계층으로 나누자

즉, 모든 UI를 컴포넌트로 구현할 생각을 하면서, 레이어를 잘 나누라는 것이다.

아래 예제처럼 주석을 통해서 미리 컴포넌트를 쪼개 두면 작업할 때 매우 편하다.

function App() {
  return (
    <div className="app">
      {/* Header */}
      
      {/* App Body */}
        {/* Sidebar */}
        {/* Feed */}
        {/* Widgets */}
    </div>
  );
}

2단계: React로 정적인 버전 만들기

  • state를 쓰지 말고,
  • 컴포넌트 간의 의존성 흐름을 "단방향 (one-way data flow)"으로 만들자

아니 state가 메인 컨셉이라면서 왜 쓰지 말라는 걸까?

아직 2단계다. UI 컴포넌트를 나누는 작업만 한 상태이므로, 우선 정적인 형태로 만들면서 컴포넌트들 간의 관계가 복잡해지지 않게 고민하고 구현하라는 의미이다.

3단계: UI state에 대한 최소한의 (하지만 완전한) 표현 찾아내기 + 위치

state는 계속 변하면서, 동적인 페이지를 만들게 한다.

뿐만 아니라 state 자체는 hook을 사용하기 때문에 다른 기능들보다 더 많은 과정을 필요로 한다.

아무리 리액트가 변경된 데이터에 대해서만 렌더링을 다시 한다고 하지만, 프로그래머는 불필요한 리소스를 최대한 줄여야만 한다.

 

즉 여기서 이야기하는 것은, 어떠한 변수를 만들 때 꼭 state를 써야 하는지 충분히 고민해보라는 것이다.

추가로, state를 만든다면, 어느 컴포넌트에 위치시킬 건지에 대해서도 충분히 고민해봐야 한다.

이는 일반적인 소프트웨어 설계에서 중요하게 생각하는 내용이므로 컴포넌트 관계도를 그려보면서 고민해보자.

 

어쨌든, 핵심은 "중복배제의 원칙"이다.

 

컴포넌트 State – React

A JavaScript library for building user interfaces

ko.reactjs.org

4단계: 역방향 데이터 흐름 추가하기

여기서의 역방향은 컴포넌트의 종속성을 망가뜨리라는 말이 아니라, 사용자의 조작, 즉 가장 하위 컴포넌트의 입력(input)으로 state를 변경해야 할 경우에 대한 이야기이다.

만약 이러한 데이터 흐름을 제대로 만들지 못한다면, 내려오는 데이터의 흐름과 충돌하여 사용자의 조작은 반영되지 않고, state에 저장된 값만 렌더링 되는 사태가 벌어질 수 있다.

즉, 순방향(내려오는)에 대한 데이터 흐름을 먼저 만들어서 View부터 완성시키고,

역방향(올라가는)에 대한 데이터 흐름을 충돌 나지 않게 만들어가는 방식으로 개발 순서를 잡아보라는 것이다.

 

무조건 어떤 방법을 따라야만 하는 것은 아니다.

하지만, 잘 설계된 라이브러리나 프레임워크는 설계자의 의도에 맞는 방식으로 하면, 좀 더 나은 개발 경험을 할 수 있는 것이 일반적이라고 생각한다. 뿐만 아니라 이렇게 공식 가이드가 있는 상황에서는 "협업"의 관점에서도 매우 중요하다고 생각한다.

 

 

반응형

'WebApp' 카테고리의 다른 글

[ReactJS] 개발 환경  (0) 2022.02.27
댓글