Why Judging APIs by Syntax is Misleading You

jjenzz.com 블로그 포스트 번역


제목이 조금 자극적이긴 하지만, 그 뒤에 숨은 아이디어는 그 어느 때보다 관련성이 높습니다. 우리는 종종 기존 API와 비슷한 목표를 가진 새로운 API를 보게 되고, 이는 익숙한 반응으로 이어집니다:
"아, 그거 알아. 나는 이미 그걸 할 수 있어. 왜 이 새로운 걸 써야 하지?"
이런 반응은 자연스럽습니다. 하지만 이런 API들은 내부적으로는 매우 다르게 동작합니다. 중요한 것은 API가 표면적으로 어떻게 보이는지뿐만 아니라, 그것이 속한 추상화 계층입니다. 바로 그곳에서 깨달음의 순간이 찾아옵니다.
계층이 명확해지면, 외형상 동일해 보이는 두 가지가 완전히 다른 기능을 제공할 수 있는 이유를 이해하기가 훨씬 쉬워집니다.
상황을 설정하기 위해 스타일링 라이브러리를 사용한 예시를 들겠습니다. 이것은 스타일링에 관한 글이 아니라, 비슷한 API가 같은 일을 한다는 가정에 대한 글입니다.

스타일링 라이브러리를 통한 계층 이해하기

이 세 가지 도구는 유용한 비교를 제공합니다. 왜냐하면 이들의 API는 비슷해 보이면서도 매우 다른 추상화 계층에 존재하기 때문입니다.

Restyle

Restyle CSS-in-JS 라이브러리를 사용하는 것은 다음과 같습니다:
import { styled } from "restyle";

const Button = styled("button", {
color: "white",
backgroundColor: "rebeccapurple",
padding: "0.5rem 1rem",
borderRadius: "999px"
})
우리는 스타일을 객체로 설명하고 React 컴포넌트를 받습니다. 내부적으로 Restyle은 React의 렌더링 모델과 새로운 스타일링 기능과 직접 통합되어 있으며, 이는 Restyle을 높은 수준의 계층에 배치합니다.
이 계층은 개발자 경험 측면에서 가장 강력한 편의성을 제공합니다. React 자체와 긴밀하게 결합되어 있기 때문입니다. 하지만 그 편의성에는 제약이 따릅니다:
  • React 외부에서 실행될 수 없습니다 (React v19 이하에서도 불가능)
  • 크로스 프레임워크 디자인 시스템에는 적합하지 않습니다
비교해보면, Vanilla Extract는 조금 더 낮은 계층에 위치합니다.

Vanilla Extract

Vanilla Extract 사용법은 다음과 같습니다:
import { style } from "@vanilla-extract/css";

export const button = style({
color: "white",
backgroundColor: "rebeccapurple",
padding: "0.5rem 1rem",
borderRadius: "999px"
})
반환된 클래스명을 원하는 컴포넌트나 요소에 수동으로 첨부합니다.
이것은 Restyle과 비슷해 보입니다: 객체 기반 스타일, 익숙한 형태이지만 계층이 다릅니다. 이것은:
  • React를 가정하지 않습니다
  • 번들러를 가정합니다
  • 정적 추출에 의존합니다
  • 빌드 파이프라인의 어딘가에서 실행되어야 합니다
Vanilla Extract로 작성된 디자인 시스템은 누구나 사용할 수 있습니다 (VE나 React를 사용할 필요가 없음). 하지만 작성할 때는 여전히 번들러 기반 추출에 의존합니다.
추상화 계층을 더 내려가려면, 번들러 가정을 완전히 제거할 수 있습니다. 바로 Tokenami와 같은 라이브러리가 위치한 곳입니다.

Tokenami

Tokenami 사용법은 다음과 같습니다:
import { css } from "@tokenami/css";

export const button = css({
"--color": "white",
"--background-color": "rebeccapurple",
"--padding": "0.5rem 1rem",
"--border-radius": "999px"
})
다시 말해, 반환된 값은 수동으로 첨부하는 것입니다. 표면적인 형태는 익숙해 보이지만, 계층이 다릅니다.
Tokenami는 번들러 아래에서 실행됩니다. 소스 파일을 스캔하고 일반 CSS를 출력하는 CLI 도구로, Tailwind와 유사합니다. Tokenami는 다음이 필요합니다:
  • Node
  • TypeScript/JavaScript
그리고 거의 다른 것은 없습니다. 이 단순함은 번들러 기반 도구보다 더 많은 환경 유연성을 제공합니다. 새로운 번들러가 나올 때마다 새로운 플러그인이 필요하지 않기 때문입니다.
낮은 계층의 도구들은 종종 더 많은 자유도를 허용합니다. 왜냐하면 더 적은 가정을 부과하기 때문입니다. 하지만 모든 계층에는 고유한 트레이드오프가 있습니다.
Vanilla Extract와 Tokenami는 스타일을 컴포넌트에 수동으로 첨부해야 하며, 정적 분석 접근 방식은 동적 스타일링을 다양한 정도로 더 어렵게 만듭니다. 따라서 편의성이 낮은 경향이 있습니다. Restyle과 같은 높은 수준의 도구는 더 많은 편의성을 제공하지만 이식성을 희생합니다. 이런 종류의 제약은 모두 다양한 API를 비교 검토하는 과정의 일부입니다.

