[MGS 3기 - 17일차 (2)] React 기초

박철연·2022년 5월 3일
0

MGS STFE 3기

목록 보기
18/35
post-thumbnail

17일차 학습 내용 중 React의 기초 개념에 대한 부분을 요약한 글입니다.

React 소개

프론트엔드 개발 분야에 관심이 있으신 분이라면, React를 모르실 수가 없을 거라고 생각합니다. 보통 Angular, Vue.js와 같이 묶여 '프레임워크 3대장'이라고 흔히 불리기도 합니다.

기업들의 채용 공고는 다양한 요건을 요구하지만, 그 와중에 React는 거의 빠지지 않고 등장하는 필수 요건입니다.

뿐만 아니라, 각종 개발자 교육 관련 매체에서도 어떤 예외도 없이 모두 React를 다루고 있죠. 그만큼 React는 대세입니다.

보시는 이미지는 2021년 말 실시된 스택오버플로우의 설문 조사입니다. 개발자들을 대상으로 가장 인기있는 웹 프레임워크를 물어 본 결과인데, React는 당당하게 1위를 차지하고 있습니다.

2020년 기준 약 43%에 육박하던 JQuery를 물리치고 드디어 가장 널리 쓰이는 웹 프레임워크의 자리를 차지했습니다. 작년 React의 점유율은 35% 정도였으니까 JQuery와 React의 자리가 완전히 뒤바뀌었다고 볼 수도 있겠습니다.

인터페이스 구축을 위한 라이브러리

위 사진은 실제 React 홈페이지의 메인 화면입니다. 보시다시피 "사용자 인터페이스를 만들기 위한 JavaScript 라이브러리"라고 되어 있습니다.

그래서 React는 애초에 설계될 때부터, 사용자로 하여금 더 많은 자유도를 누릴 수 있는 라이브러리를 지향하는 방향으로 만들어져 왔다는 점이 포인트입니다.

여담으로, Angular같은 경우에는 정통 프레임워크에 가까워서, React에 비해 더 엄격한 개발 구조를 가지고 있습니다.

또한 Vue는 Angular와 React의 중간 정도에서 양 쪽의 장점을 최대한 살리는 것을 목표로 한 프레임워크라고 볼 수 있고요.

정리하자면, React는 웹 프론트엔드 내에서 사용자 인터페이스를 만들기 위한 JS 기반 라이브러리이며, 다른 프레임워크들에 비해 사용자의 재량권이 비교적 많이 보장된다고 볼 수 있습니다.

컴포넌트 기반

다음은 아까와 마찬가지로 React 홈페이지 메인에 작성되어 있는 문구입니다.

컴포넌트 기반
스스로 상태를 관리하는 캡슐화된 컴포넌트를 만드세요. 그리고 이를 조합해 복잡한 UI를 만들어보세요.

컴포넌트라는 게 정확히 뭘까요? 다음 코드 블럭을 참고해보세요.

<!--HTML Elments-->
<img src="">
<button class="">Button</button>

<!--Components-->
<myOwnComponent1 name="Park"/>
<myOwnComponent2 prop={false}>내용</myOwnComponent2>

윗부분의 HTML 요소들은 모두 HTML을 다룰 때 흔히 보았던 태그들입니다.

아래는 컴포넌트 예시를 위해 제가 만든 가상의 컴포넌트들입니다. 보시는 것처럼 마치 HTML 태그처럼 작성이 되어 있죠.

컴포넌트는 문서(HTML), 스타일(CSS), 동작(JS)를 합쳐서 개발자가 커스터마이징한 태그를 지칭한다고 볼 수 있습니다. 그러니까 개발자가 원하는 방향으로 작동하는 태그를 하나 창조시키는 것입니다.

그래서 React가 말하는 컴포넌트 기반의 프레임워크라는 것은, 일단 기능별로 컴포넌트를 만들고, 이를 부품처럼 조합하여 복잡한 UI도 구현할 수 있다는 뜻입니다. 어떻게 보면 OOP의 연장선상에 있다고 볼 수도 있겠습니다.

Virtual DOM

React의 빼놓을 수 없는 특징은 바로 Virtual DOM, 가상 DOM을 활용한다는 점입니다. 이는 바로 이전에 다룬 컴포넌트 부분과 연관이 있습니다.

보통 프레임워크가 아니라 바닐라 JS만 경험하신 분들은 DOM을 제어할 때 직접 접근하셨던 경우가 대다수일 것입니다.

