JSX

김동현·2021년 12월 8일
0

React

목록 보기
1/27
post-thumbnail

JSX

JSX는 자바스크립트의 확장된 문법입니다. html 코드와 유사하게 작성하기 때문에 UI를 다루는 코드의 가독성을 높여줍니다. React.createElement()를 호출하는 방식보다 JSX를 사용하는 방식이 가독성을 높여줍니다.

// 1. JSX 문법
const element = (
    <div>
        <h1>Hello, world</h1>
    </div>
);

JSX 문법은 HTML 코드와 유사하여 코드로부터 UI를 쉽게 추측할 수 있습니다. JSX 문법은 브라우저가 해석할 수 없기 때문에 위에 작성한 JSX 문법은 바벨(babel)의 @babel/preset-react에 의해 아래 호출문으로 변환됩니다.

// 2. 바벨의 @babel/preset-react 프리셋으로 createElement 호출문으로 변환
const element = React.createElement('div', null, 
    React.createElement('h1', null, 'Hello, world')
);

그리고 위 호출문은 "리액트 엘리먼트(일반 객체)를 반환"됩니다. 그러므로 JSX 문법은 자바스크립트의 표현식으로 볼 수 있으며, 변수나 다른 자료구조에 할당할 수 있고 함수의 매개변수나 반환값으로도 작성이 가능합니다.

생성된 리액트 엘리먼트는 "불변객체"로서 변경이 불가능합니다.

이렇게 생성된 리액트 엘리먼트들로 가상돔을 만들고 가상돔과 실제 돔을 비교하여 차이나는 부분을 반영하는 식으로 돔을 업데이트하게 됩니다.

React.createElement( )

React.createElement(Tag Name | Component, props, ...children); 메서드에는 세 개의 인수를 전달하여 호출하면 리액트 엘리먼트를 반환합니다.

  1. 첫 번째 인수로는 "html 태그 이름을 문자열"로 전달하거나 "컴포넌트 함수"를 전달할 수 있습니다.
    첫 번째 인수로 전달한 값은 리액트 엘리먼트의 type 프로퍼티에 할당되는 값이며, 이 값이 문자열이 아닌 함수라면 그 함수를 실행하여 type 프로퍼티 값이 모두 문자열이 될 때까지 진행한다.

    • html 태그 이름을 문자열로 전달하는 경우 리액트 엘리먼트의 type 프로퍼티 값이 html 태그 이름 문자열을 갖는 리액트 엘리먼트를 생성합니다.

    • 컴포넌트 함수를 전달하는 경우 생성된 리액트 엘리먼트의 type 프로퍼티 값이 컴포넌트인 리액트 엘리먼트를 생성합니다. 이때 type 프로퍼티 값이 문자열이 아닌 경우 type 프로퍼티에 바인딩된 컴포넌트 함수를 실행하여 새로운 리액트 엘리먼트를 반환합니다.

  2. 두 번째 인수는 "객체(props)"를 전달합니다.

    • 첫 번째 인수로 문자열을 전달하는 경우 리액트 엘리먼트를 설정하는 객체를 전달합니다.

    • 컴포넌트 함수의 경우 컴포넌트를 설정하는 객체로서 컴포넌트 함수에게 인수로 전달되어 컴포넌트 함수 내부에서 사용되는 객체입니다.
      type 프로퍼티 값이 컴포넌트인 경우 컴포넌트가 호출되는데 이때 인수로 props 객체를 전달하면서 컴포넌트를 호출합니다.

  3. 세 번째 이후 인수에는 "JSX 문법 태그 사이의 내용"을 전달합니다. 즉, 태그 사이 Content 값을 전달합니다. 해당 내용은 props객체의 chlidren 프로퍼티에 존재합니다.

    • type 프로퍼티 값이 문자열인경우 JSX 문법 사이의 작성한 내용이 화면에 렌더링됩니다.

    • type 프로퍼티 값이 컴포넌트라면 JSX 문법 사이의 내용이 화면에 렌더링되지 않고, props객체의 children 프로퍼티에 저장되어 컴포넌트 함수에게 전달됩니다.

// 3. createElement는 리액트 엘리먼트(일반 객체)를 반환
const element = {
    type: 'h1',
    key: null,
    ref: null,
    props: {
        children: {
            type: 'div'
            key: null,
            ref: null,
            props: {
                children: 'Hello, world'
            }
        }
    }
};

위 객체가 React.createElement 호출문의 반환값입니다. 즉, 리액트 엘리먼트가 일반 객체인 것을 확인할 수 있습니다.

리액트 엘리먼트의 type 프로퍼티의 값이 문자열이 아닌 함수 참조인 경우에는 그 함수를 다시 호출합니다. 이렇게 type 프로퍼티의 값이 문자열이 될 때까지 이러한 동작을 하여 리액트 엘리먼트로 구성된 트리 구조를 생성하게 됩니다.

어트리뷰트

1. type: 'HTML 태그 이름'

기본적으로 JSX 문법에 작성한 어트리뷰트들은 모두 "props 객체의 프로퍼티"가 되어 리액트 엘리먼트나 컴포넌트를 설정하는 객체로 사용됩니다.

html 태그 이름(div, p, span,,)을 문자열로 JSX 문법 사용한 경우 생성된 리액트 엘리먼트의 type 프로퍼티 값이 html 태그 이름이고, 작성된 어트리뷰들은 모두 props 프로퍼티에 바인딩된 객체의 프로퍼티로서 존재합니다. 이때 JSX 문법에 작성한 어트리뷰트로 html 어트리뷰트와 대응하는 모든 어트리뷰트를 작성할 수 있으며 동일하게 적용됩니다. 주의할 점으로는 "카멜 케이스"로 작성해주어야 합니다.