이런 계층 차이가 중요한 이유

세 가지 코드 스니펫은 모두 같은 기본 형태를 공유합니다.
  1. JS 객체에서 스타일 설명
  1. 어떤 형태의 클래스명이나 컴포넌트 API 획득
  1. 그 결과를 요소에 첨부
문법이나 공유된 목표 (앱 스타일링)만으로 판단하면, 거의 상호 교환 가능해 보입니다. 하지만 계층이 명확해지면, 전혀 상호 교환 가능하지 않습니다.
계층 차이는 동일해 보이는 두 API가 완전히 다르게 동작할 수 있는 이유를 설명합니다. 계층을 이해하면 우리는 다음을 예상할 수 있습니다:
  • 이식성
  • 환경 요구사항
  • 번들러 가정
  • 런타임 vs 빌드 타임 제약
  • 동적 동작이 가능한 곳
  • 설계에서 비롯된 제약 vs 계층에서 비롯된 제약
계층을 보게 되면, 우리는 도구를 어떻게 보이는지로 비교하는 것을 멈춥니다. 우리는 그것들이 근본적으로 무엇인지로 비교합니다.
이것은 우리가 비슷한 표면 수준의 이유로 반발했던 많은 API로 우리를 이끕니다.

"use client" 지시문

React가 "use client" 지시문을 도입했을 때, 우리 많은 사람들이 같은 방식으로 반응했습니다:
"나는 이미 Remix나 Next에서 클라이언트/SSR 기능을 가지고 있어. 이게 뭘 해결하는 거야?"
혼동의 대부분은 계층이 아닌 문법에 반응하는 것에서 비롯됩니다.
지시문 이전에, 서버 또는 클라이언트 간의 결정은 프레임워크 내부에 있었습니다. 각 프레임워크는 이를 다르게 구현했으며, 종종 관례나 번들러 로직을 통해 구현했습니다. 이들은 높은 수준의 솔루션이었습니다.
지시문은 그 결정을 React 자체로 가져옵니다. 프레임워크 기능이 아닌 원시 요소가 됩니다.
낮은 수준의 원시 요소는 종종 불편해 보입니다. 컴포넌트를 분할하거나, 경계에 대해 더 명시적이어야 하거나, 타입 안전성 같은 것들 주변의 적용 범위를 줄여야 할 수 있습니다 (예: Tailwind).
높은 수준의 추상화는 보통 이런 것들을 부드럽게 처리하므로, 낮은 계층으로 내려가는 것이 불편하게 느껴집니다. 하지만 이 낮은 수준의 위치가 원시 요소에 힘을 주는 것입니다: 더 적은 가정을 하므로, 프레임워크가 그 위에 일관된 (또는 심지어 커스텀) 동작을 구축할 수 있게 합니다.
지시문은 이론상 더 낮은 계층에도 위치할 수 있습니다. 그 형태는 "use strict"와 유사합니다. JavaScript 엔진이 이해하는 종류의 지시문입니다.
물론 "use client"는 그 중 하나가 아니지만, 같은 낮은 수준의 형태를 공유한다는 사실은 흥미롭습니다. 이것은 이론상 React 특정 API보다 스택을 더 멀리 내려갈 수 있는 종류의 원시 요소입니다.

문법을 넘어 보기

새로운 API가 나타나면, 우리가 인식하는 가장 가까운 것과 그 형태를 비교하는 것이 일반적입니다. 익숙해 보이면, 우리는 이미 알고 있는 것처럼 동작한다고 가정합니다.
하지만 새로운 API에 대한 많은 의견 불일치나 혼동은 기본 계층이 명확해지면 덜 신비로워집니다. 가정이 이해가 됩니다. 제약이 이해가 됩니다. 트레이드오프가 이해가 됩니다. 때로는 마찰도 이해가 됩니다.
계층을 이해하면, 우리는 표면 수준의 문법으로 도구를 평가하는 것을 멈추고 그들의 가정과 기능으로 평가하기 시작합니다.
  • 우리는 이식성과 편의성이 같은 계층에서 거의 나오지 않는 이유를 봅니다.
  • 우리는 같아 보이는 두 API가 같은 역할을 하지 않을 수 있는 이유를 봅니다.
  • 우리는 높은 수준의 도구가 마법처럼 느껴지고 낮은 수준의 도구가 불편하게 느껴지는 이유를 봅니다.
  • 우리는 React의 지시문이 불편하게 느껴질 수 있으면서도 더 깊은 변화를 나타내는 이유를 봅니다.

결론

두 API가 같아 보이면, 표면 문법은 우리에게 많은 것을 알려주지 않습니다. 흥미로운 차이는 보통 그들이 제약하거나 허용하는 것에서 나옵니다.
  • API가 제거하는 자유도가 많을수록, 보통 더 높은 수준입니다.
  • 남겨두는 자유도가 많을수록, 보통 더 낮은 계층에 위치합니다.
계층을 인정하면, 우리는 마침내 표면 뒤의 도구를 이해할 수 있습니다. 관점의 작은 변화는 종종 더 깊은 이야기를 드러냅니다. 문법만으로는 보이지 않는 이야기 말입니다.

0
21

댓글

?

아직 댓글이 없습니다.

첫 번째 댓글을 작성해보세요!

마루쉐님의 다른 글

더보기

유사한 내용의 글