결국 JS는 클라이언트가 사용자와 상호 작용 할 수 있게 동적인 메커니즘을 추가하는 도구입니다. 그러려면 HTML 요소에 접근할 수 있어야 할 것입니다.

그 때 썼었던 document.querySelector 기억나시죠? 그건 DOM을 직접 제어하는 방식이라고 할 수 있습니다. querySelector는 실제로 존재하는 HTML 요소에 접근하는 방식이니까요.

React는 직접 DOM을 제어하는 대신, 가상의 DOM 객체를 만듭니다. 그런 다음, 어떤 이유로건 상태가 변화하는 DOM을 기록해두었다가 가상의 DOM과 비교합니다. 이러한 방식으로 동적으로 변화하는 요소를 쉽게 쉽게 바꾸는 거라고 볼 수 있겠습니다.

DOM과 Element

DOM (Document Object Model)

첫 번째 글에서, React의 큰 특징 중 하나로 Virtual DOM에 대해 설명한 적이 있습니다. 약간의 복습 느낌으로 DOM이 무엇인지에 대해 빠르게 짚고 넘어가겠습니다.

HTML을 다뤄보면 많은 요소들이 존재를 하죠. 이 요소들을 논리 트리로 나타내는 것이 바로 DOM입니다.

우리는 HTML 문서를 읽는 것만으로도 Element들의 구조를 쉽게 파악하지만, 컴퓨터는 DOM을 통해 이 구조를 이해한다고 보시면 될 것 같습니다.

이번 글을 DOM에 대한 설명으로 시작한 이유는, React 내에서 Element를 만들어 내는 방식이 DOM에 대한 이해부터 시작하기 때문입니다.

Element 만들기

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
  <script>
    const root = document.getElementById('root')
    const element = document.createElement('h1')
    element.textContent = "Hello React!"
    root.appendChild(element)
  </script>
</body>
</html>

HTML 내에 script 태그를 달아서 간단한 요소를 만드는 코드를 입력했습니다.

결과적으로 id가 root인 div 태그 안에 h1이 생성될 것이고, 내용은 textContent에 입력한 것과 같이 "Hello React!"가 나타날 것입니다.

다음 코드 블럭은 react에서 element를 생성하는 방식을 예시로 나타낸 것입니다.

...
<body>
  <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <div id="root"></div>
  <script>
    const root = document.getElementById('root')
    const element = React.createElement("h1", {children: "Hello React!"})
    ReactDOM.render(element, root)
  </script>
</body>
...

크게 두 가지 차이점이 보입니다. 먼저 children: "Hello React!"라는 부분이 보일텐데, 당연히 이는 JS 코드에서 textContent에 해당하는 부분입니다.

또 원래는 appendChild를 사용했었는데, React에서는 render()라는 함수를 통해 생성한 요소를 실제 화면에 추가해주고 있어요. 특히 React가 아니라 ReactDOM에 접근해서 render를 실행한다는 것에 유의해야 합니다.

참고로 render 함수의 첫 번째 인자는 추가할 요소이고, 두번째 인자는 그 요소를 추가할 부분입니다.

저기에서 만든 element 변수를 console.log로 찍어보면 더 이해가 쉽습니다.

React 상에서 createElement를 하면 저런 형태의 객체가 나오게 됩니다. 중간에 props 부분을 보시면 children를 포함하고 있다는 것을 알 수 있습니다.

즉, React는 Element를 특수한 형식의 객체를 만들어서 생성, 제어한다는 것을 알 수 있습니다.

컴포넌트 만들기

Class 컴포넌트

React에서 컴포넌트를 직접 만드는 방법은 두 가지로 나뉩니다. 바로 ClassFunction입니다.

그 중 먼저 Class에 대해 살펴보도록 하겠습니다.

class ClassComponent extends React.Component {
	render() {
    	return (<div>"React!"</div>)
    }
}

문법은 위 코드 블럭과 같습니다. 먼저 class와 식별자를 사용하여 class를 생성한 뒤, extends로 React.Component를 상속받아 class 컴포넌트를 만든다는 것을 선언합니다.

그런 뒤, render 함수와 실제로 렌더링하고 싶은 내용을 작성해줍니다.

다만 컴포넌트를 만들기만 하고 호출을 아직 해주지 않았습니다. 그러니 밑의 코드 블럭처럼 실제 컴포넌트를 불러와 주는 코드도 작성해야 실제로 화면에 출력될 것입니다.