tabindex 어트리뷰트는 tabIndex로 작성하고, onclick은 onClick으로 작성해야 합니다. 예외적으로 data-* 어트리뷰트와 aria-* 어트리뷰트는 기존 케밥 케이스를 사용하여 작성합니다. 또한 class 어트리뷰트는 className으로 작성하고, for 어트리뷰트의 경우 htmlFor로 작성해주어야 합니다. 그리고 style 어트리뷰트의 값으로 CSS 스타일 문자열이 아닌 객체를 전달해주어야 하는 등 차이점이 존재합니다.

정리하자면 "html 태그 이름"으로 JSX 문법을 사용하는 경우에만 html 어트리뷰트와 대응하는 어트리뷰트를 카멜케이스로 작성할 수 있으며, 이는 생성되는 엘리먼트에 그대로 적용됩니다.

2. type: Component

반면 "컴포넌트"를 JSX 문법으로 사용한 경우 type 프로퍼티 값이 컴포넌트이며, 작성한 모든 어트리뷰트들은 props 프로퍼티에 바인딩된 객체의 프로퍼티로서 존재합니다. type 프로퍼티가 컴포넌트이므로 다시 호출되는데 호출할 때 리액트 엘리먼트의 props 프로퍼티에 바인딩된 객체를 인수로 전달하면서 호출하게 됩니다.

즉, 작성한 어트리뷰트들은 컴포넌트를 설정하는 역할로서 작성한 어트리뷰트는 컴포넌트 함수 내부에서 props 객체의 프로퍼티로 값을 참조하여 컴포넌트를 설정합니다.

컴포넌트 함수로 JSX 문법 사용시 컴포넌트 함수가 실행되며, 실행될 때 인수로 props 객체가 전달됩니다. 이 props 객체의 프로퍼티는 JSX 문법에 작성한 어트리뷰트로서 컴포넌트 함수 내부에서 이를 참조하여 컴포넌트를 설정합니다.
만약 html 어트리뷰트와 대응하는 어트리뷰트를 작성해도 이는 컴포넌트 함수에게 전달되는 값으로만 사용되며 html 어트리뷰트로서 동작하지 않습니다.

이처럼 "html 태그"로 JSX 문법을 사용했는지 혹은 "컴포넌트"로 JSX 문법을 사용했는지 따라 props 객체의 동작이 다르다는 것을 알 수 있습니다.

자바스크립트 표현식 삽입

JSX 문법을 사용할 때 또 하나의 장점으로 자바스크립트 표현식을 포함시킬 수 있습니다. 주의할 점은 "값으로 평가되는 자바스크립트 표현식"만을 포함시킬 수 있습니다.

const MyComponent = () => {
    const name = 'kim';
    return (
        <div>
            <span>Hello, {name}</span>
        </div>
    );
};

위 코드처럼 JSX 문법 안에서 자바스크립트 표현식을 작성하고싶을 때 {...} 안에 자바스크립트 표현식을 작성합니다. 이는 JSX 문법이 평가될 때 자바스크립트 표현식이 평가됩니다.

이때 문자로 표현할 수 없는 값(객체)의 경우 렌더링되지 않는다는 점을 주의해야 합니다. 즉, 문자열이어야 하는 것은 아니지만 "문자열로 변환이 가능한 값"이어야 합니다. 참고로 falsy 값 또한 렌더링되지 않지만 예외적으로 0은 렌더링이 됩니다.


const MyComponent = () => {
    const date = new Date();
    
    return (
        <div>
            // data는 Date객체이며 이는 문자열로 변환되지 않음
            <span>{date}</span>
        </div>
    );
}

위 코드에서 date 변수에는 Date 객체가 할당되어 있습니다. 이를 {...}으로 JSX 문법에 삽입한 결과 브라우저에 렌더링되지 않습니다.
즉, 문자열로 변환되지 않는 값이므로 렌더링되지 않습니다.

Date 객체의 경우 Date.prototype.toLocaleString()와 같은 메서드를 호출하여 문자열로 변환시켜주는 작업이 추가로 필요합니다.

<React.Fragment>

추후에 살펴볼 컴포넌트 함수는 리액트 엘리먼트를 반환해야하는데 이때 "하나의 루트 엘리먼트로 감싼 후 반환"할 수 있습니다.

이는 자바스크립트 측면에서도 당연하다고 볼 수 있습니다. 컴포넌트는 함수이며 반환값으로는 하나의 값만 반환 가능하므로 하나의 리액트 엘리먼트만을 반환할 수 있습니다.

루트 엘리먼트로 아무런 의미 없이 하나의 엘리먼트를 반환하기 위해 div 엘리먼트를 감싼다면 이는 <React.Fragment> </React.Fragment>로 감싸는 것이 좋습니다.

// react.js
const Fragment = (props) => {
    return props.children;
}

<React.Fragment>는 실제 돔에 반영될 때, 자신은 제거되고 자신의 자식 엘리먼트들만 돔에 반영해줍니다. 이는 DocumentFragment 노드 객체와 유사하게 동작합니다.

참고로 <React.Fragment> </React.Fragment><> </>와 동일하게 동작합니다.

profile
Frontend Dev

0개의 댓글