paint-brush
TypeScript의 강력한 활용: tsconfig의 주요 고려 사항~에 의해@nodge
2,700 판독값
2,700 판독값

TypeScript의 강력한 활용: tsconfig의 주요 고려 사항

~에 의해 Maksim Zemskov13m2023/07/12
Read on Terminal Reader
Read this story w/o Javascript

너무 오래; 읽다

TypeScript는 강력한 유형 시스템과 정적 분석 기능 덕분에 복잡한 애플리케이션을 구축하는 데 널리 사용되는 언어입니다. 그러나 최대 유형 안전성을 달성하려면 tsconfig를 올바르게 구성하는 것이 중요합니다. 이 기사에서는 최적의 유형 안전성을 달성하기 위해 tsconfig를 구성하기 위한 주요 고려 사항에 대해 설명합니다.
featured image - TypeScript의 강력한 활용: tsconfig의 주요 고려 사항
Maksim Zemskov HackerNoon profile picture
0-item
1-item

복잡한 웹 애플리케이션을 구축하는 경우 TypeScript가 프로그래밍 언어로 선택될 가능성이 높습니다. TypeScript는 강력한 유형 시스템과 정적 분석 기능으로 많은 사랑을 받고 있으며, 코드가 강력하고 오류가 없는지 확인하는 강력한 도구입니다.


또한 코드 편집기와의 통합을 통해 개발 프로세스를 가속화하여 개발자가 코드를 보다 효율적으로 탐색하고 보다 정확한 힌트와 자동 완성을 얻을 수 있을 뿐만 아니라 대량의 코드를 안전하게 리팩토링할 수 있습니다.


컴파일러는 유형 정확성을 확인하고 TypeScript 코드를 JavaScript로 변환하는 역할을 하는 TypeScript의 핵심입니다. 그러나 TypeScript의 기능을 최대한 활용하려면 컴파일러를 올바르게 구성하는 것이 중요합니다.


각 TypeScript 프로젝트에는 컴파일러에 대한 모든 구성 옵션을 보유하는 하나 이상의 tsconfig.json 파일이 있습니다.


tsconfig 구성은 TypeScript 프로젝트에서 최적의 유형 안전성과 개발자 경험을 달성하는 데 중요한 단계입니다. 관련된 모든 핵심 요소를 신중하게 고려하는 데 시간을 투자하면 개발 프로세스 속도를 높이고 코드가 강력하고 오류가 없는지 확인할 수 있습니다.

표준 구성의 단점

tsconfig의 기본 구성으로 인해 개발자는 TypeScript의 대부분의 이점을 놓칠 수 있습니다. 이는 많은 강력한 유형 검사 기능을 활성화하지 않기 때문입니다. "기본" 구성이란 유형 검사 컴파일러 옵션이 설정되지 않은 구성을 의미합니다.


