TypeScript의 동적 정적 입력

게시 됨: 2022-03-10
빠른 요약 ↬ 이 기사에서는 공용체 유형, 조건부 유형, 템플릿 리터럴 유형 및 제네릭과 같은 TypeScript의 고급 기능 중 일부를 살펴봅니다. 우리는 대부분의 버그가 발생하기 전에 잡을 수 있는 방식으로 가장 동적인 JavaScript 동작을 공식화하고자 합니다. 2020년 말 Smashing Magazine에 여기에 출판된 책인 50 Lessons에서 TypeScript의 모든 챕터에서 얻은 몇 가지 학습 내용을 적용합니다. 더 자세히 알아보고 싶다면 꼭 확인하세요!

JavaScript는 본질적으로 동적 프로그래밍 언어입니다. 개발자인 우리는 적은 노력으로 많은 것을 표현할 수 있으며 언어와 런타임은 우리가 의도한 바를 파악합니다. 이것이 JavaScript를 초보자에게 인기 있게 만들고 숙련된 개발자를 생산적으로 만드는 이유입니다! 하지만 주의할 점이 있습니다. 우리는 경계해야 합니다! 실수, 오타, 올바른 프로그램 동작: 많은 것들이 우리 머리 속에서 일어납니다!

다음 예를 살펴보십시오.

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { res.status(20).send({ message: "Got you, user " + req.params.userId }); } })

경로(또는 경로)를 정의하고 URL이 요청되면 콜백을 실행할 수 있는 https://expressjs.com/-스타일 서버가 있습니다.

콜백은 두 개의 인수를 사용합니다.

  1. request 개체입니다.
    여기에서 사용된 HTTP 메소드(예: GET, POST, PUT, DELETE) 및 추가 매개변수에 대한 정보를 얻습니다. 이 예에서 userID 는 사용자 ID를 포함하는 매개변수 userID 에 매핑되어야 합니다!
  2. response 또는 reply 개체입니다.
    여기서 우리는 서버에서 클라이언트로의 적절한 응답을 준비하려고 합니다. 올바른 상태 코드(메소드 status )를 보내고 유선을 통해 JSON 출력을 보내고 싶습니다.

이 예에서 우리가 보는 것은 크게 단순화되었지만 우리가 무엇을 해야 하는지에 대한 좋은 아이디어를 제공합니다. 위의 예에도 오류가 가득합니다! 살펴보세요:

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { /* Error 1 */ res.status(20).send({ /* Error 2 */ message: "Welcome, user " + req.params.userId /* Error 3 */ }); } })

오 와우! 세 줄의 구현 코드와 세 개의 오류? 무슨 일이 일어난?

  1. 첫 번째 오류는 미묘한 차이입니다. GET 요청(따라서 app.get )을 수신하고 싶다고 앱에 알리는 동안 요청 메서드가 POST 인 경우에만 작업을 수행합니다. 애플리케이션의 이 특정 지점에서 req.methodPOST 가 될 수 없습니다. 따라서 응답을 보내지 않으므로 예기치 않은 시간 초과가 발생할 수 있습니다.
  2. 상태 코드를 명시적으로 보내면 좋습니다! 그러나 20 은 유효한 상태 코드가 아닙니다. 클라이언트는 여기서 무슨 일이 일어나고 있는지 이해하지 못할 수 있습니다.
  3. 이것은 우리가 다시 보내고 싶은 응답입니다. 구문 분석된 인수에 액세스하지만 평균 오타가 있습니다. userId 가 아닌 userID 입니다. 모든 사용자는 "환영합니다, 사용자 정의되지 않았습니다!"라고 인사합니다. 야생에서 확실히 본 것!

그리고 그런 일이 일어납니다! 특히 자바스크립트에서요. 우리는 표현력을 얻습니다. 한 번도 유형에 대해 신경쓰지 않아도 되지만, 우리가 하는 일에 세심한 주의를 기울여야 합니다.

