이 글에서는 지난 3년 동안(일부 변경 사항은 더 이전의) 자바스크립트/ECMAScript와 타입스크립트의 대부분의 변경 사항을 살펴봅니다.
소개 드리는 기능 모두 여러분과 관련이 있거나 실용적인 것은 아니지만, 어떤 것이 가능한지 보여주고 언어에 대한 이해를 심화시키는 데 도움이 될 것입니다.
"이전에는 기대했던 대로 동작하지 않았지만 이제는 기대한 대로 동작하는 것"으로 요약되어 여기서는 생략한 타입스크립트 기능이 많이 있습니다. 따라서 과거에 작동하지 않았던 기능이 있다면 지금 다시 시도해 보세요.
// 숫자가 포함된 임의의 문자열을 기록하되, 숫자의 형식을 지정하는 방법을 작성하고 싶다고 가정해 보겠습니다.
// 이를 위해 태그가 지정된 템플릿을 사용할 수 있습니다.
function formatNumbers(strings: TemplateStringsArray, number: number): string {
return strings[0] + number.toFixed(2) + strings[1];
}
console.log(formatNumbers`This is the value: ${0}, it's important.`); // This is the value: 0.00, it's important.
// 또는 문자열 내에서 키를 '번역'(여기서는 소문자로 변경)하려는 경우입니다.
function translateKey(key: string): string {
return key.toLocaleLowerCase();
}
function translate(strings: TemplateStringsArray, ...expressions: string[]): string {
return strings.reduce((accumulator, currentValue, index) => accumulator + currentValue + translateKey(expressions[index] ?? ''), '');
}
console.log(translate`Hello, this is ${'NAME'} to say ${'MESSAGE'}.`); // Hello, this is name to say message.
Symbol("foo") === Symbol("foo"); // false
const obj: { [index: string]: string } = {};
const symbolA = Symbol('a');
const symbolB = Symbol.for('b');
console.log(symbolA.description); // "a"
obj[symbolA] = 'a';
obj[symbolB] = 'b';
obj['c'] = 'c';
obj.d = 'd';
console.log(obj[symbolA]); // "a"
console.log(obj[symbolB]); // "b"
// 해당 키는 다른 심볼이나 심볼 없이는 접근할 수 없습니다.
console.log(obj[Symbol('a')]); // undefined
console.log(obj['a']); // undefined
// for...in 구문을 사용하여 열거할 때, 해당 키는 열거되지 않습니다
for (const i in obj) {
console.log(i); // "c", "d"
}
?
를 사용하여 옵셔널 체이닝을 사용할 수 있습니다. 이 기능은 인덱싱([...]
)이나 함수 호출에도 사용할 수 있습니다.// 이전 방식
// 객체 변수 또는 다른 구조가 정의되어 있는지 확실하지 않은 경우,
// 프로퍼티에 쉽게 접근할 수 없습니다.
const object: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const value = object.name; // 타입 에러: 'object'가 'undefined'일 수 있습니다.
// 먼저 정의되어 있는지 확인할 수 있지만, 이렇게 하면 가독성이 떨어지고 중첩된 객체의 경우 사용하기가 복잡해집니다.
const objectOld: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const valueOld = objectOld ? objectOld.name : undefined;
// 새로운 방식
// 대신 옵셔널 체이닝을 이용할 수 있습니다.
const objectNew: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const valueNew = objectNew?.name;
// 인덱싱 및 함수에도 사용할 수 있습니다.
const array: string[] | undefined = Math.random() > 0.5 ? undefined : ['test'];
const item = array?.[0];
const func: (() => string) | undefined = Math.random() > 0.5 ? undefined : () => 'test';
const result = func?.();
||
연산자를 사용하는 대신 새로운 ??
연산자를 사용할 수 있습니다. 모든 falsy 값에 적용되는 ||
연산자와 달리, null
과 undefined
에만 적용됩니다.const value: string | undefined = Math.random() > 0.5 ? undefined : 'test';
// 이전 방식
// 만약 값을 할당하려는데 해당 값이 undefined나 null일 경우, "||" 연산자를 사용하여 다른 값으로 조건부 할당할 수 있습니다.
const anotherValue = value || 'hello';
console.log(anotherValue); // "test" 또는 "hello"
// 이 방법은 truthy한 값에 대해서는 잘 작동하지만, 0이나 빈 문자열과 비교할 경우에도 조건이 적용되어 버리는 문제가 있습니다.
const incorrectValue = '' || 'incorrect';
console.log(incorrectValue); // 항상 "incorrect"
const anotherIncorrectValue = 0 || 'incorrect';
console.log(anotherIncorrectValue); // 항상 "incorrect"
// 새로운 방식
// 대신 새로운 널 병합 연산자를 사용할 수 있습니다. 이 연산자는 오직 undefined와 null 값에만 적용됩니다.
const newValue = value ?? 'hello';
console.log(newValue) // "test" 또는 "hello"
// falsy 값인 경우, 대체되지 않습니다.
const correctValue = '' ?? 'incorrect';
console.log(correctValue); // 항상 ""
const anotherCorrectValue = 0 ?? 'incorrect';
console.log(anotherCorrectValue); // 항상 0
import()
: 동적 import 입니다. import ... from ...
과 비슷하지만 import하지만 런타임에 동작하고, 변수를 사용합니다.let importModule;
if (shouldImport) {
importModule = await import('./module.mjs');
}
String.matchAll
: 루프를 사용하지 않고 캡처 그룹을 포함하여 정규식의 여러 개의 일치 항목을 가져옵니다.const stringVar = 'testhello,testagain,';
// 이전 방식
// 일치하는 결과를 가져올 수는 있지만, 캡처 그룹은 가져올 수 없습니다.
console.log(stringVar.match(/test([\w]+?),/g)); // ["testhello,", "testagain,"]
// 캡처 그룹을 포함하여 한 개의 일치 결과만 가져옵니다.
const singleMatch = stringVar.match(/test([\w]+?),/);
if (singleMatch) {
console.log(singleMatch[0]); // "testhello,"
console.log(singleMatch[1]); // "hello"
}
// 동일한 결과를 얻지만 매우 직관적이지 않습니다. (실행 메서드는 마지막 인덱스를 저장합니다.)
// 상태를 저장하기 위해 루프 외부에서 정의되어야 하며 전역(/g)이어야 합니다.
// 그렇지 않으면 무한 루프가 생성됩니다.
const regex = /test([\w]+?),/g;
let execMatch;
while ((execMatch = regex.exec(stringVar)) !== null) {
console.log(execMatch[0]); // "testhello,", "testagain,"
console.log(execMatch[1]); // "hello", "again"
}
// 새로운 방식
// 정규식은 전역(/g)이어야 하며, 그렇지 않으면 의미가 없습니다.
const matchesIterator = stringVar.matchAll(/test([\w]+?),/g);
// 직접 인덱싱하지 않고 반복하거나 Array.from()을 이용하여 배열로 변환해야 합니다.
for (const match of matchesIterator) {
console.log(match[0]); // "testhello,", "testagain,"
console.log(match[1]); // "hello", "again"
}
Promise.allSettled()
: Promise.all()
과 비슷하지만 모든 Promise가 완료될 때까지 기다리며 첫 번째 reject/throw시, 반환하지 않습니다. 모든 에러를 더 쉽게 처리할 수 있습니다.async function success1() {return 'a'}
async function success2() {return 'b'}
async function fail1() {throw 'fail 1'}
async function fail2() {throw 'fail 2'}
// 이전 방식
console.log(await Promise.all([success1(), success2()])); // ["a", "b"]
// 하지만 오류가 있는 경우, 다음과 같습니다.
try {
await Promise.all([success1(), success2(), fail1(), fail2()]);
} catch (e) {
console.log(e); // "fail 1"
}
// NOTE: 한 개의 오류만 포착할 수 있으며 성공 값에 접근할 수 없습니다.
// 차선책으로 이전 방식을 다음과 같이 수정할 수 있습니다.
console.log(await Promise.all([ // ["a", "b", undefined, undefined]
success1().catch(e => { console.log(e); }),
success2().catch(e => { console.log(e); }),
fail1().catch(e => { console.log(e); }), // "fail 1"
fail2().catch(e => { console.log(e); })])); // "fail 2"
// 새로운 방식
const results = await Promise.allSettled([success1(), success2(), fail1(), fail2()]);
const sucessfulResults = results
.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult<string>).value);
console.log(sucessfulResults); // ["a", "b"]
results.filter(result => result.status === 'rejected').forEach(error => {
console.log((error as PromiseRejectedResult).reason); // "fail 1", "fail 2"
});
// 또는 다음과 같습니다.
for (const result of results) {
if (result.status === 'fulfilled') {
console.log(result.value); // "a", "b"
} else if (result.status === 'rejected') {
console.log(result.reason); // "fail 1", "fail 2"
}
}
BigInt
: 새로운 BigInt
데이터 타입을 사용하면 큰 정수의 숫자를 정확하게 저장하고 연산할 수 있으므로 자바스크립트가 숫자를 부동소수점으로 저장하면서 발생하는 오류를 방지할 수 있습니다. BigInt()
생성자(오류를 방지하기 위해 가능하면 문자열과 함께 사용하는 것이 좋습니다.)를 사용하여 생성하거나 숫자 뒤에 n
을 추가하여 생성할 수 있습니다.// 이전 방식
// 자바스크립트는 숫자를 부동소수점으로 저장하기 때문에 항상 약간의 부정확성이 존재합니다.
// 하지만 더 중요한 것은 일정 숫자 이상의 정수 연산에서 부정확성이 발생하기 시작한다는 점입니다.
const maxSafeInteger = 9007199254740991;
console.log(maxSafeInteger === Number.MAX_SAFE_INTEGER); // true
// 이보다 큰 숫자를 비교하면 부정확할 수 있습니다.
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2);
// 새로운 방식
// 새로운 BigInt 데이터 타입을 사용하면 이론적으로 무한대 크기의 정수 숫자를 저장하고 연산할 수 있습니다.
// BigInt 생성자를 사용하거나 숫자 끝에 'n'을 추가하여 사용할 수 있습니다.
const maxSafeIntegerPreviously = 9007199254740991n;
console.log(maxSafeIntegerPreviously); // 9007199254740991
const anotherWay = BigInt(9007199254740991);
console.log(anotherWay); // 9007199254740991
// 숫자를 사용하여 생성자를 사용하면 MAX_SAFE_INTEGER보다 큰 정수를 안전하게 전달할 수 없습니다.
const incorrect = BigInt(9007199254740992);
console.log(incorrect); // 9007199254740992
const incorrectAgain = BigInt(9007199254740993);
console.log(incorrectAgain); // 9007199254740992
// 이런, 동일한 값으로 변환됩니다.
// 대신 문자열이나 더 나은 구문을 사용하세요.
const correct = BigInt('9007199254740993');
console.log(correct); // 9007199254740993
const correctAgain = 9007199254740993n;
console.log(correctAgain); // 9007199254740993
// 16진수, 8진수, 2진수도 문자열로 전달할 수 있습니다
const hex = BigInt('0x1fffffffffffff');
console.log(hex); // 9007199254740991
const octal = BigInt('0o377777777777777777');
console.log(octal); // 9007199254740991
const binary = BigInt('0b11111111111111111111111111111111111111111111111111111');
console.log(binary); // 9007199254740991
// 대부분의 산술 연산은 예상한 대로 작동하지만, 다른 연산자도 BigInt여야 합니다. 모든 연산은 BigInt를 반환합니다.
const addition = maxSafeIntegerPreviously + 2n;
console.log(addition); // 9007199254740993
const multiplication = maxSafeIntegerPreviously * 2n;
console.log(multiplication); // 18014398509481982
const subtraction = multiplication - 10n;
console.log(subtraction); // 18014398509481972
const modulo = multiplication % 10n;
console.log(modulo); // 2
const exponentiation = 2n ** 54n;
console.log(exponentiation); // 18014398509481984
const exponentiationAgain = 2n^54n;
console.log(exponentiationAgain); // 18014398509481984
const negative = exponentiation * -1n;
console.log(negative); // -18014398509481984
// 나눗셈은 약간 다르게 작동하는데, BigInt는 정수만 저장할 수 있기 때문입니다.
const division = multiplication / 2n;
console.log(division); // 9007199254740991
// 나눌 수 있는 정수의 경우 이 방법이 잘 작동합니다.
// 하지만 나눌 수 없는 숫자의 경우, 정수 나눗셈(반내림)처럼 작동합니다
const divisionAgain = 5n / 2n;
console.log(divisionAgain); // 2
// BigInt가 아닌 숫자와 엄격한 동등성은 없지만 느슨한 동등성은 있습니다
console.log(0n === 0); // false
console.log(0n == 0); // true
// 그러나 비교는 예상대로 작동합니다.
console.log(1n < 2); // true
console.log(2n > 1); // true
console.log(2 > 2); // false
console.log(2n > 2); // false
console.log(2n >= 2); // true
// "bigint" 타입 입니다.
console.log(typeof 1n); // "bigint"
// BigInt는 일반적인 숫자(부호가 있는 정수 및 부호 없는 정수(음수가 없음))로 다시 변환할 수 있습니다.
// 물론 이 경우 정확도가 떨어집니다. 유효 자릿수를 지정할 수 있습니다.
console.log(BigInt.asIntN(0, -2n)); // 0
console.log(BigInt.asIntN(1, -2n)); // 0
console.log(BigInt.asIntN(2, -2n)); // -2
// 일반적으로 더 많은 비트 수를 사용합니다.
// 부호가 없는 숫자로 변환할 때 음수는 2의 보수로 변환됩니다.
console.log(BigInt.asUintN(8, -2n)); // 254
globalThis
: 브라우저, Node.js 등과 같은 환경에 관계없이 전역 컨텍스트 내 변수에 접근할 수 있습니다. 여전히 나쁜 습관으로 여겨지지만 때로는 필요합니다. 브라우저의 최상위 수준에서 this
와 유사합니다.console.log(globalThis.Math); // Math 객체
import.meta
: ES 모듈을 사용하는 경우, import.meta.url
을 사용하여 현재 모듈의 URL을 가져옵니다.console.log(import.meta.url); // "file://..."
export * as am from 'another-module'
import { am } from 'module'
String.replaceAll()
: 항상 정규식을 사용하는 대신, 전역 플래그(/g)를 사용해 문자열 내의 모든 부분 문자열 인스턴스를 바꿉니다.const testString = 'hello/greetings everyone/everybody';
// 이전 방식
// 첫 번째 인스턴스만 대체합니다.
console.log(testString.replace('/', '|')); // 'hello|greetings everyone/everybody'
// 대신 성능이 더 나쁘고 이스케이프가 필요한 정규식을 사용해야 했습니다.
// 글로벌 플래그(/g)가 아닙니다.
console.log(testString.replace(/\//g, '|')); // 'hello|greetings everyone|everybody'
// 새로운 방식
// replaceAll을 사용하면 훨씬 더 명확하고 빠릅니다
console.log(testString.replaceAll('/', '|')); // 'hello|greetings everyone|everybody'
Promise.any
: Promise 목록 중 하나의 결과만 필요한 경우, 첫 번째 결과를 반환합니다. 즉시 거부되는 Promise.race
와 달리 모든 Promise가 거부되었을 때만 거부되고 AggregateError
를 반환합니다.async function success1() {return 'a'}
async function success2() {return 'b'}
async function fail1() {throw 'fail 1'}
async function fail2() {throw 'fail 2'}
// 이전 방식
console.log(await Promise.race([success1(), success2()])); // "a"
// 하지만 오류가 있는 경우, 다음과 같습니다.
try {
await Promise.race([fail1(), fail2(), success1(), success2()]);
} catch (e) {
console.log(e); // "fail 1"
}
// 알림: 하나의 오류만 catch하고 성공 값에 접근할 수 없습니다.
// 차선책으로 이전 방식을 다음과 같이 수정할 수 있습니다.
console.log(await Promise.race([ // "a"
fail1().catch(e => { console.log(e); }), // "fail 1"
fail2().catch(e => { console.log(e); }), // "fail 2"
success1().catch(e => { console.log(e); }),
success2().catch(e => { console.log(e); })]));
// 새로운 방식
console.log(await Promise.any([fail1(), fail2(), success1(), success2()])); // "a"
// 모든 프로미스가 거부될 때만 거부하고 모든 오류가 포함된 AggregateError를 반환합니다.
try {
await Promise.any([fail1(), fail2()]);
} catch (e) {
console.log(e); // [AggregateError: All promises were rejected]
console.log(e.errors); // ["fail 1", "fail 2"]
}
Nullish coalescing 할당 (??=)
: 이전에 "nullish"이었을 때(null 또는 undefined)만 값을 할당합니다.let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
// undefined는 nullish이므로 x1에 새 값을 할당합니다.
x1 ??= 'b';
console.log(x1) // "b"
// 문자열은 nullish가 아니므로 x2에 새 값을 할당하지 않습니다.
// 참고: 또한 getNewValue()는 절대 실행되지 않습니다.
x2 ??= getNewValue();
console.log(x2) // "a"
논리적 AND 할당 (&&=)
: 이전에 "truthy"이었을 때(true 또는 true로 변환 가능한 값)만 값을 할당합니다.let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
// undefined는 truthy가 아니므로 x1에 새 값을 할당하지 않습니다.
// 참고: 또한 getNewValue()는 절대 실행되지 않습니다.
x1 &&= getNewValue();
console.log(x1) // undefined
// 문자열이 truthy이므로 x2에 새 값을 할당합니다.
x2 &&= 'b';
console.log(x2) // "b"
논리적 OR 할당 (||=)
: 이전에 "falsy"이었을 때(false 또는 false로 변환 가능한 값)만 값을 할당합니다.let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
// undefined는 falsy이므로 x1에 새 값을 할당합니다.
x1 ||= 'b';
console.log(x1) // "b"
// 문자열이 falsy가 아니므로 x2에 새 값을 할당하지 않습니다.
// 참고: 또한 getNewValue()는 절대 실행되지 않습니다.
x2 ||= getNewValue();
console.log(x2) // "a"
WeakRef
: 객체가 가비지 컬렉션되는 것을 막지 않고 객체에 대한 "약한" 참조를 유지합니다.const ref = new WeakRef(element);
// 객체/엘리먼트가 여전히 존재하고 가비지 컬렉션되지 않은 경우 값을 가져옵니다.
const value = ref.deref;
console.log(value); // undefined
// 객체가 더 이상 존재하지 않는 것 같습니다.
_
를 사용하여 숫자를 구분합니다. 기능에는 영향을 주지 않습니다.const int = 1_000_000_000;
const float = 1_000_000_000.999_999_999;
const max = 9_223_372_036_854_775_807n;
const binary = 0b1011_0101_0101;
const octal = 0o1234_5670;
const hex = 0xD0_E0_F0;
await
키워드를 사용할 수 있으므로 래퍼 함수가 필요하지 않아 오류 처리가 개선됩니다.async function asyncFuncSuccess() {
return 'test';
}
async function asyncFuncFail() {
throw new Error('Test');
}
// 이전 방식
// Promise를 기다리는 것은 async 함수 안에서만 가능합니다.
// await asyncFuncSuccess(); // SyntaxError: await is only valid in async functions
// 따라서 우리는 이를 함수 안에 래핑해야 했고, 이로 인해 오류 처리와 최상위 동시성을 잃게 되었습니다.
try {
(async () => {
console.log(await asyncFuncSuccess()); // "test"
try {
await asyncFuncFail();
} catch (e) {
// 그렇지 않으면 오류가 전혀 발견되지 않거나 적절한 추적 없이 너무 늦게 발견되기 때문에 이 기능이 필요합니다.
console.error(e); // Error: "Test"
throw e;
}
})();
} catch (e) {
// 이 함수는 비동기이기 때문에 트리거되지 않거나 적절한 추적 없이 너무 늦게 트리거될 수 있습니다.
console.error(e);
}
// 비동기 함수를 기다릴 수 없기 때문에 Promise 결과보다 먼저 기록됩니다.
console.log('Hey'); // "Hey"
// 새로운 방식
// package.json에 설정되어 있고 내보내는 이름이 ".mts"인 경우인 경우, 최상위 레벨에서 기다릴 수 있습니다.
console.log(await asyncFuncSuccess()); // "test"
try {
await asyncFuncFail();
} catch (e) {
console.error(e); // Error: "Test"
}
// 모든 비동기 호출이 대기 중이므로 Promise 결과 이후에 기록됩니다.
console.log('Hello'); // "Hello"
#private
: #
로 시작하는 이름을 지정하여 클래스 멤버(프로퍼티 및 메서드)를 비공개로 설정합니다. 그러면 클래스 자체에서만 접근할 수 있으며 삭제하거나 동적으로 할당할 수 없습니다. 잘못된 동작이 발생하면 (타입스크립트가 아닌) 자바스크립트 구문 오류가 발생합니다. 타입스크립트 프로젝트에는 이 방법을 권장하지 않으며, 대신 기존 private
키워드를 사용하세요.class ClassWithPrivateField {
#privateField;
#anotherPrivateField = 4;
constructor() {
this.#privateField = 42; // Valid
this.#privateField; // Syntax error
this.#undeclaredField = 444; // Syntax error
console.log(this.#anotherPrivateField); // 4
}
}
const instance = new ClassWithPrivateField();
instance.#privateField === 42; // Syntax error
class Logger {
static id = 'Logger1';
static type = 'GenericLogger';
static log(message: string | Error) {
console.log(message);
}
}
class ErrorLogger extends Logger {
static type = 'ErrorLogger';
static qualifiedType;
static log(e: Error) {
return super.log(e.toString());
}
}
console.log(Logger.type); // "GenericLogger"
Logger.log('Test'); // "Test"
// 정적 전용 클래스의 인스턴스화는 쓸모가 없으며 여기서는 데모 목적으로만 수행됩니다.
const log = new Logger();
ErrorLogger.log(new Error('Test')); // Error: "Test" (부모 인스턴스화의 영향을 받지 않음)
console.log(ErrorLogger.type); // "ErrorLogger"
console.log(ErrorLogger.qualifiedType); // undefined
console.log(ErrorLogger.id); // "Logger1"
// log()가 인스턴스 메서드가 아니라 정적 메서드이기 때문에 발생하는 오류입니다.
console.log(log.log()); // log.log 는 함수가 아닙니다.
class Test {
static staticProperty1 = 'Property 1';
static staticProperty2;
static {
this.staticProperty2 = 'Property 2';
}
}
console.log(Test.staticProperty1); // "Property 1"
console.log(Test.staticProperty2); // "Property 2"
Import Assertions (V8에서 구현된 비표준): import ... from ... assert { type: 'json' }
를 사용하여 가져온 모듈의 타입을 단언합니다. 이를 통해 JSON을 파싱하지 않고 직접 가져올 수 있습니다.
import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42
RegExp.exec()
, String.match()
및 String.matchAll()
에서 동작합니다.const matchObj = /(test+)(hello+)/d.exec('start-testesthello-stop');
// 이전 방식
console.log(matchObj?.index);
// 새로운 방식
if (matchObj) {
// 전체 일치하는 문자열의 시작 및 종료 인덱스 (이전에는 시작 인덱스만 있었습니다.)
console.log(matchObj.indices[0]); // [9, 18]
// 캡처 그룹의 시작 및 종료 인덱스
console.log(matchObj.indices[1]); // [9, 13]
console.log(matchObj.indices[2]); // [13, 18]
}
at()
을 이용하여 끝부터 인덱싱할 수 있습니다. 이는 값을 가져올 때 arr[arr.length - 1])
와 동일합니다(할당은 불가능).console.log([4, 5].at(-1)) // 5
const array = [4, 5];
array.at(-1) = 3; // SyntaxError: Assigning to rvalue
hasOwn
: obj.hasOwnProperty()
대신 권장하는 객체가 가지고 있는 프로퍼티를 찾는 방법입니다. 일부 엣지 케이스에서 더 잘 동작합니다.const obj = { name: 'test' };
console.log(Object.hasOwn(obj, 'name')); // true
console.log(Object.hasOwn(obj, 'gender')); // false
try {
try {
connectToDatabase();
} catch (err) {
throw new Error('Connecting to database failed.', { cause: err });
}
} catch (err) {
console.log(err.cause); // ReferenceError: connectToDatabase is not defined
}
class Person {
accessor name: string;
constructor(name: string) {
this.name = name;
console.log(this.name) // 'test'
}
}
const person = new Person('test');
any
또는 unknown
을 사용하는 것보다 항상 이 방법을 우선시하는 게 좋습니다.// 제네릭을 사용하지 않는 경우입니다.
function getFirstUnsafe(list: any[]): any {
return list[0];
}
const firstUnsafe = getFirstUnsafe(['test']); // any
// 제네릭을 사용하는 경우입니다.
function getFirst<Type>(list: Type[]): Type {
return list[0];
}
const first = getFirst<string>(['test']); // string
// 이 경우 인자를 통해 매개변수를 유추할 수 있으므로 매개변수를 삭제할 수도 있습니다.
const firstInferred = getFirst(['test']); // string
// 제네릭으로 허용되는 타입은 `extends`를 사용하여 제한할 수도 있습니다. 일반적으로 타입은 T로 줄여 씁니다.
class List<T extends string | number> {
private list: T[] = [];
get(key: number): T {
return this.list[key];
}
push(value: T): void {
this.list.push(value);
}
}
const list = new List<string>();
list.push(9); // Type error: Argument of type 'number' is not assignable to parameter of type 'string'.
const booleanList = new List<boolean>(); // Type error: Type 'boolean' does not satisfy the constraint 'string | number'.
interface Test {
name: string;
age: number;
}
// Partial 유틸리티 타입은 모든 프로퍼티들을 옵셔널하게 만듭니다.
type TestPartial = Partial<Test>; // { name?: string | undefined; age?: number | undefined; }
// Required 유틸리티 타입은 Partial과 반대로 모든 프로퍼티를 필수로 만듭니다.
type TestRequired = Required<TestPartial>; // { name: string; age: number; }
// Readonly 유틸리티 타입은 모든 프로퍼티들을 읽기 전용으로 만듭니다.
type TestReadonly = Readonly<Test>; // { readonly name: string; readonly age: string }
// Record 유틸리티 타입은 objects/maps/dictionaries의 간단한 정의를 가능하게 합니다. 가능한 경우 인덱스 시그니처 대신 Record 유틸리티 타입을 사용하는 것이 좋습니다.
const config: Record<string, boolean> = { option: false, anotherOption: true };
// Pick 유틸리티 타입은 지정된 프로퍼티만 가져옵니다.
type TestLess = Pick<Test, 'name'>; // { name: string; }
type TestBoth = Pick<Test, 'name' | 'age'>; // { name: string; age: string; }
// Omit 유틸리티 타입은 지정된 properties.type을 무시합니다.
type TestFewer = Omit<Test, 'name'>; // typed as { age: string; }
type TestNone = Omit<Test, 'name' | 'age'>; // typed as {}
// Parameters 유틸리티 타입은 함수 타입의 매개변수를 가져옵니다.
function doSmth(value: string, anotherValue: number): string {
return 'test';
}
type Params = Parameters<typeof doSmth>; // [value: string, anotherValue: number]
// ReturnType 유틸리티 타입은 함수 타입의 반환 타입을 가져옵니다.
type Return = ReturnType<typeof doSmth>; // string
// 더 많은 기능이 있으며, 그 중 일부는 아래에서 자세히 소개합니다.
// 배열인 경우 배열 타입만 추출하고, 그렇지 않으면 동일한 타입을 반환합니다.
type Flatten<T> = T extends any[] ? T[number] : T;
// 배열의 요소에 대한 타입을 추출합니다.
type Str = Flatten<string[]>; // string
// 타입을 그대로 둡니다.
type Num = Flatten<number>; // number
infer
키워드가 필요합니다. 이는 임시로 추론된 타입 변수를 정의합니다.// 이전 예제를 기반으로 하여, 더 깔끔하게 작성할 수 있습니다.
type FlattenOld<T> = T extends any[] ? T[number] : T;
// 배열을 인덱싱하는 대신 배열에서 Item 타입을 추론할 수 있습니다.
type Flatten<T> = T extends (infer Item)[] ? Item : T;
// 만약 함수의 반환 타입을 가져오는 타입을 작성하고, 그렇지 않은 경우에는 undefined를 반환하도록 하고 싶다면, 이를 추론할 수도 있습니다.
type GetReturnType<Type> = Type extends (...args: any[]) => infer Return ? Return : undefined;
type Num = GetReturnType<() => number>; // number
type Str = GetReturnType<(x: string) => string>; // string
type Bools = GetReturnType<(a: boolean, b: boolean) => void>; // undefined
?
를 사용하여 옵셔널한 요소를 튜플로 선언하고 나머지는 ...
를 사용하여 다른 타입에 따라 선언합니다.// 만약 튜플의 길이를 정확히는 모르지만 적어도 1 이상인 경우, `?`를 사용하여 선택적인 타입을 지정할 수 있습니다.
const list: [number, number?, boolean?] = [];
list[0] // number
list[1] // number | undefined
list[2] // boolean | undefined
list[3] // Type error: Tuple type '[number, (number | undefined)?, (boolean | undefined)?]' of length '3' has no element at index '3'.
// 기존 타입을 기반으로 튜플을 만들 수도 있습니다.
// 만약 배열의 시작 부분을 패딩하고 싶다면, rest 연산자 `...`를 사용하여 패딩할 수 있습니다.
function padStart<T extends any[]>(arr: T, pad: string): [string, ...T] {
return [pad, ...arr];
}
const padded = padStart([1, 2], 'test'); // [string, number, number]
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earth...');
}
}
// 확장 시 추상 메서드를 구현해야 합니다.
class Cat extends Animal {} // Compile error: Non-abstract class 'Cat' does not implement inherited abstract member 'makeSound' from class 'Animal'.
class Dog extends Animal {
makeSound() {
console.log('woof');
}
}
// 추상 클래스는 인터페이스처럼 인스턴스화할 수 없으며 추상 메서드도 호출할 수 없습니다.
new Animal(); // Compile error: Cannot create an instance of an abstract class.
const dog = new Dog().makeSound(); // "woof"
interface MyInterface {
name: string;
}
interface ConstructsMyInterface {
new(name: string): MyInterface;
}
class Test implements MyInterface {
name: string;
constructor(name: string) {
this.name = name;
}
}
class AnotherTest {
age: number;
}
function makeObj(n: ConstructsMyInterface) {
return new n('hello!');
}
const obj = makeObj(Test); // Test
const anotherObj = makeObj(AnotherTest); // Type error: Argument of type 'typeof AnotherTest' is not assignable to parameter of type 'ConstructsMyInterface'.
// makeObj 함수에 대한 생성자 인자를 가져오고 싶다면 어떻게 해야 할까요?
interface MyInterface {
name: string;
}
interface ConstructsMyInterface {
new(name: string): MyInterface;
}
class Test implements MyInterface {
name: string;
constructor(name: string) {
this.name = name;
}
}
function makeObj(test: ConstructsMyInterface, ...args: ConstructorParameters<ConstructsMyInterface>) {
return new test(...args);
}
makeObj(Test); // Type error: Expected 2 arguments, but got 1.
const obj = makeObj(Test, 'test'); // Test
// 길이와 타입이 정의되지 않은 두 개의 튜플을 결합하는 함수가 있다면 어떨까요? 반환 타입을 어떻게 정의할 수 있을까요?
// 이전 방식
// 몇 가지 오버로드를 작성할 수 있습니다.
declare function concat(arr1: [], arr2: []): [];
declare function concat<A>(arr1: [A], arr2: []): [A];
declare function concat<A, B>(arr1: [A], arr2: [B]): [A, B];
declare function concat<A, B, C>(arr1: [A], arr2: [B, C]): [A, B, C];
declare function concat<A, B, C, D>(arr1: [A], arr2: [B, C, D]): [A, B, C, D];
declare function concat<A, B>(arr1: [A, B], arr2: []): [A, B];
declare function concat<A, B, C>(arr1: [A, B], arr2: [C]): [A, B, C];
declare function concat<A, B, C, D>(arr1: [A, B], arr2: [C, D]): [A, B, C, D];
declare function concat<A, B, C, D, E>(arr1: [A, B], arr2: [C, D, E]): [A, B, C, D, E];
declare function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];
declare function concat<A, B, C, D>(arr1: [A, B, C], arr2: [D]): [A, B, C, D];
declare function concat<A, B, C, D, E>(arr1: [A, B, C], arr2: [D, E]): [A, B, C, D, E];
declare function concat<A, B, C, D, E, F>(arr1: [A, B, C], arr2: [D, E, F]): [A, B, C, D, E, F];
// 각각 세 가지 항목만 봐도 이것은 정말 차선책입니다
// 대신 타입을 결합할 수 있습니다.
declare function concatBetter<T, U>(arr1: T[], arr2: U[]): (T | U)[];
// 하지만 이것은 (T | U)[] 타입 입니다.
// 새로운 방식
// 가변 튜플 타입을 사용하면 쉽게 정의하고 길이에 대한 정보를 유지할 수 있습니다.
declare function concatNew<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U];
const tuple = concatNew([23, 'hey', false] as [number, string, boolean], [5, 99, 20] as [number, number, number]);
console.log(tuple[0]); // 23
const element: number = tuple[1]; // Type error: Type 'string' is not assignable to type 'number'.
console.log(tuple[6]); // Type error: Tuple type '[23, "hey", false, 5, 99, 20]' of length '6' has no element at index '6'.
[start: number, end: number]
와 같이 지정할 수 있습니다. 요소 중 하나에 이름이 지정된 경우, 모든 요소에 이름을 지정해야 합니다.type Foo = [first: number, second?: string, ...rest: any[]];
// 이렇게 하면 여기에서 인자의 이름을 올바르게 지정할 수 있으며 에디터에도 표시됩니다.
declare function someFunc(...args: Foo);
class Animal {
// 생성자에서 타입을 할당할 때 타입을 설정할 필요가 없습니다.
name;
constructor(name: string) {
this.name = name;
console.log(this.name); // string
}
}
@deprecated
태그는 이제 타입스트립트에서 인식됩니다./** @deprecated message */
type Test = string;
const test: Test = 'dfadsf'; // Type error: 'Test' is deprecated.
${Type}
과 같은 템플릿을 통해 타입을 지정할 수 있습니다. 이를 통해 여러 문자열 리터럴을 결합할 때와 같이 복잡한 문자열 타입을 구성할 수 있습니다.type VerticalDirection = 'top' | 'bottom';
type HorizontalDirection = 'left' | 'right';
type Direction = `${VerticalDirection} ${HorizontalDirection}`;
const dir1: Direction = 'top left';
const dir2: Direction = 'left'; // Type error: Type '"left"' is not assignable to type '"top left" | "top right" | "bottom left" | "bottom right"'.
const dir3: Direction = 'left top'; // Type error: Type '"left top"' is not assignable to type '"top left" | "top right" | "bottom left" | "bottom right"'.
// 이 기능은 제네릭 및 새로운 유틸리티 타입과도 결합할 수 있습니다.
declare function makeId<T extends string, U extends string>(first: T, second: U): `${Capitalize<T>}-${Lowercase<U>}`;
[K in keyof T as NewKeyType]: T[K]
와 같이 값은 그대로 사용하면서 매핑된 타입의 키를 다시 지정할 수 있습니다.// 객체의 형식을 다시 지정하되 해당 객체의 ID에 밑줄을 추가하고 싶다고 가정해 보겠습니다.
const obj = { value1: 0, value2: 1, value3: 3 };
const newObj: { [Property in keyof typeof obj as `_${Property}`]: number }; // { _value1: number; _value2: number; _value3: number; }
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;
type P1 = Awaited<string>; // string
type P2 = Awaited<Promise<string>>; // string
type P3 = Awaited<Promise<Promise<string>>>; // string
@see variable/type/link
태그가 지원됩니다.const originalValue = 1;
/**
* 다른 값을 복사합니다.
* @see originalValue
*/
const value = originalValue;
--explainFiles
: --explainFiles
옵션은 타입스크립트 CLI에서 컴파일에 포함된 파일과 그 이유를 설명하는 데 사용할 수 있으며 디버깅에 유용할 수 있습니다. 경고: 대규모 프로젝트나 복잡한 설정의 경우, 이 옵션은 많은 출력을 생성하므로 대신 tsc --explainFiles | less
또는 이와 유사한 것을 사용하세요.tsc --explainFiles
<<output
../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es5.d.ts
Library referenced via 'es5' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.ts'
Library referenced via 'es5' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.ts'
../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.ts
Library referenced via 'es2015' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2016.d.ts'
Library referenced via 'es2015' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2016.d.ts'
../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2016.d.ts
Library referenced via 'es2016' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2017.d.ts'
Library referenced via 'es2016' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2017.d.ts'
...
output
const [_first, second] = [3, 5];
console.log(second);
// 혹은 더 짧게 사용할 수 있습니다.
const [_, value] = [3, 5];
console.log(value);
class Test {
private _value: number;
get value(): number {
return this._value;
}
set value(value: number | string) {
if (typeof value === 'number') {
this._value = value;
return;
}
this._value = parseInt(value, 10);
}
}
override
를 사용하여 상속된 클래스 메서드를 명시적으로 오버라이드로 표시하면 상위 클래스가 변경될 때 타입스크립트에서 상위 메서드가 더 이상 존재하지 않음을 알릴 수 있습니다. 이를 통해 복잡한 상속 패턴을 보다 안전하게 관리할 수 있습니다.class Parent {
getName(): string {
return 'name';
}
}
class NewParent {
getFirstName(): string {
return 'name';
}
}
class Test extends Parent {
override getName(): string {
return 'test';
}
}
class NewTest extends NewParent {
override getName(): string { // Type error: This member cannot have an 'override' modifier because it is not declared in the base class 'NewParent'.
'NewParent'.
return 'test';
}
}
static [propName: string]: string
을 사용하여 인덱스 시그니처를 설정할 수도 있습니다.// 이전 방식
class Test {}
Test.test = ''; // Type error: Property 'test' does not exist on type 'typeof Test'.
// 새로운 방식
class NewTest {
static [key: string]: string;
}
NewTest.test = '';
{@link variable/type/link}
인라인 태그가 이제 지원되며, 에디터에서 표시되고 해결됩니다.const originalValue = 1;
/**
* {@link originalValue} 복사본
*/
const value = originalValue;
--exactOptionalPropertyTypes
(또는 tsconfig.json
에서)를 사용하면 암시적으로 undefined
를 허용하는 프로퍼티에 대한 undefined
할당이 더 이상 허용되지 않습니다.(예: property?: string
) 대신 property: string | undefined
와 같이 명시적으로 undefined
를 허용해야 합니다.class Test {
name?: string;
age: number | undefined;
}
const test = new Test();
test.name = undefined; // Type error: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target.
test.age = undefined;
console.log(test.age); // undefined
Awaited<>
유틸리티 타입은 무한하게 중첩된 Promises에서 값을 추출합니다. (await
가 값을 추출하는 것과 동일) 이는 Promise.all()
의 타입 추론도 개선시켰습니다.// 만약 제네릭한 awaited 값을 가지고 싶다면, Awaited 유틸리티 타입을 사용할 수 있습니다.
// 이를 통해 무한하게 중첩된 Promises는 모두 값을 반환합니다. (이전 예제의 소스 코드에 포함되어 있습니다.)
type P1 = Awaited<string>; // string
type P2 = Awaited<Promise<string>>; // string
type P3 = Awaited<Promise<Promise<string>>>; // string
Import type
이 아닌 import 문에서는 type
키워드를 사용하여 값이 타입 컴파일에만 필요하고 제거할 수 있는 것을 나타낼 수 있습니다.// 이전 방식
// 타입을 가져오는 가장 최적의 방법은 `import type` 키워드를 사용하여 컴파일 후 실제로 가져오는 것을 방지하는 것입니다.
import { something } from './file';
import type { SomeType } from './file';
// 동일한 파일에 대해 두 개의 import 문이 필요합니다.
// 새로운 방식
// 이제 하나의 문장으로 결합될 수 있습니다.
import { something, type SomeType } from './file';
as const
를 사용하여 리터럴 타입으로 정확하게 타입 지정할 수 있습니다. 이는 많은 사용 사례가 있으며 정확한 타이핑을 쉽게 만듭니다. 또한 객체와 배열을 readonly
로 만들어서 상수 객체의 변경을 방지합니다.// 이전 방식
const obj = { name: 'foo', value: 9, toggle: false }; // { name: string; value: number; toggle: boolean; }
// 값은 일반적으로 입력되므로 어떤 값이라도 지정할 수 있습니다.
obj.name = 'bar';
const tuple = ['name', 4, true]; // (string | number | boolean)[]
// 길이와 정확한 타입은 타입에서 확인할 수 없습니다. 모든 값은 어디에나 지정할 수 있습니다.
tuple[0] = 0;
tuple[3] = 0;
// 새로운 방식
const objNew = { name: 'foo', value: 9, toggle: false } as const; // { readonly name: "foo"; readonly value: 9; readonly toggle: false; }
// "foo"로 정의되어 있고 읽기 전용이므로 값을 할당할 수 없습니다.
objNew.name = 'bar'; // type error: Cannot assign to 'name' because it is a read-only property.
const tupleNew = ['name', 4, true] as const; // readonly ["name", 4, true]
// 이제 길이와 정확한 타입이 정의되었으며 리터럴로 정의되어 읽기 전용이므로 아무 것도 할당할 수 없습니다.
tupleNew[0] = 0; // type error: Cannot assign to '0' because it is a read-only property.
tupleNew[3] = 0; // type error: Index signature in type 'readonly ["name", 4, true]' only permits reading.
interface AllowedTypes {
'number': number;
'string': string;
'boolean': boolean;
}
// Record는 허용된 타입 중에서 종류와 값 타입을 지정합니다
type UnionRecord<AllowedKeys extends keyof AllowedTypes> = { [Key in AllowedKeys]:
{
kind: Key;
value: AllowedTypes[Key];
logValue: (value: AllowedTypes[Key]) => void;
}
}[AllowedKeys];
logValue 함수는 Record 값만 허용합니다.
function processRecord<Key extends keyof AllowedTypes>(record: UnionRecord<Key>) {
record.logValue(record.value);
}
processRecord({
kind: 'string',
value: 'hello!',
// 암시적으로 string | number | boolean 타입을 갖는 데 사용되는 값입니다.
// 이제 string으로만 올바르게 추론됩니다.
logValue: value => {
console.log(value.toUpperCase());
}
});
--generateTrace <Output folder>
옵션은 타입스크립트 CLI에서 타입 검사 및 컴파일 프로세스에 관한 세부 정보가 포함된 파일을 생성하는 데 사용할 수 있습니다. 이는 복잡한 타입을 최적화하는 데 도움이 될 수 있습니다.tsc --generateTrace trace
cat trace/trace.json
<<output
[
{"name":"process_name","args":{"name":"tsc"},"cat":"__metadata","ph":"M","ts":...,"pid":1,"tid":1},
{"name":"thread_name","args":{"name":"Main"},"cat":"__metadata","ph":"M","ts":...,"pid":1,"tid":1},
{"name":"TracingStartedInBrowser","cat":"disabled-by-default-devtools.timeline","ph":"M","ts":...,"pid":1,"tid":1},
{"pid":1,"tid":1,"ph":"B","cat":"program","ts":...,"name":"createProgram","args":{"configFilePath":"/...","rootDir":"/..."}},
{"pid":1,"tid":1,"ph":"B","cat":"parse","ts":...,"name":"createSourceFile","args":{"path":"/..."}},
{"pid":1,"tid":1,"ph":"E","cat":"parse","ts":...,"name":"createSourceFile","args":{"path":"/..."}},
{"pid":1,"tid":1,"ph":"X","cat":"program","ts":...,"name":"resolveModuleNamesWorker","dur":...,"args":{"containingFileName":"/..."}},
...
output
cat trace/types.json
<<output
[{"id":1,"intrinsicName":"any","recursionId":0,"flags":["..."]},
{"id":2,"intrinsicName":"any","recursionId":1,"flags":["..."]},
{"id":3,"intrinsicName":"any","recursionId":2,"flags":["..."]},
{"id":4,"intrinsicName":"error","recursionId":3,"flags":["..."]},
{"id":5,"intrinsicName":"unresolved","recursionId":4,"flags":["..."]},
{"id":6,"intrinsicName":"any","recursionId":5,"flags":["..."]},
{"id":7,"intrinsicName":"intrinsic","recursionId":6,"flags":["..."]},
{"id":8,"intrinsicName":"unknown","recursionId":7,"flags":["..."]},
{"id":9,"intrinsicName":"unknown","recursionId":8,"flags":["..."]},
{"id":10,"intrinsicName":"undefined","recursionId":9,"flags":["..."]},
{"id":11,"intrinsicName":"undefined","recursionId":10,"flags":["..."]},
{"id":12,"intrinsicName":"null","recursionId":11,"flags":["..."]},
{"id":13,"intrinsicName":"string","recursionId":12,"flags":["..."]},
...
output
tsconfig.json
에서 지정하세요....
"compilerOptions": [
...
"module": "es2020"
]
...
package.json
의 필드 type
을 "module"
로 설정할 수 있으며, 이는 ES 모듈과 함께 node.js를 사용하는 데 필요합니다. 대부분의 경우 타입스크립트에 대해서는 이 정도면 충분하며 위의 컴파일러 옵션은 필요하지 않습니다....
"type": "module"
...
class List<T> {
private list: T[] = [];
get(key: number): T {
return this.list[key];
}
push(value: T): void {
this.list.push(value);
}
}
function makeList<T>(items: T[]): List<T> {
const list = new List<T>();
items.forEach(item => list.push(item));
return list;
}
// 목록을 생성하지만 특정 값만 허용하는 함수를 만들고 싶다고 가정해 보겠습니다.
// 이전 방식
// 래퍼 함수를 수동으로 정의하고 인자를 전달해야 합니다.
function makeStringList(text: string[]) {
return makeList(text);
}
// 새로운 방식
// 인스턴스화 표현식을 사용하면 이 작업이 훨씬 쉬워집니다.
const makeNumberList = makeList<number>;
extends
을 사용하여 직접 범위를 좁히거나 제한할 수 있습니다.// 배열의 첫 번째 요소가 string인 경우에만 가져오는 타입을 입력한다고 가정해 보겠습니다.
// 이를 위해 조건부 타입을 사용할 수 있습니다.
// 이전 방식
type FirstIfStringOld<T> =
T extends [infer S, ...unknown[]]
? S extends string ? S : never
: never;
// 하지만 여기에는 두 개의 중첩된 조건부 타입들이 필요합니다. 하나로도 수행할 수도 있습니다.
type FirstIfString<T> =
T extends [string, ...unknown[]]
// `T`에서 첫 번째 타입을 가져옵니다.
? T[0]
: never;
// 올바른 타입에 대해 배열을 인덱싱해야 하므로 여전히 차선책입니다.
// 새로운 방식
// infer 타입 변수에 제약 조건 확장을 사용하면 훨씬 쉽게 선언할 수 있습니다.
type FirstIfStringNew<T> =
T extends [infer S extends string, ...unknown[]]
? S
: never;
// 입력 방식은 이전과 동일하며, 단지 구문이 더 깔끔해졌을 뿐입니다.
type A = FirstIfStringNew<[string, number, number]>; // string
type B = FirstIfStringNew<["hello", number, number]>; // "hello"
type C = FirstIfStringNew<["hello" | "world", boolean]>; // "hello" | "world"
type D = FirstIfStringNew<[boolean, number, string]>; // never
// 다른 인터페이스를 확장하는 인터페이스/클래스가 있다고 가정해 보겠습니다.
interface Animal {
animalStuff: any;
}
interface Dog extends Animal {
dogStuff: any;
}
// 그리고 제네릭 "getter"와 "setter"도 있습니다.
type Getter<T> = () => T;
type Setter<T> = (value: T) => void;
// Getter<T1>이 Getter<T2>와 일치하는지 또는 Setter<T1>이 Setter<T2>와 일치하는지 확인하려면 이는 공변성에 따라 달라집니다.
function useAnimalGetter(getter: Getter<Animal>) {
getter();
}
// 이제 Getter를 함수에 전달할 수 있습니다.
useAnimalGetter((() => ({ animalStuff: 0 }) as Animal));
// 이것은 분명히 동작합니다.
// 하지만 대신 Dog를 반환하는 Getter를 사용하려면 어떻게 해야 할까요?
useAnimalGetter((() => ({ animalStuff: 0, dogStuff: 0 }) as Dog));
// Dog도 Animal이기 때문에 이것 또한 동작합니다.
function useDogGetter(getter: Getter<Dog>) {
getter();
}
// useDogGetter 함수에 대해 동일한 시도를 하면 동일한 동작을 얻지 못할 것입니다.
useDogGetter((() => ({ animalStuff: 0 }) as Animal)); // Type error: Property 'dogStuff' is missing in type 'Animal' but required in type 'Dog'.
// Animal이 아닌 Dog로 예상되기 때문에 작동하지 않습니다.
useDogGetter((() => ({ animalStuff: 0, dogStuff: 0 }) as Dog));
// 하지만 이것은 동작합니다.
// 직관적으로 Setter가 동일하게 작동할 것으로 예상할 수 있지만 그렇지 않습니다.
function setAnimalSetter(setter: Setter<Animal>, value: Animal) {
setter(value);
}
// 같은 타입의 Setter를 전달해도 여전히 작동합니다.
setAnimalSetter((value: Animal) => {}, { animalStuff: 0 });
function setDogSetter(setter: Setter<Dog>, value: Dog) {
setter(value);
}
// 여기에서도 동일합니다.
setDogSetter((value: Dog) => {}, { animalStuff: 0, dogStuff: 0 });
// 하지만 Dog Setter를 setAnimalSetter 함수에 전달하면 Getters와 동작이 반대로 바뀝니다.
setAnimalSetter((value: Dog) => {}, { animalStuff: 0, dogStuff: 0 }); // Type error: Argument of type '(value: Dog) => void' is not assignable to parameter of type 'Setter<Animal>'.
// 이번에는 그 반대로 작동합니다.
setDogSetter((value: Animal) => {}, { animalStuff: 0, dogStuff: 0 });
// 새로운 방식
// 타입스크립트에 이를 알리려면(필수는 아니지만 가독성을 위해 도움이 됩니다), 새로운 타입 파라미터에 대한 선택적 변성 주석을 사용하세요.
type GetterNew<out T> = () => T;
type SetterNew<in T> = (value: T) => void;
moduleSuffixes
를 사용한 사용자 정의 모듈 해석: 사용자 정의 파일 접미사가 있는 환경 (예 : 네이티브 앱 빌드의 경우 .ios
)를 사용하는 경우 타입스크립트에서 import를 올바르게 해결하도록 이러한 접미사를 지정할 수 있습니다. tsconfig.json
에서 지정하세요....
"compilerOptions": [
...
"moduleSuffixes": [".ios", ".native", ""]
]
...
import * as foo from './foo';
// 먼저 ./foo.ios.ts, ./foo.native.ts, 마지막으로 ./foo.ts를 확인합니다.
.d.ts
)보다 .ts
및 .js
파일을 선호합니다.satisfies
연산자를 사용하면 해당 타입을 실제로 할당하지 않고도 타입과의 호환성을 확인할 수 있습니다. 이를 통해 호환성을 유지하면서 보다 정확한 추론된 타입을 유지할 수 있습니다.// 이전 방식
// 다양한 항목과 그 색상을 저장하는 object/map/dictionary가 있다고 가정해 보겠습니다.
const obj = {
fireTruck: [255, 0, 0],
bush: '#00ff00',
ocean: [0, 0, 255]
} // { fireTruck: number[]; bush: string; ocean: number[]; }
// 이렇게 하면 프로퍼티를 암시적으로 타이핑하여 배열과 문자열에 대해 작업할 수 있습니다.
const rgb1 = obj.fireTruck[0]; // number
const hex = obj.bush; // string
// 특정 객체만 허용하고 싶다고 가정해 봅시다.
// Record 타입을 사용할 수 있습니다.
const oldObj: Record<string, [number, number, number] | string> = {
fireTruck: [255, 0, 0],
bush: '#00ff00',
ocean: [0, 0, 255]
} // Record<string, [number, number, number] | string>
// 하지만 이제 프로퍼티의 타이핑이 사라집니다.
const oldRgb1 = oldObj.fireTruck[0]; // string | number
const oldHex = oldObj.bush; // string | number
// 새로운 방식
// satisfies 키워드를 사용하면 실제로 할당하지 않고도 타입과의 호환성을 확인할 수 있습니다.
const newObj = {
fireTruck: [255, 0, 0],
bush: '#00ff00',
ocean: [0, 0, 255]
} satisfies Record<string, [number, number, number] | string> // { fireTruck: [number, number, number]; bush: string; ocean: [number, number, number]; }
// 여전히 프로퍼티의 타입이 유지되며, 배열이 튜플이 됨으로써 더욱 정확해졌습니다.
const newRgb1 = newObj.fireTruck[0]; // number
const newRgb4 = newObj.fireTruck[3]; // Type error: Tuple type '[number, number, number]' of length '3' has no element at index '3'.
const newHex = newObj.bush; // string
안녕하세요,
// 새로운 방식
// 대신 새로운 널 병합 연산자를 사용할 수 있습니다. 이 연산자는 오직 undefined와 null 값에만 적용됩니다.
const newValue = value ?? 'hello';
console.log(newValue) // 항상 "hello"
에서, 항상 "hello" 가 아닌 'test' 혹은 'hello' 인 것 같습니다.
Just love this idea! I hate Prettier because there are solid rules that can't be changed, but I'm forced to use them because they're not good enough for AST-based auto fixing and there's nothing else good about it besides Prettier. https://www.wikiproficiency.com/
TS 5버전이 나오면서 많은 변화가 일어났는데, 저는 이것들을 불편하고 의미 없는 요소라 생각했었습니다. 그러나 지금 제가 당연하게 쓰고 있는 기능들이 많은데, 비교적 최근에 추가된 기능이라는 것을 이 글을 통해 알게 되었습니다. 여러모로 많은 것을 생각하게 해주는 글입니다. 좋은 글 감사합니다