React 공식 튜토리얼을 바탕으로, 필요한 개념을 보충하여 학습한 기록입니다.
React는 "UI를 만들기 위한 JavaScript 라이브러리" 이다. 그렇다. '라이브러리'다.
프레임워크 : 원하는 기능 구현에 집중하여 개발할 수 있도록 필요한 기능을 갖추고 있는 것, 일정한 형태를 가지고 다양한 형태의 결과물을 만드는 것
라이브러리 : 소프트웨어를 개발할 때 프로그래밍 사용하는 비휘발성 자원의 모임, 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것
Angular
, Vue
등의 다른 프레임워크와는 달리 React
는 오직 View만 담당하는 라이브러리다.
UI = View(State)
그래서 UI를 위의 수식과 같이 표현한다면, React가 이 수식의 View 함수에 해당한다고 설명한다. (출처: JisuPark 님의 medium)
그 말은, View
를 State
가 같다면 항상 같은 UI를 결과로 갖는 함수로 본다는 것이다.
이러한 관점에서의 장점은 다음과 같다:
state
, props
에 따른 render
결과가 바뀌지 않는다.JSX
를 통해 어떻게 화면을 그릴지 정의한다.합성(Composition)
이 가능하듯이 컴포넌트
간 합성을 할 수 있다.JSX
와 엘리먼트const element = <h1>Hello, world!</h1>
앞선 글에서 말했듯, React는 JSX
문법을 사용하여 화면을 구성한다. 이 JSX
란 자바스크립트에 XML
을 더한 형태로, 마크업과 로직을 각각 별도의 파일로 구분하는 기존의 방식에서 벗어나 이 둘을 함께 포함하는 각각의 컴포넌트
들로 프로그램의 각 영역을 구분하는 데 사용된다.
JSX
의 특징이자 장점은 마크업 요소 안에 유효한 자바스크립트 표현식(값을 반환하는 식 또는 코드)을 넣을 수 있다는 것이다. 정확히는 중괄호 안에는 유효한 모든 자바스크립트 표현식을 넣을 수 있다.
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
위 코드는 공식문서에 나와 있는 예시로, 자바스크립트 함수 호출의 결과인 formatName(user)
을 <h1>
엘리먼트에 포함시켰다.
"값을 반환하는 식 또는 코드"라는 표현식의 정의를 생각해보자. 표현식은 곧 어떤 값으로 표상되기 때문에 JSX
에서는 이것을 엘리먼트에 들어갈 값으로 사용할 수 있었다.
같은 관점에서, 자바스크립트 표현식을 사용한 JSX
는 정규 자바스크립트 함수를 호출하게 되고, 즉 자바스크립트 객체로 인식된다. 즉 표현식으로써 if
구문 및 for loop
안에 사용되고, 변수에 할당되고, 인자와 리턴값으로 사용될 수 있다.
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
어트리뷰트
, 즉 속성이란 html 문서에서 엘리먼트에 추가적인 정보를 넣을 때 사용되는 요소다.
const element = <a href="https://www.reactjs.org"> link </a>;
const elementTwo = <img src={user.avatarUrl}></img>;
위와 같이 어트리뷰트의 값에 문자열 리터럴이나 자바스크립트 표현식을 넣을 수 있다.
이처럼 JSX
에서는 중괄호를 사용해서 값이 필요한 부분에 자바스크립트 표현식을 넣을 수 있다는 점을 상기하자.
각 JSX
엘리먼트는 React.createElement(component, props, ...children)
를 호출하여 트랜스파일(컴파일)된다. 그래서 사실 JSX
로 할 수 있는 모든 것들은 순수 자바스크립트로도 구현된다:
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
위와 같은 JSX
코드는 아래와 같은 순수 자바스크립트 코드로 변환될 수 있다.
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
React.Component
가 무엇인지는 아래에서 후술할 것이다. 지금 눈여겨볼 점은 <div>Hello {this.props.toWhat}</div>
라는 하나의 엘리먼트가 React.createElement('div', null, 'Hello ${this.props.toWhat}')
라는 하나의 함수 호출에 대응된다는 점이다.
React.createElement(
type, // string | React.createClass()
[props], // null | object
[...children] // null | string | React.createClass() | React.createElement()
)
React.createElement
의 기본 형태는 위와 같다. 이 API를 내부적으로 들여다본 글을 찾았는데, 인자를 받아서 아래와 같은 값을 가지는 ReactElement
객체로 만들어준다고 한다.
const ReactElement = function(type, key, ref, self, source, owner, props){
const element = {
$$typeof : REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
}
...
return element
}
즉, React.createElement
를 통해 엘리먼트에 대한 정보를 가지는 객체를 생성하고, 이를 In-Memory
에 저장한 후 ReactDOM.render
함수를 통해 Web API(document.createElement)
를 이용해서 실제 웹 브라우저에 그려주는 방식으로 동작한다고 한다. (아직은 이 내용이 조금 어렵다)
다시 한 번 엘리먼트에 대해 간단히 짚고 넘어가자.
엘리먼트는 웹 페이지의 일부이며, XML
혹은 HTML
내부에서 텍스트나 이미지의 일부 혹은 데이터 아이템을 가지고 있을 수 있다. 물론 아무것도 가지고 있지 않는 것도 가능하다. 일반적인 엘리먼트는 어트리뷰트(속성)와 함께, 열린 태그로 시작하며 그 내부에 텍스트가 있고 닫는 태그로 끝나게 된다. (참고: jakeseo-javascript.js)
다만, 위의 도식에서도 알 수 있듯 엘리먼트와 태그는 동일한 것이 아니다. 태그는 소스코드에서 엘리먼트를 시작하거나 끝내긴 하지만, 엘리먼트는 브라우저에서 페이지를 보여주는 document model
인 DOM
의 일부이다.
이와 달리, React에서 엘리먼트란 React 앱의 가장 작은 단위이다. 엘리먼트는 화면에 표시할 내용을 기술한다.
const element = <h1>Hello, world</h1>;
브라우저 DOM 엘리먼트
와 달리, React 엘리먼트는 일반 객체이며 쉽게 생성할 수 있다. React DOM
은 React 엘리먼트와 일치하도록 화면이 보여질 수 있게 DOM
을 업데이트한다. 이후 언급되는 '엘리먼트'란 이 React 엘리먼트를 뜻한다.
DOM
에 엘리먼트 렌더링하기공식문서를 보면, "HTML 파일 어딘가에 <div>
가 있다고 가정해 봅시다" 라며 아래와 같은 코드를 보여준다:
<div id="root"></div>
"이 안에 들어가는 모든 엘리먼트를 React DOM
에서 관리하기 때문에 이것을 “루트(root)” DOM 노드
라고 부릅니다" 라고 한다. 나는 잠깐 여기서 루트 노드가 어떤 의미인지 의문이 생겼다.
리액트에서는 직접 HTML을 코딩하는 것이 아닌, src
폴더에서 JSX
문법을 이용해서 HTML뷰를 생성해 낸다. 즉 HTML로 모든 뷰를 이미 만들어놓은 뒤 보여주는 정적 방식이 아니라, 동적으로 HTML 뷰를 생성하여 id
가 root
인 div
안에 들어가게 된다. create-react-app
으로 리액트 프로젝트를 생성한 직후 index.html
파일을 열어 보면 이를 확인할 수 있다. <body>
태그 안에는 id="root"
인 <div>
태그 하나가 덩그러니 있을 것이다.
따라서 만들어낸 React 엘리먼트를 이 루트 DOM 노드
에 렌더링하려면 둘 다 ReactDOM.render()
로 전달하면 된다.
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
위 코드를 보면 render()
함수의 두 번째 인자로 getElementById('root')
가 주어져 있는 것을 볼 수 있다. 즉 React에서는 일반적으로 id가 root인 요소
를 찾아 루트 노드로 삼기로 한다.
React 엘리먼트는 불변객체다. 엘리먼트를 생성한 이후에는 해당 엘리먼트의 자식이나 속성을 변경할 수 없다. 엘리먼트는 영화에서 하나의 프레임과 같이 특정 시점의 UI를 보여준다.
지금까지 정리한 내용을 바탕으로 하면 UI를 업데이트하는 유일한 방법은 새로운 엘리먼트를 생성하고 이를 ReactDOM.render()
로 전달하는 것이다.
React DOM
은 해당 엘리먼트와 그 자식 엘리먼트를 이전의 엘리먼트와 비교하고 DOM
을 원하는 상태로 만드는데 필요한 경우에만 DOM
을 업데이트한다.