이것은 또한 JavaScript가 동적 프로그래밍 언어에 익숙하지 않은 프로그래머로부터 많은 반발을 받는 곳이기도 합니다. 그들은 일반적으로 가능한 문제를 지적하고 오류를 미리 잡아내는 컴파일러를 가지고 있습니다. 그들은 모든 것이 제대로 작동하는지 확인하기 위해 머리 속에서 해야 하는 추가 작업의 양에 눈살을 찌푸릴 때 멍청한 것처럼 보일 수 있습니다. JavaScript에는 유형이 없다고 말할 수도 있습니다. 사실이 아닙니다.

TypeScript의 수석 설계자인 Anders Hejlsberg는 MS Build 2017 기조연설에서 “ JavaScript에 유형 시스템이 없다는 것은 아닙니다. 형식화할 방법이 없다 ”고 말했다.

그리고 이것이 TypeScript의 주요 목적입니다. TypeScript는 당신보다 당신의 JavaScript 코드를 더 잘 이해하기를 원합니다. TypeScript가 의미를 파악할 수 없는 경우 추가 유형 정보를 제공하여 지원할 수 있습니다.

점프 후 더! 아래에서 계속 읽기 ↓

기본 타이핑

이것이 바로 우리가 할 일입니다. Express 스타일 서버에서 get 메소드를 가져오고 가능한 한 많은 범주의 오류를 제외할 수 있도록 충분한 유형 정보를 추가해 보겠습니다.

