-
[TypeScript/3.8] 타입스크립트 3.8에서 바뀐 것들에 대하여Frontend 2020. 3. 1. 21:56
2월 20일에 발표된 TypeScript 3.8에는 다가오는 새로운 ECMAScript standard features를 포함하여 많은 새로운 기능들이 담겼다.
TypeScript에 포함된 새로운 기능들에 대해서 간단하게 정리해보려고 한다.
Type-Only Imports and Exports
// ./foo.ts interface SampleConfig { // ... } export function doSmth(config: SampleConfig) { // ... } // ./bar.ts import { SampleConfig, doSmth } from './foo.js'; function doSmthMore (config: SampleConfig) { // ... // doSmth(config); }
TypeScript에서는 위와 같이 doSmth 함수 뿐만 아니라 TypeScript type인 SampleConfig까지 import할 수 있다.
TypeScript 파일이 JavaScript 파일로 변환될 때, SampleConfig가 오직 type으로만 쓰이는 것을 알아채고 자동으로 import시켜주는 것이다. 신경쓰지 않아도 알아서 import되기 때문에 매우 편리한 기능이다.
문제는 어떤 것을 import할 때, 이것이 type인지 아니면 value인지 알아내기가 어렵다는 점이다.
import { SomeThing } from './some-module.ts'; export { SomeThing };
위 예제를 보자. SomeThing은 type인가 value인가? 이것만 봐서는 알 수가 없다.
문제는 TypeScript에서 오직 type만 import하는 문장을 지워버린다는 것이다.
(왜냐면 TypeScript는 어떤 value를 import할 때 자동으로 그 value와 같이 사용되는 type을 import하므로
compile 단계에서 type만 import하는 문장은 지워버리는 side-effects가 발생한다.)
그래서 side-effects로부터 안전하기 위해서는 다음과 같이 import문을 하나 더 추가해야한다.
// import elision 때문에 이 statement는 지워질 것이다. import { SomeTypeFoo, SomeOtherTypeBar } from './module-with-side-effects'; // 이 statement가 항상 주변에 붙어있어야 한다. import './module-with-side-effects';
그래서 TypeScript 3.8에는 type만 import, export하는 것을 명확하게 명시할 수 있도록
type-only imports/exports가 추가되었다.
import type { SomeThing } from './some-module.js'; export type { SomeThing };
import type은 오직 type annotations/declarations만 import/export한다.
그리고 실제 런타임에서는 완벽하게 지워진다.
만약에 class를 type-only import한다면 다음과 같이는 사용할 수 없다.
import type { Component } from 'react'; interface ButtonProps { // ... } class Button extends Component<ButtonProps> { // ~~~~~~~~~ // error! 'Component' only refers to a type, but is being used as a value here. // ... }
또한 명확하게 하기 위해서 몇가지 규칙이 있는데,
우선 typescript는 default import나 name bindings 모두 사용 가능하지만 두 개를 섞어 쓸 수 없다.
// Is only 'Foo' a type? Or every declaration in the import? // We just give an error because it's not clear. import type Foo, { Bar, Baz } from 'some-module'; // ~~~~~~~~~~~~~~~~~~~~~~ // error! A type-only import can specify a default import or named bindings, but not both.
이것과 관련해서 TypeScript에서는 importsNotUsedAsValues라는 flag를 추가했는데, 이 flag는 3개의 value들을 가질 수 있다.
- remove: default값으로 type만 import하는 문장은 런타임 단계에서 지워진다.
- preserve: type만 import하는 문장을 지우지않고 보존한다. imports/side-effects를 발생시킬 수 있다.
- error: preserve 옵션과 마찬가지로 지우지 않고 보존하지만 type만 import하는 문장을 error로 처리한다. 어떤 value가 실수로 import되는 것을 막는데 효과적이지만 여전히 side-effect imports를 일으킬 수 있다.
ECMAScript Private Fields
ECMAScript의 private fields를 사용할 수 있게 되었다.
class Person { #name: string constructor(name: string) { this.#name = name; } greet() { console.log(`Hello, my name is ${this.#name}!`); } } let jeremy = new Person("Jeremy Bearimy"); jeremy.#name // ~~~~~ // Property '#name' is not accessible outside class 'Person' // because it has a private identifier.
private modifier를 포함해서 원래 쓰이던 regular properties와는 다르게 private fields는 다음과 같은 규칙이 지켜져야 한다.
- Private fields는 #로 시작한다.
- 모든 private field의 범위는 그 field를 포함하는 class로 한정되고, 그 안에서 이름은 unique해야 한다.
- 즉, 어떤 A라는 class 내에서 #foo가 사용되었다면 그 #foo의 scope는 A class 내부로 한정된다. A를 extend하는 B 클래스 내에서 다시 #foo를 사용했다면 A 내부의 foo와는 별개로 취급된다.
- Private fields로 public이나 private modifiers는 사용할 수 없다.
- Private fields는 그 field를 포함하고 있는 class 외부에서 접근할 수 없다. 이것을 hard privacy라고 부른다.
- 기존 private modifier는 soft privacy이다. 즉, 런타임때는 보통 property처럼 사용되며, 오직 compile 단계에서만 private하게 사용된다. 그러나 private fields는 어떠한 경우에도 외부에서 접근할 수 없다.
class Square { #sideLength: number; constructor(sideLength: number) { this.#sideLength = sideLength; } equals(other: any) { return this.#sideLength === other.#sideLength; } } const a = new Square(100); const b = { sideLength: 100 }; // Boom! // TypeError: attempted to get private field on non-instance // This fails because 'b' is not an instance of 'Square'. console.log(a.equals(b));
위 코드는 TypeError를 일으키는데 b가 Square의 instance가 아니기 때문에 private field에 접근할 수 없기 때문이다.
class C { // No declaration for '#foo' // :( constructor(foo: number) { // SyntaxError! // '#foo' needs to be declared before writing to it. this.#foo = foo; } }
private field 사용 시 무조건 사용 전에 선언을 해주어야 한다(!)
export * as ns Syntax
import * as utilities from './utilities.js'; export { utilities };
위와 같이 import하는 경우는 흔하게 볼 수 있다.
위와 같이 import * as ns 라고 쓰면, 해당 파일에서 export되는 모든 모듈을 ns라는 이름으로 import하겠다는 뜻이다.
그리고 이번에 ECMAScript 2020에서 이와 똑같은 pattern을 가지는 export syntax를 추가되었다.😍
export * as utilities from './utilities.js';
그리고 물론 TypeScript 3.8부터 이 기능이 지원된다.
Top-Level await
대부분의 JavaScript 개발 환경에서 비동기 요청이 사용된다. 또한 많은 현재 API들은 Promise를 리턴한다.
이는 많은 non-blocking operations에 기여하는 이점을 가지고 있지만,
파일이나 외부 컨텐츠 로딩과 같은 특정한 케이스에서 매우 느리게 동작하기도 한다.
Promise를 사용할 때 .then 체인을 피하기 위해서 JavaScript 개발자들은 종종 async 함수를 사용한다.
await 키워드를 사용하기 위해 async function을 선언한 다음, 선언 직후에 바로 함수를 호출하는 방법으로 주로 사용한다.
async function main() { const response = await fetch('...') const greeting = await response.text(); console.log(greeting); } main() .catch(e => console.error(e));
그런데 이제 await 키워드를 사용하기 위해 async 함수를 사용하는 대신,
ECMAScript 2020에 새로 추가될 top-level await을 사용할 수 있게 되었다.
기존에는 await 키워드가 async 함수 내부에서만 사용 가능했으나
이제는 module의 top level에서 바로 await 키워드를 사용할 수 있게 되었다.
const response = await fetch('...'); const greeting = await response.text(); console.log(greeting); // Make sure we're a module export {};
top-level await은 오직 모듈의 top level에서만 사용 가능하다.
TypeScript에서는 import 또는 export가 존재하는 파일들만 모듈이라고 인식한다.
즉, module로 인식시키기 위해서 boilerplate로 export {} 라는 문장을 추가해야할 것이다.
현재 이 기능은 target compiler option이 es2017 이상인 경우에만 사용 가능하다.
위 4가지가 가장 크게 바뀐 점이고,
이 외에도 자잘하게 watchOptions가 새로 생겼다거나 하는 바뀐 점들이 추가로 있다.
모두 살펴보고 싶다면 아래 링크를 읽어보기 바란다~!
https://devblogs.microsoft.com/typescript/announcing-typescript-3-8/
반응형'Frontend' 카테고리의 다른 글
[TypeScript] 타입스크립트 - Generic (252) 2020.03.03 [TypeScript] 타입스크립트 - Type assertions, Type alias (252) 2020.03.02 [React] Typescript와 Redux hooks를 이용하여 간단한 TODO LIST 만들기 (253) 2020.02.23 [React] Context Api에 대해서 (252) 2020.02.09 [TypeScript] 타입스크립트 - Installation & Basic Types (252) 2020.01.27 COMMENT