2026년 기준으로 TypeScript 사용자에게 중요한 Flow의 변화와, TypeScript에는 없지만 Flow에는 있는 기능, 그리고 Flow가 더 안전하게 잡아내는 런타임 오류 사례를 비교해 설명합니다.
전체 크기로 이미지를 보려면 엔터를 누르거나 클릭하세요

4분 읽기
2026년 6월 4일
2026년의 Flow는 중요하게 볼 만한 세 가지 변화가 있습니다:
match 표현식과 일급 component/hook/renders입니다.이게 Flow일까요, 아니면 TypeScript일까요? 구분할 수 없습니다:
type User = {
readonly name: string,
readonly age: number,
readonly metadata: unknown,
}; function get<K extends keyof User>(user: User, key: K): User[K] {
return user[key];
}
declare const user: User;
const age: number = get(user, 'age');
TypeScript를 안다면 이 모든 것이 익숙해 보일 것입니다. 위 예제에는 keyof, readonly 프로퍼티, unknown, 인덱스 접근(T[K]), extends 경계가 쓰였고, 이 밖에도 조건부 타입, 매핑된 타입, 타입 가드, as const가 있습니다.
이 글의 나머지 부분에서는 Flow가 TypeScript를 넘어서는 지점을 살펴봅니다.
Flow에는 TypeScript에 없는 기능이 있습니다:
match 표현식과 문Flow에는 match가 있습니다. 패턴 매칭을 표현식으로 사용할 수 있습니다. 이는 완전성 검사를 받으며, 매칭하는 동시에 구조적 패턴으로 구조 분해를 수행합니다. 예를 들어 type: 'remove' 같은 케이스를 빼먹으면 match가 이를 표시하고 무엇을 추가해야 하는지 알려줍니다.
type Action =
| {type: 'add', text: string}
| {type: 'toggle', id: string}
| {type: 'remove', id: string}; declare const action: Action;
const description = match (action) {
{type: 'add', const text} => Add: ${text},
{type: 'toggle', const id} => Toggle ${id},
};
match에는 문 형태도 있습니다. 폴스루가 없는 더 안전한 switch이며, match 표현식의 모든 기능을 갖추고 있습니다.
component, hook, renderscomponent문법: React 컴포넌트를 일급 언어 구성 요소로 다룹니다. 이름 있는 매개변수로 props를 직접 선언할 수 있고, 타입 검사기가 강제할 수 있는 React 전용 정확성도 제공합니다.renders타입: 컴포넌트를 위한 조합 계약을 선언합니다. 디자인 시스템과 라이브러리는 어떤 슬롯이 무엇을 받을 수 있고 어떤 컴포넌트가 무엇을 생성하는지 지정할 수 있으며, 타입 검사기는 래퍼 컴포넌트 전반에 걸쳐 이 계약을 강제합니다.hook문법: hook을 일반 함수와 구별되는 일급 종류로 선언합니다. Flow는 별도의 ESLint 플러그인 없이 타입 검사기에서 Rules of React를 강제합니다. 조건부 호출, hook과 함수의 혼동, 렌더링 중 hook 반환값의 안전하지 않은 변경을 잡아냅니다.component Header(text: string, color: string) {
return <div style={{color}}>{text}</div>;
}
component MainHeader(text: string) renders Header {
return <Header text={text} color="red" />;
} component Layout(header: renders Header) {
return <div>
{header}
<section>Content</section> </div>;}
const ok = <Layout header={<MainHeader text="Flow" />} />;
const bad = <Layout header={<footer />} />;
바로 이 지점에서 두 도구는 여전히 갈라집니다. 아래의 각 예제는 strict를 활성화한 TypeScript 6.0.3에서는 타입 검사를 통과하지만, 이후 런타임에서 충돌합니다.
this 바인딩을 잃습니다class Counter {
count: number = 0;
increment(): number {
return ++this.count;
}
}
const counter = new Counter();
const tick = counter.increment;
tick();
counter.increment를 인스턴스에서 떼어내면 메서드가 counter와 분리되므로, 나중의 tick()은 this가 undefined인 상태로 실행되고 ++this.count에서 예외가 발생합니다. TypeScript는 이 추출된 메서드를 일반 함수로 타입 지정하고 호출을 그대로 통과시킵니다. Flow는 this를 잃는 시점에서 바인딩되지 않은 메서드 추출을 거부합니다.
이 작성자의 업데이트를 받으려면 Medium에 무료로 가입하세요.
더 빠른 로그인을 위해 나를 기억하기
2. TypeScript에서는 간접 할당을 통해 여분의 프로퍼티가 슬며시 들어올 수 있습니다
type Prices = {apple: number, banana: number};
const items = {apple: 1.5, banana: 0.5, sample: "free"};
const prices: Prices = items;
Object.values(prices).map(
price => price.toFixed(2),
);
TypeScript의 객체 타입은 타입 수준에서 여분의 프로퍼티를 허용합니다. {apple: 1.5, sample: "free"}를 잡아낼 수 있는 “초과 프로퍼티 검사”는 직접적인 객체 리터럴 할당에서만 작동합니다. 변수 경유 할당처럼 간접 경로로 가면 이 검사가 사라지고, 추가된 sample 프로퍼티가 그대로 스며듭니다. Flow의 객체 타입은 기본적으로 정확하므로 여분의 프로퍼티는 항상 거부됩니다.
3. TypeScript는 더 좁은 가변 배열에 더 넓은 값을 push하도록 허용합니다
function appendError(errs: Array<string | Error>) {
errs.push(new Error("oops"));
}
const errors: Array<string> = [];
appendError(errors);
errors[0].toUpperCase();
TypeScript는 Array<string>을 Array<string | Error>의 하위 타입으로 취급합니다(TS에서는 가변 배열이 공변적입니다). 그래서 이 호출은 통과하고, appendError 내부의 push는 호출자의 string 타입 배열에 Error를 추가합니다. Flow는 가변 배열의 불공변성 때문에 호출 지점에서 이 확장을 막습니다. 함수가 실제로 변경을 할 필요가 없다면 해결책은 ReadonlyArray<string | Error>입니다. 변경 가능성을 제거하면 이 확장은 안전해집니다.
4. TypeScript에서는 타입 가드 본문이 검증되지 않습니다
function isString(x: unknown): x is string {
return typeof x === "number";
}
const data: unknown = 1;
if (isString(data)) {
data.toUpperCase();
}
TypeScript는 술어의 _시그니처_는 검사하지만 본문은 검사하지 않습니다. Flow는 본문을 양방향으로 검증합니다. 모든 return은 실제로 가드 타입으로 정제되어야 하고, 부정 조건은 그 가드 타입을 배제하도록 정제되어야 합니다. 무엇을 검사하는지에 대해 거짓말하는 술어는 거부합니다.
전체 문서인 2026년 TypeScript 사용자를 위한 Flow에는 Flow와 TypeScript를 나란히 비교한 20개 이상의 항목이 담겨 있습니다.