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

yongkini ·2023년 2월 17일
0

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
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

0개의 댓글