라이브러리, 프레임워크•2026.06.23
if, switch, try, for와 너무 비슷해 보였습니다. 이로 인해 사람이 코드를 훑어보기가 더 어려워졌고, LLM이 안전하게 설명하거나 편집하기도 힘들었습니다. 커스텀 템플릿 시맨틱이 무엇을 생성하는지에 대한 명확한 표시 없이 실제 JavaScript인 것처럼 위장하고 있었기 때문입니다."" 또는 {''}를 통해 강제로 입력하게 하는 것이 번거롭고 직관적이지 않다는 의견도 분명히 들었습니다. JSX 텍스트는 JSX의 가장 유용한 편의 기능 중 하나이며, TSRX는 이미 잘 작동하는 것을 사람들이 다시 배우게 하는 대신 JSX와의 하위 호환성을 유지해야 합니다. 이는 구성(composition)에도 적용됩니다. 팀들은 단일 엘리먼트, 여러 엘리먼트가 포함된 프래그먼트(fragment), 또는 JSX 제어 흐름 값을 반환하기를 원하며, 각 케이스가 별개의 언어 기능처럼 느껴지지 않기를 바랍니다.const Greeting = (): JSX.Element => <div>"Hello there!"</div>;const EmptyState = ({ search }: EmptyStateProps): JSX.Element => <section> <h2>"No results for "{search}</h2> <p>"Try a different search term."</p></section>;const Greeting = (): JSX.Element => <div>Hello there!</div>;const EmptyState = ({ search }: EmptyStateProps): JSX.Element => <section> <h2>No results for {search}</h2> <p>Try a different search term.</p></section>;<div>Hello there!</div>는 JSX 사용자가 기대하는 의미를 갖습니다. {} 안에는 JSX나 React에서와 동일하게 JavaScript 표현식 구문을 작성하면 됩니다. 포매터를 호출하거나, 속성을 읽거나, 값을 선택하거나, 계산된 prop을 전달할 수 있습니다.const Toolbar = (): JSX.Element => <nav> // 키보드 사용자를 위해 주요 액션을 먼저 배치합니다. <button>Save</button> <button>Publish</button> /* * 보조 액션은 나중에 그룹화할 수 있습니다. */ <a href="/settings">Settings</a></nav>;{value}가 있습니다. TSRX는 문 버전인 @{...}를 추가합니다. 이것은 여전히 표현식이므로 컴포넌트 본문, 할당된 값, 반환 값 또는 다른 엘리먼트 내부의 자식 등 JSX가 나타날 수 있는 모든 곳에 나타날 수 있습니다. 유일한 차이점은 이제 JavaScript 문을 포함할 수 있고, 마지막에 배치되었을 때 JSX 템플릿을 산출(yield)할 수 있다는 점입니다.const App = (): JSX.Element => @{ const message = formatMessage(user); <p>{message}</p>};const summary: JSX.Element = @{ const count = items.length; const label = count === 1 ? 'item' : 'items'; <p>{count} {label}</p>};return <aside>{summary}</aside>;@if와 같은 JSX 제어 흐름일 수 있습니다. 여러 엘리먼트가 필요한 경우 프래그먼트가 필요하며, 일반 JSX 텍스트도 마찬가지입니다. 해당 출력이 나타나면 컨테이너는 종료됩니다. 그 뒤에는 어떤 JavaScript 로직도 올 수 없습니다.@는 중요한 경계입니다. 일반적인 {} 함수 본문에서 설정 문들을 작성한 뒤 바로 JSX 엘리먼트를 작성하면, 컴파일러는 본문이 @{...}가 되도록 누락된 @를 추가하라고 알려줄 것입니다.const App = (): JSX.Element => @{ const title = getTitle(); // 텍스트와 여러 자식은 하나의 프래그먼트로 감쌉니다. <> Plain text goes in a fragment. <h2>{title}</h2> </>}; type ProductCardProps = { product: Product; }; const ProductCard = ({ product }: ProductCardProps): JSX.Element => @{ const price = formatCurrency(product.price); <article> <h2>{product.name}</h2> <p>{price}</p> </article>};export function UserBadge({ user }: UserBadgeProps): JSX.Element @{ if (!user) { return <span class="muted">Signed out</span>; } const initials = user.name.slice(0, 2).toUpperCase(); <button title={user.name}>{initials}</button>}@{...}가 컴포넌트 함수의 본문일 때 최상위 수준의 조기 반환(early return)이 허용됩니다. 이는 일반적인 컴포넌트 가드 반환처럼 동작합니다. 필요할 때 일찍 종료하고, 그렇지 않으면 하나의 최종 템플릿 출력으로 문 컨테이너를 마칩니다.export function AccountPanel({ user }: AccountPanelProps): JSX.Element @{ if (!user) { return <a href="/login">Sign in</a>; } const displayName = user.name.trim(); <section> <h2>{displayName}</h2> <p>{user.plan}</p> </section>}const DetailsPanel = ({ open }: DetailsPanelProps): JSX.Element => @{ const [selected, setSelected] = useState('summary'); (open) { <DetailsEditor /> } { <button onClick={() => setSelected('details')}>Showing {selected}</button> }};function DetailsEditor(): JSX.Element { const [draft, setDraft] = useState(''); return <Editor value={draft} onInput={setDraft} />;}@if, @for, @switch, @try를 사용하세요. 각 분기나 블록은 @{...}와 동일한 구조적 경계를 따릅니다. 즉, 선택적인 TypeScript 설정이 먼저 오고, 그 다음 단일 렌더링 템플릿 출력이 옵니다.@는 실질적인 역할을 합니다. 그냥 for는 JavaScript 제어 흐름처럼 보이기 때문에, 사람과 AI 에이전트 모두 자연스럽게 break, continue, fallthrough 또는 분기 로컬 반환과 같은 JavaScript 규칙을 떠올리게 됩니다. 템플릿 제어 흐름은 다릅니다. 무엇을 렌더링할지 선택하는 JSX입니다. 접두사(prefix)는 누군가가 문맥에서 이를 추론하기 전에 그 경계를 가시화해 줍니다.const ActivityPanel = ({ result }: ActivityPanelProps): JSX.Element => @{ { const activity = result.value; const latest = activity[0]; (latest) { <ActivityCard activity={latest} /> } { <p>No activity yet</p> } } { <p>Loading activity...</p> } (error) { <p>{getErrorMessage(error)}</p> }};continue를 사용하는 것이 아주 자연스러워 보입니다.const ProductList = ({ products }: ProductListProps): JSX.Element => @{ <ul> for (const product of products) { if (!product.available) { continue; } <li>{product.name}</li> } </ul>};continue나 return을 몰래 넣는 대신 루프의 @empty 분기를 사용하세요.const ProductList = ({ products }: ProductListProps): JSX.Element => @{ const visibleProducts = products.filter((product) => product.available); <ul> @for (const product of visibleProducts; key product.id) { const price = formatCurrency(product.price); <li> <span>{product.name}</span> <strong>{price}</strong> </li> } @empty { <li>No products available</li> } </ul>}; 1 const Profile = ({ user }: ProfileProps): JSX.Element => @{ 2 (user) { 3 const displayName = user.name.trim(); 4 5 <> 6 <h2>{displayName}</h2> 7 <p>{user.bio}</p> 8 </> 9 } {10 <a href="/login">Sign in</a>11 }12 };switch 분기를 저장하기 위해 헬퍼 함수로 감쌀 필요가 없습니다.const StatusBadge = ({ status }: StatusBadgeProps): JSX.Element => { const badge: JSX.Element = (status) { 'active': { <strong>Active</strong> } 'idle': { <span>Idle</span> } : { <span>Offline</span> } }; return <header>{badge}</header>;};JSX.Element로 주석을 달 수 있고, 컴포넌트 화살표 함수는 JSX.Element를 반환한다고 선언할 수 있습니다. 타입은 특별한 컴포넌트 선언 형식에 의존하는 대신, 당신이 생성하고 있는 값 그 자체를 설명합니다.@로 특별하다고 표시되며, 일반적인 JavaScript는 일반적인 JavaScript로 남을 수 있게 되었습니다.아직 댓글이 없습니다.
첫 번째 댓글을 작성해보세요!

Playwright Fixtures API의 마법 파헤치기
Inkyu Oh • Front-End

React Router v8
Inkyu Oh • 라이브러리, 프레임워크