ReactDOM.render(
	<ClassComponent />, document.querySelector('#root')
)

이제 우리가 class 방식으로 만든 ClassComponent는 id가 root인 요소에 성공적으로 출력되게 됩니다.

Function 컴포넌트

function 컴포넌트를 만드는 건 사실 class와 별반 다르지 않습니다. 그냥 기명 함수를 만들고 Element를 반환하는 함수 내용을 작성하는 형식입니다.

function FunctionComponent1() {
    	return (<div>"React!"</div>)
    }

function FunctionComponent2 = () => {
	return (<div>"React!"</div>)
}

함수의 형태만 취하면 되는 거라, 아래쪽 코드처럼 화살표 함수를 사용해서 작성해도 전혀 무방합니다. 두 코드는 완벽히 같은 코드입니다.

실제로 화면에 출력되게끔 호출하는 방식 역시 class 컴포넌트와 동일합니다.

createElement로 컴포넌트 만들기

위의 두 가지 방법말고, createElement로도 컴포넌트를 만들 수 있습니다. 다만 class나 function처럼 직접 선언하는 것이 아니라, render 메서드의 인자로 넣을 수 있습니다.

하지만 그 전에, 먼저 createElement의 문법을 정확하게 정리하고자 합니다.

createElement

React.createElement(
	type,   // 태그 이름, React 컴포넌트, React.Fragment
  	[props],   // React 컴포넌트에 넣어주는 데이터 객체
  	[...children]   // 컴포넌트의 자식으로 넣어주는 요소들
)

createElement의 인자로는 3가지 유형이 들어가는 데, type, [props], [...children]입니다.

컴포넌트 만들기

그러면 위 코드 블럭의 주석을 유념하면서 실제 예시를 한번 만들어 보겠습니다.

// 태그 이름 사용
ReactDOM.render(
	React.createElement('h1', null, '태그 이름 사용!'),
  	document.querySelector('#root')
)

// React 컴포넌트 사용
const Component = () => {
	return <h1>컴포넌트 사용!</h1>
}

ReactDOM.render(
	React.createElement(Component, null, null),
  	document.querySelector('#root')
)

// React.Fragment 사용
ReactDOM.render(
	React.createElement(React.Fragment, null, 'Fragment 사용!'),
  	document.querySelector('#root')
)

(참고로 props 부분은 여기서 자세히 다루지 않기 때문에 공통적으로 null을 넣어주었습니다.)

React Fragment

위에서 react fragment라는 개념이 등장했습니다.

그런데 콘솔로 살펴보면, React.Fragment를 사용해서 만든 컴포넌트는 따로 요소로 처리되지 않고 root div에 날 것의 문자로 들어가 있습니다.

위 이미지에서 보이는 것처럼 root div외에 다른 태그는 없고 출력하는 문자열만 들어가 있는 것을 확인 할 수 있습니다.

이렇게 React.Fragment를 사용하게 되면, 최상위 요소에 자식 요소를 그대로 넣어버리는 효과가 있습니다. 그렇기 때문에 자식 요소를 여러 개 만들 때 이 Fragment를 활용한다는 사실을 알아 둘 필요가 있습니다.

복잡한 Element 만들기

지금까지 예시들은 매우 간단한 Element들을 만들어내는 컴포넌트를 다루었습니다. 하지만 실제로는 훨씬 복잡한 구조의 element를 다루게 될 것입니다.

우리가 다음과 같은 Element 구조를 만든다고 가정해 봅시다.

<section>
  <div>
    <h1>Hello</h1>
    <ul>
      <li>React1</li>
      <li>React2</li>
    </ul>
  </div>
</section>

태그들이 다양하게 중첩되었습니다. 이것도 컴포넌트를 만드는 방식으로 변환할 수 있습니다.

ReactDOM.render(
  React.createElement(
    "section", 
    null, 
    React.createElement(
      "div", 
      null, 
      React.createElement("h1", null, "Hello"),
      React.createElement("ul",
                          null,
                          React.createElement("li", null, 'React1'),
                          React.createElement("li", null, 'React2'))
    )),
  document.querySelector('#root')
)

들여쓰기를 적용해서 나름 구조를 표현하고자 했는데, 중첩된 코드가 너무 많아서 가독성이 대단히 떨어집니다.

JSX

JSX 소개