몇 가지 기본 유형 정보로 시작합니다. get 함수를 가리키는 app 객체가 있습니다. get 함수는 문자열인 path 와 콜백을 취합니다.

 const app = { get, /* post, put, delete, ... to come! */ }; function get(path: string, callback: CallbackFn) { // to be implemented --> not important right now }

string 이 소위 기본 유형인 반면, CallbackFn 은 명시적으로 정의해야 하는 복합 유형입니다.

CallbackFn 은 두 개의 인수를 사용하는 함수 유형입니다.

  • req , 유형 ServerRequest
  • ServerReply 유형의 reply

CallbackFnvoid 를 반환합니다.

 type CallbackFn = (req: ServerRequest, reply: ServerReply) => void;

ServerRequest 는 대부분의 프레임워크에서 매우 복잡한 개체입니다. 우리는 데모 목적으로 단순화된 버전을 수행합니다. "GET" , "POST" , "PUT" , "DELETE" 등에 대한 method 문자열을 전달합니다. params 레코드도 있습니다. 레코드는 키 집합을 속성 집합과 연결하는 개체입니다. 지금은 모든 string 키가 string 속성에 매핑되도록 허용하려고 합니다. 우리는 이것을 나중에 리팩토링합니다.

 type ServerRequest = { method: string; params: Record<string, string>; };

ServerReply 의 경우 실제 ServerReply 개체에 훨씬 더 많은 기능이 있다는 것을 알고 몇 가지 기능을 배치합니다. send 함수는 보내려는 데이터와 함께 선택적 인수를 취합니다. 그리고 status 기능으로 상태 코드를 설정할 수 있습니다.

 type ServerReply = { send: (obj?: any) => void; status: (statusCode: number) => ServerReply; };

그것은 이미 무언가이며 몇 가지 오류를 배제할 수 있습니다.

 app.get("/api/users/:userID", function(req, res) { if(req.method === 2) { // ^^^^^^^^^^^^^^^^^ Error, type number is not assignable to string res.status("200").send() // ^^^^^ Error, type string is not assignable to number } })

그러나 우리는 여전히 잘못된 상태 코드(모든 숫자 가능)를 보낼 수 있으며 가능한 HTTP 메서드(모든 문자열 가능)에 대한 단서가 없습니다. 유형을 수정해 보겠습니다.

더 작은 세트

기본 유형은 특정 범주의 가능한 모든 값의 집합으로 볼 수 있습니다. 예를 들어 string 은 JavaScript로 표현할 수 있는 모든 가능한 문자열을 포함하고 number 는 배정밀도 부동 소수점 정밀도로 가능한 모든 숫자를 포함합니다. boolean 에는 truefalse 인 가능한 모든 부울 값이 포함됩니다.

TypeScript를 사용하면 이러한 집합을 더 작은 하위 집합으로 세분화할 수 있습니다. 예를 들어, HTTP 메소드에 대해 수신할 수 있는 모든 가능한 문자열을 포함하는 Method 유형을 작성할 수 있습니다.

 type Methods= "GET" | "POST" | "PUT" | "DELETE"; type ServerRequest = { method: Methods; params: Record<string, string>; };

Method 는 더 큰 string 집합의 더 작은 집합입니다. Method 는 리터럴 유형의 공용체 유형입니다. 리터럴 유형은 주어진 집합의 가장 작은 단위입니다. 리터럴 문자열입니다. 리터럴 숫자입니다. 모호함이 없습니다. 바로 "GET" 입니다. 다른 리터럴 유형과 결합하여 더 큰 유형의 하위 집합을 만듭니다. stringnumber 의 리터럴 유형 또는 다른 복합 객체 유형으로 하위 집합을 수행할 수도 있습니다. 리터럴 유형을 결합하고 결합에 넣을 수 있는 가능성이 많이 있습니다.

이것은 서버 콜백에 즉각적인 영향을 미칩니다. 갑자기 우리는 이 네 가지 방법(또는 필요한 경우 더 많은 방법)을 구별할 수 있고 코드의 모든 가능성을 소진할 수 있습니다. TypeScript는 다음과 같이 안내합니다.

 app.get("/api/users/:userID", function (req, res) { // at this point, TypeScript knows that req.method // can take one of four possible values switch (req.method) { case "GET": break; case "POST": break; case "DELETE": break; case "PUT": break; default: // here, req.method is never req.method; } });

TypeScript는 모든 case 문에서 사용 가능한 옵션에 대한 정보를 제공할 수 있습니다. 직접 사용해 보세요. 모든 옵션을 다 사용한 경우 TypeScript는 default 분기에서 이러한 일이 발생할 수 never 알려줍니다. 이것은 문자 그대로 never 처리해야 하는 오류 상태에 도달했음을 의미합니다.

그것은 오류의 한 범주가 적습니다. 이제 어떤 가능한 HTTP 메서드를 사용할 수 있는지 정확히 알고 있습니다.

statusCode 가 취할 수 있는 유효한 숫자의 하위 집합을 정의하여 HTTP 상태 코드에 대해서도 동일한 작업을 수행할 수 있습니다.

 type StatusCode = 100 | 101 | 102 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599; type ServerReply = { send: (obj?: any) => void; status: (statusCode: StatusCode) => ServerReply; };

StatusCode 유형은 다시 통합 유형입니다. 이를 통해 다른 범주의 오류를 제외합니다. 갑자기 다음과 같은 코드가 실패합니다.

 app.get("/api/user/:userID", (req, res) => { if(req.method === "POS") { // ^^^^^^^^^^^^^^^^^^^ 'Methods' and '"POS"' have no overlap. res.status(20) // ^^ '20' is not assignable to parameter of type 'StatusCode' } })
그리고 우리 소프트웨어는 훨씬 더 안전해집니다! 하지만 우리는 더 많은 것을 할 수 있습니다!

제네릭 입력

app.get 으로 라우트를 정의할 때 가능한 유일한 HTTP 메소드는 "GET" 이라는 것을 암묵적으로 알고 있습니다. 그러나 유형 정의를 사용하면 여전히 공용체의 모든 가능한 부분을 확인해야 합니다.

가능한 모든 HTTP 메서드에 대해 콜백 함수를 정의할 수 있으므로 CallbackFn 의 유형은 정확하지만 명시적으로 app.get 을 호출하면 입력을 준수하는 데만 필요한 몇 가지 추가 단계를 저장하는 것이 좋습니다.

TypeScript 제네릭이 도움이 될 수 있습니다! 제네릭은 정적 유형에서 가장 동적인 동작을 얻을 수 있도록 해주는 TypeScript의 주요 기능 중 하나입니다. TypeScript 50과에서 마지막 세 장에서는 제네릭의 모든 복잡성과 고유한 기능을 파헤칩니다.

지금 알아야 할 것은 전체 집합 대신 Methods 의 일부를 지정할 수 있는 방식으로 ServerRequest 를 정의하려는 것입니다. 이를 위해 함수에서와 같이 매개변수를 정의할 수 있는 일반 구문을 사용합니다.

 type ServerRequest<Met extends Methods> = { method: Met; params: Record<string, string>; };

다음과 같은 일이 발생합니다.

  1. 꺾쇠 괄호로 표시된 대로 ServerRequest 가 일반 유형이 됩니다.
  2. Methods 유형의 하위 집합인 Met 라는 일반 매개변수를 정의합니다.
  3. 이 일반 매개변수를 일반 변수로 사용하여 메서드를 정의합니다.

또한 제네릭 매개변수 이름 지정에 대한 제 기사를 확인하는 것이 좋습니다.

그 변경으로 우리는 중복 없이 다른 ServerRequest 를 지정할 수 있습니다.

 type OnlyGET = ServerRequest<"GET">; type OnlyPOST = ServerRequest<"POST">; type POSTorPUT = ServerRquest<"POST" | "PUT">;

ServerRequest 의 인터페이스를 변경했으므로 CallbackFnget 함수와 같이 ServerRequest 를 사용하는 다른 모든 유형을 변경해야 합니다.

 type CallbackFn<Met extends Methods> = ( req: ServerRequest<Met>, reply: ServerReply ) => void; function get(path: string, callback: CallbackFn<"GET">) { // to be implemented }

get 함수를 사용하여 실제 인수를 일반 유형에 전달합니다. 우리는 이것이 단지 Methods 의 하위 집합이 아니라는 것을 알고 있으며, 우리가 다루고 있는 하위 집합을 정확히 알고 있습니다.

이제 app.get 을 사용할 때 app.get 에 가능한 req.method .

 app.get("/api/users/:userID", function (req, res) { req.method; // can only be get });

이렇게 하면 app.get 콜백을 생성할 때 "POST" 또는 이와 유사한 HTTP 메서드를 사용할 수 있다고 가정하지 않습니다. 우리는 이 시점에서 우리가 무엇을 다루고 있는지 정확히 알고 있으므로 우리 유형에 반영해 보겠습니다.

request.method 가 합리적으로 유형이 지정되고 실제 상황을 나타내도록 하기 위해 이미 많은 작업을 수행했습니다. Methods 공용체 유형을 서브셋팅하여 얻을 수 있는 한 가지 좋은 이점은 유형이 안전한 app.get 외부 에 범용 콜백 함수를 생성할 수 있다는 것입니다.

 const handler: CallbackFn<"PUT" | "POST"> = function(res, req) { res.method // can be "POST" or "PUT" }; const handlerForAllMethods: CallbackFn<Methods> = function(res, req) { res.method // can be all methods }; app.get("/api", handler); // ^^^^^^^ Nope, we don't handle "GET" app.get("/api", handlerForAllMethods); // This works

매개변수 입력

우리가 아직 건드리지 않은 것은 params 객체를 입력하는 것입니다. 지금까지 모든 string 키에 액세스할 수 있는 레코드를 얻었습니다. 그것을 조금 더 구체적으로 만드는 것이 이제 우리의 임무입니다!

다른 제네릭 변수를 추가하여 이를 수행합니다. 하나는 메소드용이고 하나는 Record 의 가능한 키용입니다.

 type ServerRequest<Met extends Methods, Par extends string = string> = { method: Met; params: Record<Par, string>; };

일반 유형 변수 Par 는 유형 string 의 하위 집합일 수 있으며 기본값은 모든 문자열입니다. 이를 통해 ServerRequest 에 예상되는 키를 알릴 수 있습니다.

 // request.method = "GET" // request.params = { // userID: string // } type WithUserID = ServerRequest<"GET", "userID">

요청된 매개변수를 설정할 수 있도록 get 함수와 CallbackFn 유형에 새 인수를 추가해 보겠습니다.

 function get<Par extends string = string>( path: string, callback: CallbackFn<"GET", Par> ) { // to be implemented } type CallbackFn<Met extends Methods, Par extends string> = ( req: ServerRequest<Met, Par>, reply: ServerReply ) => void;

Par 를 명시적으로 설정하지 않으면 Par 가 기본적으로 string 으로 설정되기 때문에 유형이 이전처럼 작동합니다. 그래도 설정하면 갑자기 req.params 객체에 대한 적절한 정의가 생깁니다!

 app.get<"userID">("/api/users/:userID", function (req, res) { req.params.userID; // Works!! req.params.anythingElse; // doesn't work!! });

대단해! 하지만 개선할 수 있는 작은 것이 하나 있습니다. 우리는 여전히 모든 문자열을 app.getpath 인수에 전달할 수 있습니다. 거기에도 Par 를 반영할 수 있다면 좋지 않을까요?

우리는 할 수 있습니다! 버전 4.1 릴리스에서 TypeScript는 템플릿 리터럴 유형 을 만들 수 있습니다. 구문적으로는 문자열 템플릿 리터럴처럼 작동하지만 유형 수준에서 작동합니다. 세트 string문자열 리터럴 유형 의 하위 집합으로 분할할 수 있었던 경우(메소드에서 수행한 것처럼) 템플릿 리터럴 유형을 사용하면 전체 스펙트럼의 문자열을 포함할 수 있습니다.

IncludeRouteParams 라는 유형을 만들어 보겠습니다. 여기서 Par 가 매개변수 이름 앞에 콜론을 추가하는 Express 스타일 방식에 제대로 IncludesRouteParams 되었는지 확인합니다.

 type IncludesRouteParams<Par extends string> = | `${string}/:${Par}` | `${string}/:${Par}/${string}`;

제네릭 형식 IncludesRouteParamsstring 의 하위 집합인 하나의 인수를 사용합니다. 두 템플릿 리터럴의 공용체 유형을 만듭니다.

  1. 첫 번째 템플릿 리터럴은 string 로 시작 하여 / 문자, : 문자, 매개변수 이름을 포함합니다. 이렇게 하면 매개변수가 경로 문자열의 끝에 있는 모든 경우를 포착할 수 있습니다.
  2. 두 번째 템플릿 리터럴은 임의의 string 로 시작하고 그 뒤에 동일한 패턴의 / , : 및 매개변수 이름이 옵니다. 그런 다음 다른 / 문자와 그 뒤에 임의의 문자열이 있습니다. Union 유형의 이 분기는 매개변수가 경로 내 어딘가에 있는 모든 경우를 포착하도록 합니다.

다음은 매개변수 이름이 userIDIncludesRouteParams 가 다른 테스트 사례에서 작동하는 방식입니다.

 const a: IncludeRouteParams<"userID"> = "/api/user/:userID" // const a: IncludeRouteParams<"userID"> = "/api/user/:userID/orders" // const a: IncludeRouteParams<"userID"> = "/api/user/:userId" // const a: IncludeRouteParams<"userID"> = "/api/user" // const a: IncludeRouteParams<"userID"> = "/api/user/:userIDAndmore" //

get 함수 선언에 새로운 유틸리티 유형을 포함시켜 보겠습니다.

 function get<Par extends string = string>( path: IncludesRouteParams<Par>, callback: CallbackFn<"GET", Par> ) { // to be implemented } app.get<"userID">( "/api/users/:userID", function (req, res) { req.params.userID; // YEAH! } );

엄청난! 우리는 실제 경로에 매개변수를 추가하는 것을 놓치지 않도록 또 다른 안전 메커니즘을 얻었습니다! 얼마나 강력한지.

일반 바인딩

하지만 아직까지는 만족스럽지 않습니다. 이러한 접근 방식에는 경로가 조금 더 복잡해지면 분명해지는 몇 가지 문제가 있습니다.

  1. 첫 번째 문제는 일반 유형 매개변수에 매개변수를 명시적으로 지정해야 한다는 것입니다. 함수의 경로 인수에 지정하더라도 Par"userID" 에 바인딩해야 합니다. 이것은 JavaScript-y가 아닙니다!
  2. 이 접근 방식은 하나의 경로 매개변수만 처리합니다. 유니온을 추가하는 순간, 예를 들어 "userID" | "orderId" "userID" | "orderId" 비상 안전 검사는 사용 가능한 인수 중 하나만 으로 만족합니다. 이것이 세트가 작동하는 방식입니다. 하나일 수도 있고 다른 것일 수도 있습니다.

더 나은 방법이 있어야 합니다. 그리고 있습니다. 그렇지 않으면 이 기사는 매우 쓰라린 메모로 끝날 것입니다.

순서를 반대로 해보자! 제네릭 유형 변수에 경로 매개변수를 정의하지 말고 app.get 의 첫 번째 인수로 전달한 path 에서 변수를 추출해 보겠습니다.

실제 값을 얻으려면 TypeScript에서 제네릭 바인딩 이 어떻게 작동하는지 확인해야 합니다. 이 identity 함수를 예로 들어 보겠습니다.

 function identity<T>(inp: T) : T { return inp }

지금까지 본 것 중 가장 지루한 일반 함수일 수 있지만 한 가지 점을 완벽하게 보여줍니다. identity 는 하나의 인수를 취하고 동일한 입력을 다시 반환합니다. 형식은 제네릭 형식 T 이며 동일한 형식도 반환합니다.

이제 Tstring 에 바인딩할 수 있습니다. 예를 들면 다음과 같습니다.

 const z = identity<string>("yes"); // z is of type string

이 명시적으로 일반 바인딩은 stringsidentity 에 전달하도록 하고 명시적으로 바인딩하므로 반환 유형도 string 입니다. 바인딩하는 것을 잊어버리면 흥미로운 일이 발생합니다.

 const y = identity("yes") // y is of type "yes"

이 경우 TypeScript는 전달한 인수에서 유형을 유추하고 T문자열 리터럴 유형 "yes" 에 바인딩합니다. 이것은 함수 인수를 리터럴 형식으로 변환하는 좋은 방법이며, 그런 다음 다른 제네릭 형식에서 사용합니다.

app.get 을 적용하여 그렇게 합시다.

 function get<Path extends string = string>( path: Path, callback: CallbackFn<"GET", ParseRouteParams<Path>> ) { // to be implemented }

Par 제네릭 유형을 제거하고 Path 를 추가합니다. Path 는 모든 string 의 하위 집합일 수 있습니다. 이 제네릭 유형 Path 에 대한 path 를 설정했습니다. 즉, get 에 매개변수를 전달하는 순간 문자열 리터럴 유형을 포착합니다. 아직 생성하지 않은 새로운 일반 유형 ParseRouteParamsPath 를 전달합니다.

ParseRouteParams 에 대해 작업해 보겠습니다. 여기서 다시 이벤트 순서를 바꿉니다. 경로가 괜찮은지 확인하기 위해 요청된 경로 매개변수를 일반에 전달하는 대신 경로 경로를 전달하고 가능한 경로 매개변수를 추출합니다. 이를 위해 조건부 유형을 생성해야 합니다.

조건부 유형 및 재귀 템플릿 리터럴 유형

조건부 유형은 구문적으로 JavaScript의 삼항 연산자와 유사합니다. 조건을 확인하고 조건이 충족되면 분기 A를 반환하고 그렇지 않으면 분기 B를 반환합니다. 예를 들면 다음과 같습니다.

 type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}` ? P : never;

여기에서 우리는 Rte 가 Express 스타일 끝에서 매개변수로 끝나는 모든 경로의 부분집합인지 확인합니다(앞에 "/:" 가 있음). 그렇다면 이 문자열을 유추합니다. 즉, 내용을 새 변수로 캡처합니다. 조건이 충족되면 새로 추출된 문자열을 반환하고, 그렇지 않으면 "경로 매개변수가 없습니다"와 같이 절대로 반환하지 않습니다.

우리가 그것을 시도한다면, 우리는 다음과 같은 것을 얻습니다:

 type Params = ParseRouteParams<"/api/user/:userID"> // Params is "userID" type NoParams = ParseRouteParams<"/api/user"> // NoParams is never --> no params!

좋아, 이미 우리가 이전에 했던 것보다 훨씬 낫습니다. 이제 다른 모든 가능한 매개변수를 포착하려고 합니다. 이를 위해 다른 조건을 추가해야 합니다.

 type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}/${infer Rest}` ? P | ParseRouteParams<`/${Rest}`> : Rte extends `${string}/:${infer P}` ? P : never;

조건부 유형은 이제 다음과 같이 작동합니다.

  1. 첫 번째 조건에서 경로 사이 어딘가에 경로 매개변수가 있는지 확인합니다. 그렇다면 경로 매개변수와 그 뒤에 오는 모든 것을 추출합니다. Rest 를 사용하여 동일한 제네릭 유형을 재귀적으로 호출하는 유니온에서 새로 찾은 경로 매개변수 P 를 반환합니다. 예를 들어 "/api/users/:userID/orders/:orderID" 경로를 ParseRouteParams 에 전달하면 "userID"P 로, "orders/:orderID"Rest 로 유추합니다. Rest 와 같은 유형을 호출합니다.
  2. 이것은 두 번째 조건이 들어오는 곳입니다. 여기서 우리는 끝에 유형이 있는지 확인합니다. 이것은 "orders/:orderID" 의 경우입니다. "orderID" 를 추출하고 이 리터럴 유형을 반환합니다.
  3. 더 이상 경로 매개변수가 남아 있지 않으면 절대로 반환하지 않습니다.

Dan Vanderkam은 ParseRouteParams 에 대해 유사하고 더 정교한 유형을 보여주지만 위에서 본 유형도 작동해야 합니다. 새로 조정된 ParseRouteParams 를 시도하면 다음과 같은 결과를 얻습니다.

 // Params is "userID" type Params = ParseRouteParams<"/api/user/:userID"> // MoreParams is "userID" | "orderID" type MoreParams = ParseRouteParams<"/api/user/:userID/orders/:orderId">

이 새로운 유형을 적용하고 app.get 의 최종 사용법을 살펴보겠습니다.

 app.get("/api/users/:userID/orders/:orderID", function (req, res) { req.params.userID; // YES!! req.params.orderID; // Also YES!!! });

와. 그것은 우리가 처음에 가지고 있었던 JavaScript 코드처럼 보입니다!

동적 동작을 위한 정적 유형

하나의 함수 app.get 에 대해 방금 생성한 유형은 가능한 많은 오류를 제외하도록 합니다.

  1. res.status() 에 적절한 숫자 상태 코드만 전달할 수 있습니다.
  2. req.method 는 4개의 가능한 문자열 중 하나이며 app.get 을 사용할 때 "GET" 만 알고 있습니다.
  3. 경로 매개변수를 구문 분석하고 콜백 내부에 오타가 없는지 확인할 수 있습니다.

이 기사의 시작 부분에 있는 예를 보면 다음과 같은 오류 메시지가 표시됩니다.

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { // ^^^^^^^^^^^^^^^^^^^^^ // This condition will always return 'false' // since the types '"GET"' and '"POST"' have no overlap. res.status(20).send({ // ^^ // Argument of type '20' is not assignable to // parameter of type 'StatusCode' message: "Welcome, user " + req.params.userId // ^^^^^^ // Property 'userId' does not exist on type // '{ userID: string; }'. Did you mean 'userID'? }); } })

그리고 실제로 코드를 실행하기 전의 모든 것! Express 스타일 서버는 JavaScript의 동적 특성을 보여주는 완벽한 예입니다. 호출하는 메서드, 첫 번째 인수에 전달하는 문자열에 따라 콜백 내에서 많은 동작이 변경됩니다. 다른 예를 들면 모든 유형이 완전히 다르게 보입니다.

그러나 몇 가지 잘 정의된 유형을 사용하면 코드를 편집하는 동안 이 동적 동작을 포착할 수 있습니다. 붐이 일어나는 런타임이 아니라 정적 유형으로 컴파일 타임에!

이것이 TypeScript의 힘입니다. 우리 모두가 잘 알고 있는 모든 동적 JavaScript 동작을 공식화하려고 하는 정적 유형 시스템입니다. 방금 만든 예제를 사용해보고 싶다면 TypeScript 플레이그라운드로 이동하여 만지작거리십시오.


Stefan Baumgartner의 50가지 강의의 TypeScript 이 기사에서 우리는 많은 개념을 다루었습니다. 더 자세히 알고 싶다면 TypeScript in 50 Lessons를 확인하십시오. 여기에서 작고 쉽게 소화할 수 있는 수업에서 유형 시스템에 대한 부드러운 소개를 얻을 수 있습니다. 전자책 버전은 즉시 사용할 수 있으며 인쇄본은 코딩 라이브러리에 대한 훌륭한 참조가 될 것입니다.