이제 리액트의 핵심 요소들을 하나씩 알아보자.
JSX(JavaScript XML)
많은 프론트엔드 개발자들이 착각하는 것은 JSX가 리액트의 전유물이라는 것이다. 물론 JSX가 리액트가 등장하며 소개된 구문인점은 맞지만 리액트와는 독립적으로 동작하는 문법이다.
JSX는 ECMAScript 자바스크립트 표준의 일부는 아니기 때문에 해당 코드를 바로 실행하면 에러가 발생한다.
따라서 JSX를 활용하려면 트랜스파일러를 거쳐서 의미있는 JS 코드로 변환하는 작업이 필요하다.
JSX의 주 목적중 하나는 HTML이나 XML을 JS내부에 표현하는 것이다. 그 외에도 JSX내부에 트리구조로 표현하고 싶은 다양한 것들을 작성하고 이를 JS가 이해할 수 있는 코드로 변경하는 것이 주된 목적이라고 할 수 있다.
JSXElement
JSX를 구성하는 가장 기본 요소로 HTML의 요소와 비슷한 역할을 하며 아래 형태중 하나를 따라야 한다.
JSXOpeningElement -> <JSXElement JSXAttributes(optional)>
JSXClosingElement -> <JSXElement />
JSXSelfClosingElement -> <JSXElement JSXAttributes(opional)/>
JSXFragment -> <></>
리액트의 요소명은 반드시 대문자로 시작해야한다.
JSXElement 표준에는 없는 내용이지만 리액트에서는 HTML태그명과 사용자가 만든 컴포넌트를 구분하기 위해 위와같은 방식을 채택하고 있다.
JSXElementName
JSXElement의 요소 이름으로 쓸 수 있는것을 의미한다.
JSXIdentifier : JSX내부에서 사용할 수 있는 식별자로 JS 식별자 규칙과 동일하다.
function Valid1(){
return <$></$>
}
function Valid2(){
return <_></_>
}
//불가능
function Invalid1(){
return <1></1>
}
JSXNamespacedName : ":" 을 통해 서로 다른 식별자를 이어주는것 또한 하나의 식별자로 취급되며 ":"은 한개의 식별자만 묶을 수 있다.
function valid(){
return <foo:bar></foo:bar>
}
function invalid(){
return <foo:bar:baz></foo:bar:baz>
}
JSXMemberExpression : "."을 통해 다른 식별자를 연결해준다. ":"과 다르게 3개이상의 식별자를 묶는것 또한 가능하다.
단 , ":"과 "."을 같이 사용하는것은 불가능
function valid(){
return <foo.bar></foo.bar>
}
function valid2(){
return <foo.bar.baz></foo.bar.baz>
}
function invalid(){
return <foo:bar.baz></foo:bar.baz>
}
JSXAttributes
JSXElement에 부여할 수 있는 속성으로 필수값이 아니다.
JSXSpreadAttributes : JS의 전개 연산자와 동일한 역할 수행
JSXAttribute : 속성을 나타내는 키와 값으로 짝을 이루어서 표현
JSXAttributeValue : 속성의 키에 할당할 수 있는 값
function Child({attribute}) {
return <div>{attribute}</div>
}
export default function App(){
return (
<div>
<Child attribute=<div>hello</div> />
</div>
)}
아래 방식처럼 사용하지 않아도 오류가 나지 않는다. 이는 문법 오류가 아닌 prettier의 규칙이었다고 한다.
<Child attribute={<div>hello</div>} />
JSXStrings
자바스크립트에서는 "\"문자를 특수문자 처리를 위해 사용된다. 하지만 HTML에서는 "\"문자가 어떤 제약이 존재하지 않는데 JSX는 HTML의 내용을 손쉽게 JSX를 가지고 올 수 있게 의도적으로 설계되었으므로 "\"를 이스케이프 문자열로 처리하고 있지 않다.
-> 페이스북팀이 이 정책을 수정할 수 있는 가능성을 언급했지만 큰 가능성은 없을 것 같다.
JSX 예제
아래 예제는 모두 유효한 JSX구조를 가지고 있다.
import { ReactNode } from 'react'
function A({
children,
required,
}: {
required?: boolean
children?: ReactNode
}) {
return (
<>
<input type="text" required={required} />
<div>{children}</div>
</>
)
}
function B({
text,
optionalChildren,
}: {
text?: string
optionalChildren?: ReactNode
}) {
return (
<>
<h1>{text}</h1>
{optionalChildren}
</>
)
}
// 하나의 요소로 구성된 가장 단순한 형태
const ComponentA = <A>안녕하세요.</A>
// 자식이 없이 SelfClosingTag로 닫혀있는 형태도 가능하다.
const ComponentB = <A />
// 옵션을 { } 와 전개 연산자로 넣을 수 있다.
const ComponentC = <A {...{ required: true }} />
// 옵션명만 넣어도 가능하다.
const ComponentD = <A required />
// 옵션명과 속성을 넣을 수 있다.
const ComponentE = <A required={false} />
const ComponentF = (
<A>
{/* 문자열은 쌍따옴표및 홀따옴표 모두 가능하다. */}
<B text="리액트" />
</A>
)
const ComponentG = (
<A>
{/* 옵선의 값으로 JSXElement를 넣는 것 또한 올바른 문법이다. */}
<B optionalChildren={<>안녕하세요.</>} />
</A>
)
const ComponentH = (
<A>
{/* 여러개의 자식도 포함할 수 있다. */}
안녕하세요
<B text="리액트" />
</A>
)
export function Component1() {
return (
<>
<div>{ComponentA}</div>
<div>{ComponentB}</div>
<div>{ComponentC}</div>
<div>{ComponentD}</div>
<div>{ComponentE}</div>
<div>{ComponentF}</div>
<div>{ComponentG}</div>
<div>{ComponentH}</div>
</>
)
}
이 외에도 리액트 내에서는 유효하지 않거나 사용되는 경우가 거의 없는 문법도 JSX문법 자체로는 유효한 경우들이 있다.
function ComponetA(){
return <A.B></A.B>
}
JSX의 변환
자바스크립트에서 JSX가 변환되는 방식을 알기 위해서는 @babel/plugin-transform-react-jsx 플러그인을 알아야한다.
https://babeljs.io/docs/babel-plugin-transform-react-jsx
아래와 같이 JSX코드를 자바스크립트 코드로 바꿔준다.
이렇게 JS코드를 변환하는데는 특성이 있다.
1. JSXElement를 첫 번째 인수로 선언하여 요소를 정의
2. 옵셔널인 JSXChildren,JSXAttributes,JSXStrings는 이후 인수로 넘겨주어 처리
이 특성을 활용하면 다른 JSXElement를 렌더링 해야할때 굳이 요소 전체를 감싸지 않는 경우를 처리할 수 있다.
import { createElement, PropsWithChildren } from "react";
function TextOrHeading({
isHeading,
children,
}: PropsWithChildren<{ isHeading: boolean }>) {
return isHeading ? (
<h1 className="text">{children}</h1>
) : (
<span className="text">{children}</span>
);
}
//단순 props여부에 따라 children요소만 달라지는 경우 코드 중복이 일어난다.
import { createElement } from "react";
function TextOrHeading({
isHeading,
children,
}: PropsWithChildren<{ isHeading: boolean }>) {
return createElement(
isHeading ? 'h1' : 'span',
{className : 'text'},
children
)
}
//불필요한 코드중복 X
JSX반환값이 React.createElement로 귀결된다는 사실을 안다면 위와같이 리팩터링하는 것이 가능하다.
'FrontEnd > Deep Dive' 카테고리의 다른 글
[React] Deep Dive 모던 리액트(6) 클래스형&함수형 컴포넌트 (0) | 2023.12.19 |
---|---|
[React] Deep Dive 모던 리액트(5) 가상 DOM & 리액트 파이버 (1) | 2023.12.18 |
[React] Deep Dive 모던 리액트(3)타입스크립트 (0) | 2023.12.15 |
[React] DeepDive 모던리액트(2) 동등 비교 (0) | 2023.12.14 |
[React] DeepDive 모던 리액트 (1) 리액트의 역사 (0) | 2023.12.13 |