바로 위에서 복잡한 element를 컴포넌트 형식으로 만들면 코드 구조가 지나치게 복잡해진다는 것을 확인했습니다. 이를 해결하기 위해 등장한 것이 JSX 문법입니다.

JSX는 JavaScript Extends의 줄임말로, 컴포넌트 구성을 쉽게 하기 위해 만들어진 문법입니다.

JSX는 기본적으로 다음과 같은 형태를 가지고 있습니다.

function App(){
	return (
    	<div>
          Hello <b>react</b>
        </div>
    );
}

직관적으로 눈에 딱 들어오는 부분은, HTML의 태그 형태를 그대로 가져왔다는 것입니다.

그러다 보니 작성하는 코드의 길이도 줄어들고, 구조를 알아보기도 쉽고, 코드 가독성이 월등하게 향상됩니다.

위에서 만들었던 예시도 한번 JSX로 바꿔보겠습니다.

function App(){
	return (
    	<section>
      	  <div>
      	    <h1>Hello</h1>
            <ul>
      	      <li>React1</li>
              <li>React2</li>
            </ul>
          </div>
        </section>
    );
}

휠씬 깔끔한데다가 문서 구조 파악도 너무 쉽습니다. 이러한 가독성 이슈 외에도, Babel을 통해 컴파일되는 과정에서 문법적 오류를 찾아내기 쉽기 때문에 JSX는 여러모로 유용합니다.

JSX 문법

JSX가 우리에게 아주 유용한 도구가 될 수 있다는 것은 방금 확인했습니다. 그러면 실제 프로젝트에서 JSX를 사용할 때 몇 가지 유의해야 할 문법 규칙을 알아보겠습니다.

  1. 태그 닫는 법
    JSX는 태그 닫는 것에 매우 민감합니다. HTML에서는 가끔씩 태그 실수를 해도 너그럽게 넘어가주지만 JSX는 그렇지 않아요.

    특히, 자식 요소가 있다면 꼭 닫는 태그를 달아주어야 하고, 자식 요소가 없으면 열면서 닫아야 합니다.
<p>Blah Blah</p>
<br />
  1. 최상위 요소는 오로지 하나만
    우리가 JSX로 변환했었던 예시를 살펴보면, 최상위 요소는 section이라는 것을 알 수 있습니다. 이 section과 같은 수준에 요소를 하나 이상 생성하면 JSX는 이를 엄격하게 지적합니다.

    바꿔 말하면, 컴포넌트 내의 여러 요소들은 하나의 요소로만 감싸져 있어야 합니다. 이는 Virtual DOM에서 컴포넌트 변화를 효율적으로 감지하기 위함입니다.

    최상위 요소를 리턴할 때에는 반드시 ()로 감싸주어야 합니다.

  2. 자바스크립트 표현식
    JSX 안에서는 {}를 사용하면 자바스크립트 표현식을 작성할 수 있습니다. 아래 코드 블록을 참고하세요.

function App() {
	const name = 'Park'
    return(
    	<div>
      	  <h1>Hi! {name}</h1>
        </div>
    )
}
  1. if문 사용금지
    JSX 내에서는 if문을 사용할 수 없습니다. 만약 조건문을 사용하고 싶다면 JSX 문법 밖에서 코드를 작성하거나, {} 안에 삼항 연산자를 사용해야 합니다.

Props & State

props와 state 개요

지난 시간에는 컴포넌트를 만드는 방법과 컴포넌트의 사용에 대해 다루어 보았습니다.

오늘 다룰 props와 state는 컴포넌트에 데이터를 주는 방식에 따라 구분되는 개념이라는 것을 미리 알고 가면 좋습니다.

먼저 props는 컴포넌트 외부에서 컴포넌트에게 주는 데이터입니다. props는 React 내에서 element가 만들어질 때 사용됩니다.

반면 state는 컴포넌트 내부에서 변경 가능한 데이터입니다.

일반적으로 React에서 흔히 언급되는 '상태'라는 개념이 바로 이 state인데, 컴포넌트의 상태에 따라 다르게 보일 수 있도록 조절 가능합니다.

중요한 포인트는 props가 바뀌던, state가 바뀌던 렌더링이 다시 일어나게 된다는 것입니다.

쉽게 말해 우리가 사용하는 Render 함수는 props와 state가 바뀔 때 컴포넌트를 다시 화면에 그리게 됩니다.