예를 들어:


 { "compilerOptions": { "target": "esnext", "module": "esnext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, }, "include": ["src"] }


몇 가지 주요 구성 옵션이 없으면 두 가지 주요 이유로 인해 코드 품질이 저하될 수 있습니다. 첫째, TypeScript의 컴파일러는 다양한 경우에 nullundefined 유형을 잘못 처리할 수 있습니다.


둘째, any 유형이 코드베이스에 제어할 수 없게 나타날 수 있으며, 이로 인해 이 유형에 대한 유형 검사가 비활성화될 수 있습니다.


다행히도 이러한 문제는 구성에서 몇 가지 옵션을 조정하면 쉽게 해결할 수 있습니다.

엄격 모드

 { "compilerOptions": { "strict": true } }


엄격 모드는 광범위한 유형 검사 동작을 활성화하여 프로그램 정확성을 더욱 강력하게 보장하는 필수 구성 옵션입니다.


tsconfig 파일에서 엄격 모드를 활성화하는 것은 최대 유형 안전성과 더 나은 개발자 경험을 달성하기 위한 중요한 단계입니다.


tsconfig를 구성하는 데 약간의 추가 노력이 필요하지만 프로젝트 품질을 향상시키는 데 큰 도움이 될 수 있습니다.


strict 컴파일러 옵션은 noImplicitAny , strictNullChecks , strictFunctionTypes 등을 포함한 모든 엄격 모드 제품군 옵션을 활성화합니다.


이러한 옵션은 별도로 구성할 수도 있지만 해당 옵션을 끄는 것은 권장되지 않습니다. 그 이유를 알아보기 위해 예를 살펴보겠습니다.

암시적 추론

 { "compilerOptions": { "noImplicitAny": true } }


any 유형은 정적 유형 시스템의 위험한 허점이며 이를 사용하면 모든 유형 검사 규칙이 비활성화됩니다. 결과적으로 TypeScript의 모든 이점이 사라집니다. 버그가 누락되고, 코드 편집기 힌트가 제대로 작동하지 않는 등의 문제가 발생합니다.


극단적인 경우나 프로토타입 제작이 필요한 경우 any 것을 사용해도 괜찮습니다. 최선의 노력에도 불구하고 any 유형이 때때로 암시적으로 코드베이스에 몰래 들어갈 수 있습니다.


기본적으로 컴파일러는 코드베이스에 any 나타나는 대가로 많은 오류를 용서합니다. 특히 TypeScript를 사용하면 유형을 자동으로 추론할 수 없는 경우에도 변수 유형을 지정하지 않을 수 있습니다.


문제는 예를 들어 함수 인수에 변수 유형을 지정하는 것을 실수로 잊어버릴 수 있다는 것입니다. 오류를 표시하는 대신 TypeScript는 자동으로 변수 유형을 any 로 추론합니다.


 function parse(str) { // ^? any return str.split(''); } // TypeError: str.split is not a function const res1 = parse(42); const res2 = parse('hello'); // ^? any


noImplicitAny 컴파일러 옵션을 활성화하면 컴파일러는 변수 유형이 자동으로 any 로 유추되는 모든 위치를 강조 표시합니다. 이 예에서 TypeScript는 함수 인수의 유형을 지정하라는 메시지를 표시합니다.


 function parse(str) { // ^ Error: Parameter 'str' implicitly has an 'any' type. return str.split(''); }


유형을 지정하면 TypeScript는 문자열 매개변수에 숫자를 전달하는 오류를 빠르게 포착합니다. res2 변수에 저장된 함수의 반환 값도 올바른 유형을 갖습니다.


 function parse(str: string) { return str.split(''); } const res1 = parse(42); // ^ Error: Argument of type 'number' is not // assignable to parameter of type 'string' const res2 = parse('hello'); // ^? string[]


Catch 변수의 알 수 없는 유형

 { "compilerOptions": { "useUnknownInCatchVariables": true } }


useUnknownInCatchVariables 구성하면 try-catch 블록에서 예외를 안전하게 처리할 수 있습니다. 기본적으로 TypeScript는 catch 블록의 오류 유형이 any 라고 가정하므로 오류에 대해 무엇이든 할 수 있습니다.


예를 들어, 발견된 오류를 있는 그대로 Error 인스턴스를 허용하는 로깅 함수에 전달할 수 있습니다.


 function logError(err: Error) { // ... } try { return JSON.parse(userInput); } catch (err) { // ^? any logError(err); }


그러나 실제로는 오류 유형에 대한 보장이 없으며 오류가 발생한 런타임 시에만 오류의 실제 유형을 확인할 수 있습니다. 로깅 함수가 Error 가 아닌 것을 수신하면 런타임 오류가 발생합니다.


따라서 useUnknownInCatchVariables 옵션은 오류 유형을 any 에서 unknown 으로 전환하여 작업을 수행하기 전에 오류 유형을 확인하도록 상기시킵니다.


 try { return JSON.parse(userInput); } catch (err) { // ^? unknown // Now we need to check the type of the value if (err instanceof Error) { logError(err); } else { logError(new Error('Unknown Error')); } }


이제 TypeScript는 err 변수를 logError 함수에 전달하기 전에 해당 변수의 유형을 확인하라는 메시지를 표시하여 더 정확하고 안전한 코드를 생성합니다. 불행하게도 이 옵션은 promise.catch() 함수나 콜백 함수의 입력 오류에는 도움이 되지 않습니다.


하지만 다음 기사에서는 any 경우에 대처하는 방법을 논의할 것입니다.

호출 유형 확인 및 적용 방법

 { "compilerOptions": { "strictBindCallApply": true } }


또 다른 옵션은 callapply 통해 any 내 호출의 모양을 수정합니다. 이는 처음 두 경우보다 덜 일반적인 경우이지만 여전히 고려하는 것이 중요합니다. 기본적으로 TypeScript는 이러한 구성의 유형을 전혀 확인하지 않습니다.


예를 들어 함수의 인수로 무엇이든 전달할 수 있으며 결국에는 항상 any 유형을 받게 됩니다.


 function parse(value: string) { return parseInt(value, 10); } const n1 = parse.call(undefined, '10'); // ^? any const n2 = parse.call(undefined, false); // ^? any


strictBindCallApply 옵션을 활성화하면 TypeScript가 더 똑똑해 지므로 반환 유형이 number 로 올바르게 추론됩니다. 그리고 잘못된 유형의 인수를 전달하려고 하면 TypeScript는 오류를 가리킵니다.


 function parse(value: string) { return parseInt(value, 10); } const n1 = parse.call(undefined, '10'); // ^? number const n2 = parse.call(undefined, false); // ^ Argument of type 'boolean' is not // assignable to parameter of type 'string'.


실행 컨텍스트에 대한 엄격한 유형

 { "compilerOptions": { "noImplicitThis": true } }


프로젝트에 나타나는 any 방지하는 데 도움이 될 수 있는 다음 옵션은 함수 호출의 실행 컨텍스트 처리를 수정합니다. JavaScript의 동적 특성으로 인해 함수 내부의 컨텍스트 유형을 정적으로 결정하기가 어렵습니다.


기본적으로 TypeScript는 이러한 경우 컨텍스트에 any 유형을 사용하고 경고를 제공하지 않습니다.


 class Person { private name: string; constructor(name: string) { this.name = name; } getName() { return function () { return this.name; // ^ 'this' implicitly has type 'any' because // it does not have a type annotation. }; } }


noImplicitThis 컴파일러 옵션을 활성화하면 함수에 대한 컨텍스트 유형을 명시적으로 지정하라는 메시지가 표시됩니다. 이렇게 하면 위의 예에서 Person 클래스의 name 필드 대신 함수 컨텍스트에 액세스하는 오류를 잡을 수 있습니다.


TypeScript의 Null 및 정의되지 않은 지원

 { "compilerOptions": { "strictNullChecks": true } }


다음으로 strict 모드에 포함된 여러 옵션은 코드베이스에 any 유형도 나타나지 않습니다. 그러나 TS 컴파일러의 동작을 더욱 엄격하게 만들고 개발 중에 더 많은 오류가 발견될 수 있도록 합니다.


첫 번째 옵션은 TypeScript에서 nullundefined 처리를 수정합니다. 기본적으로 TypeScript는 nullundefined 모든 유형에 대해 유효한 값이라고 가정하므로 예기치 않은 런타임 오류가 발생할 수 있습니다.


strictNullChecks 컴파일러 옵션을 활성화하면 개발자는 nullundefined 경우가 발생할 수 있는 경우를 명시적으로 처리해야 합니다.


예를 들어 다음 코드를 고려해보세요.


 const users = [ { name: 'Oby', age: 12 }, { name: 'Heera', age: 32 }, ]; const loggedInUser = users.find(u => u.name === 'Max'); // ^? { name: string; age: number; } console.log(loggedInUser.age); // ^ TypeError: Cannot read properties of undefined


이 코드는 오류 없이 컴파일되지만 "Max"라는 이름의 사용자가 시스템에 존재하지 않고 users.find() undefined 반환하는 경우 런타임 오류가 발생할 수 있습니다. 이를 방지하기 위해 strictNullChecks 컴파일러 옵션을 활성화할 수 있습니다.


이제 TypeScript는 users.find() 에 의해 반환되는 null 또는 undefined 가능성을 명시적으로 처리하도록 강제합니다.


 const loggedInUser = users.find(u => u.name === 'Max'); // ^? { name: string; age: number; } | undefined if (loggedInUser) { console.log(loggedInUser.age); }


nullundefiined 가능성을 명시적으로 처리함으로써 런타임 오류를 방지하고 코드가 더욱 강력하고 오류가 없는지 확인할 수 있습니다.

엄격한 함수 유형

 { "compilerOptions": { "strictFunctionTypes": true } }


strictFunctionTypes 활성화하면 TypeScript의 컴파일러가 더욱 지능화됩니다. 버전 2.6 이전에는 TypeScript가 함수 인수의 반공변성을 확인하지 않았습니다. 잘못된 유형의 인수를 사용하여 함수를 호출하면 런타임 오류가 발생합니다.


예를 들어, 함수 유형이 문자열과 숫자를 모두 처리할 수 있더라도 문자열만 처리할 수 있는 해당 유형에 함수를 할당할 수 있습니다. 여전히 해당 함수에 숫자를 전달할 수 있지만 런타임 오류가 발생합니다.


 function greet(x: string) { console.log("Hello, " + x.toLowerCase()); } type StringOrNumberFn = (y: string | number) => void; // Incorrect Assignment const func: StringOrNumberFn = greet; // TypeError: x.toLowerCase is not a function func(10);


다행히 strictFunctionTypes 옵션을 활성화하면 이 동작이 수정되고, 컴파일러는 컴파일 타임에 이러한 오류를 포착하여 함수의 유형 비호환성에 대한 자세한 메시지를 표시할 수 있습니다.


 const func: StringOrNumberFn = greet; // ^ Type '(x: string) => void' is not assignable to type 'StringOrNumberFn'. // Types of parameters 'x' and 'y' are incompatible. // Type 'string | number' is not assignable to type 'string'. // Type 'number' is not assignable to type 'string'.


클래스 속성 초기화

 { "compilerOptions": { "strictPropertyInitialization": true } }


마지막으로 strictPropertyInitialization 옵션을 사용하면 undefined 값을 값으로 포함하지 않는 유형에 대한 필수 클래스 속성 초기화를 확인할 수 있습니다.


예를 들어 다음 코드에서는 개발자가 email 속성을 초기화하는 것을 잊었습니다. 기본적으로 TypeScript는 이 오류를 감지하지 않으며 런타임에 문제가 발생할 수 있습니다.


 class UserAccount { name: string; email: string; constructor(name: string) { this.name = name; // Forgot to assign a value to this.email } }


그러나 strictPropertyInitialization 옵션이 활성화되면 TypeScript는 이 문제를 강조 표시합니다.


 email: string; // ^ Error: Property 'email' has no initializer and // is not definitely assigned in the constructor.

안전한 인덱스 서명

 { "compilerOptions": { "noUncheckedIndexedAccess": true } }


noUncheckedIndexedAccess 옵션은 strict 모드의 일부는 아니지만 프로젝트의 코드 품질을 향상시키는 데 도움이 될 수 있는 또 다른 옵션입니다. 이를 통해 인덱스 액세스 표현식에 null 또는 undefined 반환 유형이 있는지 확인하여 런타임 오류를 방지할 수 있습니다.


캐시된 값을 저장하기 위한 객체가 있는 다음 예제를 고려해보세요. 그런 다음 키 중 하나의 값을 얻습니다. 물론 원하는 키의 값이 실제로 캐시에 존재한다는 보장은 없습니다.


기본적으로 TypeScript는 값이 존재하고 string 유형을 갖는다고 가정합니다. 이로 인해 런타임 오류가 발생할 수 있습니다.


 const cache: Record<string, string> = {}; const value = cache['key']; // ^? string console.log(value.toUpperCase()); // ^ TypeError: Cannot read properties of undefined


TypeScript에서 noUncheckedIndexedAccess 옵션을 활성화하려면 undefined 반환 유형에 대한 인덱스 액세스 표현식을 확인해야 하며, 이는 런타임 오류를 방지하는 데 도움이 될 수 있습니다. 이는 배열의 요소에 액세스하는 경우에도 적용됩니다.


 const cache: Record<string, string> = {}; const value = cache['key']; // ^? string | undefined if (value) { console.log(value.toUpperCase()); }

권장 구성

논의된 옵션에 따라 최적의 유형 안전성을 위해 프로젝트의 tsconfig.json 파일에서 strictnoUncheckedIndexedAccess 옵션을 활성화하는 것이 좋습니다.


 { "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, } }


strict 옵션을 이미 활성화한 경우 strict: true 옵션이 중복되지 않도록 다음 옵션을 제거하는 것이 좋습니다.


  • noImplicitAny
  • useUnknownInCatchVariables
  • strictBindCallApply
  • noImplicitThis
  • strictFunctionTypes
  • strictNullChecks
  • strictPropertyInitialization


또한 유형 시스템을 약화시키거나 런타임 오류를 일으킬 수 있는 다음 옵션을 제거하는 것이 좋습니다.


  • keyofStringsOnly
  • noStrictGenericChecks
  • suppressImplicitAnyIndexErrors
  • suppressExcessPropertyErrors


이러한 옵션을 신중하게 고려하고 구성하면 TypeScript 프로젝트에서 최적의 유형 안전성과 더 나은 개발자 경험을 얻을 수 있습니다.

결론

TypeScript는 컴파일러와 유형 시스템을 지속적으로 개선하면서 많은 발전을 이루었습니다. 그러나 이전 버전과의 호환성을 유지하기 위해 TypeScript 구성은 유형 검사 품질에 큰 영향을 미칠 수 있는 옵션이 많아 더욱 복잡해졌습니다.


이러한 옵션을 신중하게 고려하고 구성하면 TypeScript 프로젝트에서 최적의 유형 안전성과 더 나은 개발자 경험을 얻을 수 있습니다. 프로젝트 구성에서 어떤 옵션을 활성화하고 제거할지 아는 것이 중요합니다.


특정 옵션을 비활성화하면 결과를 이해하면 각 옵션에 대해 정보를 바탕으로 결정을 내릴 수 있습니다.


엄격한 입력으로 인해 결과가 발생할 수 있다는 점을 명심하는 것이 중요합니다. JavaScript의 동적 특성을 효과적으로 처리하려면 단순히 변수 뒤에 "숫자" 또는 "문자열"을 지정하는 것 이상으로 TypeScript에 대한 충분한 이해가 필요합니다.


발생할 유형 관련 문제를 보다 효과적으로 해결하려면 더 복잡한 구조와 TypeScript 우선 라이브러리 및 도구 생태계에 익숙해져야 합니다.


결과적으로 코드를 작성하려면 조금 더 많은 노력이 필요할 수 있지만, 내 경험에 따르면 이러한 노력은 장기 프로젝트에서는 그만한 가치가 있습니다.


이 기사에서 새로운 것을 배웠기를 바랍니다. 이것은 시리즈의 첫 번째 부분입니다. 다음 기사에서는 TypeScript 표준 라이브러리의 유형을 개선하여 더 나은 유형 안전성과 코드 품질을 달성하는 방법에 대해 논의하겠습니다. 계속 지켜봐 주시기 바랍니다. 읽어주셔서 감사합니다!

유용한 링크