[React] React 복습겸 토이 프로젝트 진행 (1)

yongkini ·2023년 2월 17일

React

목록 보기
11/19

리액트 개발 준비 단계에서 리액트 복습

  • 리액트는 페이스북에서(현재 meta) 2013년에 발표한 오픈 소스 자바스크립트 프레임워크이다.
  • 리액트 프레임워크는 가상 DOM(Document Object Model)과 JSX(Javascript XML)라는 새로운 방식으로 동작하는 프레임워크이다.

JSX란?

: Javascript + XML

XML 이란?

eXtensible Markup Language의 두문자어로, W3C 권고 확장성 있는 마크업 언어로, W3C가 인간과 응용프로그램간, 혹은 응용프로그램 간에 정보를 쉽게 교환하기 위해 만든 데이터 교환 포멧입니다. 즉, 확장될 수 있는 표시언어입니다.

: 위에서 응용프로그램 간이라고 했을 때, 리액트로 치면

<div>Hello, world!</div>

라는 JSX 코드(XML)는

/*#__PURE__*/React.createElement("div", null, "Hello, world!");

위와 같은 데이터로 Babel 트랜스파일러에 의해 변환된다. 결론적으로, JSX(XML)는 위의 데이터를 전달하기 위한(React에 전달한다고 생각) 마크업 언어인 것이고, 이건 페이스북 팀에서 커스텀해서 만든거다.

XML vs HTML

: Markup Language하면 생각나는 또다른 언어인 HTML과 XML을 비교 해보면 다음과 같다.

  • XML은 Data를 전달하는 데에 포커스를 맞춘 언어이지만, HTML 데이터를 표현하는데 혹은 보여주는데 포커스를 맞춘 언어이다.
  • HTML의 태그는 이미 약속한 태그들만 사용 가능하지만, XML은 사용자가 임의로 만들어서 사용이 가능하다.

다시 돌아와서 JSX란?

: 위에서 JSX는 Javascript XML이라 했는데, 그럼 JSX는 XML적 특성이 그대로 반영된걸까?. 일단 JSX는 위에서 바벨이 트랜스파일링 한 데이터처럼, 데이터 전달이 목적인 XML이 맞다. 즉, html 처럼 그 자체로 화면상에 특정한 데이터를 보여주기 위한 마크업 언어는 아니다. 하지만, JSX의 문법을 보면 HTML과 똑같음을 볼 수 있는데, 이런 부분을 통해 알 수 있는건 JSX는 React에서 화면을 렌더링할 때 이런식으로 보여질거다 라는걸 JSX로 작성하고(HTML 처럼) 이러한 XML(데이터 전달 언어)을 이용해 React.createElement 와 같은 메서드를 통해 전달된 데이터를 React 내에서 렌더링 하고, 계산하는 역할로 쓰이는 것 같다.

JSX도 표현식이다.

: 타입스크립트 복습편에서 했었는데, 표현식이란 리터럴, 연산자 등이 표현된 일종의 실행 단위이다. 그리고 이부분을 계산하면 결과를 리턴한다. 이처럼 JSX도 이부분을 계산하면 특정한 결과값이 나온다.

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

공식문서에서 가져온건데, 위와 같이 실행문식 문(statement)에서 return 키워드 뒤에 JSX를 써놓은걸 보면 JSX도 표현식인 것을 알 수 있다. 같은 원리로 JSX 내부에도 표현식을 포함시킬 수 있다(중괄호, 템플릿 리터럴 등을 써서). 이 때, 솔직하게 여태까지 모르고 쓰고 있던 부분인데 JSX 내부에 이터레이터를 돌릴 때 map, filter 혹은 삼항연산자 등만 쓸 수 있던 이유는, 예를 들어,

const Cards = () => {
	return (<div>
  				{for(let i=0;i<=cardData.length;i++) {
                  	return <Card idx={i}/>                                  
                }}
  			</div>)
}

이런식으로 쓰지 못하는 이유는 위의 for문은 JSX 내부에는 표현식이 와야하는데, for문은 표현식을 포함할 수 있는 실행문이기 때문에 쓸 수 없는 것이다. 이에 따라 우리가 흔히 쓰는 map, filter와 같은 방식의 표현식으로만 위의 행위?가 가능하다.

/*#__PURE__*/React.createElement("div", null, "Hello, world!");

즉, 위와 같은 React 메서드를 써서(createElement) 리턴되는 결과값이(이것도 표현식이니까) JS 객체 형태라는 것이다. 다시 정리해보면, JSX는 JS 객체를 표현하는 표현식이다.

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

이에 따라 위에 두개는 같다(위에 코드를 babel로 트랜스파일하면 아래가 된다). 아래와 같은 객체를 react element라고 하며, 화면에서 보고 싶은 것을 나타내는 '표현'이라 생각할 수 있다(HTML과 유사한 역할 하지만 HTML은 아님). React는 이 객체를 읽으면서 DOM을 구성하고, 최신 상태로 유지할 때 사용한다.