그래서 우리가 Render 함수 안의 코드를 짜는 것은 props가 이런 값이라면, 이걸 그려주세요! 혹은 state가 이런 상태라면 이걸 그려주세요! 등의 내용을 작성하는 것이라고 보면 되겠습니다.

Props와 Components

이제 실제 코드를 가지고 컴포넌트 내에서 props를 정의해보고, 컴포넌트와 어떻게 상호작용하는 지 살펴보겠습니다.

첫 번째 예시는 function 컴포넌트입니다.

일단 function 컴포넌트에 인자로 props를 넣어주었습니다. 그리고 render 함수 내에 해당 컴포넌트를 부르고, props의 message를 Hello라고 입력해 주었습니다.

이 과정에서, 우리가 컴포넌트를 만들 때 인자로 넣어주었던 props는 다음과 같은 형태의 객체가 될 것입니다.

{message: 'Hello!'}

따라서 h1 태그 안에 넣어준 props.message가 성공적으로 렌더링될 것입니다.

당연히 제가 넣어준 message의 값이 변경되면 컴포넌트는 다시 렌더링되고, h1에는 변경된 message 값이 대신 그려지게 됩니다.

class 컴포넌트를 사용해서도 예시를 한 번 만들어 보겠습니다.

function 컴포넌트 같은 경우에는 인자로 props를 넣어줄 수 있기 때문에 상관없지만, class 컴포넌트는 props를 인자로 넣는 게 불가능합니다.

그래서 function 컴포넌트 때와는 다르게 {this.props.message}가 들어가는 것이라고 보시면 됩니다.

추가적으로, props 객체 내의 데이터들은 기본값을 설정할 수 있습니다.

props는 외부에서 들어오는 데이터인데, 외부에서 데이터를 입력하지 않을 수도 있을 것이고, 어떤 이유든 간에 데이터가 잘못 전달이 되서 undefined가 되었다고 생각해 보세요.

그러면 화면에 렌더링되지 않을 테니, 우리의 예상과 어긋난 화면이 그려질 수도 있을 것입니다.

그런 경우를 위해 props 기본값을 정하는 방법이 있습니다.

이런 식으로 코드를 작성하면, 우리가 컴포넌트 태그 안에서 message의 값을 따로 입력하지 않아도 default라는 단어가 대신 그 자리를 채울 것입니다.

주의하실 점은, 첫번째 방법은 class와 function 컴포넌트 모두 사용 가능한 방법이지만, static을 사용하는 두 번째 방법은 오로지 class 컴포넌트만 가능합니다.

State와 Component

이제 컴포넌트와 state의 사용법에 대해서 알아보겠습니다. 먼저 class 컴포넌트에서 state를 만들어 보겠습니다.

주의할 점은 state는 언제나 객체의 형태여야 한다는 것입니다. state = 0 처럼 직접적으로 값을 할당하는 방식은 금물입니다.

한번 state를 변경해 볼까요? 아직 다룬 적은 없지만, state가 화면에 mount될 때 실행되는 메서드인 componentDidMount를 써보겠습니다.

일단 코드 자체만 읽어보면, state를 무리없이 변경할 수 있어보입니다. 하지만 개발자 도구를 열어 보면, count는 전혀 변하지 않는다는 걸 알 수 있습니다.

state는 immutable이라고 해서, 일반적인 방법으로는 절대 변경할 수 없습니다.

state는 React 내에서 약속에 의해 딱 하나의 방식으로만 변경이 가능합니다. 그것이 바로 setState 함수입니다.

state를 생성하면, 실제 state에 해당 하는 값(예시에서는 count) 말고도 setState 함수가 같이 생성됩니다.

이 함수는 오로지 state 변경만을 위한 함수로, 넣어준 인자의 값으로 state를 변경시킵니다.

오늘 서두에서 말씀드린 것 처럼, state에 변경이 일어나면 자동으로 화면이 다시 렌더링되는 과정을 거칩니다.

따라서 우리는 setState 함수로 state 변경만 잘 조정해주면, 화면에 새롭게 렌더링되는 것은 걱정하지 않아도 되겠죠? 컴퓨터가 알아서 할테니까요.

추가적으로 state 생성은 constructor 함수로도 가능합니다. 밑의 예시를 한번 참고해보세요.

constructor는 생성자 함수로, 최초에 props를 인자로 받게 되는데, 이는 상속되는 개념이기 때문에 super를 사용하여 props를 받아줍니다. 그 후에 state에 대해 정의하면 됩니다.

profile
Frontend Developer

0개의 댓글