평소에 React로 개발하면서 JSX문법도 사용하고, 컴포넌트도 만들고... 근데 가끔 "이게 정확히 어떻게 동작하는 거지?" 하는 생각이 들더라고요.
이번 기회에 React의 핵심인 element에 대해 제대로 이해하고 한 단계 성장해보려고 합니다🥹
React 엘리먼트는 React의 가장 기본적인 구성 단위예요.
쉽게 말하면 "화면에 그려질 내용을 담고 있는 자바스크립트 객체"라고 할 수 있죠. DOM 엘리먼트가 실제 화면의 요소라면, React 엘리먼트는 그것을 표현하는 설계도라고 생각하시면 됩니다
React 엘리먼트의 type에는 크게 두 가지가 있어요
문자열 타입
'div', 'span' 같은 일반 HTML 태그를 나타내요
함수/클래스 타입
우리가 만든 컴포넌트를 나타내요
최종적으로 HTML 엘리먼트로 변환될 때까지 처리됩니다
이게 무슨 이야기냐구요?
예를 들어볼게요. 우리가 보통 이렇게 코드를 작성하잖아요
// JSX로 작성한 코드
<div className='box'>
<p>안녕!</p>
</div>
이게 실제로는 이런 객체로 변환된답니다
// 문자열 타입
{
type: 'div',
props: {
className: 'box',
children: {
type: 'p',
props: {
children: '안녕!'
}
}
}
}
신기하죠? 우리가 작성한 JSX가 이런 순수 자바스크립트 객체로 변환되는 거예요.
만약 type 에 우리가 작성한 컴포넌트가 위치한다면 어떻게 되는 걸까요 ?
// 먼저 이런 컴포넌트가 있다고 해볼게요
function Button(props) {
return <p>{props.children}</p>;
}
// 이렇게 사용하면
<Button>안녕!</Button>
// React element 는 이런 모양이 됩니다
{
type: Button, // 여기서 type은 Button 함수 자체입니다!
props: {
children: '안녕!'
}
}
// Button 컴포넌트가 실행되면 다음과 같은 엘리먼트가 반환됩니다
{
type: 'p', // 이제 type이 문자열입니다
props: {
children: '안녕!'
}
}
여기까지 흐름을 봤다면 궁금증이 생기지 않나요 ?
{
type: Button, // 여기서 type은 Button 함수 자체입니다!
props: {
children: '안녕!'
}
}
type 의 Button 컴포넌트가 어떻게
props: {
children: '안녕!'
}
의 값을 받아서 사용할수 있을지 ? 궁금하잖아요 🥹🥹🥹
이해하기 쉽게 element 처리 과정을 작성해보자면
// 간단한 의사 코드로 보는 React의 처리 과정
function reconcileElement(element) {
if (typeof element.type === 'string') {
return createDOMElement(element);
}
else if (typeof element.type === 'function') {
const childElement = element.type(element.props);
return reconcileElement(childElement);
}
else if (typeof element.type === 'class') {
const childElement = element.type(element.props);
return reconcileElement(childElement);
}
}
짜잔 ! 다음같은 흐름으로 컴포넌트가 props 를 사용할수 있게 되는것이였어요!
이제 간략하게 알아봤으니 이번에는 딥~하게 알아볼까요~~
{
type: 'div',
props: {
className: 'box',
children: {
type: 'p',
props: {
children: '안녕!'
}
}
}
}
react fiber node 를 생성하는 단계에서
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
다음 코드가 실행되게 됩니다!
HostComponent 코드는 내부적으로
if (props 에 children이 또 없다면) {
nextChildren = null
} else if (아니 ?! 나는 또 children에 자식이 있는데 ?!) {
// 다음 자식을 탐색하기위한 처리
}
element 를 검사해요
이게 무슨 이야기냐
- if 의 경우
{
type: 'div',
props: {
className: 'box',
children: '안녕!'
}
}
- else if 의 경우
{
type: 'div',
props: {
className: 'box',
children: {
type: 'p',
props: {
children: '안녕!'
}
}
}
}
라는 이야기인거죠
그후에는 다시 fiber node 를 반환해줍니다 !
return workInProgress.child;
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps : 다른 로직
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
형태가 되어 updateFunctionComponent 를 실행하게 되어요!
updateFunctionComponent 는 내부적으로
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
다음 코드를 다시 실행합니다!
props 로 전달 되었던 Component 가 다시 전달되어 nextChildren 이 되는데 !
위의 코드를 봤다면 알겠지만
const Component = workInProgress.type; 입니다!!
그렇기때문에 제가 보여줬던 간략한 처리 과정 코드
else if (typeof element.type === 'function') {
const childElement = element.type(element.props);
return reconcileElement(childElement);
}
구조가 될수 있었던 거죠 🥹
마지막으로
만약에 우리가 작성한 코드가 그냥 문자열이라면 ?
const hi = () => {
return '안녕!'
}
이라면 어떻게 처리 될까요 ?
case HostText:
return updateHostText(current, workInProgress);
다음 코드가 실행되고 updateHostText 은 내부적으로
function updateHostText(current: null | Fiber, workInProgress: Fiber) {
return null;
}
단순 문자열이기때문에 null 처리가 되는걸 확인할수있네요!!
React Element는 type에 따라 다르게 처리됩니다
문자열 타입 ('div', 'span' 등)
HTML 태그를 표현
updateHostComponent를 통해 처리
children 존재 여부에 따라 다른 로직 실행
함수/클래스 타입 (우리가 만든 컴포넌트)
해당 컴포넌트 함수 실행
반환된 엘리먼트를 다시 처리
최종적으로 HTML 엘리먼트가 될 때까지 반복
--
간단하게 이해하기는 쉽지 않은 내용이라 내용이 길어졌네요 🤐
분명 제가 이해한 내용에 오류가 있을수도 있어요 !! 만약 저의 이야기에 틀린 내용이 있다면 주저마시고! 바로 잡아주시면 감사하겠습니다 🙇🏻♂️