아래 예시는 다른 블로그에서 퍼왔는데

// 일반적인 jsx문법
return <SomeComponent a={42} b="testing">Text here</SomeComponent>

// 이것을 호출해서 변환된다.
return React.createElement(SomeComponent, {a: 42, b: "testing"}, "Text Here")

// 호출결과 element를 나타내는 객체로 변환된다.
{type: SomeComponent, props: {a: 42, b: "testing"}, children: ["Text Here"]}

위와 같은 단계로 컴파일 되고, 결국 JSX가 어떤 JS 객체를 최종적으로 표현하는지를 알 수 있다.

** 추가로 React가 렌더링을 할 때 위에서 말한 JS 객체(Object)를 최상단부터 트리 구조로 관리하는데, 이를 가상돔이라고도 하고 오브젝트 트리라고도 할 수 있다. 이 오브젝트 트리를 만들어놓고, 업데이트가 있을 때 새로운 오브젝트 트리와 이전 오브젝트 트리를 비교하여 리렌더링하는 것이다(=reconciliation).

JSX는 XML이자 하나의 문법이고, Babel이 이를 React 코드로 바꿔준다.

: 위에서 했던말의 반복이지만, JSX는 XML이고(Javascript를 확장한 문법), 이를 통해 특정한 데이터를 전달하는 데에 목적이 있다. 이 때, 그 특정한 데이터라는 것은 변환된 리액트 코드가 표현하는 결과값인 JS 객체(리액트 엘리먼트)라고 할 수 있다.

결과적으로 JSX는 어떻게 실제 DOM에 적용되나?

const HelloBtn = ({ name  }) => {
    const handleClick = () => {
      alert(name);
    }
	return <button onClick={handleClick}>{children}</button>
}

위와 같은 HelloBtn 컴포넌트가 있을 때 이를 렌더링한다 하면, 일단 이 JSX 코드를 Babel을 통해 트랜스파일링한다.

React.createElement('button', {
  onClick: handleClick
}, 'click');

이 때, React.createElement를 호출하면

{
  type: 'button',
  props: {
    onClick: function handleClick() { alert('world') },
    children: 'hello'
  },
};

위와 같이 JS 오브젝트를 리턴한다.

ReactDOM.render(
  <HelloBtn name="0715yk" />,
  document.getElementById('root')
);

주로 index.tsx에서 보는 코드인데, 여기서 ReactDOM.render를 통해 실제 DOM에 HelloBtn을 적용하게 된다.

실제로 코드를 실행하는 순서는
ReactDOM.render -> HelloBtn -> React.creactElement -> JS Object Tree(App.js라고 하면 상단부터 설계된 트리구조의 오브젝트를 쭉 탐색해나가는 것이고, 이 JS오브젝트 각각에는 자식 오브젝트 정보도 있어서 트리 탐색이 가능) 이 오브젝트 트리를 바탕으로 실제 DOM에 적용하는 것.

React.createElement로 개발하면 되지 않나?

: 예를 들어, JSX 를 써서 개발하면

const app = (
  <ul>
    <li>
      <a href="https://www.google.com">
        <p>go to google</p>
      </a>
    </li>
  </ul>
)
root.render(app)

이렇게 되지만, 이걸 React.createElement를 사용해서 개발하려면

const RC = React.createElement

const reactElementApp = RC('ul', null, [
  RC('li', null, [
    RC('a', {href: 'https://www.google.com'}, [RC('p', null, 'go to google')])
  ])
])
root.render(reactElementApp)

이렇게 작성해야한다. 마치 콜백지옥에 빠진 Promise를 async/await로 구현한 것과 같지 않은가..? 결국 React.createElement 호출의 복잡성을 해결하고자 자바스크립트 언어에 없는 JSX 기능을 언어 확장 형태로 추가한게 XML = JSX 이고, 이게 리액트를 쓰는 이유중 큰 부분이라고 할 수 있을 정도이다.

여기에 첨언해보면, 아까 for문이나 console.log 같은 실행문 단위는 JSX 내부에 삽입할 수 없다(중괄호를 통해). 그 이유는 React.createElement를 통해 element를 생성할 한줄 한줄이 모두 React.createElement 호출 코드로 변환돼야 하는데, console.log 등 React.createElement 호출로 변환할 수 없어서 오류가 발생한다. 그리고 이렇게 createElement가 반환하는 값은 가상 DOM 객체이므로 변수나 배열에 담을 수 있다. 흔히, children에 컴포넌트를 넣어서 렌더링하는 방식을 쓰기도 하는데 그게 가능한 근거가 이러한 부분에서다.

profile
Web3.0에 관심이 많은 FE 개발자입니다. VPA와 캔들 차트 분석을 기반으로 정량적 트레이딩 시스템을 직접 개발하여 암호화폐를 트레이딩하고 있습니다.

0개의 댓글