一.기본 용법
TypeScript 도 JSX 를 서포트하며, Babel 과 마찬가지로 JSX 를 JavaScript 로 컴파일할 수 있을 뿐만 아니라, 타입 체크도 제공합니다
TypeScript 로 JSX 를 쓰려면 2 스텝만 필요합니다:
-
소스 파일에
.tsx확장자 사용 -
--jsx옵션을 유효화
게다가, TypeScript 는 3 종류의 JSX 처리 모드를 제공하며, 각각 다른 코드 생성 룰에 대응합니다:
| Mode | Input | Output | Output File Extension |
|---|---|---|---|
| preserve | <div /> | <div /> | .jsx |
| react | <div /> | React.createElement("div") | .js |
| react-native | <div /> | <div /> | .js |
즉:
-
preserve:
.jsx파일을 생성하지만, JSX 구문을 변환하지 않고 유지하며, 후속 빌드環節 (Babel 등) 에 처리를 위임합니다 -
react:
.js파일을 생성하며, JSX 구문을React.createElement로 변환합니다 -
react-native:
.js파일을 생성하지만, JSX 구문을 변환하지 않고 유지합니다
이들 모드는 --jsx 옵션으로 지정하며, 디폴트는 "preserve" 로, 코드 생성에만 영향주고, 타입 체크에는 영향주지 않습니다 (예를 들어 --jsx "preserve" 는 변환하지 않는 것을 요구하지만, 그래도 JSX 에 대해 타입 체크를 수행합니다)
구체적 사용에서는, JSX 구문은 완전히 일치하며, 유일하게 주의가 필요한 것은 타입 어설션입니다
타입 어설션
JSX 에서는
as type만 사용할 수 있습니다 (산괄호 구문이 JSX 구문과 충돌하기 때문)
let someValue: any = "this is a string";
// <type>
let strLength: number = (<string>someValue).length;
.tsx 파일에서는 에러가 발생합니다:
JSX element 'string' has no corresponding closing tag.
'</' expected.
구문의 충돌로 인해, <string>someValue 중의 타입 어설션 부분 (<string>) 이 JSX 요소로서 취급됩니다. 따라서 .tsx 에서는 as type 형식의 타입 어설션만 사용할 수 있습니다:
// as type
let strLength: number = (someValue as string).length;
P.S. TypeScript 타입 어설션의 더 많은 정보는, [三。타입 어설션](/articles/기본 타입-typescript 노트 2/#articleHeader4) 참조
二.요소 타입
JSX 식 <expr /> 에 대해, expr 는 환경 중의 내재 요소 (intrinsic element, 즉 내장 컴포넌트, 예를 들어 DOM 환경 중의 div 또는 span) 또는 값 기반 요소 (value-based element), 즉 커스텀 컴포넌트 중 하나입니다. 2 개의 요소의 차이는:
-
생성되는 타겟 코드가 다름
React 에서는, 내재 요소는 문자열을 생성합니다 (예를 들어
React.createElement("div")), 커스텀 컴포넌트는 생성하지 않습니다 (예를 들어React.createElement(MyComponent)) -
요소 속성 (즉 Props) 타입의 검색 방법이 다름
내재 요소의 속성은 알려져 있지만, 커스텀 컴포넌트는 자신의 속성 세트를 지정하고 싶을 수 있습니다
형식상으로는, 커스텀 컴포넌트는 반드시 대문자로 시작해야 합니다, 이것으로 2 개의 JSX 요소를 구분합니다
P.S. 실제로는, 내재 요소/값 기반 요소와 내장 컴포넌트/커스텀 컴포넌트는 같은 것을 지칭하며, TypeScript 컴파일러에게, 내장 컴포넌트의 타입은 알려져 있어, 이를 내재 요소라고 부르고, 커스텀 컴포넌트의 타입은 컴포넌트 선언 (값) 과 관련되어, 이를 값 기반 요소라고 부릅니다
내재 요소
내재 요소의 타입은 JSX.IntrinsicElements 인터페이스에서 검색합니다. 이 인터페이스를 선언하지 않은 경우, 모든 내재 요소는 타입 체크되지 않습니다. 선언한 경우, JSX.IntrinsicElements 상에서 대응하는 속성을 검색하여, 타입 체크의 근거로 합니다:
declare namespace JSX {
interface IntrinsicElements {
foo: any
}
}
// 올바른
<foo />;
// 에러 Property 'bar' does not exist on type 'JSX.IntrinsicElements'.
<bar />;
물론, 인덱스 시그니처 와 조합하여, 미지의 내장 컴포넌트의 사용을 허가할 수도 있습니다:
declare namespace JSX {
interface IntrinsicElements {
foo: any;
[elemName: string]: any;
}
}
// 올바른
<bar />;
이점은, 장래 새로운 내장 컴포넌트의 서포트를 확장한 후, 바로 타입 선언을 수정할 필요가 없는 것으로, 대가는 화이트리스트의 엄격한 검증을 잃는 것입니다
값 기반 요소
값 기반 요소는 직접 스코프 내로부터 대응하는 식별자를 찾습니다. 예를 들어:
import MyComponent from "./myComponent";
// 올바른
<MyComponent />
// 에러 Cannot find name 'SomeOtherComponent'.
<SomeOtherComponent />
값 기반 요소는 2 종류 있습니다:
-
스테이트리스 함수형 컴포넌트 (Stateless Functional Component, 소위 SFC)
-
클래스 컴포넌트 (Class Component)
이들 2 つ는 JSX 식의 형식상으로는 구분할 수 없으므로, 먼저 SFC 로서 함수 오버로드에 따라 해석을 시도하고, 해석에 실패한 경우에 클래스 컴포넌트로서 처리하며, 더욱 실패하면 에러가 됩니다
스테이트리스 함수형 컴포넌트
형식상으로는 일반적인 함수로, 첫 번째 파라미터는 props 오브젝트이며, 반환 타입은 JSX.Element(또는 그 [서브타입](/articles/深入 타입 시스템-typescript 노트 8/#articleHeader4)) 일 필요가 있습니다. 예를 들어:
function Welcome(props: { name: string }) {
return <h1>Hello, {props.name}</h1>;
}
동様に, [함수 오버로드](/articles/함수-typescript 노트 5/#articleHeader9) 도 적용됩니다:
function Welcome(props: { content: JSX.Element[] | JSX.Element });
function Welcome(props: { name: string });
function Welcome(props: any) {
<h1>Hello, {props.name}</h1>;
}
<div>
<Welcome name="Lily" />
<Welcome content={<span>Hello</span>} />
</div>
P.S. JSX.Element 타입 선언은 [@types/react](https://www.npmjs.com/package/ @types/react) 에서 왔습니다
클래스 컴포넌트
클래스 컴포넌트는 React.Component 를 상속하며, JavaScript 판과 거의 차이가 없습니다:
class WelcomeClass extends React.Component {
render() {
return <h1>Hello, there.</h1>;
}
}
<WelcomeClass />
Class 의 [이중 타입 의미](/articles/클래스-typescript 노트 4/#articleHeader8) 와 유사하게, JSX 식 <Expr /> 에 대해, 클래스 컴포넌트의 타입은 2 부분으로 나뉩니다:
-
요소 클래스 타입 (element class type):
Expr의 타입, 즉typeof WelcomeClass -
요소 인스턴스 타입 (element instance type):
Expr클래스 인스턴스의 타입, 즉{ render: () => JSX.Element }
예를 들어:
// 요소 클래스 타입
let elementClassType: typeof WelcomeClass;
new elementClassType();
// 요소 인스턴스 타입
let elementInstanceType: WelcomeClass;
elementInstanceType.render();
요소 인스턴스 타입은 JSX.ElementClass 의 서브타입일 필요가 있으며, 디폴트의 JSX.ElementClass 타입은 {} 로, React 에서는 render 메서드를 가질 필요가 있습니다:
namespace JSX {
interface ElementClass extends React.Component<any> {
render(): React.ReactNode;
}
}
(DefinitelyTyped/types/react/index.d.ts 에서 발췌)
否则 에러가 됩니다:
class NotAValidComponent {}
function NotAValidFactoryFunction() {
return {};
}
<div>
{/* 에러 JSX element type 'NotAValidComponent' is not a constructor function for JSX elements. */}
<NotAValidComponent />
{/* 에러 JSX element type '{}' is not a constructor function for JSX elements. */}
<NotAValidFactoryFunction />
</div>
三.속성 타입
속성 체크는 먼저 요소 속성 타입 (element attributes type) 을 확정할 필요가 있으며, 내재 요소와 값 기반 요소는 속성 타입에 조금 차이가 있습니다:
-
내재 요소의 속성 타입:
JSX.IntrinsicElements상 대응하는 속성의 타입 -
값 기반 요소 속성 타입: 요소 인스턴스 타입 상 특정 속성 타입 상 대응하는 속성의 타입, 이 특정 속성은
JSX.ElementAttributesProperty로 지정
P.S. JSX.ElementAttributesProperty 를 선언하지 않은 경우, 컴포넌트 클래스 생성자 또는 SFC 첫 번째 파라미터의 타입을 취득합니다
구체적으로는, 내재 요소 속성은 a 의 href 를 예로:
namespace JSX {
interface IntrinsicElements {
// 각 내재 요소及其び그 속성 타입을 선언
a: {
download?: any;
href?: string;
hrefLang?: string;
media?: string;
rel?: string;
target?: string;
type?: string;
referrerPolicy?: string;
}
}
}
// 요소 속성 타입은 { href?: string }
<a href="">링크</a>
값 기반 요소 속성은 예를 들어:
namespace JSX {
// 특정 속성명을 props 로 지정
interface ElementAttributesProperty { props: {}; }
}
class MyComponent extends React.Component {
// 속성 타입을 선언
props: {
foo?: string;
}
}
// 요소 속성 타입은 { foo?: string }
<MyComponent foo="bar" />
옵션 속성, 전개 연산자 등도 동일하게 적용되며, 예를 들어:
class MyComponent extends React.Component {
// 속성 타입을 선언
props: {
requiredProp: string;
optionalProp?: string;
}
}
const props = { optionalProp: 'optional' };
// 올바른
<MyComponent { ...props } requiredProp="required" />
P.S. 게다가, JSX 프레임워크는 JSX.IntrinsicAttributes 를 통해 프레임워크에 필요한 추가 속성을 지정할 수 있습니다. 예를 들어 React 내의 key. 상세는 Attribute type checking 참조
P.S. 特殊的에는, 속성 체크는 속성명이 합법한 JavaScript 식별자의 속성에 대해서만 수행되며, data-* 등은 체크되지 않습니다
자식 컴포넌트 타입 체크
자식 컴포넌트의 타입은 요소 속성 타입 상의 children 속성에서 오며, ElementAttributesProperty 로 props 를 지정하는 것과 마찬가지로, 여기서는 JSX.ElementChildrenAttribute 를 사용하여 children 을 지정합니다:
namespace JSX {
// 특정 속성명을 children 으로 지정
interface ElementChildrenAttribute { children: {}; }
}
const Wrapper = (props) => (
<div>
{props.children}
</div>
);
<Wrapper>
<div>Hello World</div>
{"This is just a JS expression..." + 1000}
</Wrapper>
children 에 타입을 지정하는 방법은 일반적인 속성과 유사합니다:
interface PropsType {
children: JSX.Element
name: string
}
class Component extends React.Component<PropsType, {}> {
render() {
return (
<h2>
{this.props.children}
</h2>
)
}
}
<Component name="hello">
<h1>Hello World</h1>
</Component>
자식 컴포넌트 타입이 일치하지 않으면 에러가 됩니다:
// 에러 Type '{ children: Element[]; name: string; }' is not assignable to type 'Readonly<PropsType>'.
<Component name="hello">
<h1>Hello World</h1>
<h1>Hi</h1>
</Component>
四.결과 타입
디폴트에서는, JSX 식의 결과 타입은 any 입니다:
// a 의 타입은 any
let a = <a href="" />;
a = {};
JSX.Element 를 통해 지정할 수 있습니다. 예를 들어 React 에서는:
let a = <a href="" />;
// 에러 Type '{}' is missing the following properties from type 'Element': type, props, key.
a = {};
대응하는 타입 선언은 유사합니다:
namespace JSX {
interface Element<T, P> {
type: T;
props: P;
key: string | number | null;
}
}
P.S. React 내의 구체적인 JSX 요소 타입 선언은 DefinitelyTyped/types/react/index.d.ts 참조
五.묻힌 식
JSX 는 태그 내에서 꽃괄호 구문 ({ }) 을 통해 식을 삽입하는 것을 허가합니다:
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
(Embedding Expressions in JSX 에서 발췌)
TypeScript 도 서포트하며, 묻힌 식에 대해 타입 체크를 수행할 수 있습니다:
const a = <div>
{/* 에러 The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. */}
{["foo", "bar"].map(i => <span>{i / 2}</span>)}
</div>
六.React 와의 결합
[React 타입 정의](https://www.npmjs.com/package/ @types/react) 를 도입한 후, Props 의 타입을 간단히 기술할 수 있습니다:
interface WelcomeProps {
name: string;
}
// Props 의 타입을 첫 번째 타입 파라미터로서 건넴
class WelcomeClass extends React.Component<WelcomeProps, {}> {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 에러 Property 'name' is missing in type '{}' but required in type 'Readonly<WelcomeProps>'.
let errorCase = <WelcomeClass />;
let correctCase = <WelcomeClass name="Lily" />;
P.S. 타입 파라미터 및 제네릭의 더 많은 정보는, [二。타입 변수](/articles/제네릭-typescript 노트 6/#articleHeader3) 참조
팩토리 함수
React 모드 (--jsx react) 에서는, 구체적으로 사용하는 JSX 요소 팩토리 메서드를 구성할 수 있으며, 2 가지 방법이 있습니다:
-
--jsxFactory옵션: 프로젝트 레벨 구성 -
인라인
@jsx코멘트 지시: 파일 레벨 구성
디폴트는 --jsxFactory "React.createElement" 로, JSX 태그를 팩토리 메서드 호출로 변환합니다:
const div = <div />;
// 컴파일 결과
var div = React.createElement("div", null);
Preact 내에서는 대응하는 JSX 요소 팩토리 메서드는 h 입니다:
/* @jsx preact.h */
import * as preact from "preact";
<div />;
// 또는
/* @jsx h */
import { h } from "preact";
<div />;
P.S. 주의, @jsx 코멘트 지시는 반드시 파일의 선두 행에 나타날 필요가 있으며,其余의 위치에서는 무효입니다
컴파일 결과는 각각:
/* @jsx preact.h */
var preact = require("preact");
preact.h("div", null);
// 또는
/* @jsx h */
var preact_1 = require("preact");
preact_1.h("div", null);
P.S. 게다가, 팩토리 메서드 구성은 JSX 네임스페이스의 검색에도 영향줍니다. 예를 들어 디폴트 --jsxFactory React.createElement 의 경우, 우선적으로 React.JSX 를 검색하고, 다음에 글로벌 JSX 네임스페이스를 봅니다. --jsxFactory h 를 지정하면, 우선적으로 h.JSX 를 검색합니다
七.정리
TypeScript 중 JSX 의 타입 서포트는 요소 타입, 속성 타입, 결과 타입의 3 부분으로 나뉘며, 아래 그림과 같습니다:

아직 댓글이 없습니다