├── .gitbook └── assets │ ├── autocomplete-js.png │ ├── autocomplete-ts.png │ ├── image.png │ ├── undefined.png │ └── vscode-main.png ├── 01-introducing-typescript ├── elements-of-typescript.md ├── history-of-typescript.md ├── intro.md ├── static-type-analysis.md └── why-typescript.md ├── 02-ecmascript ├── block-level-scope │ ├── README.md │ ├── declaration-using-const.md │ ├── declaration-using-let.md │ └── scope-best-practice.md ├── element-enumeration │ ├── README.md │ ├── for-of.md │ ├── foreach.md │ ├── iterable-protocol.md │ └── iterator-protocol.md ├── function │ ├── README.md │ ├── arrow-functions.md │ └── default-parameter.md ├── handling-asynchronous │ ├── README.md │ ├── async-await.md │ └── promises.md ├── intro.md ├── object-and-array │ ├── README.md │ ├── destructuring-assignment.md │ ├── object-literal-diff.md │ └── rest-and-spread-operator.md ├── outro.md └── template-literal │ ├── README.md │ ├── multiline-string.md │ └── string-substitution.md ├── 03-basic-grammar ├── array-and-tuple.md ├── enums.md ├── function.md ├── generics.md ├── intersection-type.md ├── intro.md ├── object.md ├── primitive-types.md ├── type-alias.md └── union-type.md ├── 04-interface-and-class ├── class-advanced │ ├── README.md │ ├── abstract-classes.md │ ├── access-specifiers.md │ ├── accessors.md │ └── static-members.md ├── classes.md ├── connecting-interface-and-class.md ├── extending-classes.md ├── extending-interfaces.md ├── indexable-types.md ├── interface-basics.md ├── intro.md └── outro.md ├── 05-type-compatibility ├── 6-4.md ├── classes.md ├── enums.md ├── functions.md ├── generics.md ├── intro.md ├── objects.md ├── outro.md └── primitive-types.md ├── 06-type-system-deepdive ├── 6-18.md ├── disjoint-union-type.md ├── intro.md ├── outro.md ├── type-assertion.md ├── type-inference.md ├── type-narrowing.md └── types-as-sets.md ├── 07-advanced-types ├── 7-2.md └── intro.md ├── 08-modules-and-namespaces ├── 7-16.md └── intro.md ├── 09-real-world-projects ├── 7-30.md └── intro.md ├── 10-libraries ├── 8-13.md ├── intro.md └── outro.md ├── README.md ├── SUMMARY.md ├── appendix-ii-js-ecosystem ├── ecmascript-tc39.md ├── example-array.prototype.includes.md ├── tc39-process.md └── typescript-and-ecmascript.md └── intro.md /.gitbook/assets/autocomplete-js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heejongahn/ts-for-jsdev/d817777730cbb6385ecfcec2df5ef2168c2a9be7/.gitbook/assets/autocomplete-js.png -------------------------------------------------------------------------------- /.gitbook/assets/autocomplete-ts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heejongahn/ts-for-jsdev/d817777730cbb6385ecfcec2df5ef2168c2a9be7/.gitbook/assets/autocomplete-ts.png -------------------------------------------------------------------------------- /.gitbook/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heejongahn/ts-for-jsdev/d817777730cbb6385ecfcec2df5ef2168c2a9be7/.gitbook/assets/image.png -------------------------------------------------------------------------------- /.gitbook/assets/undefined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heejongahn/ts-for-jsdev/d817777730cbb6385ecfcec2df5ef2168c2a9be7/.gitbook/assets/undefined.png -------------------------------------------------------------------------------- /.gitbook/assets/vscode-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heejongahn/ts-for-jsdev/d817777730cbb6385ecfcec2df5ef2168c2a9be7/.gitbook/assets/vscode-main.png -------------------------------------------------------------------------------- /01-introducing-typescript/elements-of-typescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트를 이루는 구성요소에는 어떤 것들이 있는지 살펴본다. 3 | --- 4 | 5 | # 1.3 타입스크립트의 구성요소 6 | 7 | 우리가 “타입스크립트”라고 지칭할 때, 실제로는 다양한 구성 요소의 합을 뭉뚱그려 부르는 것에 가깝다. 이 구성 요소들을 분류할 수 있는 단 하나의 올바른 기준은 없지만, 다음과 같이 큼지막한 덩어리들로 나눠 볼 수 있겠다. 8 | 9 | ## 언어 **명세** 10 | 11 | 가장 먼저, 타입스크립트 언어 명세\(specification\)가 있다. 이 명세는 `.ts`\(또는 `.tsx`\) 확장자를 갖는 **타입스크립트 코드가 어떤 의미를 갖는지에 대한 약속**이다. 예를 들어 아래와 같은 코드를 생각해보자. 12 | 13 | ```typescript 14 | const a: number = 32; 15 | const b: string = 3; // error 16 | ``` 17 | 18 | 타입스크립트를 아는 프로그래머라면 누구나 위의 코드를 읽고 다음과 같이 예측할 수 있다. 19 | 20 | > 이 코드는 `a` 변수는 `number` 타입이고 `32`라는 값을 가지며, `b` 변수는 `string` 타입이고 `3`이라는 값을 갖는다. 그런데 `3`은 `string` 타입이 아니니까 여기선 에러가 나야한다. 21 | 22 | 이 때 서로 다른 프로그래머는 각기 다른 생각을 가짐에도 모두 동일한 예측을 할 수 있다. 그 이유는 **코드가 어떤 의미를 갖고 어떻게 동작해야하는지를 정확히 기술해 둔 문서, 즉 언어 명세**가 존재하기 때문이다. 23 | 24 | 사실 2018년 5월 기준으로, 타입스크립트 명세는 그다지 잘 관리되고 있지 않다. 타입스크립트는 매우 활발하고 빠르게 발전하고 있는 프로젝트이다. 그런 인기에 비해 노동력이 부족한 탓인지, 2018년 5월 기준 최신 버전이 2.8임에도 깃허브에 올라온 명세 문서는 아직 2016년 1월에 작성된 1.8 버전에 머물러 있다. 25 | 26 | [“Spec” 라벨이 달린 이슈들](https://github.com/Microsoft/TypeScript/labels/Spec)이 오래 전부터 꾸준히 생성되어 현재 매우 많이 열려있는 것으로 미루어보아 코어 개발팀 역시 문제는 인지하고 있는 것으로 보인다. 부디 빠르게 개선이 되길 바란다. 27 | 28 | ## **컴파일러** 29 | 30 | 명세는 중요하지만 기본적으로 규약일 뿐 실제 구현체가 아니다. 명세가 업데이트 되지 않고 있더라도 구현체, 즉 타입스크립트 컴파일러\(`tsc`\)는 꾸준히 업데이트 되어왔다. 그 때문에 타입스크립트 버전이 꾸준히 올라가고 사람들은 새 버전을 별 문제 없이 사용할 수 있다. 타입스크립트 컴파일러는 **타입스크립트 코드를 입력**으로 받아, 명세에 맞게 해석한 후 **대응되는 자바스크립트 코드를 출력**으로 내뱉는다. 31 | 32 | 컴파일러는 많은 부분의 합으로 이루어진 복잡한 프로그램이다. 대표적인 부품으론 코드를 읽고 구문을 해석하는 파서\(parser\), 타입 정보에 모순이 없는지 검사하는 타입 검사기\(type checker\), 그리고 브라우저가 실행 가능한 자바스크립트 파일을 뱉어내는 에미터\(emitter\) 등이 있다. 대부분의 사용자는 컴파일러의 사용법을 아는 것으로 충분하지만, 혹 컴파일러의 내부 구조가 궁금하다면 [관련 위키 문서](https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview)를 참조할 것을 추천한다. 33 | 34 | ## **생태계** 35 | 36 | 마지막으로 광의에서 타입스크립트를 구성한다 할 수 있는 생태계가 있다. 타입스크립트의 코어 개발자를 비롯한 수많은 컨트리뷰터, 타입스크립트를 지원하는 도구, 타입스크립트로 쓰여진 라이브러리, 개발자 커뮤니티 등이 여기에 해당한다. 아무리 디자인이 훌륭하고 뛰어난 아이디어를 구현한 언어여도 커뮤니티 형성에 실패하면 시들기 마련이다. 타입스크립트는 이 점에 많은 공을 들였고, 현재로선 **아주 크게 성공했다**. 37 | 38 | -------------------------------------------------------------------------------- /01-introducing-typescript/history-of-typescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트의 역사를 간략히 다룬다. 3 | --- 4 | 5 | # 1.4 타입스크립트의 역사 6 | 7 | 마지막으로 타입스크립트의 간략한 역사를 살펴보자. 타입스크립트는 [Turbo Pascal](https://en.wikipedia.org/wiki/Turbo_Pascal), [Delphi](https://en.wikipedia.org/wiki/Delphi_%28IDE%29), 그리고 [C\#](https://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29)의 핵심 개발자인 Microsoft 직원 Anders Hejlsberg의 리드 하에 개발되었고, 2년간의 내부 개발을 거쳐 2012년 10월 1일 공개되었다. 공개 당시의 비디오는 지금도 [웹에서 확인할 수 있다](https://channel9.msdn.com/posts/Anders-Hejlsberg-Introducing-TypeScript). 8 | 9 | 그 후, 2014년 4월 마이크로소프트의 Build 행사에서 타입스크립트의 1.0 버전이 공개되었다. 그 사이의 많은 마이너 업데이트를 거쳐 2016년 9월 22일 2.0 버전이 릴리스되었고, 2018년 1월 현재는 2.7.0 버전의 릴리스 후보\(release candidate\)가 공개된 상태다. 10 | 11 | 현재 타입스크립트는 약 2-3달을 기준으로 마이너 버전이 업데이트 되고 있다. 앞으로 타입스크립트가 추구할 대략적인 목표는 깃허브 위키의 [로드맵 문서](https://github.com/Microsoft/TypeScript/wiki/Roadmap)를 통해 엿볼 수 있으나, 다함께 만들어나가는 오픈 소스 프로젝트의 특성 상 그 방향은 어느정도는 열려 있다. 12 | 13 | -------------------------------------------------------------------------------- /01-introducing-typescript/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: '타입스크립트란 무엇인지 알아보고, 그 역사와 더불어 왜 타입스크립트를 배워야 하는지에 대한 근거를 제시한다.' 3 | --- 4 | 5 | # 1.0 들어가며 6 | 7 | 2018년 1월 현재, 타입스크립트 홈페이지의 대문엔 다음과 같은 문장이 적혀 있다. 8 | 9 | > 타입스크립트는 자바스크립트로 컴파일되는, 자바스크립트의 타입이 있는 상위집합입니다. _TypeScript is a typed superset of JavaScript that compiles to plain JavaScript._ 10 | 11 | 이 문장에서 핵심이 되는 두 단어는 타입이 있는\(typed\) 과 상위집합\(superset\)이다. 12 | 13 | ### 타입이 있는 자바스크립트 14 | 15 | 먼저 타입이 있는 자바스크립트란 단어는 보다 정확히는 **정적 타입 시스템\(static type system\)을 도입한 자바스크립트**라는 뜻이다. 정적 타입 시스템이 있는 언어, 즉 정적 타입 언어\(statically typed language\)에서는 프로그램의 예상 동작을 타입을 통해 나타내고, 그 예상에 걸맞게 동작할 지의 여부를 타입 검사기\(type checker\)를 통해 실행 전에 확인할 수 있다. 16 | 17 | 예를 들어 아래와 같은 자바스크립트 코드를: 18 | 19 | ```javascript 20 | function preferTypeScript(person) { 21 | return person.favoriteLanguages.includes('TypeScript'); 22 | } 23 | ``` 24 | 25 | 타입스크립트로는 아래와 같이 작성할 수 있다. 26 | 27 | ```typescript 28 | type Language = 'TypeScript' | 'JavaScript' | 'Python' | 'Rust' | 'Haskell'; 29 | interface Person { 30 | favoriteLanguages: Array; 31 | } 32 | function preferTypeScript(person: Person): boolean { 33 | return person.favoriteLanguages.includes('TypeScript'); 34 | } 35 | ``` 36 | 37 | 이 코드를 지금 당장 이해할 수 없다고 해서 당황할 필요는 없다. 기본적으로는 같은 내용이지만 함수의 인자나 리턴값 등이 만족해야 할 특정 조건에 대한 정보를 추가적으로 담고 있다는 정도로만 받아들이면 충분하다. 타입스크립트 컴파일러에 의해, 이 코드는 앞서 봤던 것과 동일한 자바스크립트 코드로 컴파일된다. 38 | 39 | ```javascript 40 | function preferTypeScript(person) { 41 | return person.favoriteLanguages.includes('TypeScript'); 42 | } 43 | ``` 44 | 45 | ### 자바스크립트의 상위집합 46 | 47 | 다음으로, **타입스크립트는 자바스크립트의 상위집합이다**. 정적 타입 시스템이라는, 기존 자바스크립트에 전혀 없었던 개념을 제공함에도 불구하고 타입스크립트는 자바스크립트와 완전히 동떨어진 다른 언어가 아니다. 모든 적법한 자바스크립트 코드는 적법한 타입스크립트 코드이며, 타입스크립트는 한 글자의 추가적 타이핑도 없이 자바스크립트 코드를 이해할 수 있다. 그 뿐만 아니라 거대한 자바스크립트 프로젝트에서의 점진적인 마이그레이션 또한 지원한다. 48 | 49 | 새로운 언어를 바닥부터 만드는 대신, 그 난해함으로 악명 높은 자바스크립트 코드를 포용하는 결정에는 분명 비용이 뒤따른다. 하지만 바로 그 결정으로 인해 타입스크립트 도입의 진입 장벽은 한껏 낮아졌다. 덕분에 전세계의 수많은 자바스크립트 프로젝트는 보다 적은 노력으로도 정적 타입 분석의 이점을 누릴 수 있게 되었다. 50 | 51 | 타입스크립트는 현존하는 자바스크립트의 문제를 풀기 위해 등장했고, 그 수단으로 정적 타입 분석을 내세웠다. 그렇다면 정적 타입 분석은 무엇이며, 어떤 장점을 제공할까? 정적 타입 분석을 제공하는 여러 대체재 중 타입스크립트를 사용해야 하는 이유는 무엇일까? 또 타입스크립트는 어떤 요소들로 구성되어 있으며, 어떤 역사를 가질까? 52 | 53 | 1장에선 이런 질문들에 대해 답하고자 한다. 54 | 55 | -------------------------------------------------------------------------------- /01-introducing-typescript/static-type-analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: '타입스크립트가 제공하는 정적 타입 분석은 무엇이고, 어떤 장점을 갖는지 다룬다.' 3 | --- 4 | 5 | # 1.1 정적 타입 분석 6 | 7 | 타입은 프로그램의 ‘올바른 동작’이 무엇인지에 대한 프로그래머의 의도를 인코딩하는 수단이다. 8 | 9 | 프로그램의 타입을 분석하는 방식을 기준으로 프로그래밍 언어를 크게 둘로 나눌 수 있다. 바로 프로그램이 실제로 실행 될 때에 타입 분석을 진행하는 동적 타입 언어\(dynamically typed language\)와 프로그램을 실행해보지 않고도 런타임 이전에 진행하는 정적 타입 언어\(statically typed language\)다. 10 | 11 | 정적 타입 언어와 동적 타입 언어 중 어느 쪽이 더 우수한지에 대한 논쟁은 오래도록 이루어져왔고, 아직도 명확한 승자는 정해지지 않았다. 정적 타입 언어는 프로젝트 초기에 더 많은 노력을 필요로 하는 경향이 있다. 또한 요구사항이 정확히 정해지지 않았거나 빠르게 변하는 경우 \(프로토타이핑 등\) 적합하지 않은 도구일 수 있다. 12 | 13 | **정적 타입 분석이 제시하는 장점은 시스템의 평균적인 복잡도가 늘어남에 따라 빛나기 시작한다**. 주요한 장점을 나열해보면 다음과 같다. 14 | 15 | ## **보다 빠른 버그 발견** 16 | 17 | 정적 타입 시스템은 **프로그램이 실제로 실행되기 전에 상당수의 오류를 잡아낼 수 있다**. 같은 종류의 오류가 동적 타입 언어에서는 코드 리뷰, 심지어는 실제 배포가 일어날 때 까지도 안 발견되는 경우도 잦다. 소프트웨어 개발 파이프라인에서는 오류가 늦게 발견 될수록 더 큰 금전적, 시간적 비용을 치루어야 하므로 이는 매우 큰 이점이다. 18 | 19 | ICSE\(International Conference on Software Engineering\) 2017에 발표 된 “[To Type or Not to Type: Quantifying Detectable Bugs in JavaScript](http://earlbarr.com/publications/typestudy.pdf)”이란 논문은 깃허브\(GitHub\)의 공개된 버그를 이용해 정적 타입 시스템의 효과에 대해 다룬다. 20 | 21 | 연구진은 실험군으로 설정한 자바스크립트 코드의 버그 중 약 15% 정도는 정적 타입 시스템이 있었다면 커밋조차 되기 전에 잡혔을 것이라 결론짓는다. **실사용자가 맞닥뜨리는 버그 중 15%를 사전에 예방할 수 있다**는 인상적인 결과에 Microsoft의 한 엔지니어링 매니저는 다음과 같이 반응했다 한다. 22 | 23 | > 충격적이다. 만약 개발하는 방식에 어떤 변화를 주어서 저장소에 들어오는 버그중 10% 이상을 줄일 수 있다면, 고민할 이유가 전혀 없다. 개발에 쓰이는 시간이 두 배 이상 늘어나는 수준이 아닌 한, 우리는 그 변화를 택할 것이다. 24 | 25 | ## **툴링** 26 | 27 | 소스 코드에 대한 정적 타입 분석이 가능하다면 **컴파일러 및 코드 에디터가 코드를 실행하지 않고도 프로그램에 대해 훨씬 더 많은 정보를 알 수 있다**. 그리고 이 정보는 코드 작성 과정에서 유용하게 사용 가능하다. 28 | 29 | 대표적인 예시가 바로 에디터의 자동 완성 기능이다. 만약 타입 시스템이 어떤 변수의 타입 정보를 정확히 안다면, 해당 변수의 멤버로 존재할 수 있는 변수만을 자동 완성 후보로 추천할 수 있다. 30 | 31 | ![타입스크립트 코드의 자동완성](../.gitbook/assets/autocomplete-ts.png) 32 | 33 | 한편 타입 정보를 활용할 수 없는 상황에서는 데이터의 형태에 대한 확신을 가질 수 없다. 이 경우 에디터는 해당 파일에 존재하는 식별자를 전부 늘어놓는 식의 휴리스틱 기반 대안으로 만족하게 된다. 물론 이는 상황에 따라\(해당 객체의 형태가 코드 실행 전에 알려져있는가? 등\) 그리고 편집기의 성능에 따라 달라진다. 하지만 타입 정보가 있을 때 더 신뢰도 높은 추천 결과를 얻을 수 있단 사실은 명백하다. 34 | 35 | ![자바스크립트 코드의 자동완성](../.gitbook/assets/autocomplete-js.png) 36 | 37 | 그 외에도 정적 타입 분석을 이용해 프로그램을 더 잘 이해함으로서 다양한 편의기능을 구현할 수 있다. 일부 예시로 식별자의 정의로 바로 이동하는 기능\(Go to Definition\)이라던지 프로그램 전체에서 식별자의 이름을 바꾸거나 특정 코드 블록을 함수로 빼내는 등의 리팩토링 기능을 들 수 있다. 38 | 39 | ## **주석으로서의 타입** 40 | 41 | 마지막으로, 타입은 프로그래머의 의도를 기술하는 주석과 같은 역할을 한다. 이 때 타입은 보통의 주석에 비해 더 강력한데, **타입 검사기에 의해 검사 및 강제되므로 프로그램의 동작과 일정 수준 이상 괴리될 수 없기 때문**이다. 예를 들어 아래 코드를 보자. 42 | 43 | ```javascript 44 | // 자기 자신을 리턴한다 45 | function sum(a, b) { 46 | return a + b; 47 | } 48 | function concatString(a, b) { 49 | return a - b; 50 | } 51 | concatString("a", "b"); // NaN 52 | ``` 53 | 54 | 위의 `sum`, `concatString` 함수는 각각 주석 또는 함수명을 읽고 추론할 수 있는 것과는 전혀 동떨어진 동작을 하고 있다. 하지만 이런 불일치에 불만을 가지는 건 프로그래머 뿐이다. 이 둘은 분명 유효한 자바스크립트 함수이며, 자바스크립트 실행기는 조금의 거리낌도 없이 이들을 실행할 것이다. 55 | 56 | 이런 상황이 생기는 원인은 주석과 변수명은 상대적으로 추상적인, 프로그램의 실제 동작과 직접 상호작용하지 않는 정보이기 때문이다. 반면 타입은 어떨까? 아래 타입스크립트 코드를 보자. 57 | 58 | ```typescript 59 | type IdentityFunction = (a: number) => number; 60 | const sum: IdentityFunction = (a: number, b: number) => { 61 | return a + b; 62 | } 63 | // error TS2322: Type '(a: number, b: number) => number' is not assignable to type 'IdentityFunction'. 64 | function concatString(a: string, b: string): string { 65 | return a - b; 66 | } 67 | // error TS2322: Type 'number' is not assignable to type 'string'. 68 | // error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. 69 | // error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. 70 | ``` 71 | 72 | 두 함수는 각각 함수 아래에 주석으로 적혀진 오류를 발생시킨다. 올바르지 않은 타입 정보를 가진 프로그램을 실행할 수단이 원천적으로 차단되는 것이다. 주석이나 변수명과는 다르게, **타입 정의와 다르게 동작하는 프로그램은 실행 자체가 불가능하다**는 점에서 타입은 앞서 언급된 다른 수단보다 훌륭한 명세 수단으로 동작한다. 73 | 74 | ## **자바스크립트와 정적 타입 분석** 75 | 76 | 자바스크립트는 기본적으로 동적 타입 분석을 채택한 언어이며, 그마저도 매우 미약한 수준이다. 때문에 아주 간단한 실수조차 실제로 코드를 실행 해 봐야만 잡아낼 수 있는 경우가 많다. 예를 들어, 아래 코드는 문자열을 써야 할 곳에 문자열이 아닌 객체를 사용하고 있다. 하지만 이런 간단한 오류조차 실제로 프로그램을 돌려 해당 코드가 실행되기 전에는 감지할 수 없다. 77 | 78 | ```javascript 79 | const notString = { isString: false }; 80 | console.log(notString.substr(0, 1)); // TypeError: notString.substr is not a function 81 | ``` 82 | 83 | 간단한 애플리케이션을 만들 때라면 모를까, 이런 동작은 프로젝트가 커짐에 따라 아주 큰 걸림돌이 된다. 자바스크립트가 갈수록 다양하고 복잡한 용도로 쓰임에 따라 자연스레 코드 작성에 정적 타입 검사를 도입하고자 하는 다양한 시도가 등장했다. 84 | 85 | 이 책의 주인공인 타입스크립트를 비롯해 서문에서 언급된 페이스북의 [Flow](https://flow.org/), 언어 수준에서 싱글 페이지 애플리케이션\(SPA\)을 기반에 두고 설계한 [Elm](http://elm-lang.org/), 하스켈의 영향을 크게 받은 [PureScript](http://www.purescript.org/), OCaml에 기반한 [ReasonML](https://reasonml.github.io/) 등이 대표적인 예다. 86 | 87 | -------------------------------------------------------------------------------- /01-introducing-typescript/why-typescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 자바스크립트에 정적 타이핑을 도입하려는 수많은 시도 중 타입스크립트가 빛나는 지점에 대해 다룬다. 3 | --- 4 | 5 | # 1.2 왜 타입스크립트인가 6 | 7 | 타입스크립트는 자바스크립트 개발에 정적 타입 시스템을 도입하고자 시도한 수많은 선택지 중 현재 가장 많은 사용자 커뮤니티를 갖고 있다. 과연 타입스크립트는 어떤 장점 덕분에 다른 경쟁자를 제치고 선두주자의 위치를 차지할 수 있었을까? 8 | 9 | ## **자바스크립트의 상위집합** 10 | 11 | 앞서 언급한 자바스크립트에 정적 타이핑을 도입한 시도 중 Elm이나 Reason, 또는 PureScript 등의 언어는 자바스크립트의 그것과는 상당히 이질적인 문법을 갖는다. 이러한 접근법은 자바스크립트의 문법에 얽매이지 않을 수 있다는 장점을 제공하지만, 다음과 같이 매우 명백한 단점 또한 따라온다. 12 | 13 | * **기존 자바스크립트 코드베이스의 마이그레이션이 매우 비싸진다**. 전혀 다른 문법 탓에 사실상 프로젝트를 바닥부터 다시 작성하는 작업이 되기 때문이다. 14 | * 기존 자바스크립트 프로그래머가 체감하는 **학습 곡선이 훨씬 가파라진다**. 이는 곧 직장 및 프로젝트를 위한 구인의 어려움으로 이어진다는 점에서 치명적이다. 15 | * **서드파티 자바스크립트 패키지의 사용이 어려워진다.** 심한 경우 불가능 한 경우도 생긴다. 16 | 17 | 반면, 타입스크립트는 시작부터 수많은 기존 코드베이스와 생태계의 포용을 큰 우선순위로 두었다. 18 | 19 | **모든 자바스크립트 코드는 타입스크립트 코드다**. 때문에 타입스크립트 컴파일러는 확장자만 바꾸면, 심지어는 \(특정 옵션을 켠다면\) 확장자를 바꾸지 않고도, 자바스크립트 코드를 이해한다. 뿐만 아니라 타입스크립트는 최신 ECMAScript 표준 및 여러 유용한 프러포절\(부록 2 참조\)들을 지원한다. 20 | 21 | 이러한 접근을 통해 앞서 언급한 다른 접근법의 단점을 그대로 뒤집은 장점을 얻게 된다. 22 | 23 | * **기존 자바스크립트 코드베이스의 마이그레이션에 드는 노력이 적다.** 24 | * **완만한 학습 곡선을 가지며 그 덕에 구인 또한 쉬워진다.** 25 | * **서드파티 자바스크립트 패키지의 사용이 상대적으로 수월하다.** 26 | 27 | ## **트레이드오프** 28 | 29 | 타입스크립트 깃허브 위키에는 [TypeScript Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals)라는 페이지가 있다. 이 문서는 타입스크립트를 만들며 목표로 삼았던 점\(Goals\)과 그렇지 않은 점\(Non-goals\)을 담고 있는데, 목표가 아닌 것들 중 흥미로운 항목이 있다. 30 | 31 | > 안전하고 “증명 가능하게 올바른” 타입 시스템 적용하기. 그 대신 정확성과 생산성 사이의 균형을 노린다. _Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity._ 32 | 33 | 안전하고 올바른 타입 시스템을 적용하는 것이 목표가 아니라는 점은 언뜻 듣기엔 비직관적이다. 안정성을 위해 정적 타입 시스템을 도입하는게 아니던가? **하지만 사실은 꼭 그렇게 말이 안 되는 이야기만은 아니다**. 34 | 35 | 아래 코드를 보자. 36 | 37 | ```typescript 38 | function getFirstThreeCharsUnsafe(arg: { x: string | null }) { 39 | if (arg.x !== null) { 40 | window.alert('arg.x is string!'); 41 | console.log(arg.x.substr(0, 3)); 42 | } 43 | } 44 | ``` 45 | 46 | 3장에서 더 자세히 살펴보겠지만, 첫 줄의 `arg: { x: string | null }` 은 인자로 받는 `arg`에 `x`라는 필드가 존재하고, 해당 필드가 `string` 또는 `null` 타입이라는 의미이다. 함수 내부를 보면, 먼저 `arg.x` 가 `null`인지 여부를 체크한다. 만약 `null`이 아니라면 `arg.x`는 문자열이라 판단하고, 안심하고`String.prototype.substr`을 호출한다. 47 | 48 | 별 문제가 없는 함수처럼 보이지만, 이 함수는 런타임 오류를 일으킬 수 있다. 다음 경우를 생각해보자. 49 | 50 | ```typescript 51 | var a: { x: string | null } = { x: 'ok' }; 52 | window.alert = function (str: string) { 53 | a.x = null; 54 | }; 55 | getFirstThreeCharsUnsafe(a); 56 | ``` 57 | 58 | 위 코드는 **기존에 정의된** `window.alert` **함수를 덮어쓴다**. 새로 정의된 `window.alert`는 호출되는 순간 `a.x`를 `null`로 바꾸어버린다. `null` 에는 `substr` 메소드가 없으므로 `getFirstThreeCharsUnsafe` 내의 `arg.x.substr(0, 3)`가 불리는 순간 `TypeError: Cannot read property 'substr' of null` 오류가 던져진다. 타입 검사를 통과했음에도 런타입 오류가 발생하는 것이다. 59 | 60 | 이를 예방하기 위해선 `if` 블록 안에서도 함수가 하나라도 호출 된 시점에서, 해당 블록에 들어오게 한 조건문이 참일 것이라는 가정을 버려야 한다. 위 함수를 아래와 같이 다시 고치면 문제는 해결된다. 61 | 62 | ```typescript 63 | function getFirstThreeCharsSafe(arg: { x: string | null }) { 64 | if (arg.x !== null) { 65 | window.alert('arg.x is string!'); 66 | if (arg.x !== null) { 67 | console.log(arg.x.substr(0, 3)); 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | 런타임 오류가 발생할 수 있는 경로가 명백하므로 ‘안전한’ 타입 시스템은 `getFirstThreeCharsSafe` 처럼 함수 호출이 일어날 때마다 조건 검사를 다시 하도록 강제해야 한다. 실제로 [안전성을 최우선 과제로 천명한](https://flow.org/en/docs/lang/types-and-expressions/#soundness-and-completeness-a-classtoc-idtoc-soundness-and-completeness-hreftoc-soundness-and-completenessa) Flow는 `getFirstThreeCharsUnsafe`를 통과시켜 주지 않는다. 74 | 75 | 하지만 위 예시에서 나타나듯, **이런 정책은 가능성이 매우 낮은 경우를 커버하기 위해 아주 자주 발생하는 패턴의 코드를 전부 장황하게 만든다**. 당장 작업중인 코드베이스의 `if` 문들을 살펴보고 이런 ‘안전한’ 타입 시스템이 통과 시켜 줄 코드가 얼마나 되는지 찾아보라! 76 | 77 | 타입스크립트는 이런 경우 안전함을 다소 희생하는 선택지와 프로그래머를 짜증나게 하더라도 안전성을 택하는 선택지 중 후자의 비용이 더 크다고 판단한다. 때문에 [이런 문제를 알고 있음에도 불구하고](https://github.com/Microsoft/TypeScript/issues/9998) `getFirstThreeCharsUnsafe` 와 같은 사용법을 허용한다. 78 | 79 | 이렇듯 타입 안정성을 일부 희생하면서 사용성을 극대화시키는 과감한 선택 덕에 **타입스크립트 사용자는 생산성의 희생 없이도 대부분의 경우 안전한 코드를 작성할 수 있다.** 타입스크립트가 트레이드오프가 발생했을 때 내린 구체적인 선택에 대해서는 이후 하나씩 살펴본다. 80 | 81 | ## **VS Code** 82 | 83 | Visual Studio Code\(이하 VS Code\)는 Microsoft에 의해 개발되어 2015년 공개된 통합개발환경\(Integrated Development Environment, IDE\)이다. [The State of JavaScript 2017 통계](https://octoverse.github.com/)에 따르면 **응답자 중 가장 많은 수인 약 30% 가량이 VS Code를 사용한다**. Atom, Sublime Text 등의 다른 에디터에 비해 정식 출시가 가장 늦다는 점을 감안하면 상당히 고무적인 결과다. 84 | 85 | 또한 VS Code는 수많은 활발한 컨트리뷰터를 갖고 있다. Octoverse 2017에 따르면 **2017년 깃허브 전체 저장소 중 가장 많은 사람이** **VS Code의 깃허브 저장소에 기여했으며, 해당 저장소에서는 전체 저장소 중 4번째로 많은 논의가 이루어졌다**고 한다. 86 | 87 | 이렇게 많은 이들의 사랑을 받는 VS Code는 모든 언어에 사용할 수 있는 IDE를 지향한다. 하지만 그 중에서도 VS Code와 타입스크립트가 갖는 관계는 특히 각별하다. 88 | 89 | ![VS Code 공식 홈페이지의 소개 이미지. 타입스크립트 코드가 예시로 띄워져 있다.](../.gitbook/assets/vscode-main.png) 90 | 91 | 저장소에서 가장 먼저 눈에 띄는 점은 코드 90% 이상이 타입스크립트로 작성되었단 점이다. 즉 **VS Code 개발팀은 타입스크립트 개발 환경을 개선할 만한 아주 강한 인센티브를 가진다.** 실제로 현 시점에서 VS Code는 다른 어떤 툴보다도 강력한 타입스크립트 툴링을 갖고 있다. 그 때문일까? VS Code의 릴리스 노트에서는 자바스크립트/타입스크립트 관련 변경 및 개선 사항이 유독 많이 눈에 띈다. 92 | 93 | 뿐만 아니라, [타입스크립트](https://blogs.msdn.microsoft.com/typescript/)와 [VS Code](https://code.visualstudio.com/updates/)의 릴리스 노트를 나란히 보면 종종 동일한 내용이 등장하는 것을 확인할 수 있다. 타입 정의 자동 설치 기능이 그 예시다. 이 기능은 2017년 10월 31일 [타입스크립트 2.6 릴리스 노트](https://blogs.msdn.microsoft.com/typescript/2017/10/31/announcing-typescript-2-6/)에서 에디터가 곧 지원할 것이라는 코멘트 및 동작하는 버전의 gif 이미지와 함께 언급되었다. 그리고 얼마 후 2017년 11월 8일 [VS Code 1.18 버전 릴리스](https://code.visualstudio.com/updates/v1_18)에 포함되었다. 94 | 95 | 이렇듯, Microsoft가 관리하는 이 두 프로젝트는 아주 긴밀하게 연결되어 있다. 이 둘의 상생관계는 앞으로도 굳건히 지속될 것이며, 두 프로젝트와 그들을 둘러싼 생태계에게 긍정적인 영향을 끼칠 것이다. 96 | 97 | ## **커뮤니티** 98 | 99 | 마지막으로, 타입스크립트는 양과 질 모두 이미 검증된 커뮤니티를 갖고 있다. 100 | 101 | 먼저 정성적인 부분을 살펴보자. Microsoft, Google, Palantir, [Reddit](https://redditblog.com/2017/06/30/why-we-chose-typescript/), [Slack](https://slack.engineering/typescript-at-slack-a81307fa288d), [Tumblr](https://javascript.tumblr.com/post/165082071937/flow-and-typescript), [Lyft](https://eng.lyft.com/typescript-at-lyft-64f0702346ea?gi=373fbd34acaf) 를 비롯한 세계 유수의 기업이 타입스크립트를 사용한다. 공식 홈페이지의 [Friends Of TypeScript](https://www.typescriptlang.org/community/friends.html)에서 더 많은 기업을 확인할 수 있다. 특히 구글의 경우 타입스크립트를 공식 언어로 채택했고 Google Analytics, Firebase, and Google Cloud Platform 등 다양한 구글 제품에서 사용하고 있다고 [2017년 4월에 밝힌 바 있다](http://angularjs.blogspot.kr/2017/04/official-languages-at-google.html). 102 | 103 | VS Code를 비롯해 웹 프레임워크 [Angular](https://github.com/angular/angular), 자바스크립트용 ReactiveX 라이브러리 [RxJS](https://github.com/ReactiveX/rxjs), 그래픽스 프레임워크 [Babylon.js](https://github.com/BabylonJS/Babylon.js), GraphQL 클라이언트 [Apollo Client](https://github.com/apollographql/apollo-client) 등이 타입스크립트로 작성되었다. 분야를 막론하고 다양한 거대 규모 프로젝트에서 이미 사용되고 있는 것이다. 104 | 105 | Google Trends를 통해 [근 5년간의 “타입스크립트” 주제의 시간 흐름에 따른 관심도 변화](https://trends.google.com/trends/explore?date=today%205-y&q=%2Fm%2F0n50hxv)를 살펴보면, 그 관심도가 꾸준히 성장해 왔으며 성장세가 꾸준히 빨라지고 있음을 확인할 수 있다. 106 | 107 | ![근 5년간의 “타입스크립트” 주제 관심도 변화](../.gitbook/assets/undefined.png) 108 | 109 | 다음으론 정량적인 부분을 살펴보자. 보다 와닿는 비교를 위해 두 번째로 많은 사용자군을 갖는 Flow와 지표들을 대비한 표를 만들어 보았다. \(모든 숫자는 2018년 1월 기준\) 110 | 111 | | | Flow | TypeScript | 112 | | --- | --- | --- | --- | --- | --- | --- | 113 | | Stack Overflow 질문 개수 | [1,132](https://stackoverflow.com/questions/tagged/flowtype) \(flowtype 태그\) | [45,315](https://stackoverflow.com/questions/tagged/typescript) \(typescript 태그\) | 114 | | GitHub 저장소 이슈 개수 | [1,884 Open / 2,472 Closed](https://github.com/facebook/flow/issues) | [2,503 Open / 12,782 Closed](https://github.com/Microsoft/TypeScript/issues) | 115 | | GitHub 저장소 PR 개수 | [106 Open / 1,223 Closed](https://github.com/facebook/flow/pulls) | [121 Open / 5,875 Closed](https://github.com/Microsoft/TypeScript/pulls) | 116 | | npm 월별 다운로드 수 | [852,756](https://www.npmjs.com/package/flow-bin) | [8,393,830 ](https://www.npmjs.com/package/typescript) | 117 | | 외부 타입 정의 개수 | [429](https://github.com/flowtype/flow-typed/tree/master/definitions/npm) | [4,010](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types) | 118 | | 내부 타입 정의 개수 | [~2,000](https://github.com/search?utf8=✓&q=flow+extension%3A.flow+size%3A>10) | N/A | 119 | 120 | Stack Overflow 질문 개수나 GitHub의 이슈 및 풀 리퀘스트 개수, npm 월별 다운로드 수 모두 타입스크립트가 압도적으로 많다. 특히 GitHub 이슈 및 풀 리퀘스트의 경우 전체 양이 크게 차이나는데에 비해 열려 있는 이슈 및 풀 리퀘스트의 차이는 상대적으로 적다는 점에서 타입스크립트 쪽이 더 활발하게 처리되고 있음을 짐작할 수 있다. 121 | 122 | “외부 타입 정의 개수” 항목의 경우 Flow와 타입스크립트가 각각 라이브러리의 타입 정보를 저장하기 위해 관리하는 [flow-typed](https://github.com/flowtype/flow-typed) 와 [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) 저장소를 기준으로 했다. 라이브러리가 플랫폼이 해석할 수 있는 타입 정보를 제공하지 않는다면 사용자의 선택지는 두 가지다. 직접 타입 정보를 작성하는 것과 해당 라이브러리를 사용하지 않는 것이다. 두 선택지 모두 자명하게 불편함을 초래하므로 타입 정의의 수는 해당 플랫폼의 사용자 경험과 직결된다. 123 | 124 | 타입스크립트와 Flow 모두 외부 저장소 대신 라이브러리 내부에 타입 정보를 직접 포함하는 방법을 제공하는데, “내부 타입 정의 개수” 항목은 이렇게 직접 포함된 타입 정의의 수를 의미한다. Flow의 경우 해당 용도로 사용되는 `.flow` 확장자를 가지며 내용이 비어 있지 않은 파일이 약 2천 개 정도 존재했다. 타입스크립트의 경우 GitHub 검색의 한계와 더불어 `node_modules` 파일을 전부 커밋한 저장소가 너무 많아 신뢰할만한 수를 얻기 어려웠지만, 다른 지표들로 미루어보아 내부 타입 정의의 수 역시 Flow보다 많을 것으로 추정한다. 125 | 126 | -------------------------------------------------------------------------------- /02-ecmascript/block-level-scope/README.md: -------------------------------------------------------------------------------- 1 | # 2.1 블록 수준 스코프 2 | 3 | ES5까지의 자바스크립트에는 변수 선언을 위하여 사용할 수 있는 수단이 var 키워드 뿐이였다. 자바스크립트의 변수 선언은 다른 프로그래밍 언어에 익숙한 이들에게 많은 혼란을 선사하곤 하는데, 가장 큰 두 이유는 바로 **함수 수준 스코프**와 **호이스팅**이다. 4 | 5 | ### **함수 수준 스코프** 6 | 7 | 함수 수준 스코프란, 단어 자체에서 짐작할 수 있듯 모든 변수 선언이 함수 수준에서 이루어짐을 의미한다. 즉, 자바스크립트에서 코드 블록\(`{...}`\)은 새로운 스코프를 생성하지 않는다. 8 | 9 | ```javascript 10 | function foo() { 11 | var abc = 123; 12 | if (true) { 13 | var abc = 456; 14 | } 15 | console.log(abc); 16 | } 17 | foo(); // 456 18 | ``` 19 | 20 | 블록 수준의 스코핑을 지원하는 언어에서는 `if` 블록 바깥에서 콘솔에 찍어본 `abc`의 값은 `123`으로 남아 있을 것이다. 하지만 자바스크립트는 해당 코드를 감싸고 있는 가장 가까운 함수 \(또는 전역\) 가 달라질 때에만 새로운 스코프가 생성된다. 따라서 2번 라인과 4번 라인의 `abc`는 동일한 변수를 가리킨다. 21 | 22 | `if` 블록을 새로운 함수로 대체 했을 때에는 예상 대로의 결과가 나오는 것을 확인할 수 있다. 23 | 24 | ```javascript 25 | function foo() { 26 | var abc = 123; 27 | function bar() { 28 | var abc = 456; 29 | } 30 | console.log(abc); 31 | } 32 | foo(); // 123 33 | ``` 34 | 35 | ### **호이스팅** 36 | 37 | 호이스팅이란 변수의 선언과 초기화가 동시에 이루어졌을 때, 자바스크립트 인터프리터가 변수의 선언을 함수의 맨 위로 이동시키는 동작을 뜻한다. 38 | 39 | ```javascript 40 | function foo() { 41 | console.log(bar); // undefined 42 | var bar = 123; 43 | } 44 | ``` 45 | 46 | `bar` 라는 변수를 선언 전에 참조하는 이러한 코드는 많은 언어에서 에러를 일으킬 것이다. 하지만 자바스크립트에서는 이 함수는 정상적으로 실행되며 콘솔엔 `undefined`가 찍힌다. 자바스크립트 엔진이 해당 함수를 아래와 같이 함수 시작점에 선언이 있고 이후 초기화되는 식으로 해석하기 때문이다. 47 | 48 | ```javascript 49 | function foo() { 50 | var bar; 51 | console.log(bar); // undefined 52 | bar = 123; 53 | } 54 | ``` 55 | 56 | ### **블록 수준 스코프** 57 | 58 | 자바스크립트의 이 두 독특한 동작 방식은 많은 프로그래머에게 혼란을 미쳐 왔다. ES6는 이러한 혼란을 피할 수 있도록 `let`과 `const`이라는 새로운 변수 선언 키워드를 도입했다. 두 키워드를 사용해 새로운 함수가 만들어질 때와 더불어 대괄호\(`{ ... }`\) 로 감싼 블록마다 생성되는 **블록 수준 스코프**의 지배를 받는 **블록 수준 변수**를 정의할 수 있다. 59 | 60 | -------------------------------------------------------------------------------- /02-ecmascript/block-level-scope/declaration-using-const.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: const를 이용해 재할당이 불가능한 블록 레벨 변수를 선언할 수 있다. 3 | --- 4 | 5 | # 2.2.2 const를 이용한 선언 6 | 7 | `const`를 이용해 **재할당이 불가능한 블록 레벨 변수**를 선언할 수 있다. `const`를 이용해 선언한 변수의 값을 블록 내에서 재할당하려 하면 에러가 발생한다. 8 | 9 | ```javascript 10 | function notOk() { 11 | const a = 1; 12 | a = 2; 13 | } 14 | notOk(); // TypeError: Assignment to constant variable. 15 | ``` 16 | 17 | `let`과 마찬가지로 `const`으로 선언한 변수 또한 정의문이 평가되기 전 접근될 경우 `ReferenceError`를 발생시킨다. 18 | 19 | ```javascript 20 | function foo() { 21 | console.log(a); 22 | const a = 3; 23 | } 24 | foo(); // ReferenceError: a is not defined 25 | ``` 26 | 27 | 선언 후 재할당이 불가능하단 점에서 짐작할 수 있듯이 `const`를 이용한 선언은 항상 값의 초기화를 수반해야 한다. `var`이나 `let`을 사용할 때처럼 변수를 선언만 해놓은 뒤 그 값을 추후에 초기화하는 것은 불가능하다. 28 | 29 | ```javascript 30 | function ok() { 31 | const a = 3; 32 | console.log(a); 33 | } 34 | function notOk() { 35 | const a; 36 | a = 3; 37 | console.log(a); 38 | } 39 | ok(); // 3 40 | notOk(); // SyntaxError: Missing initializer in const declaration 41 | ``` 42 | 43 | `const`로 선언한 변수는 **재할당이 불가능할 뿐, 불변값이 아니라는 점**을 명심해야 한다. 예를 들어, `Object`나 `Array` 타입의 변수를 `const`로 정의 했더라도 그 객체의 내부 상태를 조작하는 다양한 수단은 모두 아무런 문제 없이 실행할 수 있다. 44 | 45 | ```javascript 46 | const a = 3; 47 | a = 4; // TypeError: Assignment to constant variable. 48 | const obj = {}; 49 | obj.a = 42; // OK 50 | const arr = []; 51 | arr.push(3); // OK 52 | ``` 53 | 54 | `const` 선언으로 막을 수 있는 것은 오로지 블록 내 값의 재할당 뿐이다. 55 | 56 | -------------------------------------------------------------------------------- /02-ecmascript/block-level-scope/declaration-using-let.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: let을 이용해 재할당이 가능한 블록 레벨 변수를 선언할 수 있다. 3 | --- 4 | 5 | # 2.1.1 let을 이용한 선언 6 | 7 | `let`을 이용해 **재할당이 가능한 블록 레벨 변수**를 선언할 수 있다. 아래 예시 코드를 보자. 8 | 9 | ```javascript 10 | { 11 | let goOut = true; 12 | if (true) { 13 | let goOut = false; 14 | } 15 | console.log(goOut); // true 16 | goOut = false; 17 | console.log(goOut); // false 18 | } 19 | console.log(goOut); // ReferenceError: goOut is not defined. 20 | ``` 21 | 22 | 두 가지 주목할 점이 있다. 23 | 24 | 1. `let`을 이용한 선언은 블록 수준에서 이루어지므로, 대괄호 블록과 `if` 블록 내에서 `goOut`의 값을 선언하는 행위는 해당 블록 안에서만 의미를 갖는다. 25 | 2. `var`을 이용한 선언과 마찬가지로, `let`으로 선언한 변수를 블록 내에서 재할당할 수 있다. 26 | 27 | `let`으로 선언한 변수는 엄밀한 의미에서 호이스팅 되지만, `var`로 선언한 변수와 달리 변수의 정의문이 평가되기 전 해당 변수를 참조할 경우 `ReferenceError`가 발생한다. 이 때 변수 접근이 에러를 발생시키는, 정의문이 평가되기 전까지의 구간을 [Temporal Dead Zone](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified)이라 한다. 28 | 29 | {% hint style="info" %} 30 | 이 부분에 존재했던 오류에 [정성스러운 이슈](https://github.com/heejongahn/ts-for-jsdev/issues/14)를 제기해주신 한재엽님 감사합니다 :\) 31 | {% endhint %} 32 | 33 | ```javascript 34 | function foo() { 35 | console.log(a); 36 | let a = 3; 37 | } 38 | 39 | foo(); // ReferenceError: a is not defined 40 | ``` 41 | 42 | 또한 `var`과 다르게 어떤 변수명에 대한 `let`을 이용한 선언은 한 블록 내에서 한 번만 가능하다. 43 | 44 | ```javascript 45 | function ok() { 46 | var a = 1; 47 | var a = 2; 48 | console.log(a); 49 | } 50 | function notOk() { 51 | let a = 1; 52 | let a = 2; 53 | console.log(a); 54 | } 55 | ok(); // 2 56 | notOk(); // SyntaxError: Identifier 'a' has already been declared 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /02-ecmascript/block-level-scope/scope-best-practice.md: -------------------------------------------------------------------------------- 1 | # 2.2.3 스코프 베스트 프랙티스 2 | 3 | 타입스크립트 코드와 같이 let / const가 사용 가능한 환경에서는 항상 var 대신 let 또는 const를 사용하는 것이 좋다. 특히 둘 중에선 **const를 기본적으로 쓰고, 재할당이 반드시 필요한 변수만** **let을 이용해 선언하라**. 이런 코딩 습관은 코드를 읽는 이들이 특정 변수의 값이 블록 내에서 유지될 것이라 짐작하고 잠재적인 실수를 찾아내는 일을 돕는다. 4 | 5 | -------------------------------------------------------------------------------- /02-ecmascript/element-enumeration/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 최신 ECMAScript 명세에 추가된 원소 순회 수단에 대해 알아본다. 3 | --- 4 | 5 | # 2.5 원소 순회 6 | 7 | 어떤 컬렉션\(collection\)의 원소들을 순회하고 싶다는 요구사항은 굉장히 흔하다. 자연히 자바스크립트도 순회를 위한 다양한 방법을 제공한다. 8 | 9 | 예를 들어, 아래와 같은 배열의 원소를 순회하며 그 이름을 찍고 싶은 상황을 생각해보자. 10 | 11 | ```javascript 12 | const langs = ['TypeScript', 'JavaScript', 'Python']; 13 | ``` 14 | 15 | 먼저 C 스타일로 초기 조건, 조건문, 증가문을 이용해 순회하는 다음과 같은 방법이 존재한다. 16 | 17 | ```javascript 18 | for (let i = 0; i < langs.length; i++) { 19 | console.log(langs[i]); 20 | } 21 | ``` 22 | 23 | `for-in` 을 이용한 살짝 다른 버전의 반복도 가능하다. 24 | 25 | ```javascript 26 | for (const index in langs) { 27 | console.log(langs[index]); 28 | } 29 | ``` 30 | 31 | 최신 ECMAScript에는 이런 고전적인 방법 말고도 순회를 위한 다양한 방법이 추가되었다. 어떤 방법이 추가되었고, 기존의 접근에 비해 어떤 강점을 갖는지 살펴보자. 32 | 33 | -------------------------------------------------------------------------------- /02-ecmascript/element-enumeration/for-of.md: -------------------------------------------------------------------------------- 1 | # 2.5.2 for-of 문법 2 | 3 | ES6에는 어떤 컬렉션\(collection\)의 각 요소를 순회하며 특정 작업을 반복 수행하기 위한 `for-of` 문법이 추가되었다. 4 | 5 | ```javascript 6 | const languages = ['JavaScript', 'TypeScript', 'Python']; 7 | for (const lang of languages) { 8 | if (!lang.includes('Script')) { 9 | break; 10 | } 11 | console.log(lang); 12 | } 13 | 14 | // JavaScript 15 | // TypeScript 16 | ``` 17 | 18 | 비슷하게 생겼지만, `for-of` 문법은 기존의 `for-in` 문법과 비교했을 때 아래와 같은 차이점을 갖고 있다. 19 | 20 | * 순회 순서가 항상 같을 것이 보장된다. 21 | * `for (const elem in arr) { ... }` 의 `elem`에는 **원소의 키에 해당하는 문자열**이 바인딩 된다. 한편 `for (const elem of arr) { ... }` 의 `elem`에는 **원소의 실제 값**이 바인딩 된다. 22 | * 예를 들어, `arr`가 함수의 배열\(`[ () => 42, () => true]`\)이라면, `for-in`의 `elem`에는 각 함수의 인덱스 문자열\(`"0"`, `"1"`\)이 바인딩 된다. `for-of` 문법의 `elem`에는 실제 함수값\(`() => 42`, `() => true`\)이 바인딩 된다. 23 | 24 | 또한 `forEach`와 달리 `break` / `continue` 등의 키워드로 실행 흐름을 제어할 수 있다. 25 | 26 | 주의할 점은 임의의 `Object` 인스턴스에 대해 `for-of`를 사용 가능한 것은 아니라는 것이다. 27 | 28 | ```javascript 29 | const obj = { a: 1 }; 30 | for (const elem of obj) { 31 | console.log(elem); 32 | } 33 | // TypeError: obj is not iterable 34 | ``` 35 | 36 | 에러 메시지를 보면 짐작할 수 있듯이, `for-of` 문법은 이터러블\(`iterable`\) 프로토콜을 구현한 \(즉, 순회 가능한\) 객체에 대해서만 사용할 수 있다. 이터러블 프로토콜을 구현한 객체란 이터레이터를 원소로 갖는다. 이 용어들이 어떤 의미를 갖는지 차례로 살펴보자. 37 | 38 | -------------------------------------------------------------------------------- /02-ecmascript/element-enumeration/foreach.md: -------------------------------------------------------------------------------- 1 | # 2.5.1 forEach 메소드 2 | 3 | ES5부터 `Array.prototype.forEach` 메소드가 추가되었다. 이 메소드는 배열의 원소를 인자로 받는 콜백 함수를 인자로 받아, 배열 내의 각 원소에 대해 해당 콜백을 실행한다. 4 | 5 | ```javascript 6 | langs.forEach(lang => { 7 | console.log(lang); 8 | } 9 | ``` 10 | 11 | `forEach` 메소드는 간결하지만, 반복문 중간에 `break` 혹은 `return` 등을 사용해 **실행 흐름을 제어할 수 없다**는 단점을 갖고 있다. 12 | 13 | -------------------------------------------------------------------------------- /02-ecmascript/element-enumeration/iterable-protocol.md: -------------------------------------------------------------------------------- 1 | # 2.5.4 이터러블 프로토콜 2 | 3 | 어떤 객체가 `Symbol.iterator`의 키의 값으로 메소드를 갖고, 해당 메소드를 실행했을 때 이터레이터 인스턴스가 반환될 때 그 객체가 **이터러블 프로토콜을 구현한다**, 또는 **순회 가능하다**고 한다. `Symbol.iterator`는 ES6에 추가된 `Symbol` 타입의 값으로, 그 중에서도 표준에 정의되어 있는 특수한 심볼이다. 4 | 5 | 예를 들어, 다음과 같이 순회 가능한 객체 `iterableObj`를 정의할 수 있다. 6 | 7 | ```javascript 8 | function makeIterator(array) { 9 | var nextIndex = 0; 10 | return { 11 | next: function() { 12 | return nextIndex < array.length 13 | ? { value: array[nextIndex++], done: false } 14 | : { done: true}; 15 | } 16 | }; 17 | } 18 | const iterableObj = { 19 | [Symbol.iterator]() { return makeIterator([1, 2, 3]); } 20 | }; 21 | ``` 22 | 23 | 순회 가능한 객체에 대해서는 `for-of` 문법이 사용 가능하다. 이 때 `for-of` 반복문은 내부적으로 이터레이터의 `next()`를 실행해 `done === true`인 경우 반복을 중단한다. `done === false` 인 경우, `for (const elem of iterable)` 문의 변수 `elem`에 `value`를 할당하고, 함수 본문을 실행한다. 24 | 25 | ```javascript 26 | for (const elem of iterableObj) { 27 | console.log(elem); 28 | } 29 | // 1 30 | // 2 31 | // 3 32 | ``` 33 | 34 | 또한, 순회 가능한 객체에 대해 전개 연산자가 사용 가능하다. 이 때 전개 연산자는 해당 순회 가능한 객체의 이터레이터가 리턴한 `value` 들의 나열로 대체된다. 35 | 36 | ```javascript 37 | console.log(...iterableObj); // [1, 2, 3] 38 | ``` 39 | 40 | `Array`, `Map`, `Set` 등의 표준 객체는 모두 이터러블 프로토콜을 구현한다. 그 덕분에 프로그래머는 서로 전혀 다른 형태의 여러 객체들을 일관적인 문법을 사용해 순회할 수 있다. **이터러블 프로토콜은 프로그래머에게 임의의 객체에 대해 해당 객체를 어떻게 순회할지를 명시하고, 동일할 문법으로 여러 객체를 순회할 수 있는 수단을 제공한다**. 41 | 42 | -------------------------------------------------------------------------------- /02-ecmascript/element-enumeration/iterator-protocol.md: -------------------------------------------------------------------------------- 1 | # 2.5.3 이터레이터 프로토콜 2 | 3 | 객체가 특정 조건을 만족하는 `next()` 메소드를 가질 때, 이러한 객체를 이터레이터\(반복자, Iterator\)라 부른다. 이 때, `next()` 메소드는 해당 객체의 요소들을 어떤 방식과 어떤 순서로 순회할지를 정의하며, 호출 될 때마다 아래 두 가지 값을 담은 객체를 반환해야 한다. 4 | 5 | * 순회가 끝났는지를 나타내는 불리언 값인 `done` 6 | * \(`done === false` 일시\) 이번 원소의 값인 `value` 7 | 8 | 아래 코드는 객체를 받아 객체의 요소를 순회하기 위한 이터레이터를 생성하고, 실제로 해당 이터레이터를 순회하는 간단한 예시다. 9 | 10 | ```javascript 11 | function makeIterator(array) { 12 | var nextIndex = 0; 13 | return { 14 | next: function() { 15 | return nextIndex < array.length 16 | ? { value: array[nextIndex++], done: false } 17 | : { done: true}; 18 | } 19 | }; 20 | } 21 | const iter = makeIterator([1, 2, 3]); 22 | iter.next(); // { value: 1, done: false } 23 | iter.next(); // { value: 2, done: false } 24 | iter.next(); // { value: 3, done: false } 25 | iter.next(); // { done: true } 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /02-ecmascript/function/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 최신 ECMAScript 명세에서 자바스크립트 함수에 어떤 변경점이 있었는지 알아본다. 3 | --- 4 | 5 | # 2.3 함수 6 | 7 | 자바스크립트의 함수는 일급 객체다. 함수를 다른 함수의 인자로 넘길 수도 있고, 함수가 함수를 리턴하기도 하는 등 함수가 다른 문자열, 숫자 등의 값과 동일하게 취급된다. 때문에 함수는 자바스크립트 프로그래밍에서 가장 핵심적인 역할을 차지한다. 이 장에서는 최신 ECMAScript 명세에서 자바스크립트 함수에 어떤 변경점이 있었는지 알아본다. 8 | 9 | -------------------------------------------------------------------------------- /02-ecmascript/function/arrow-functions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 화살표 함수는 자바스크립트 함수 내부에서 this가 야기하는 혼란을 줄여준다. 3 | --- 4 | 5 | # 2.3.2 화살표 함수 6 | 7 | 기본적으로 자바스크립트 함수 내부에서 참조된 `this` 키워드의 값은 함수가 정의되는 시점이 아닌 실행되는 시점에 결정된다. 동일한 내부 구조를 가진 함수가 동일한 블록 내에서 실행 되더라도 어떤 방식으로 호출되냐에 따라 함수 내에서의 `this`값은 달라질 수 있다. 8 | 9 | ```javascript 10 | function getName() { 11 | console.log(this.name); 12 | } 13 | const a = { 14 | name: 'javascript', 15 | getName: getName 16 | }; 17 | function getNames() { 18 | a.getName(); // 'javascript' 19 | getName(); // TypeError: Cannot read property 'name' of undefined 20 | } 21 | ``` 22 | 23 | 이러한 `this`의 동작은 자주 혼란을 야기한다. 24 | 25 | ES6에 추가된 화살표 함수\(arrow function\) 문법을 사용하면 함수 내의 `this`가 실행 시점이 아닌 정의 시점에 결정되는 함수를 정의할 수 있다. 화살표 함수 내에서의 모든 `this` 참조는 해당 함수가 정의되는 시점에서 함수를 둘러싼 문맥의 `this` 값으로 고정된다. 화살표 함수는 `(인자) => (함수 본문)`의 문법으로 정의한다. 26 | 27 | ```javascript 28 | const obj = { 29 | a: 1, 30 | normalFunc: function() { console.log(this); }, 31 | arrowFunc: () => { console.log(this); }, 32 | }; 33 | const { normalFunc, arrowFunc } = obj; 34 | obj.normalFunc(); // { 35 | // a: 1, 36 | // normalFunc: [Function: normalFunc], 37 | // arrowFunc: [Function: arrowFunc] 38 | // } 39 | normalFunc(); // undefined 40 | obj.arrowFunc(); // (global object) 41 | arrowFunc(); // (global object) 42 | ``` 43 | 44 | 만약 인자가 하나일 경우, 화살표 함수의 인자를 둘러싼 괄호를 생략할 수 있다. 45 | 46 | ```javascript 47 | const a = args => { console.log(args); }; 48 | console.log(a); 49 | ``` 50 | 51 | 함수 본문이 한 줄의 표현식으로 이루어졌을 시, 본문을 감싸는 대괄호를 생략할 수 있다. 이 때 해당 표현식의 값이 함수의 반환값이 된다. 52 | 53 | ```javascript 54 | const isOdd = n => (n % 2 === 1); 55 | console.log(isOdd(3)); // true 56 | ``` 57 | 58 | `this`의 동작 이외에도 화살표 함수는 `function` 키워드를 이용해 선언한 함수와 다음의 차이점을 갖는다. 59 | 60 | * 생성자로 사용할 수 없다. 61 | * 함수 내에 `arguments` 바인딩이 존재하지 않는다. 62 | * `prototype` 프로퍼티를 갖고 있지 않는다. 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /02-ecmascript/function/default-parameter.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: ES6 기본 매개변수 문법을 사용해 매개변수의 기본값을 간결하게 표현할 수 있다. 3 | --- 4 | 5 | # 2.3.1 기본 매개변수 6 | 7 | 자바스크립트 함수는 매개변수의 수를 느슨하게 체크한다. 예를 들어, 3개의 매개변수를 기대하는 함수에 1개 혹은 2개만 주어졌을 때, 런타임 에러를 내는 대신 나머지 매개변수로 `undefined` 값이 들어온 것과 동일하게 함수를 실행한다. 8 | 9 | ```javascript 10 | function looseCheck(a, b, c) { 11 | console.log([a, b, c]); 12 | } 13 | looseCheck(1, 2, 3); // [1, 2, 3] 14 | looseCheck(1, 2); // [1, 2, undefined) 15 | looseCheck(1); // [1, undefined, undefined) 16 | ``` 17 | 18 | 이러한 함수의 특성 상 모든 매개변수가 넘겨졌는지 검사하고 싶거나, 기본값을 채워주고 싶은 경우 해당 처리를 프로그래머가 매번 수동으로 해와야 했다. 19 | 20 | ```javascript 21 | function tightCheck(a, b, c) { 22 | if ( 23 | (typeof a === 'undefined') || 24 | (typeof b === 'undefined') || 25 | (typeof c === 'undefined') 26 | ) { 27 | throw new Error('Not all parameters are specified.'); 28 | } 29 | console.log([a, b, c]); 30 | } 31 | tightCheck(1, 2, 3); // [1, 2, 3] 32 | tightCheck(1, 2); // Error: Not all parameters are specified. 33 | function useDefault(_a, _b, _c) { 34 | const a = typeof _a === 'undefined' ? 1 : _a; 35 | const b = typeof _b === 'undefined' ? 1 : _b; 36 | const c = typeof _c === 'undefined' ? 1 : _c; 37 | console.log([a, b, c]); 38 | } 39 | useDefault(1, 2, 3); // [1, 2, 3] 40 | useDefault(1, 2); // [1, 2, 1] 41 | ``` 42 | 43 | 이러한 수고를 덜기 위해 ES6에는 기본 매개변수 문법이 추가되었다. 이 문법은 위의 `useDefault` 함수와 거의 동일한 일을 하는 문법 설탕이다. 매개변수 뒤에 `= (기본값)`을 덧붙여 해당 매개변수의 기본값을 설정할 수 있다. 44 | 45 | ```javascript 46 | function useDefaultES6(a = 1, b = 2, c = 3) { 47 | console.log([a, b, c]); 48 | } 49 | useDefault(1, 2, 3); // [1, 2, 3] 50 | useDefault(1, 2); // [1, 2, 3] 51 | useDefault(1, undefined, 4); // [1, 2, 4] 52 | ``` 53 | 54 | -------------------------------------------------------------------------------- /02-ecmascript/handling-asynchronous/README.md: -------------------------------------------------------------------------------- 1 | # 2.6 비동기 처리 2 | 3 | 자바스크립트는 싱글 스레드 기반의 프로그래밍 언어다. 이러한 한계와 더불어 유저 인터페이스와 매우 밀접하게 엮인 언어로 시작했다는 특징 때문에, 동기 처리를 이용한 프로그래밍 패턴은 자바스크립트에서 실질적으로 사용불가능하다. 네트워크 요청이 응답을 받을 때까지 싱글 스레드를 점유하고, 그 동안은 브라우저가 유저의 마우스, 키보드에 전혀 반응하지 않는다고 생각해보라! 4 | 5 | 이런 제약 때문에 비싼 작업을 비동기로 처리하는 것이 자바스크립트에선 매우 흔한 패턴이다. 수 년 전까지만 해도 자바스크립트 코드에서는 그런 비동기 작업이 끝난 후, 그 결과에 따라 추가적인 처리를 하기 위해 비동기 서브루틴에게 콜백 함수를 넘기는 패턴이 자주 쓰였다. 다음 코드 예시는 네트워크에서 문서를 받아온 뒤, 해당 문서를 작성한 유저의 다른 글들을 가져오는 가상의 코드 예시다. 6 | 7 | ```javascript 8 | fetchDocument(url, function(err, document) { 9 | if (err) { 10 | console.log(err); 11 | } else { 12 | fetchAuthor(document, function(err, author) { 13 | if (err) { 14 | console.log(err); 15 | } else { 16 | fetchPostsFromAuthor(author.id, function(err, posts) { 17 | if (err) { 18 | console.log(err); 19 | } else { 20 | /* do something with posts */ 21 | } 22 | }) 23 | } 24 | }) 25 | } 26 | }) 27 | ``` 28 | 29 | 위 코드에서 볼 수 있듯이 콜백을 사용해 비동기 작업을 처리하면 비동기로 처리하는 작업의 단계가 깊어짐에 따라 들여쓰기 또한 급격히 깊어진다. 이런 코드는 미관상으로도 좋지 않을 뿐더러, 프로그래머가 비동기로 일어나는 일련의 작업 흐름을 따라가기 매우 힘들게 한다. 이런 불편을 해소하고자, 최신 ECMAScript에는 이러한 콜백을 이용한 접근보다 개선된 비동기 처리 패턴이 추가되었다. 30 | 31 | -------------------------------------------------------------------------------- /02-ecmascript/handling-asynchronous/async-await.md: -------------------------------------------------------------------------------- 1 | # 2.6.2 Async / Await 2 | 3 | 프로미스는 콜백을 이용한 사용 패턴의 문제 중 상당 부분을 해소했지만, 완벽한 해결책은 아니었다. ECMAScript 2017에 추가된 `Async` / `Await` 문법은 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해준다. 4 | 5 | `Async` / `Await` 을 지원하는 환경에서는 함수 선언 앞에 `async` 키워드를 덧붙여 비동기 함수\(async function\)를 정의할 수 있다. `async` 함수가 반환하는 값은 암시적으로 프로미스로 감싸진다. 6 | 7 | ```javascript 8 | async function returnTheAnswer() { 9 | return 42; 10 | } 11 | const implicitlyReturnedPromise = returnTheAnswer(); 12 | console.log(implicitlyReturnedPromise instanceof Promise); // true 13 | implicitlyReturnedPromise.then(answer => console.log(answer)); // 42 14 | ``` 15 | 16 | 비동기 함수 내에서는 표현식 앞에 `await` 키워드를 사용할 수 있다. 이때 `await` 키워드가 붙은 해당 표현식 `expr`은 다음과 같은 값을 갖는다. 17 | 18 | * 뒤따르는 값이 프로미스가 아닐 시, `expr`은 그 값을 그대로 갖는다. 19 | * 뒤따르는 값이 프로미스일 시, 해당 프로미스가 처리될 때까지 실행을 중지한다. 만약 프로미스가 완료될 경우, `expr`은 `resolve()`의 인자로 사용된 값을 갖는다. 만약 그 프로미스가 거부 될 시, `expr`은 오류를 그대로 위로 던진다. 20 | 21 | `Async` / `Await`을 이용하면 `then` 또는 `catch` 호출 없이도, 마치 동기 코드를 작성하듯 프로미스를 처리할 수 있다. 22 | 23 | ```javascript 24 | async function asyncExample() { 25 | const a = await new Promise(resolve => resolve(42)); 26 | const b = await 42; 27 | let c; 28 | try { 29 | c = await new Promise((_, reject) => reject('Error on await')); 30 | } catch (e) { 31 | console.log(e); 32 | } 33 | console.log(`a: ${a}, b: ${b}, c: ${c}`); 34 | } 35 | asyncExample(); // Error on await 36 | // a: 42, b: 42, c: undefined 37 | ``` 38 | 39 | 이때 이 코드는 비동기 루틴을 포함하고 있음에도 불구하고 코드가 작성된 순서대로, 위에서부터 아래로 순차적으로 실행된다. 예를 들어, 첫 번째 프로미스를 3초가 지나서 처리되도록 해보면 어떨까? 40 | 41 | ```javascript 42 | async function asyncExample2() { 43 | const a = await new Promise(resolve => { setTimeout(() => resolve(42), 3000) }); 44 | const b = await 42; 45 | let c; 46 | try { 47 | c = await new Promise((_, reject) => reject('Error on await')); 48 | } catch (e) { 49 | console.log(e); 50 | } 51 | console.log(`a: ${a}, b: ${b}, c: ${c}`); 52 | } 53 | asyncExample2(); // Error on await 54 | // a: 42, b: 42, c: undefined 55 | ``` 56 | 57 | 3초가 지나야만 성공하는 첫 프로미스가 실행된 후에 코드가 진행되었음을 로그의 `a값`이 `42`로 찍힌 것으로부터 확인할 수 있다. 따라서 앞서 언급한 콜백 헬 예제 다음과 같이 작성 할 수 있게 된다. 58 | 59 | ```javascript 60 | try { 61 | const document = await fetchDocument(url); 62 | const author = await fetchAuthor(document); 63 | const posts = await fetchPostsFromAuthor(author); 64 | /* do somethin gwith posts */ 65 | } catch (err) { 66 | console.log(err); 67 | } 68 | ``` 69 | 70 | 핸들러로 한 단계 더 들어가던 코드가 사라지고, 별도의 핸들러 없이 오류 처리도 `try-catch` 블록으로 처리하여 훨씬 읽기 쉬운 코드가 되었다. 이처럼 `Async` / `Await` 문법을 이용해 ‘이 작업을 먼저 진행한 후 그게 끝나면 이 다음 작업을 진행한다’라는 식의 사람의 마음 속 동작 방식을 그대로 코드로 옮길 수 있다. 71 | 72 | -------------------------------------------------------------------------------- /02-ecmascript/handling-asynchronous/promises.md: -------------------------------------------------------------------------------- 1 | # 2.6.1 프로미스 2 | 3 | 콜백 헬의 대안으로 가장 먼저 제안된 API는 프로미스\(`Promise`\)다. 프로미스는 비동기로 처리될 수 있는 연산에 사용되며, 생성자의 인자로 `resolve`와 `reject`, 두 핸들러를 인자로 받는 한 함수를 받다. 프로그래머는 함수 본문에서 \(비동기로 작동할 수 있는\) 특정 로직을 실행하고 그 결과에 따라 `resolve` 또는 `reject`를 호출할 수 있다. 4 | 5 | ```javascript 6 | function getRandomPromise () { 7 | return new Promise((resolve, reject) => { 8 | setTimeout(function () { 9 | const destiny = Math.random(); 10 | if (destiny > 0.5) { 11 | resolve(); 12 | } else { 13 | reject(); 14 | } 15 | }) 16 | }); 17 | } 18 | ``` 19 | 20 | ### **then 과 catch 메소드** 21 | 22 | 프로미스 인스턴스는 아래 두 개의 메소드를 갖는다. 23 | 24 | * `resolve()`의 호출된 경우, 즉 해당 비동기 작업이 완료 된 경우의 핸들러인 `then()` 25 | * `reject()`의 호출된 경우, 즉 해당 비동기 작업이 거부된 경우의 핸들러인 `catch()` 26 | 27 | 각 핸들러는 `resolve()` 혹은 `reject()`에 넘겨진 인자를 그대로 받는다. 프로미스가 완료되거나 거부되는 것을 처리되었다\(setteled\)고 하는데, 한 프로미스는 최대 한 번만 처리 될 수 있다. 즉 이미 완료 혹은 거부가 일어난 후의 또다른 `resolve()` 또는 `reject()` 호출은 무의미하다. 28 | 29 | 아래 예시에서는 웹에서의 네트워크 요청을 위한 Fetch API의 사용을 예로 살펴본다. 모던 브라우저에 내장되어 있는 `fetch()` 함수는 네트워크 요청을 만들기 위한 여러 정보를 인자로 받아 네트워크 요청을 실행하고, 그 요청에 연결된 프로미스를 리턴한다. 30 | 31 | 네트워크 요청이 성공적으로 끝난 경우, 이 프로미스는 내부적으로 서버가 넘긴 응답을 인자로 `resolve()`를 호출한다. 이 응답은 `then()` 핸들러에서 접근 가능하다. 32 | 33 | ```javascript 34 | fetch('http://example.com').then(response => { 35 | const { ok, status } = response; 36 | console.log(ok, status); // true, 200 37 | }); 38 | ``` 39 | 40 | 만약 네트워크 요청을 보내는 과정에서 오류가 발생했을 경우, 이 프로미스는 내부적으로 던져진 에러를 인자로 `reject()`를 호출한다. 이 응답은 `catch()` 핸들러에서 접근 가능하다. 41 | 42 | ```javascript 43 | fetch('https://this-is-invalid-url.really').catch(err => { 44 | const { message } = err; 45 | console.log(message); // Failed to fetch 46 | }); 47 | ``` 48 | 49 | `then` 핸들러는 실제로는 두 개의 콜백을 인자로 받는다. 그 두 번째 콜백은 에러가 발생했을 때에 실행되며 에러 객체를 인자로 받는다. 즉 위 코드는 아래와 같이 다시 쓸 수 있다. 50 | 51 | ```javascript 52 | fetch('https://this-is-invalid-url.really').then(null, err => { 53 | const { message } = err; 54 | console.log(message); // Failed to fetch 55 | }); 56 | ``` 57 | 58 | ### **프로미스 체인** 59 | 60 | `then`과 `catch` 두 메소드는 호출된 경우 또다시 프로미스를 반환한다. 때문에 프로미스는 연쇄될 수 있다\(chainable\). 이 때, 프로미스 체인의 다음 프로미스는 이번 프로미스가 반환한 값으로 `resolve`를 호출한다. 61 | 62 | ```javascript 63 | fetch('http://example.com').then(response => { 64 | const { status } = response; 65 | return status; 66 | }).then(status => { 67 | console.log(`The request has status ${status}!`); // The request has status 200! 68 | }); 69 | ``` 70 | 71 | 종합하면, 프로미스가 지원되는 환경이라면 앞서 살펴보았던 콜백 헬 예제를 다음과 같이 고쳐 쓸 수 있다. 72 | 73 | ```javascript 74 | function errorHandler(err) { 75 | if (err) { 76 | console.log(err); 77 | } 78 | } 79 | fetchDocument(url) 80 | .then(document => fetchAuthor(document), errorHandler) 81 | .then(author => fetchPostsFromAuthor(author), errorHandler) 82 | .then(posts => /* do something with posts */, errorHandler); 83 | ``` 84 | 85 | ### **정리** 86 | 87 | 콜백을 이용한 접근에 비해 갖는 장점을 금세 확인할 수 있을 것이다. 88 | 89 | * 들여쓰기의 깊이가 단계의 수에 비례해 늘어나지 않는다. 90 | * 코드의 흐름이 보다 한 눈에 들어온다. 91 | * 성공한 경우의 핸들러와 에러가 발생한 경우의 핸들러 중 한 쪽만 실행된다는 것이 보장되므로 `if-else` 문을 사용하지 않고 훨씬 깔끔한 코드를 작성할 수 있다. 92 | 93 | -------------------------------------------------------------------------------- /02-ecmascript/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트의 기반이 되는 언어 ECMAScript 최신 명세에 추가된 여러 유용한 기능들을 살펴본다. 3 | --- 4 | 5 | # 2.0 ECMAScript 6 | 7 | 앞서 언급했듯, 타입스크립트는 자바스크립트의 상위집합\(superset\)이다. **모든 자바스크립트 코드는 곧 적법한 타입스크립트 코드이다**. 때문에 타입스크립트 프로그래머는 자바스크립트 관련 지식을 십분 활용할 수 있는 한편, 자바스크립트 언어의 한계 또한 안고 가야 한다. 어느 쪽에 초점을 맞추든, 타입스크립트 프로그래밍을 위해서 자바스크립트 언어에 관한 지식은 필수적이다. 8 | 9 | **타입스크립트는 최신 자바스크립트 표준의 명세를 대부분 지원한다.** 최신 자바스크립트 명세에 익숙해지는 과정은 실력 있는 타입스크립트 프로그래머로 나아가기 위한 첫걸음이나 다름 없다. 이 책은 자바스크립트 경험을 가진 독자를 대상으로 한다. 그럼에도 ECMAScript 2015 이후의 표준을 비롯해 상대적으로 최근 들어 추가된 기능에 아직 익숙해지지 않은 독자가 꽤 있을 것이다. 10 | 11 | 2장에서는 보다 나은 타입스크립트 프로그래머가 되기 위해 알아야 할 최신 자바스크립트 기능 중 특히 자주 쓰일 일부를 소개한다. ES5에서 ES7에 이르기까지의 방대한 변경 사항을 전부 담는 것은 이 책의 목표가 아니므로, **이 장에서는 새로운 스펙 중 상대적으로 자주 쓰이는 기능에 대해서만 다룬다**. 12 | 13 | {% hint style="info" %} 14 | 이 장의 코드 예제는 모두 [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)에서 실행될 것을 가정하고 있다. 15 | {% endhint %} 16 | 17 | {% hint style="info" %} 18 | 2장을 적으며 니콜라스 자카스\(Nicholas C. Zakas\)가 저술하고 국내에서는 인사이트 출판사에서 번역해 출판한 [『모던 자바스크립트 : 예제로 배우는 ECMAScript 6 핵심 기능』](http://www.insightbook.co.kr/book/programming-insight/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8)의 내용으로부터 많은 도움을 받았다. 여기 소개된 기능을 비롯해 ECMAScript 6에 추가된 다양한 기능에 대한 더 자세한 설명을 원하는 독자에게는 해당 서적을 추천한다. 19 | {% endhint %} 20 | 21 | **만약 이미 최신 ECMAScript에 익숙한 독자라면 2장을 건너뛰고 바로 3장으로 가도 무방하다**. 또한 이 장에서는 독자가 ECMAScript, TC39 등의 자바스크립트 언어 표준에 관한 기본적인 지식을 갖고 있을 것이라 가정한다. 만약 그렇지 않다면 아래 링크를 통해 _부록 II : 자바스크립트 언어 생태계_를 참조하라. 22 | 23 | {% page-ref page="../appendix-ii-js-ecosystem/ecmascript-tc39.md" %} 24 | 25 | -------------------------------------------------------------------------------- /02-ecmascript/object-and-array/README.md: -------------------------------------------------------------------------------- 1 | # 2.2 객체와 배열 2 | 3 | 객체\(`Object`\)와 배열\(`Array`\)는 자바스크립트의 가장 기본적인 자료구조이며, 온갖 용도로 사용된다. 이 장에서는 최신 ECMAScript에 들어온 객체 및 배열 관련 주요 변경사항을 다룬다. 4 | 5 | -------------------------------------------------------------------------------- /02-ecmascript/object-and-array/destructuring-assignment.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 비구조화 할당 문법을 이용해 이전까지 여러 라인에 걸쳐 적어야만 했던 할당을 보다 간결하게 쓸 수 있다. 3 | --- 4 | 5 | # 2.2.1 비구조화 할당 6 | 7 | ### **배열의 비구조화 할당** 8 | 9 | 먼저 배열을 보자. 한 배열의 각 원소에 새로운 이름을 붙여야 하는 경우, 기존에는 아래와 같이 코드를 반복해서 적어야 했다. 10 | 11 | ```javascript 12 | const arr = [1, 2]; 13 | const a = arr[0]; 14 | const b = arr[1]; 15 | ``` 16 | 17 | 비구조화 할당을 지원하는 ES6부터는 위 코드를 아래와 같이 간결하게 쓸 수 있다. 18 | 19 | ```javascript 20 | const arr = [1, 2]; 21 | const [a, b] = [1, 2]; 22 | console.log(a); // 1 23 | console.log(b); // 2 24 | ``` 25 | 26 | 만약 좌항 배열이 우항 배열보다 더 큰 `length`를 갖고 있다면, 대응하는 우항 배열의 원소가 없는 좌항 배열의 원소에는 `undefined`가 할당된다. 27 | 28 | ```javascript 29 | const [c, d] = [1]; 30 | console.log(d); // undefined 31 | ``` 32 | 33 | ### **객체의 비구조화 할당** 34 | 35 | 비슷한 문법을 객체에도 적용가능하다. 36 | 37 | ```javascript 38 | const obj = { a: 1, b: 2 }; 39 | const a = obj.a; 40 | const b = obj.b; 41 | ``` 42 | 43 | 비구조화 할당을 이용해 위의 코드를 아래와 같이 적을 수 있다. 44 | 45 | ```javascript 46 | const obj = { a: 1, b: 2 }; 47 | const { a, b } = obj; 48 | console.log(a); // 1 49 | console.log(b); // 2 50 | ``` 51 | 52 | 마찬가지로 없는 속성를 참조 할 시 `undefined`가 할당되며, 기본값을 넣어줄 수도 있다. 53 | 54 | ```javascript 55 | const { c, d, e = 3 } = { c: 1 }; 56 | console.log(d); // undefined 57 | console.log(e); // 3 58 | ``` 59 | 60 | 또한 객체의 경우, 우항과 다른 변수명을 사용하고 싶은 경우 아래와 같이 콜론\(`:`\)을 사용해 변수에 새로운 이름을 줄 수 있다. 61 | 62 | ```javascript 63 | const { a: newA } = { a: 1 }; 64 | console.log(newA); // 1 65 | ``` 66 | 67 | 비구조화 할당은 함수 인자에서도 사용 가능하다. 68 | 69 | ```javascript 70 | function useless({ a, b }) { 71 | console.log(a, b); 72 | } 73 | useless({ a:1, b: 2}); // 1 2 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /02-ecmascript/object-and-array/object-literal-diff.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 최신 ECMAScript에서의 객체 리터럴 변경사항에 대해 다룬다. 3 | --- 4 | 5 | # 2.2.3 객체 리터럴 변경사항 6 | 7 | ### **트레일링 콤마 \(trailing comma\)** 8 | 9 | ES5로부터는 객체 내에서의 트레일링 콤마 사용이 허용된다. 10 | 11 | ```javascript 12 | const objWithTrailingComma = { 13 | a: 1, 14 | b: 1, 15 | }; 16 | ``` 17 | 18 | 트레일링 콤마가 허용되면 원소의 순서를 재배열하거나 이후 새로운 원소를 추가할 때에 보다 깔끔한 변경사항을 가질 수 있다. 이는 git을 비롯한 버전 관리 시스템의 사용에 큰 도움이 된다. 19 | 20 | ### **단축 속성명 \(shorthand property name\)** 21 | 22 | ES6부터는 이미 존재하는 어떤 변수의 변수명을 속성 이름으로, 변수의 값을 속성 값으로 사용해 객체 리터럴을 생성할 때 보다 간결한 문법을 사용 할 수 있다. 23 | 24 | ```javascript 25 | const [a, b] = [1, 2]; 26 | const obj = { a: a, b: b }; 27 | const obj2 = { a, b }; // same as { a: a, b: b } 28 | ``` 29 | 30 | ### **단축 메소드명 \(shorthand method name\)** 31 | 32 | 또한 ES6에는 객체 메소드를 정의하기 위한 보다 간결한 문법이 추가되었다. 33 | 34 | ```javascript 35 | // old 36 | const objWithFunction = { 37 | f: function () { console.log(1); } 38 | }; 39 | objWithFunction.f(); // 1 40 | 41 | // new (ES6~ ) 42 | const objWithFunction2 = { 43 | f() { console.log(1); } 44 | }; 45 | objWithFunction2.f(); // 1 46 | ``` 47 | 48 | ### **계산된 속성 이름\(computed property name\)** 49 | 50 | 마지막으로, ES6부터 계산된 속성 이름을 사용할 수 있다. 객체 리터럴에서 키를 중괄호\(`[]`\)로 감쌀 경우 해당 표현식이 계산된 값을 속성 이름으로 사용한다. 이 때 중괄호 안에는 모든 표현식이 들어갈 수 있다. 51 | 52 | ```javascript 53 | const name = 'heejong'; 54 | const obj = { [name]: 'ahn' }; 55 | console.log(obj); // { heejong: 'ahn' } 56 | const obj3 = { ['ab' + 'c']: 3 }; 57 | console.log(obj3); // { abc: 3 } 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /02-ecmascript/object-and-array/rest-and-spread-operator.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 나머지 연산자와 전개 연산자는 여러 값을 보다 간결하게 할당하는 것을 돕는다. 3 | --- 4 | 5 | # 2.2.2 나머지 연산자와 전개 연산자 6 | 7 | ## **나머지 연산자 \(rest operator\)** 8 | 9 | 비구조화 할당을 사용하되, **배열의 일부 부분 배열을 다른 변수에 할당하고자 할 때** 나머지 연산자를 이용할 수 있다. 10 | 11 | ```javascript 12 | const [a, ...restArr] = [1, 2, 3, 4]; 13 | console.log(restArr); // [2, 3, 4] 14 | ``` 15 | 16 | 나머지 연산자는 함수 인자에서도 사용할 수 있다. 17 | 18 | ```javascript 19 | function normalFunc(p1, p2, ...rest) { 20 | console.log(rest); 21 | } 22 | normalFunc(1, 2, 3, 4); // [3, 4] 23 | ``` 24 | 25 | ## **전개 연산자 \(spread operator\)** 26 | 27 | **여러 개의 변수가 들어갈 자리에 한 배열의 원소들을 분포시키고자 할 때**에 전개 연산자를 사용할 수 있다. 나머지 연산자가 여러 원소를 하나의 배열로 묶어주는 역할을 한다면, 전개 연산자는 하나의 배열을 여러 원소들로 풀어주는 역할을 한다. 둘은 일종의 함수–역함수 관계에 있고, 이런 맥락에서 같은 기호\(`...`\)의 사용을 이해할 수 있다. 28 | 29 | ```javascript 30 | function logThings(a, b, c) { 31 | console.log(a); 32 | console.log(b); 33 | console.log(c); 34 | } 35 | const arr = [1, 2, 3]; 36 | logThings(...[1, 2, 3]); 37 | ``` 38 | 39 | 배열뿐만이 아닌 객체에 대해서도 비슷하게 나머지/전개 연산자를 추가하는 [`proposal-object-rest-spread` 프러포절](https://github.com/tc39/proposal-object-rest-spread)이 진행중이며, 2017년 12월 기준으 3단계에 있다. 40 | 41 | ```javascript 42 | const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; 43 | x; // 1 44 | y; // 2 45 | z; // { a: 3, b: 4 } 46 | const n = { x, y, ...z }; 47 | n; // { x: 1, y: 2, a: 3, b: 4 } 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /02-ecmascript/outro.md: -------------------------------------------------------------------------------- 1 | # 2.7 맺으며 2 | 3 | 이상 최신 ECMAScript 표준에 추가된 주요 기능 중 일부를 살펴보았다. 자바스크립트는 늘어나는 인기와 사용자 수에 걸맞고 빠른 속도로 개선되고 있다. 그리고 자바스크립트의 상위집합으로서, **타입스크립트는 최신 ECMAScript 표준에 포함된 기능을 \(그리고 아직 프러포절 단계인 기능 일부까지도\) 발빠르게 지원한다**. 덕분에 타입스크립트 사용자들은 추가적인 노력 없이도 예전보다 훨씬 나은 개발 환경을 누릴 수 있다. 4 | 5 | 비록 타입스크립트는 엄밀히는 자바스크립트와 다른 언어이지만, 그 뿌리가 되는 언어인 자바스크립트에 대한 숙련도는 타입스크립트를 이용한 프로그래밍의 생산성과 직결된다. 2장에서 다룬 여러 기능은 책을 통틀어, 그리고 그 이후에 타입스크립트로 프로그래밍을 하면서도 자주 만나게 될 것이다. 다음 장부터는 본격적으로 타입스크립트 고유의 영역에 대해 다루어본다. 가장 먼저 논의할 내용은 자바스크립트와 차별화되는 타입스크립트의 기초 문법이다. 6 | 7 | -------------------------------------------------------------------------------- /02-ecmascript/template-literal/README.md: -------------------------------------------------------------------------------- 1 | # 2.4 템플릿 리터럴 2 | 3 | ES6에는 문자열 관련 다양한 편의 기능을 제공하는 템플릿 리터럴이 추가되었다. 템플릿 리터럴은 문자열과 비슷하되, 따옴표나 쌍따옴표가 아닌 백틱\(`````\)으로 감싸진다. 상대적으로 덜 흔한 용례인 [태그된 템플릿\(tagged template\)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates)은 여기선 다루지 않는다. 4 | 5 | -------------------------------------------------------------------------------- /02-ecmascript/template-literal/multiline-string.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 템플릿 리터럴을 이용해 여러 줄에 걸친 문자열을 손쉽게 표현할 수 있다. 3 | --- 4 | 5 | # 2.4.1 멀티라인 문자열 6 | 7 | 이전 버전의 자바스크립트는 멀티라인 문자열을 손쉽게 생성할 수 있는 수단을 제공하지 않았다. 따라서 프로그래머가 직접 문자열을 더하기 연산자를 이용해 연결하거나, 배열의 `Array.prototype.join` 함수 등을 이용하는 식의 접근이 필요했다. 8 | 9 | 템플릿 리터럴은 여러 라인에 걸쳐 정의할 수 있으며, 해당 문자열은 문자열 내의 공백 및 줄바꿈을 모두 보존한다. 10 | 11 | ```javascript 12 | const a = ` 13 | First line 14 | Second line 15 | `; 16 | console.log(a); // First line 17 | // Second line 18 | ``` 19 | 20 | 이 때 **공백도 보존된다**는 점에 주의해야 한다. 들여쓰기를 맞추기 위해 문자열 내에서 공백을 넣을 시 그 공백은 문자열에 포함된다. 21 | 22 | ```javascript 23 | const a = `First line 24 | Second line`; 25 | console.log(a); // First line 26 | // Second line 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /02-ecmascript/template-literal/string-substitution.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 템플릿 리터럴은 문자열의 일부를 특정 값으로 치환할 수 있는 수단을 제공한다. 3 | --- 4 | 5 | # 2.4.2 문자열 치환 6 | 7 | 매우 흔하게 요구되는 사항임에도 불구하고 이전 버전의 자바스크립트에는 문자열 포매팅을 위한 이렇다할 기능이 없었다. 때문에 문자열 포매팅을 위해선 주로 `Array.prototype.join` 메소드를 사용하거나 문자열을 더하는 방식이 사용되었다. 8 | 9 | ```javascript 10 | const name = 'Ahn Heejong'; 11 | function greetingsWithConcat(name) { 12 | console.log('Hello, ' + name + '!'); 13 | } 14 | function greetingsWithArrayJoin(name) { 15 | const greetings = ['Hello, ', name, '!']; 16 | console.log(greetings.join('')); 17 | } 18 | ``` 19 | 20 | 템플릿 리터럴의 가장 유용한 사용예 중 하나가 바로 문자열 포매팅이다. 템플릿 리터럴 문법을 사용하면 문자열의 특정 부분을 자바스크립트 값으로 런타임에 손쉽게 치환할 수 있다. 템플릿 리터럴 내에서 `${value}`로 참조된 부분은 런타임에 자바스크립트 값 `value`로 대체된다. 21 | 22 | ```javascript 23 | function beforeES6(firstName, lastName) { 24 | console.log('My name is ' + firstName + ' ' + lastName +'!'); 25 | } 26 | function sinceES6(firstName, lastName) { 27 | console.log(`My name is ${firstName} ${lastName}!`); 28 | } 29 | beforeES6('Heejong', 'Ahn'); // My name is Heejong Ahn! 30 | sinceES6('Heejong', 'Ahn'); // My name is Heejong Ahn! 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /03-basic-grammar/array-and-tuple.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | 순서가 있는 원소의 모음(collection)을 나타내는 가장 간단하면서도 유용한 자료구조인 배열, 그리고 그 사촌 튜플을 나타내는 타입에 4 | 대해 다룬다. 5 | --- 6 | 7 | # 3.2 배열과 튜플 8 | 9 | ### **배열** 10 | 11 | 배열 타입은 자바스크립트 `Array` 값의 타입을 나타내는데 쓰인다. 원소 타입 뒤에 대괄호\(`[]`\)를 붙여 표현한다. 12 | 13 | ```typescript 14 | const pibonacci: number[] = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]; 15 | const myFavoriteBeers: string[] = ['Imperial Stout', 'India Pale Ale', 'Weizenbock']; 16 | ``` 17 | 18 | 배열 타입을 표현하는 또다른 방식이 있다. 19 | 20 | ```typescript 21 | const pibonacci: Array = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]; 22 | const myFavoriteBeers: Array = ['Imperial Stout', 'India Pale Ale', 'Weizenbock']; 23 | ``` 24 | 25 | 이 문법의 의미는 3.6절에서 **제너릭**을 소개하며 좀 더 자세히 살펴본다. 26 | 27 | ### **튜플** 28 | 29 | 튜플 타입을 이용해 원소의 수와 각 원소의 타입이 정확히 지정된 배열의 타입을 정의할 수 있다. 30 | 31 | ```typescript 32 | const nameAndHeight: [string, number] = ['안희종', 176] 33 | ``` 34 | 35 | **튜플 타입 변수는 정확히 명시된 개수 만큼의 원소만을 가질 수 있다**. 만약 타입 정의보다 더 많은, 혹은 더 적은 원소를 갖는 배열을 할당한다면 에러를 낸다. 36 | 37 | ```typescript 38 | const invalidNameAndHeight: [string, number] = ['안희종', 176, 42]; 39 | // error TS2322: Type '[string, number, boolean]' is not assignable to type '[string, number]'. 40 | // Types of property 'length' are incompatible. 41 | // Type '3' is not assignable to type '2'. 42 | ``` 43 | 44 | 다만 튜플 타입의 값을 `Array` 프로토타입의 메소드를 통해 조작하는 것은 금지되지 않는다는 점에 유의해야 한다. 예를 들어 아래와 같은 코드는 에러를 내지 않는다. 45 | 46 | ```typescript 47 | const validNameAndHeight: [string, number] = ['안희종', 176]; 48 | validNameAndHeight.push(42); // no error 49 | ``` 50 | 51 | {% hint style="info" %} 52 | 타입스크립트 2.6 이하 버전에서는 튜플 타입은 정확한 원소 개수를 보장하지 않는다. 예를 들어 다음과 같은 코드가 허용되었다. 53 | 54 | ```typescript 55 | const nameAndHeight: [string, number] = ['안희종', 176, true]; 56 | ``` 57 | 58 | 이러한 동작 방식은 대부분의 실사용례에서 도움이 되지 않는다고 판단되어 2.7 버전부터는 현재와 같이 동작하도록 변경되었다. 59 | {% endhint %} 60 | 61 | -------------------------------------------------------------------------------- /03-basic-grammar/enums.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 유한한 경우의 수를 갖는 값의 집합을 표현하기 위해 사용하는 열거형(enum) 타입에 대해 배운다. 3 | --- 4 | 5 | # 3.9 열거형 6 | 7 | ### **숫자 열거형\(Numeric Enum\)** 8 | 9 | 숫자 열거형은 `number` 타입 값에 기반한 열거형이다. 만약 열거형을 정의하며 멤버의 값을 초기화하지 않을 경우, 해당 멤버의 값은 `0`부터 순차적으로 증가하는 숫자 값을 갖는다. 예를 들어 아래 예제 두 예제는 동일하게 동작한다. 10 | 11 | ```typescript 12 | enum Direction { 13 | East, 14 | West, 15 | South, 16 | North 17 | } 18 | enum ExplicitDirection { 19 | East = 0, 20 | West = 1, 21 | South = 2, 22 | North = 3 23 | } 24 | ``` 25 | 26 | 이렇게 정의한 열거형의 멤버에는 객체의 속성에 접근하는 것과 동일한 방식으로 접근할 수 있다. 어떤 열거형 `Enum`의 모든 멤버는 `Enum` 타입을 갖는다. 27 | 28 | ```typescript 29 | const south: Direction = Direction.South; 30 | console.log(south); // 2 31 | ``` 32 | 33 | ### **멤버 값 초기화** 34 | 35 | `0`부터 시작되는 자동 초기화에 의존하는 대신, 각 멤버의 값을 직접 초기화 할 수 있다. 36 | 37 | ```typescript 38 | enum InitializedDirection { 39 | East = 2, 40 | West = 4, 41 | South = 8, 42 | North = 16 43 | } 44 | ``` 45 | 46 | 만약 초기화 되지 않은 멤버가 섞여있다면, 그 멤버의 값은 이전에 초기화된 멤버의 값으로부터 순차적으로 증가해서 결정된다. 47 | 48 | ```typescript 49 | enum InitializedDirection2 { 50 | East = 3, 51 | West /* 4 */, 52 | South = 7, 53 | North /* 8 */ 54 | } 55 | ``` 56 | 57 | ### **문자열 열거형\(String Enum\)** 58 | 59 | `number` 타입 값 대신 `string` 타입 값을 사용해서 멤버 값을 초기화하는 것도 가능하다. 60 | 61 | ```typescript 62 | enum Direction { 63 | East = 'EAST', 64 | West = 'WEST', 65 | South = 'SOUTH', 66 | North = 'NORTH' 67 | } 68 | ``` 69 | 70 | 문자열 열거형은 숫자 열거형과 다음 부분을 제외하고는 많은 부분 동일하다. 71 | 72 | * 문자열을 ‘자동 증가’ 시킨다는 개념은 성립하지 않는다. 따라서 문자열 멤버 이후로 정의된 모든 멤버는 명시적으로 초기화되어야 한다. 73 | * 숫자 열거형과 달리, 문자열 열거형이 컴파일된 자바스크립트 코드에는 값 → 키 의 역방향 매핑\(reverse mapping\)이 존재하지 않는다. 74 | 75 | {% hint style="info" %} 76 | 한 열거형에서 숫자 멤버와 문자열 멤버를 모두 사용하는 식의 이형 열거형\(Heterogeneous Enum\)도 문법 상 허용은 된다. 하지만 이형 열거형을 사용해 큰 이득을 얻을 수 있는 경우는 드물고, 대부분의 경우 혼란을 불러 올 수 있어 권장되지 않는다. 77 | {% endhint %} 78 | 79 | ### **상수 멤버와 계산된 멤버** 80 | 81 | 지금까지 다룬 열거형의 멤버는 모두 명시적이든, 암시적든 컴파일 타임에 알 수 있는 상수값으로 초기화 되었다. 이런 열거형 멤버를 상수 멤버\(constant member\)라 부른다. 82 | 83 | 한 편, 런타임에 결정되는 값을 열거형의 멤버 값으로 사용할 수도 있다. 이런 멤버를 계산된 멤버\(computed member\)라고 부른다. 계산된 멤버의 값은 실제로 코드를 실행시켜봐야만 알 수 있으므로, 계산된 멤버 뒤에 오는 멤버는 반드시 초기화되어야 한다는 점에 유의하라. 84 | 85 | ```typescript 86 | function getAnswer() { 87 | return 42; 88 | } 89 | enum SpecialNumbers { 90 | Answer = getAnswer(), 91 | Mystery // error TS1061: Enum member must have initializer. 92 | } 93 | ``` 94 | 95 | ### **런타임에서의 열거형** 96 | 97 | 기본적으로 아래와 같은 타입스크립트 코드에서의 열거형 정의 및 접근은 98 | 99 | ```typescript 100 | enum Direction { 101 | East, 102 | West, 103 | South, 104 | North 105 | } 106 | const east: Direction = Direction.East; 107 | ``` 108 | 109 | 아래와 같은 자바스크립트 코드로 컴파일 된다. 110 | 111 | ```javascript 112 | var Direction; 113 | (function (Direction) { 114 | Direction[Direction["East"] = 0] = "East"; 115 | Direction[Direction["West"] = 1] = "West"; 116 | Direction[Direction["South"] = 2] = "South"; 117 | Direction[Direction["North"] = 3] = "North"; 118 | })(Direction || (Direction = {})); 119 | var east = Direction.East; 120 | ``` 121 | 122 | 이 코드를 보면 두 가지 일이 일어나고 있음을 확인할 수 있다. 123 | 124 | * 식별자에 키 → 값으로의 매핑이 정의된다. \(`Direction["EAST"] = 0`\) 125 | * 식별자에 값 → 키로의 역방향 매핑이 정의된다. \(`Direction[Direction["East"] = 0] = "East"`\) 126 | 127 | {% hint style="info" %} 128 | 문자열 열거형의 경우 앞서 언급한대로 역방향 매핑이 존재하지 않는다. 아래의 문자열 열거형은 129 | 130 | ```typescript 131 | enum Direction { 132 | East = 'EAST', 133 | West = 'WEST', 134 | South = 'SOUTH', 135 | North = 'NORTH' 136 | } 137 | ``` 138 | 139 | 아래 자바스크립트 코드로 컴파일된다. 140 | 141 | ```typescript 142 | var Direction; 143 | (function (Direction) { 144 | Direction["East"] = "EAST"; 145 | Direction["West"] = "WEST"; 146 | Direction["South"] = "SOUTH"; 147 | Direction["North"] = "NORTH"; 148 | })(Direction || (Direction = {})); 149 | ``` 150 | {% endhint %} 151 | 152 | 컴파일된 코드로부터 열거형 멤버에 접근 할 때 실제로 코드가 실행될 때에도 객체 속성 접근이 발생함을 알 수 있다. 이 오버헤드는 대부분의 경우 무시 가능할 수준이다. 그럼에도 성능 향상을 꾀하고 싶다면 `const` 열거형을 사용할 수 있다. 153 | 154 | 모든 멤버가 컴파일 시간에 알려진 상수값인 열거형의 경우 `enum` 키워드 대신 `const enum` 키워드를 이용해 정의할 수 있다. 이렇게 정의한 열거형의 구조는 컴파일 과정에서 완전히 사라지고, 멤버의 값은 상수값으로 대체된다. 아래의 예제를 보자. 155 | 156 | ```typescript 157 | const enum ConstEnum { 158 | A, 159 | B = 2, 160 | C = B * 2, 161 | D = -C, 162 | } 163 | console.log(ConstEnum.A); 164 | ``` 165 | 166 | 위 코드는 아래 자바스크립트 코드로 컴파일된다. 167 | 168 | ```typescript 169 | console.log(0 /* A */); 170 | ``` 171 | 172 | 주석을 제외하고는 열거형의 원래 구조에 대한 어떠한 정보도 남아있지 않고, 상수값으로 대체되어 있음을 확인할 수 있다. 173 | 174 | ### **유니온 열거형** 175 | 176 | 열거형의 모든 멤버가 아래 경우 중 하나에 해당하는 열거형을 유니온 열거형\(union enum\)이라 부른다. 177 | 178 | * 암시적으로 초기화 된 값 \(값이 표기되지 않음\) 179 | * 문자열 리터럴 180 | * 숫자 리터럴 181 | 182 | 예를 들어 아래 `ShapeKind` 열거형은 유니온 열거형이다. 183 | 184 | ```typescript 185 | enum ShapeKind { 186 | Circle, 187 | Triangle = 3, 188 | Square 189 | } 190 | ``` 191 | 192 | 유니온 열거형의 멤버는 값인 동시에 타입이 된다. 따라서 예를 들어 아래와 같은 코드를 작성할 수 있다. 193 | 194 | ```typescript 195 | type Circle = { 196 | kind: ShapeKind.Circle; 197 | radius: number; 198 | } 199 | type Triangle = { 200 | kind: ShapeKind.Triangle; 201 | maxAngle: number; 202 | } 203 | type Square = { 204 | kind: ShapeKind.Square; 205 | maxLength: number; 206 | } 207 | type Shape = Circle | Triangle | Square; 208 | ``` 209 | 210 | 또한 컴파일러는 유니온 열거형의 특징으로부터 컴파일 타임에 추가적인 검사를 시행할 수 있다. 이에 대해서는 추후 타입 좁히기\(type narrowing\)에 대해 다룰 때 함께 다룬다. 211 | 212 | ### **유니온 타입을 이용한 열거형 표현** 213 | 214 | 타입스크립트는 숫자, 문자열 그리고 불리언 값을 타입으로 사용하는 리터럴 타입\(literal type\)을 지원한다. 리터럴 타입을 이용해 단 하나의 값만을 갖는 타입을 정의할 수 있다. 215 | 216 | ```typescript 217 | const answer: 42 = 42; 218 | const wrongAnswer: 42 = 24; // error TS2322: Type '24' is not assignable to type '42'. 219 | ``` 220 | 221 | 이 때 리터럴 타입과 유니온 타입을 조합해 열거형과 유사한 타입을 만들 수 있다. 222 | 223 | ```typescript 224 | type Direction = 'EAST' | 'WEST' | 'SOUTH' | 'NORTH'; 225 | const east: Direction = 'EAST'; 226 | const center: Direction = 'CENTER'; // error TS2322: Type '"CENTER"' is not assignable to type 'Direction'. 227 | ``` 228 | 229 | -------------------------------------------------------------------------------- /03-basic-grammar/function.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 자바스크립트 프로그램에서 가장 핵심적인 역할을 차지하는 함수 타입이 타입스크립트에서 어떻게 표현되는지 다룬다. 3 | --- 4 | 5 | # 3.5 함수 6 | 7 | ## **함수의 타입** 8 | 9 | 함수의 타입을 결정하기 위해서는 다음 두 가지 정보가 필요하다. 10 | 11 | * 매개변수\(parameter\)의 타입 12 | * 반환값\(return value\)의 타입 \(반환 타입\) 13 | 14 | 매개변수의 경우, 변수의 타입을 표기할 때와 마찬가지로 매개변수 뒤에 콜론\(`:`\)을 붙이고 타입을 적는다. \(`param1: number`\) 15 | 16 | 반환 타입은 매개변수 목록을 닫는 괄호\(`)`\)와 함수 본문을 여는 여는 대괄호\(`{`\) 사이에 콜론을 붙이고 표기한다. \(`function (): number { ... }`\) 17 | 18 | 예를 들어 **두 숫자를 받아** 그 **합을 반환**하는 함수는 다음과 같이 타입 표기한다. 19 | 20 | ```typescript 21 | function sum(a: number, b: number): number { 22 | return (a + b); 23 | } 24 | ``` 25 | 26 | 만약 함수가 아무런 값도 반환하지 않고 종료된다면 반환 타입으로 `void` 를 사용한다. 27 | 28 | ```typescript 29 | function logGreetings(name: string): void { 30 | console.log(`Hello, ${name}!`); 31 | } 32 | ``` 33 | 34 | `void` 반환 타입을 갖는 함수가 `undefined`나 `null` 이외의 값을 반환하면 타입 에러가 발생한다. `void`가 아닌 반환 타입을 갖는 함수가 아무 값도 반환하지 않는 경우도 마찬가지다. 35 | 36 | ```typescript 37 | function notReallyVoid(): void { 38 | return 1; 39 | } 40 | // error TS2322: Type '1' is not assignable to type 'void'. 41 | 42 | function actuallyVoid(): number { } 43 | // error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. 44 | ``` 45 | 46 | ## **함수 값의 타입 표기** 47 | 48 | 함수 타입의 값에 타입 표기를 붙이기 위해서는 화살표 함수 정의 문법과 비슷한 문법을 사용한다. 49 | 50 | ```typescript 51 | (...매개변수 나열) => 반환 타입 52 | ``` 53 | 54 | 매개변수가 없는 함수의 경우 매개변수를 생략해 아래와 같이 적는다. 55 | 56 | ```typescript 57 | () => 반환 타입 58 | ``` 59 | 60 | 예시를 들어보면 아래와 같다. 화살표 함수 문법을 사용한 함수 또한 비슷하게 정의 가능하다. 61 | 62 | ```typescript 63 | const yetAnotherSum: (a: number, b: number) => number = sum; 64 | const onePlusOne: () => number = () => 2; 65 | const arrowSum: (a: number, b: number) => number = (a, b) => (a + b); 66 | ``` 67 | 68 | 타입 별칭 또한 사용 가능하다. 69 | 70 | ```typescript 71 | type SumFunction = (a: number, b: number) => number; 72 | const definitelySum: SumFunction = (a, b) => (a + b); 73 | ``` 74 | 75 | ## **기본 매개변수** 76 | 77 | ES6와 마찬가지로, 타입스크립트에서도 기본 매개변수 문법을 사용할 수 있다. 이 때 기본값은 78 | 79 | ```typescript 80 | 매개변수명: 타입 = 기본값 81 | ``` 82 | 83 | 의 형태로 표기한다. 84 | 85 | ```typescript 86 | function greetings(name: string = 'stranger'): void { 87 | console.log(`Hello, ${name}`); 88 | } 89 | greetings('Heejong'); // Hello, Heejong! 90 | greetings(); // Hello, stranger! 91 | ``` 92 | 93 | ## **선택 매개변수** 94 | 95 | 많은 프로그래밍 언어는 함수 정의에 명시된 매개변수의 수보다 많거나 적은 수의 인자가 들어온 경우 에러를 뱉는다. 한편, 자바스크립트는 더 들어온 인자는 버리고, 덜 들어온 인자는 `undefined`가 들어온 것과 동일하게 취급한 후 어떻게든 함수를 실행하려 시도한다. 96 | 97 | 이런 언어의 특성 및 기존 사용례를 포용하면서 타입 안정성을 확보하기 위해 타입스크립트는 **선택 매개변수**를 지원한다. 함수의 매개변수 이름 뒤에 물음표\(`?`\) 기호를 붙여 해당 매개변수가 생략 될 수 있음을 명시할 수 있다. 예를 들어, `optional?: number` 로 선언된 선택 매개변수 `optional`를 함수 본문에서 `number` 타입 값으로 사용하려면 해당 값이 `undefined`가 아닌지를 먼저 검사해야 한다. 98 | 99 | ```typescript 100 | function fetchVideo(url: string, subtitleLanguage?: string) { 101 | const option = { url }; 102 | if (subtitleLanguage) { 103 | option.subtitleLanguage = true; 104 | } 105 | /* ... */ 106 | } 107 | fetchVideo('https://example.com', 'ko'); // okay 108 | fetchVideo('https://example.com'); // also okay 109 | ``` 110 | 111 | 이 때 매개변수 정의 순서에서 선택 매개변수 이후에 필수 매개변수를 두는 것은 허용되지 않는다. 112 | 113 | ```typescript 114 | function invalidFetchVideo(subtitleUrl?: string, url: string) { 115 | /* ... */ 116 | } 117 | //error TS1016: A required parameter cannot follow an optional parameter. 118 | ``` 119 | 120 | 이러한 제약이 존재하는 이유는 만약 필수 매개변수가 선택 매개변수 뒤에 있을 시, 인자가 어떤 매개변수의 값인지 구분할 수 없기 때문이다. 예를 들어 위의 두 함수를 아래와 같이 호출하는 경우를 생각해보자. 121 | 122 | ```typescript 123 | fetchVideo('https://example.com'); 124 | invalidFetchVideo('https://example.com'); 125 | ``` 126 | 127 | 이 때 첫 번째 호출의 경우 인자가 `url` 매개변수의 값이라는 것이 명백하다. 한편 두 번째 호출에서는 `'https://example.com'` 이라는 값이 선택매개변수인 `subtitleUrl`의 값으로 쓰인건지, 또는 `url`의 값으로 쓰인 건지 모호하다. 따라서 타입스크립트는 이런 식의 함수 정의를 만나면 오류를 발생시킨다. 128 | 129 | ## **함수 오버로딩** 130 | 131 | 자바스크립트에서는 한 함수가 여러 쌍의 매개변수-반환 타입 쌍을 갖는 경우가 매우 흔하다. 이런 함수의 타입을 정의할 수 있게 하고자 타입스크립트는 함수 오버로딩\(function overloading\)을 지원한다. 132 | 133 | 타입스크립트의 함수 오버로딩은 다음과 같은 특징을 갖는다. 134 | 135 | * 함수는 **하나 이상의 타입 시그니처**를 가질 수 있다. 136 | * 함수는 **단 하나의 구현**을 가질 수 있다. 137 | 138 | 즉, 오버로딩을 통해서 여러 형태의 함수 타입을 정의할 수 있지만, 실제 구현은 한 번만 가능하므로 여러 경우에 대한 분기는 함수 본문 내에서 이루어져야 한다. 139 | 140 | 예를 들어 다음 함수들을 보자. 141 | 142 | ```typescript 143 | function doubleString(str: string): string { 144 | return `${str}${str}`; 145 | } 146 | function doubleNumber(num: number): number { 147 | return (num * 2); 148 | } 149 | function doubleBooleanArray(arr: boolean[]): boolean[] { 150 | return arr.concat(arr); 151 | } 152 | ``` 153 | 154 | 이 함수들은 각각 문자열, 숫자, 그리고 불리언의 배열을 받아 두 배로 만드는 함수다. 이 때, ‘두 배’가 의미하는 건 타입에 따라 다르고, 세 함수는 인풋 타입에 따라 다른 타입의 값을 반환한다. 이 세 함수를 함수 오버로딩을 사용해서 하나의 `double` 이라는 함수로 합쳐보자. 155 | 156 | 먼저 각 경우의 타입 시그니쳐를 정의한다. 타입 시그니쳐는 함수 정의와 동일하되, 본문이 생략된 형태다. 157 | 158 | ```typescript 159 | function double(str: string): string; 160 | function double(num: number): number; 161 | function double(arr: boolean[]): boolean[]; 162 | ``` 163 | 164 | 그 후엔 함수의 본문을 구현한다. 165 | 166 | ```typescript 167 | function double(arg) { 168 | if (typeof arg === 'string') { 169 | return `${arg}${arg}`; 170 | } else if (typeof arg === 'number') { 171 | return arg * 2; 172 | } else if (Array.isArray(arg)) { 173 | return arg.concat(arg); 174 | } 175 | } 176 | ``` 177 | 178 | 이렇게 오버로딩을 통해 정의된 `double` 함수는 호출하는 인자의 타입에 따라 반환 타입이 달라진다. 179 | 180 | ```typescript 181 | const num = double(3); // number 182 | const str = double('ab'); // string 183 | const arr = double([true, false]); // boolean[] 184 | ``` 185 | 186 | ## **This 타입** 187 | 188 | 앞서 2장에서 언급했지만, 자바스크립트 함수 내부에서의 `this` 값은 함수가 정의되는 시점이 아닌 **실행되는 시점**에 결정된다. 이런 특성은 함수 내부에서 `this` 의 타입을 추론하는 일을 매우 어렵게 만든다. 타입스크립트는 이런 어려움을 해결하기 위해 함수 내에서의 `this` 타입을 명시할 수 있는 수단을 제공한다. 189 | 190 | 함수의 `this` 타입을 명시하기 위해선 함수의 타입 시그니쳐에서 매개변수 가장 앞에 `this` 를 추가해야 한다. 이 때 `this` 타입은 타입 시스템을 위해서만 존재하는 일종의 가짜 타입이다. 즉, `this` 매개변수를 추가한다고 해도 함수가 받는 인자 수와 같은 실제 동작은 변하지 않는다. 191 | 192 | ```typescript 193 | interface HTMLElement { 194 | tagName: string; 195 | /* ... */ 196 | } 197 | interface Handler { 198 | (this: HTMLElement, event: Event, callback: () => void): void; 199 | } 200 | let cb: any; 201 | // 실제 함수 매개변수에는 this가 나타나지 않음 202 | const onClick: Handler = function(event, cb) { 203 | // this는 HTMLElement 타입 204 | console.log(this.tagName); 205 | cb(); 206 | } 207 | ``` 208 | 209 | 만약 `this`의 타입을 `void`로 명시한다면 함수 내부에서 `this`에 접근하는 일 자체를 막을 수 있다. 210 | 211 | ```typescript 212 | interface NoThis { 213 | (this: void): void; 214 | } 215 | const noThis: NoThis = function() { 216 | console.log(this.a); // Property 'a' does not exist on type 'void'. 217 | } 218 | ``` 219 | 220 | -------------------------------------------------------------------------------- /03-basic-grammar/generics.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 제너릭을 이용해 여러 타입에 대해 동일한 규칙을 갖고 동작하는 타입을 손쉽고 우아하게 정의할 수 있다. 3 | --- 4 | 5 | # 3.6 제너릭 6 | 7 | ### **동기부여** 8 | 9 | 다음의 자바스크립트 함수를 생각해보자. 인자로 넘겨지는 배열은 항상 같은 타입의 값으로 이루어져 있다고 가정한다. 10 | 11 | ```typescript 12 | function getFirstElem(arr) { 13 | if (!Array.isArray(arr)) { 14 | throw new Error('getFirstElemOrNull: Argument is not array!'); 15 | } 16 | if (arr.length === 0) { 17 | throw new Error('getFirstElemOrNull: Argument is an empty array!'); 18 | } 19 | return arr[0] ? arr[0] : null; 20 | } 21 | ``` 22 | 23 | 배열을 받아 첫 번째 원소가 있을 시 그 원소를 리턴하는 매우 간단한 함수다. 문자열의 배열을 인자로 호출하면 문자열 타입의 값을, 숫자의 배열을 인자로 호출하면 숫자 타입의 값을 반환할 것이다. 이 함수의 타입을 어떻게 정의할 수 있을까? 24 | 25 | 만약 `getFirstElem`이 문자열과 숫자 두 타입만을 지원한다면 함수 오버로딩을 이용해 다음과 같이 쓸 수 있다. 26 | 27 | ```typescript 28 | function getFirstElem(arr: string[]): string; 29 | function getFirstElem(arr: number[]): number; 30 | function getFirstElem(arr) { 31 | if (!Array.isArray(arr)) { 32 | throw new Error('getFirstElemOrNull: Argument is not array!'); 33 | } 34 | if (arr.length === 0) { 35 | throw new Error('getFirstElemOrNull: Argument is an empty array!'); 36 | } 37 | return arr[0] ? arr[0] : null; 38 | } 39 | ``` 40 | 41 | 하지만 이 함수가 **임의의 타입 값을 원소로 갖는 배열**에 대해 동작하도록 만들려면 어떻게 해야 할까? 존재할 수 있는 모든 타입에 대해 오버로딩을 적는 건 \(가능한 타입의 수가 무한하므로\) 불가능하다. 인자와 반환 타입을 any로 정의한다면 동작은 하겠지만, 타입 정보를 모두 잃게 되므로 좋은 방법이 아니다. 42 | 43 | 우리가 원하는 기능은 다음과 같다. **여러 타입에 대해 동작하는 함수를 정의하되, 해당 함수를 정의할 때는 알 수 없고 사용할 때에만 알 수 있는 타입 정보를 사용하고 싶다.** 제너릭은 바로 그러한 기능을 제공한다. 44 | 45 | ### **타입 변수** 46 | 47 | 함수를 호출하는 시점이 되어야만 알 수 있는 값을 함수 내부에서 사용하기 위해서는 그 값을 담아둘 매개변수가 필요하다. 마찬가지로, **요소를 사용하는 시점에서만 알 수 있는 타입을 담아두기 위해서는 타입 변수\(type variable\)가 필요하다**. 타입 변수와 타입의 관계는 매개변수와 인자 값의 관계와 비슷하다. 48 | 49 | | | 함 | 제너릭 | 50 | | --- | --- | --- | --- | --- | 51 | | 정의 시점 | 매개변수 `a: number` | 타입 변수 `` | 52 | | 정의 예시 | `function (a: number) { ... }` | `type MyArray = T[]` | 53 | | 사용 시 | 실제 값 \(`3`, `42` 등\) 사용 | 실제 타입\(`number`, `string` 등\) 사용 | 54 | | 사용 예시 | `a(3); a(42);` | `type MyNumberArray = MyArray` | 55 | 56 | 타입 변수는 부등호\(`<`,`>`\)로 변수명을 감싸 정의한다. 이렇게 정의한 타입 변수를 요소의 타입 정의 \(예를 들어 함수의 인자 및 반환 타입\)에 사용할 수 있다. 부등호 기호 안에 정의된 타입 변수의 실제 타입은 실제 값이 넘어오는 시점에 결정된다. 57 | 58 | 컨벤션 상 타입스크립트의 타입 변수는 대문자로 시작하며 PascalCase 명명법을 사용한다. 59 | 60 | ### **제너릭 함수** 61 | 62 | 타입 변수를 이용해 위의 `getFirstElem` 함수를 다음과 같이 제너릭 함수로 정의할 수 있다. 63 | 64 | ```typescript 65 | function getFirstElem(arr: T[]): T { 66 | /* 함수 본문 */ 67 | } 68 | ``` 69 | 70 | 위의 타입 정의는 다음과 같이 읽을 수 있다. 71 | 72 | > **임의의 타입** `T`에 대해, `getFirstElem`은 `T` 타입 값의 배열 `arr`를 인자로 받아 `T` 타입 값을 반환하는 함수다. 73 | 74 | 보다 일반적으로는 다음과 같은 꼴이 된다. 이 때 인자 타입과 반환 타입을 표현할 때 타입 변수를 사용할 수 있다. 75 | 76 | ```typescript 77 | function 함수명<타입 변수>(인자 타입): 반환 타입 { 78 | /* 함수 본문 */ 79 | } 80 | ``` 81 | 82 | 함수를 호출 할 때에는 정의에서 매개변수가 있던 자리에 인자를 넣어준다. 마찬가지로, 제너릭 함수를 호출할 때에는 정의에서 타입 변수가 있던 자리에 타입 인자를 넣어준다. 83 | 84 | ```typescript 85 | const languages: string[] = ['TypeScript', 'JavaScript']; 86 | const language = getFirstElem(languages); // 이 때 language의 타입은 문자열 87 | ``` 88 | 89 | ### **제너릭 타입 별칭** 90 | 91 | 타입 별칭 정의에도 제너릭을 사용할 수 있다. 이 때 타입 변수 정의는 별칭 이름 다음에 붙여 쓴다. 92 | 93 | ```typescript 94 | type MyArray = T[]; 95 | const drinks: MyArray = ['Coffee', 'Milk', 'Beer']; 96 | ``` 97 | 98 | ### **제너릭의 사용처** 99 | 100 | 타입 변수와 제너릭의 핵심은 **여러 타입에 대해 동작하는 요소를 정의하되, 해당 요소를 사용할 때가 되어야 알 수 있는 타입 정보를 정의에 사용하는 것**이다. 이러한 개념이 적용되는 범위는 함수와 타입 별칭에 국한되지 않는다. 제너릭을 이용해 추후 다룰 인터페이스, 클래스 등 다양한 타입의 표현력을 높힐 수 있다. 4장에서 추후 해당 주제를 다룰 때에 좀 더 자세히 다룬다. 101 | 102 | -------------------------------------------------------------------------------- /03-basic-grammar/intersection-type.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 인터섹션 타입을 이용해 “여러 경우에 모두 해당”하는 타입을 표현할 수 있다. 3 | --- 4 | 5 | # 3.8 인터섹션 타입 6 | 7 | ### **동기부여** 8 | 9 | 예를 들어, 프로그래머를 나타내는 타입과 값을 다음과 같이 정의했다고 하자. 10 | 11 | ```typescript 12 | type Programmer = { favoriteLanguage: string }; 13 | const programmer: Programmer = { favoriteLanguage: 'TypeScript' }; 14 | ``` 15 | 16 | 그리고 맥주를 좋아하는 사람의 타입과 값을 다음과 같이 정의했다. 17 | 18 | ```typescript 19 | type BeerLover = { favoriteBeer: string }; 20 | const beerLover: BeerLover = { favoriteBeer: 'Imperial Stout' }; 21 | ``` 22 | 23 | 그렇다면, **맥주를 좋아하는 프로그래머의 타입**은 어떻게 나타낼 수 있을까? 물론 모든 필드를 다 적어 새로운 타입을 정의하는 식의 단순한 접근도 가능하다. 24 | 25 | ```typescript 26 | type BeerLovingProgrammer = { favoriteLanguage: string; favoriteBeer: string; }; 27 | const AhnHeejong: BeerLovingProgrammer = { 28 | favoriteLanguage: 'TypeScript', 29 | favoriteBeer: 'Imperial Stout', 30 | }; 31 | ``` 32 | 33 | 하지만 이런 접근은 코드 복사–붙여넣기와 동일하게 변화에 취약하다는 단점을 갖는다. 예를 들어 추후 `Programmer` 타입에 문자열 타입 `textEditor` 속성이 추가된다면, 프로그래머를 나타내는 모든 타입을 찾아 해당 속성을 추가해야 한다. 귀찮은 것은 차치하더라도, 이 과정에서 어디인가 빼먹을 가능성이 높다. 34 | 35 | 이런 비효율을 피하고 변화에 유연하게 대응하기 위해선 **이미 존재하는 여러 타입을 모두 만족하는 타입**을 표현하기 위한 수단이 필요하다. **인터섹션 타입**은 바로 그걸 가능케 한다. 36 | 37 | ### **문법** 38 | 39 | 여러 타입을 앰퍼샌드\(`&`\) 기호로 이어서 인터섹션 타입을 나타낼 수 있다. 40 | 41 | ```typescript 42 | type BeerLovingProgrammer = Programmar & BeerLover; 43 | ``` 44 | 45 | `A & B` 타입의 값은 `A` 타입에도, `B` 타입에도 할당 가능해야 한다. 만약 `A`와 `B` 모두 객체 타입이라면 `A & B` 타입의 객체는 `A`와 `B` 타입 각각에 정의된 속성 모두를 가져야 한다. 46 | 47 | 이 때 어떤 값도 만족하지 않는 인터섹션 타입이 생길 수도 있다는 점에 유의하라. 48 | 49 | ```typescript 50 | type Infeasible = string & number 51 | ``` 52 | 53 | 문자열인 **동시에** 숫자인 값은 존재하지 않으므로, 위 `Infeasible` 타입은 실제로는 어떤 값도 가질 수 없다. 54 | 55 | 인터섹션 타입 역시 몇 개든 이어가며 정의할 수 있다. 56 | 57 | ```typescript 58 | type Awesome = Programmer & BeerLover & CatLover; 59 | ``` 60 | 61 | ### **여러 줄에 걸쳐 적은 인터섹션 타입** 62 | 63 | 인터섹션 타입을 여러 줄에 걸쳐 적을 때 유니온 타입의 그것과 동일한 내용이 적용된다. 아래 두 방식으로 정의할 수 있다. 64 | 65 | ```typescript 66 | type BeerLovingProgrammer 67 | = Programmer 68 | & BeerLover; 69 | 70 | type BeerLovingProgrammer2 = 71 | & Programmer 72 | & BeerLover; 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /03-basic-grammar/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3장에선 타입스크립트의 기초적인 문법과 타입 시스템에 대해 소개한다. 3 | --- 4 | 5 | # 3.0 타입스크립트 기초 문법 6 | 7 | ## **타입 표기 \(Type Annotation\)** 8 | 9 | 타입스크립트 코드에서 어떤 변수 또는 값의 타입을 표기하기 위해 **타입 표기**를 사용한다. 타입 표기는 식별자 또는 값 뒤에 콜론\(`:`\)을 붙여 `value: type` 의 형태로 표기한다. 10 | 11 | ```typescript 12 | const areYouCool: boolean = true; 13 | const answer: number = 42; 14 | const typescript: string = "great"; 15 | const greetings: string = ` 16 | Hello, Readers! 17 | Welcome to TypeScript. 18 | `; 19 | const hasType: Object = { 20 | TypeScript: true, 21 | JavaScript: false 22 | }; 23 | ``` 24 | 25 | ## **예제 코드의 실행 환경** 26 | 27 | 1장에서 언급했듯, 기본 코드 베이스 이식의 용이성은 타입스크립트의 언어 디자인의 큰 목표 중 하나다. 그 목표를 달성하기 위한 핵심 장치가 바로 **점진적 타이핑**이다. 28 | 29 | 점진적 타이핑이란 말 그대로 **점진적으로 타입 안정성을 키워가는 것을 허용하는 타입 시스템**이다. 즉 일단 프로그램의 일부에만 정적 타입 검사를 시행하고 나머지 부분은 추후 타입 정보를 추가하는 식의 접근이 가능한 것이다. 이러한 점진적 타이핑의 일환으로, 타입스크립트 컴파일러는 타입 시스템의 엄격한 정도를 선택하기 위한 다양한 옵션을 제공한다. 30 | 31 | 특별히 따로 언급하지 않는 한, 이 책은 **모든 예제 코드에서** `--strict` **컴파일러 플래그가 켜진 환경을 가정한다**. 구체적으로 어떤 플래그들이 켜지며 그 외에도 어떤 옵션이 있는지는 8장에서 다룬다. 현재로서는 이 플래그가 켜진 환경에서 상대적으로 엄격한 타입 검사가 수행된다는 것을 인지하는 정도로 충분하다. 32 | 33 | 타입스크립트는 \(이후 6장에서 다룰\) 타입 추론을 지원한다. 즉, 프로그래머가 명시적으로 타입 정보를 적지 않아도 컴파일러가 이미 알고 있는 정보와 주변 맥락을 기반으로 타입을 추론할 수 있다. 하지만 본 책의 예제 코드는 **타입 추론에 최소한도로만 의존한다.** 즉 추론할 수 있는 타입 정보도 명시적으로 적어 주는 것을 선호한다. 34 | 35 | 이 두 가지 결정은 **더 명시적인 코드 작성을 강제**한다는 공통점을 갖고 있다. 점진적 타이핑과 타입 추론, 둘 모두 실사용시 큰 편리함을 주는 타입스크립트의 장점이다. 하지만 이 책은 입문서인만큼 간결함을 다소 희생하더라도 더 명시적인 쪽을 택하는 것이 독자의 혼란을 줄일 수 있으리라 판단해 그런 결정을 내렸다. 36 | 37 | -------------------------------------------------------------------------------- /03-basic-grammar/object.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 자바스크립트에서 가장 일반적이고 널리 사용되는 자료 구조인 객체의 타입에 대해 다룬다. 3 | --- 4 | 5 | # 3.3 객체 6 | 7 | ### **객체 타입** 8 | 9 | 자바스크립트의 오브젝트 리터럴을 정의하듯 중괄호\(`{}`\)를 이용해 객체 타입\(object type\)을 표현할 수 있다. 10 | 11 | ```typescript 12 | const user: { name: string; height: number; } = { name: '안희종', height: 176 }; 13 | ``` 14 | 15 | 이 때 객체 타입 정의는 오브젝트 리터럴과 다음과 같은 차이점을 갖는다. 16 | 17 | * 콜론\(`:`\)의 우변에는 값 대신 해당 속성의 타입이 들어간다. 18 | * 구분자로 콤마\(`,`\) 뿐만 아니라 세미콜론\(`;`\)을 사용할 수 있다. 19 | 20 | ### **선택 속성** 21 | 22 | 함수의 선택 매개변수와 비슷하게 속성명 뒤에 물음표\(`?`\)를 붙여 해당 속성이 존재하지 않을 수도 있음을 표현할 수 있다. 23 | 24 | ```typescript 25 | const userWithUnknownHeight: { name: string; height?: number; } = { 26 | name: '김수한무' 27 | }; 28 | ``` 29 | 30 | ### **읽기 전용 속성** 31 | 32 | 속성명 앞에 `readonly` 키워드를 붙여 해당 속성의 재할당을 막을 수 있다. `readonly` 키워드가 붙은 속성은 `const` 키워드를 이용한 변수의 정의와 비슷하게 동작한다. 33 | 34 | ```typescript 35 | const user: { 36 | readonly name: string; 37 | height: numer; 38 | } = { name: '안희종', height: 176 }; 39 | user.name = '종희안'; // error TS2540: Cannot assign to 'name' because it is a constant or a read-only property. 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /03-basic-grammar/primitive-types.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트가 제공하는 기본 타입을 살펴본다. 이후 다룰 모든 타입은 이 기본 타입들로부터 파생된다. 3 | --- 4 | 5 | # 3.1 기본 타입 6 | 7 | ## **불리언** 8 | 9 | 자바스크립트의 `boolean`에 대응하는, 참 또는 거짓을 나타내는 타입이다. 10 | 11 | ```typescript 12 | const isTypeScriptAwesome: boolean = true; 13 | const doesJavaScriptHasTypes: boolean = false; 14 | ``` 15 | 16 | ## **숫자** 17 | 18 | 숫자를 나타내는 타입이다. 자바스크립트에서는 정수, 부동 소수점 등의 구분이 따로 없고 모든 수가 [IEEE754](https://ko.wikipedia.org/wiki/IEEE_754) 표준을 따르는 부동소수점이고, 타입스크립트의 `number` 타입도 마찬가지다. 19 | 20 | ```typescript 21 | const yourScore: number = 100; 22 | const ieee754IsAwesome: number = 0.1 + 0.2; // 0.30000000000000004 23 | ``` 24 | 25 | ## **문자열** 26 | 27 | 문자열을 나타내는 타입이다. ES6 템플릿 리터럴 역시 `string` 타입의 값이다. 28 | 29 | ```typescript 30 | const authorName: string = '안희종'; 31 | const toReaders: string = ` 32 | 책을 읽어주셔서 감사합니다. 33 | 도움이 되었으면 좋겠습니다. 34 | `; 35 | ``` 36 | 37 | ## **null / undefined** 38 | 39 | `null` 타입과 `undefined` 타입은 각각 `null`과 `undefined`라는 하나의 값만을 갖는다. 이 두 값을 자기 자신의 타입, 그리고 아래에서 언급될 `void` 타입 이외의 타입에 할당하려 하면 타입 에러\(`TS2322: Type 'null' is not assignable to type 'number'` 등\)가 발생한다. 40 | 41 | ```typescript 42 | const nullValue: null = null; 43 | const undefinedValue: undefined = undefined; 44 | const numberValue: number = null; // TS2322: Type 'null' is not assignable to type 'number' 45 | ``` 46 | 47 | {% hint style="info" %} 48 | 타입스크립트에서, 원래 `null`과 `undefined`는 기본적으로 모든 타입의 서브타입이다. 즉 아무런 설정이 없다면 아래와 같은 식의 할당이 허용된다. 49 | 50 | ```typescript 51 | const a: number = null; // okay 52 | ``` 53 | 54 | 하지만 이런 동작은 버그를 양산하기 쉽다. 때문에 타입스크립트 2.0에 `null`과 `undefined` 값을 다른 타입에 할당하는 것을 막는 `--strictNullChecks` 플래그가 [추가되었다](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#--strictnullchecks). 55 | 56 | 앞서 언급했듯 이 책의 모든 코드 예제는 `--strict`플래그가 켜진 환경을 가정하고 있으며, `--strictNullChecks` 플래그는 `--strict` 플래그에 포함된다. 실 프로젝트에서도 해당 플래그를 켜는 것을 추천한다. 57 | {% endhint %} 58 | 59 | ## **특별한 타입** 60 | 61 | 자바스크립트에서 직접적으로 대응되는 값은 없지만 타입스크립트가 제공하는 특수한 타입이 몇 가지 있다. 62 | 63 | ### **any** 64 | 65 | `any` 타입은 모든 타입과 호환 가능하다. 즉, 모든 값의 타입을 `any` 로 지정할 수 있고, `any` 타입의 변수에는 모든 값을 할당할 수 있다. 66 | 67 | ```typescript 68 | let bool: any = true; 69 | bool = 3; 70 | bool = 'whatever'; 71 | bool = {}; 72 | ``` 73 | 74 | 또한 `any` 타입 값의 메소드를 호출할 시에도 타입 검사가 아예 수행되지 않는다. 이 때 해당 실제로 존재하지 않는다면 타입 검사는 통과하되 런타임 에러가 발생할 것이다. 75 | 76 | ```typescript 77 | bool.nonExistingMethod(); 78 | bool.whatever(false); 79 | ``` 80 | 81 | `any` 타입은 타입스크립트 타입 시스템의 비상 탈출구\(escape hatch\)이다. `any`는 타입 정의를 제공하지 않는 라이브러리, 일단 무시하고 넘어가고 이후에 정확히 적고 싶은 부분 또는 코드 작성 시점에 형태를 알 수 없는 값 등의 타입 표기에 유용하다. 하지만 `any`를 남용하면 타입 안정성에 구멍이 뚫린 코드가 되어 타입스크립트를 사용하는 의의가 사라지므로 꼭 필요한 경우에만 사용해야 한다. 82 | 83 | ### **void** 84 | 85 | `void`는 `null`과 `undefined` 만을 값으로 가질 수 있는 타입이다. 아무런 값도 반환하지 않는 함수의 반환 타입을 표시할 때 사용한다. 86 | 87 | ```typescript 88 | function nothing(): void { } 89 | ``` 90 | 91 | ### **never** 92 | 93 | `never`는 아무런 값도 가질 수 없는 타입이다. 아무런 값도 가질 수 없는 타입은 과연 어떤 쓸모가 있을까? 아래 함수를 보자. 94 | 95 | ```typescript 96 | function alwaysThrow(): ??? { 97 | throw new Error(`I'm a wicked function!`); 98 | } 99 | ``` 100 | 101 | 의미상으로 `never` 타입은 – 그 이름이 암시하듯 – 절대 존재할 수 없는 값을 암시한다. 따라서 `never` 타입의 변수에는 `null`, `undefined`를 포함해 어떤 값도 할당할 수 없다. 위의 `alwaysThrow` 함수는 항상 에러를 `throw` 하므로 어떤 값도 반환하지 않는다. 이 때, 이런 함수의 반환 타입을 `never` 타입을 사용해 나타낼 수 있다. 102 | 103 | ```typescript 104 | function alwaysThrow(): never { 105 | throw new Error(`I'm a wicked function!`); 106 | } 107 | ``` 108 | 109 | -------------------------------------------------------------------------------- /03-basic-grammar/type-alias.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | 타입 별칭(type alias)을 이용해 이미 존재하는 타입에 다른 이름을 붙여 복잡한 타입을 간단하게 쓸 수 있다. 또한, 프로그래머의 4 | 의도를 보다 명확하게 나타낼 수 있다. 5 | --- 6 | 7 | # 3.4 타입 별칭 8 | 9 | ### **타입 별칭 정의** 10 | 11 | 타입 별칭은 다음과 같이 정의한다. 12 | 13 | ```typescript 14 | type NewType = Type; 15 | ``` 16 | 17 | 별칭을 갖게 될 타입\(위에서는 `Type`\)의 자리엔 기본 타입을 포함한 모든 타입이 올 수 있다. 18 | 19 | ```typescript 20 | type UUID = string; 21 | type Height = number; 22 | type AnotherUUID = UUID; 23 | type Animals = Animal[]; 24 | type User = { 25 | name: string; 26 | height: number; 27 | }; 28 | ``` 29 | 30 | 이 때 별칭은 단순히 새로운 이름을 붙일 뿐이고, 실제로 새로운 타입이 생성되는 것은 아니라는 점에 유의하라. 예를 들어, 아래와 같은 코드의 에러 메시지에는 `UUID` 대신 `string` 이 사용된다. 31 | 32 | ```typescript 33 | type UUID = string; 34 | function getUser(uuid: UUID) { 35 | /* 함수 본문 */ 36 | } 37 | getUser(7); // error TS2345: Argument of type '7' is not assignable to parameter of type 'string'. 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /03-basic-grammar/union-type.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 유니온 타입을 이용해 “여러 경우 중 하나”인 타입을 표현할 수 있다. 3 | --- 4 | 5 | # 3.7 유니온 타입 6 | 7 | ### **동기부여** 8 | 9 | 아래 함수를 한 번 살펴보자. 10 | 11 | ```typescript 12 | function square(value: number, returnString: boolean = false): ??? { 13 | const squared = value * value; 14 | if (returnString) { 15 | return squared.toString(); 16 | } 17 | return squared; 18 | } 19 | ``` 20 | 21 | 함수 `square`는 숫자 타입 인자를 하나 받고, 불리언 타입 인자를 하나 더 받아 그 값에 따라 문자열 또는 숫자 타입의 값을 반환한다. 이 함수의 반환 타입은 어떻게 표현할 수 있을까? 일단 이 경우 반환 타입이 **인자의 타입이 아닌 값에 의존한다**. 따라서 제너릭으로는 표현하기 까다롭다 짐작할 수 있다. 22 | 23 | 오버로딩을 이용하면 아래와 같이 표현은 가능하다. 하지만 하나의 타입을 제외하고 모든 부분이 똑같은데도 여러번 써야 해 비효율적이다. 게다가 오버로딩으로 함수를 정의한다 한들, 반환값을 할당하는 변수의 타입을 정의하기 어렵다는 문제가 남는다. 24 | 25 | ```typescript 26 | function square(value: number, returnString: boolean): number; 27 | function square(value: number, returnString: boolean): string; 28 | function square(value, returnString = false) { 29 | /* 본문 동일 */ 30 | } 31 | const mystery: ??? = square(randomNumber, randomBoolean); 32 | ``` 33 | 34 | **어떤 타입이 가질 수 있는 경우의 수를 나열**할 때 사용하는 **유니온 타입**으로 이 함수의 반환 타입을 표현할 수 있다. 35 | 36 | ### **문법** 37 | 38 | 유니온 타입은 가능한 모든 타입을 파이프\(`|`\) 기호로 이어서 표현한다. “`A` 또는 `B` 타입일 수 있는 타입”을 `A | B` 로 쓰는 식이다. `square` 함수의 타입은 아래와 같이 적을 수 있다. 39 | 40 | ```typescript 41 | function square(value: number, returnString: boolean = false): string | number { 42 | /* 본문 동일 */ 43 | } 44 | const stringOrNumber: string | number = square(randomNumber, randomBoolean); 45 | ``` 46 | 47 | 타입 별칭 문법을 사용해 유니온 타입에 이름을 붙일 수 있다. 자주 사용되는 타입, 또는 인라인으로 작성하기에 너무 복잡한 타입의 경우 이 방식을 추천한다. 48 | 49 | ```typescript 50 | type SquaredType = string | number; 51 | function square(value: number, returnOnString: boolean = false): SquaredType { 52 | /* 본문 동일 */ 53 | } 54 | ``` 55 | 56 | 유니온 타입이 가질 수 있는 타입의 수가 꼭 2개일 필요는 없다. 몇 개든 이어가며 정의할 수 있다. 57 | 58 | ```typescript 59 | type Whatever = number | string | boolean; 60 | ``` 61 | 62 | ### **여러 줄에 걸친 유니온 타입** 63 | 64 | 여러 줄에 걸쳐 유니온 타입을 적을 때에는 보통 아래와 같이 정렬을 맞춘다. 65 | 66 | ```typescript 67 | type Fruits 68 | = Apple 69 | | Banana 70 | | Cherry; 71 | ``` 72 | 73 | 추가로 유니온 타입의 맨 앞에도 파이프를 쓰는 것이 허용된다. 이렇게 하면 여러 줄에 걸쳐 유니온 타입을 정의할 때 각 라인의 형태를 통일할 수 있다. 실질적인 의미 차이는 없으니 선호대로 사용하면 된다. 74 | 75 | ```typescript 76 | type Fruits = 77 | | Apple 78 | | Banana 79 | | Cherry; 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /04-interface-and-class/class-advanced/README.md: -------------------------------------------------------------------------------- 1 | # 4.6 클래스 심화 2 | 3 | -------------------------------------------------------------------------------- /04-interface-and-class/class-advanced/abstract-classes.md: -------------------------------------------------------------------------------- 1 | # 4.6.4 추상 클래스 2 | 3 | `class` 키워드 대신 `abstract class` 키워드를 사용해 추상 클래스를 선언할 수 있다. 일반 클래스는 `extends` 키워드를 사용해 추상 클래스를 확장할 수 있다. 추상 클래스는 **인스턴스화가 불가능하다**는 점에서 일반 클래스와 다르다. 또한 추상 클래스는 **구현을 일부 포함할 수 있다**는 점에서 인터페이스와 다르다. 공식 문서의 예제를 보자. 4 | 5 | ```typescript 6 | abstract class Animal { 7 | move(): void { 8 | console.log("roaming the earth..."); 9 | } 10 | abstract makeSound(): void; 11 | } 12 | ``` 13 | 14 | `abstract class` 키워드를 이용해 정의된 추상 클래스 `Animal`은 두 멤버를 갖는다. 먼저 `move` 메소드는 일반 클래스 메소드와 동일하다. 한 편, `abstract` 키워드가 앞에 붙어 있는 `makeSound` 는 **가상 메소드**\(`abstract method`\)로, 타입 정보 이외의 실제 구현은 포함하지 않고 있다. 15 | 16 | 가상 클래스를 확장하는 서브 클래스는 슈퍼 클래스의 모든 가상 메소드를 구현해야 한다. 만약 이 예시에서 `Animal`을 확장하는 일반 클래스가 `makeSound` 메소드를 올바르게 구현하지 않는다면 타입 에러가 발생한다. 17 | 18 | ```typescript 19 | class Dog extends Animal { } 20 | // error TS2515: Non-abstract class 'Dog' does not implement inherited abstract member 'makeSound' from class 'Animal'. 21 | ``` 22 | 23 | -------------------------------------------------------------------------------- /04-interface-and-class/class-advanced/access-specifiers.md: -------------------------------------------------------------------------------- 1 | # 4.6.2 접근 제어자 2 | 3 | 현재까지 다룬 모든 클래스 멤버\(생성자, 속성, 메소드 등\)는 클래스 바깥에서 자유롭게 접근 할 수 있었다. 하지만 보안적/구조적 이유로 그러한 접근을 제한하고 싶을 수 있다. 이럴 때 사용할 수 있는 것이 접근 제어자\(access modifier\)다. 접근 제어자를 사용해 인스턴스의 멤버에 대한 접근 권한을 지정할 수 있다. 4 | 5 | {% hint style="info" %} 6 | 접근 제어자는 타입스크립트의 기능으로, ES6 클래스에는 포함되지 않는다. 7 | {% endhint %} 8 | 9 | ### **public** 10 | 11 | `public` 멤버는 접근 제한이 전혀 존재하지 않으며, 프로그램의 어느 곳에서나 접근 가능하다. 접근 제어자가 명시되지 않은 멤버는 모두 암시적으로 `public` 접근 권한을 갖는다. 아래의 두 정의는 의미상 동일하다. 12 | 13 | ```typescript 14 | // implicit public member 15 | class Triangle { 16 | vertices: number; 17 | 18 | constructor() { 19 | this.vertices = 3; 20 | } 21 | } 22 | 23 | // explicit public member 24 | class Triangle { 25 | public vertices: number; 26 | 27 | public constructor() { 28 | this.vertices = 3; 29 | } 30 | } 31 | ``` 32 | 33 | ### **private** 34 | 35 | `private` 멤버에는 해당 클래스 내부의 코드만이 접근 가능하다. 만약 클래스 바깥에서 `private` 멤버에 접근하려 할 시 에러가 발생한다. 36 | 37 | ```typescript 38 | class User { 39 | private password: string; 40 | 41 | constructor (password: string) { 42 | this.password = password; 43 | } 44 | } 45 | 46 | const yoonha = new User('486'); 47 | console.log(yoonha.password); 48 | // error TS2341: Property 'password' is private and only accessible within class 'User'. 49 | ``` 50 | 51 | `private` 멤버의 접근 제한은 서브클래스에도 적용된다. 기본적으로 서브클래스는 슈퍼클래스의 멤버에 접근할 수 있지만, 만약 해당 멤버가 `private` 멤버라면 접근할 수 없다. 52 | 53 | ```typescript 54 | class CarOwner extends User { 55 | carId: string; 56 | 57 | constructor (password: string, carId: string) { 58 | super(password); 59 | this.carId = carId; 60 | } 61 | 62 | setPassword(newPassword: string) { 63 | this.password = newPassword; 64 | // error TS2341: Property 'password' is private and only accessible within class 'User'. 65 | } 66 | } 67 | ``` 68 | 69 | ### **protected** 70 | 71 | `protected` 권한의 멤버는 `private`과 비슷하게 동작하지만, **서브클래스에서의 접근 또한 허용된다**는 점이 다르다. 위의 예시에서 `User`의 멤버 `password`의 접근 제어자를 `private`에서 `protected`로 변경하면 에러가 사라진다. 72 | 73 | ```typescript 74 | class User { 75 | protected password: string; 76 | 77 | constructor (password: string) { 78 | this.password = password; 79 | } 80 | } 81 | 82 | class CarOwner extends User { 83 | carId: string; 84 | 85 | constructor (password: string, carId: string) { 86 | super(password); 87 | this.carId = carId; 88 | } 89 | 90 | setPassword(newPassword: string) { 91 | this.password = newPassword; 92 | // Okay 93 | } 94 | } 95 | ``` 96 | 97 | ### **생성자에서의 접근 제어자** 98 | 99 | 멤버 선언 외에도 생성자의 매개변수 앞에 접근 제어자를 명시할 수 있다. **접근 제어자가 붙은 생성자 매개변수는 같은 이름의 속성으로 선언되고, 해당 매개변수의 인자는 암묵적으로 인스턴스에 할당된다**. 즉 다음 코드는 100 | 101 | ```typescript 102 | class User { 103 | constructor (public id: string, private password: string) { } 104 | } 105 | ``` 106 | 107 | 아래와 동일하게 동작한다. 108 | 109 | ```typescript 110 | class User { 111 | public id: string; 112 | private password: string; 113 | 114 | constructor (id: string, password: string) { 115 | this.id = id; 116 | this.password = password; 117 | } 118 | } 119 | ``` 120 | 121 | 생성자의 인자로 받은 값을 인스턴스에 할당하는 건 실제로 매우 빈번하게 보이는 패턴이다. 생성자에서 접근 제어자를 사용함으로써 그런 패턴의 코드를 보다 간결하게 작성할 수 있다. 122 | 123 | -------------------------------------------------------------------------------- /04-interface-and-class/class-advanced/accessors.md: -------------------------------------------------------------------------------- 1 | # 4.6.3 접근자 2 | 3 | 4 | 5 | {% hint style="info" %} 6 | 접근자는 타입스크립트 컴파일 타겟이 ES3 이하인 경우 사용할 수 없다. 7 | {% endhint %} 8 | 9 | 지금까지 다룬 클래스들의 모든 속성은 외부에 직접적으로 노출되었다. 많은 경우엔 이러한 접근 방식으로도 충분하지만, 속성을 읽거나 쓸 때마다 매번 특정한 로직을 실행하고 싶은 경우엔 어떻게 할까? 10 | 11 | 한 가지 방법은 접근 제어자를 사용해 직접적인 접근을 막고 읽기, 쓰기를 위한 메소드를 노출하는 것이다. 12 | 13 | ```typescript 14 | class Shape { 15 | private _vertices: number = 3; 16 | 17 | getVertices() { 18 | console.log('Vertices getter called.'); 19 | return this._vertices; 20 | } 21 | 22 | setVertices(value) { 23 | console.log('Vertices setter called.'); 24 | this._vertices = value; 25 | } 26 | } 27 | ``` 28 | 29 | 이런 메소드를 이용한 접근법은 잘 동작하고, 논리상으로는 전혀 문제가 없다. 하지만 평소처럼 속성에 직접 접근해도\(`instance.prop`\) 내부적으로는 관련 로직이 실행되도록 정의할 수 있다면 보다 간결하고 직관적인 코드 작성이 가능할 것이다. 30 | 31 | 타입스크립트는 이를 위해 접근자\(accessor\)를 제공한다. 접근자는 이름 그대로 클래스 속성에 대해 접근할 때 실행될 로직을 정의하는 함수다. 멤버 접근은 크게 두 가지로 나뉘고, 대응하는 접근자 역시 두 가지가 존재한다. 32 | 33 | * 읽기 접근\(`const value = instance.prop`\): 게터\(getter\) 34 | * 쓰기 접근\(`instance.prop = value`\): 세터\(setter\) 35 | 36 | ### 읽기 접근을 위한 **게터** 37 | 38 | 속성 이름의 메소드 정의 앞에 `get` 키워드를 붙여 게터\(getter\), 즉 속성의 값을 읽어올 때 실행할 함수를 정의할 수 있다. 게터는 인자를 받을 수 없으며, 게터의 반환값은 외부에 해당 속성의 값으로 노출된다. 39 | 40 | ```typescript 41 | class Shape { 42 | constructor (public vertices: number) { } 43 | get vertices(): number { 44 | console.log('Vertices getter called.'); 45 | return 3; 46 | } 47 | } 48 | const triangle: Shape = new Shape(3); 49 | const vertices = triangle.vertices; // Vertices getter called. 50 | console.log(vertices); // 3 51 | ``` 52 | 53 | ### 쓰기 접근을 위한 **세터** 54 | 55 | 속성 이름의 메소드 정의 앞에 `set` 키워드를 붙여 세터\(setter\), 즉 속성의 값을 쓸 때 실행될 함수를 정의할 수 있다. 세터는 새로 할당되는 값을 인자로 받는다. 56 | 57 | ```typescript 58 | class Shape { 59 | private _vertices: number = 3; 60 | get vertices() { 61 | console.log('Vertices getter called.'); 62 | return this._vertices; 63 | } 64 | set vertices(value) { 65 | console.log('Vertices setter called.'); 66 | this._vertices = value; 67 | } 68 | } 69 | const square = new Shape(); 70 | square.vertices = 4; // Vertices setter called. 71 | const vertices = square.vertices; // Vertices getter called. 72 | console.log(vertices); // 4 73 | ``` 74 | 75 | 위 `Shape` 클래스는 내부적으로만 접근 가능한 `private` 속성\(`_vertices`\)을 유지하며, 외부에서는 `vertices` 게터와 세터를 통한 접근만을 허용한다. 지금으로서는 로그를 찍는 것 외엔 별다른 의미가 없지만, 추후 유효성 검사 등의, 읽기/쓰기 시 실행될 여러 로직을 추가할 수 있을 것이다. 76 | 77 | {% hint style="info" %} 78 | 만약 게터만 존재하고 세터가 존재하지 않는 멤버의 경우, 읽기만 가능할 뿐 쓸 수단이 없다. 따라서 타입스크립트는 해당 멤버를 `readonly` 로 인식한다. 79 | {% endhint %} 80 | 81 | ### 82 | 83 | -------------------------------------------------------------------------------- /04-interface-and-class/class-advanced/static-members.md: -------------------------------------------------------------------------------- 1 | # 4.6.1 스태틱 멤버 2 | 3 | 지금까지 다룬 속성과 메소드는 클래스의 인스턴스 별로 각각 생성되고 관리되었다. 이와는 다르게 **클래스 전체에서 공유되는 값**이 필요한 경우, 스태틱 멤버\(static member\)를 사용할 수 있다. 스태틱 멤버에는 클래스 이름을 사용해 접근 할 수 있다. 4 | 5 | ### **스태틱 속성** 6 | 7 | 속성 선언 앞에 `static` 키워드를 붙여 스태틱 속성을 정의할 수 있다. 아래 예시에서는 `count` 라는 이름의 스태틱 속성을 정의하고, 접근하고 있다. 스태틱 속성이므로 `this.count` 가 아닌 `Counter.count` 와 같은 방식으로 접근함을 볼 수 있다. 8 | 9 | ```typescript 10 | class Counter { 11 | static count: number = 0; 12 | } 13 | console.log(Counter.count); // 0 14 | ``` 15 | 16 | ### **스태틱 메소드** 17 | 18 | 비슷하게, 메소드 선언 앞에 `static` 키워드를 붙여 스태틱 메소드를 정의할 수 있다. 19 | 20 | ```typescript 21 | class Counter { 22 | static count: number = 0; 23 | static increaseCount() { 24 | Counter.count += 1; 25 | } 26 | static getCount() { 27 | return Counter.count; 28 | } 29 | } 30 | Counter.increaseCount(); 31 | console.log(Counter.getCount()); // 1 32 | Counter.increaseCount(); 33 | console.log(Counter.getCount()); // 2 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /04-interface-and-class/classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 객체 지향적 구조화 수단인 클래스에 대해 다룬다. 3 | --- 4 | 5 | # 4.4 클래스 6 | 7 | ES6에는 기존의 객체 지향 언어와 비슷하게 클래스를 선언할 수 있는 `class` 키워드가 추가되었다. 타입스크립트의 클래스는 ES6 클래스의 상위집합으로, ES6 클래스를 포함할 뿐 아니라 여러 추가기능을 제공한다. 가장 간단한 형태의 클래스는 다음과 같다. 8 | 9 | ```typescript 10 | class NothingImportant {} 11 | ``` 12 | 13 | 이렇게 정의된 클래스는 `new` 키워드를 이용해 인스턴스화\(instantiation\) 할 수 있다. 클래스는 인스턴스를 찍어내는 일종의 틀로 생각할 수 있다. 14 | 15 | ```typescript 16 | const nothingImportant: NothingImportant = new NothingImportant() 17 | ``` 18 | 19 | `A`라는 클래스를 정의할 때, 이름은 같지만 의미는 다른 두 식별자가 동시에 생성된다는 점에 주의하라. 20 | 21 | * 타입 `A`: `A` 클래스 인스턴스의 타입 \(`const a: A`\) 22 | * 함수 `A`: `new` 키워드와 함께 호출되는 클래스 `A`의 생성자 \(`new A()`\) 23 | 24 | 예를 들어, `const a: A = new A()` 라는 코드에서 앞의 `A`는 인스턴스의 **타입**을 가리키는 반면, 뒤의 `A`는 해당 클래스의 생성자, 즉 함수 타입의 **값**이다. 25 | 26 | ### **생성자** 27 | 28 | `constructor` 키워드를 사용해 클래스 생성자\(class constructor\)를 정의할 수 있다. 만약 어떤 클래스가 생성자를 정의하지 않았다면, 본문이 비어있는 함수가 생성자로 사용된다. \(`constructor() {}` \)객체 생성자와 유사하게, 클래스 생성자를 통해 클래스 인스턴스가 생성될 때 실행될 로직을 정의할 수 있다. 29 | 30 | ```typescript 31 | class Dog { 32 | constructor() { 33 | console.log('constructing!'); 34 | } 35 | } 36 | const dog: Dog = new Dog(); // constructing! 37 | ``` 38 | 39 | 생성자는 임의의 매개변수를 취할 수 있다. 이 변수들은 클래스를 인스턴스화 할 때 인자로 넘겨진다. 예를 들어, 아래에서는 생성자의 `barkingSound` 매개변수에 들어갈 값으로 인자 `'월'` 을 넘기고 있다. 40 | 41 | ```typescript 42 | class BarkingDog { 43 | constructor(barkingSound: string) { 44 | console.log(`${barkingSound}!`); 45 | } 46 | } 47 | 48 | const barkingDog: BarkingDog = new BarkingDog('월'); // 월! 49 | ``` 50 | 51 | 생성자의 함수 시그니쳐와 맞지 않는 타입의 인스턴스화를 시도할 시 에러가 발생한다. 52 | 53 | ```typescript 54 | const err: BarkingDog = new BarkingDog(); 55 | // error TS2554: Expected 1 arguments, but got 0. 56 | 57 | const err2: BarkingDog = new BarkingDog(3); 58 | // error TS2345: Argument of type '3' is not assignable to parameter of type 'string'. 59 | ``` 60 | 61 | ### **속성** 62 | 63 | 객체 속성과 유사하게 클래스 인스턴스도 속성\(`property`\)를 가질 수 있다. 클래스 내에서는 속성엔 `this` 키워드를 이용해 접근 가능하다. 모든 클래스 속성은 `이름: 타입` 꼴의 속성 선언\(property declaration\)을 통해 타입을 표기해주어야 한다. 64 | 65 | ```typescript 66 | class Triangle { 67 | // 속성 선언 68 | vertices: number; 69 | constructor() { 70 | // 속성 할당 71 | this.vertices = 3; 72 | } 73 | } 74 | const triangle: Triangle = new Triangle(); 75 | console.log(triangle.vertices); // 3 76 | ``` 77 | 78 | {% hint style="info" %} 79 | **--strictPropertyInitialization 옵션** 80 | 81 | 앞서 3장의 첫 부분에서 우리는 `--strict` 컴파일러 옵션이 켜진 환경을 가정한다 언급한 적 있다. `--strict` 옵션은 `--strictPropertyInitialization` 옵션을 포함한다. 이 옵션이 켜진 환경에선 `undefined` 를 포함하지 않는 클래스 속성이 반드시 **속성 선언** 또는 **생성자**, 두 장소 중 한 곳에서 초기화 되어야 한다. 82 | 83 | 생성자와 속성 선언 두 곳 모두에서 초기화되지 않는 경우 속성 값이 `undefined`일 가능성이 존재한다. 따라서 타입스크립트는 그 정보가 타입에 포함되어 있지 않다면 타입 에러를 낸다. 다음 예시를 보자. 84 | 85 | ```typescript 86 | class User { 87 | private password: string; 88 | } 89 | // error TS2564: Property 'password' has no initializer and is not definitely assigned in the constructor. 90 | ``` 91 | 92 | `password`는 속성 선언에서도, 생성자에서도 초기화되지 않고 있으므로 접근되는 시점에 `undefined` 일 수 있다. 이 값은 `string` 타입이 아니므로 타입 에러가 발생하는 것이 합당하다. 이 에러를 없애기 위해선 두 가지 방법이 존재한다. 93 | 94 | * 실제 상황을 정확히 반영하기 위해 `password`을 선택 속성으로 선언한다. 95 | * 즉 `password: string` 를 `password?: string` \(또는 `password: string | undefined`\) 로 변경한다. 96 | * `password`가 `undefined`여서 문제가 생길 일이 없다고 확신한다면, 속성 이름 뒤에 느낌표\(`!`\)를 붙여 확정적 할당 단언\(definitive assignment assertion\)을 제공할 수 있다. 97 | * 즉 `password: T`를 `password!: T`로 변경한다. 이 경우 컴파일러는 `password`의 초기화 체크를 건너뛴다. 98 | {% endhint %} 99 | 100 | ### **속성 기본값** 101 | 102 | 함수의 기본 인자와 유사하게 클래스 속성에도 기본값을 제공할 수 있다. 103 | 104 | ```typescript 105 | class Triangle { 106 | vertices: number = 3; 107 | } 108 | const triangle: Triangle = new Triangle(); 109 | console.log(triangle.vertices); // 3 110 | ``` 111 | 112 | ### **읽기 전용 속성** 113 | 114 | `readonly` 키워드를 사용해 읽기 전용 속성을 정의할 수 있다. **속성 선언 또는 생성자 외의 장소에서는 읽기 전용 속성에 값을 할당 할 수 없다**. 115 | 116 | ```typescript 117 | class Triangle { 118 | readonly vertices: number; 119 | constructor() { 120 | this.vertices = 3; 121 | } 122 | } 123 | const triangle: Triangle = new Triangle(); 124 | triangle.vertices = 4; 125 | // error TS2540: Cannot assign to 'vertices' because it is a constant or a read-only property. 126 | ``` 127 | 128 | 이 때, 읽기 전용 속성은 `--strictPropertyInitialization` 옵션 여부와 **무관하게** 반드시 속성 선언 또는 생성자에서 초기화되어야 한다. 읽기 전용이라는 정의 자체에 의해 그 두 곳 이외에선 할당 자체가 불가능하기 때문이다. 만약 둘 중 어느 곳에서도 초기화 되지 않았다면 에러가 발생한다. 129 | 130 | ```typescript 131 | class Triangle { 132 | vertices: number; 133 | readonly angles: number[]; 134 | constructor() { 135 | this.vertices = 3; 136 | } 137 | } 138 | // error TS2564: Property 'angles' has no initializer and is not definitely assigned in the constructor. 139 | ``` 140 | 141 | ### **메소드** 142 | 143 | 객체의 단축 메소드명과 유사한 문법을 사용해 인스턴스의 메소드를 정의할 수 있다. 메소드 내에서는 `this` 키워드를 사용해 해당 메소드가 호출되는 인스턴스를 참조할 수 있다. 예를 들어, 메소드를 사용해 위의 예제를 아래와 같이 변경할 수 있다. 144 | 145 | ```typescript 146 | class BarkingDog { 147 | barkingSound: string; 148 | 149 | constructor(barkingSound: string) { 150 | this.barkingSound = barkingSound; 151 | } 152 | 153 | bark(): void { 154 | console.log(`${this.barkingSound}!`); 155 | } 156 | } 157 | const barkingDog: BarkingDog = new BarkingDog('월'); 158 | barkingDog.bark(); // 월! 159 | ``` 160 | 161 | 위에서처럼 함수를 속성으로 정의한 경우엔 별도의 속성 선언이 필요하지만, 메소드 문법을 사용할 경우 속성 선언을 생략할 수 있다. 162 | 163 | -------------------------------------------------------------------------------- /04-interface-and-class/connecting-interface-and-class.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 인터페이스와 클래스가 서로 어떤 방식으로 의존할 수 있는지에 대해 이야기한다. 3 | --- 4 | 5 | # 4.7 인터페이스와 클래스의 관계 6 | 7 | 값이 가져야 하는 특정한 형태를 기술한다는 점에서 인터페이스와 클래스는 의미상으로 유사한 지점이 있다. 때문에 인터페이스 또는 클래스 확장과 유사하게 인터페이스와 클래스 사이에도 연결고리를 생성하는 방법 또한 존재한다. 8 | 9 | ### **클래스의 인터페이스 구현** 10 | 11 | 앞서 언급했듯 인터페이스는 본질적으로 값이 어떤 멤버를 반드시 가져야 하며 그 멤버들의 타입은 어때야 한다는 **제약**을 나타내는 수단이다. `implements` 키워드를 사용해 클래스가 이러한 제약을 따라야 함을 표현할 수 있다. 아래 코드를 보자. 12 | 13 | ```typescript 14 | interface Animal { 15 | legs: number; 16 | } 17 | 18 | class Dog implements Animal { } 19 | ``` 20 | 21 | 이 코드는 다음 에러를 발생시킨다. 22 | 23 | ```typescript 24 | error TS2420: Class 'Dog' incorrectly implements interface 'Animal'. 25 | Property 'legs' is missing in type 'Dog'. 26 | ``` 27 | 28 | `Dog` 클래스가 따라야 할 제약인 `Animal` 인터페이스에 따르면 `legs: number` 속성이 존재해야 하는데, 그렇지 않다는 내용이다. 해당 속성을 추가해 클래스가 인터페이스를 구현하도록 변경하면 에러는 사라진다. 29 | 30 | ```typescript 31 | interface Animal { 32 | legs: number; 33 | } 34 | 35 | class Dog implements Animal { 36 | legs: number = 4; 37 | } 38 | // Okay 39 | ``` 40 | 41 | 인터페이스 구현 문법을 사용해 클래스가 특정 인터페이스를 따르도록 할 수 있다. 42 | 43 | ### **인터페이스의 클래스 확장** 44 | 45 | 앞서 클래스가 인터페이스를 구현하는 경우를 살펴보았다. 반대로, 인터페이스가 기존에 존재하는 클래스의 **형태**를 확장하는 것 또한 가능하다. 인터페이스 확장과 유사하게 `extends` 키워드를 사용해 클래스를 확장할 수 있다. 공식 문서에 있는 예제를 살펴보자. 46 | 47 | ```typescript 48 | class Point { 49 | x: number; 50 | y: number; 51 | } 52 | 53 | interface Point3d extends Point { 54 | z: number; 55 | } 56 | 57 | const point3d: Point3d = {x: 1, y: 2, z: 3}; 58 | ``` 59 | 60 | 이 경우, `Point3d` 인터페이스는 자신의 `z: number` 속성 이외에도 `Point` 클래스의 멤버인 `x: number`, `y: number` 속성을 가진다. 61 | 62 | -------------------------------------------------------------------------------- /04-interface-and-class/extending-classes.md: -------------------------------------------------------------------------------- 1 | # 4.5 클래스 확장 2 | 3 | 인터페이스 확장과 유사하게, 클래스 역시 `extends` 키워드를 사용해 기존에 존재하는 클래스를 확장할 수 있다. 클래스 `A`가 클래스 `B`를 확장 할 때, 4 | 5 | * `A`를 `B`의 서브클래스\(subclass\) 6 | * `B`를 `A`의 슈퍼클래스\(superclass\) 7 | 8 | 라고 부른다. 인터페이스 확장과 마찬가지로, 서브클래스는 슈퍼클래스의 멤버\(속성, 메소드\)를 갖는다. 9 | 10 | ```typescript 11 | class Base { 12 | answer: number = 42; 13 | greetings() { 14 | console.log('Hello, world!'); 15 | } 16 | } 17 | class Extended extends Base { } 18 | const extended: Extended = new Extended(); 19 | console.log(extended.answer); // 42 20 | extended.greetings(); // Hello, world! 21 | ``` 22 | 23 | ### 클래스 확장 시 **생성자** 24 | 25 | **슈퍼클래스의 생성자는 서브클래스의 생성자에서 자동 호출되지 않는다**. 따라서 서브클래스의 생성자에선 반드시 `super` 키워드를 사용해 슈퍼클래스의 생성자를 호출해줘야 한다. 26 | 27 | ```typescript 28 | class Base { 29 | baseProp: number; 30 | constructor() { 31 | this.baseProp = 123; 32 | } 33 | } 34 | class Extended extends Base { 35 | extendedProp: number; 36 | constructor() { 37 | super(); // 반드시 이 호출을 직접 해 주어야 한. 38 | this.extendedProp = 456; 39 | } 40 | } 41 | const extended: Extended = new Extended(); 42 | console.log(extended.baseProp); // 123 43 | console.log(extended.extendedProp); // 456 44 | ``` 45 | 46 | 만약 서브클래스 생성자에서 슈퍼클래스 생성자의 호출을 빠트릴 경우 에러가 발생한다. 47 | 48 | ```typescript 49 | class ExtendedWithoutSuper extends Base { 50 | constructor() { } 51 | } 52 | // error TS2377: Constructors for derived classesmust contain a 'super' call. 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /04-interface-and-class/extending-interfaces.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 한 인터페이스가 다른 인터페이스를 확장하는 상황에 대해 이야기한다. 3 | --- 4 | 5 | # 4.3 인터페이스 확장 6 | 7 | 앞서 예시로 여러번 들었던 `User` 인터페이스를 다시 살펴보자. 8 | 9 | ```typescript 10 | interface User { 11 | name: string; 12 | readonly height: number; 13 | favoriteLanguage?: string; 14 | } 15 | ``` 16 | 17 | 이 때, `User` 인터페이스가 갖는 특성을 모두 가지면서 추가적인 속성을 갖는 타입을 정의하고 싶은 경우를 생각해보자. 예를 들어, 로그인한 시간을 나타내는 `loggedInAt: Date` 속성을 추가로 들고 있는 `LoggedInUser` 와 같은 타입이 필요 할 수 있다. 18 | 19 | 이런 경우 `extends` 키워드를 이용한 **인터페이스 확장**을 통해 기존 인터페이스를 효율적으로 재사용 할 수 있다. 인터페이스 확장 문법은 아래와 같다. 20 | 21 | ```typescript 22 | interface LoggedInUser extends User { 23 | loggedInAt: Date; 24 | } 25 | ``` 26 | 27 | `LoggedInUser` 인터페이스는 `User` 인터페이스의 모든 속성\(`name: string`, `readonly height: number`, `favoriteLanguage?: string`\)을 가지며, 추가적으로 `loggedInAt: Date` 속성 또한 갖는다. 28 | 29 | ### **다수의 인터페이스 동시 확장** 30 | 31 | 인터페이스는 동시에 하나 이상의 인터페이스를 확장할 수 있다. 이 경우 확장 대상이 되는 인터페이스들은 반점\(`,`\)으로 구분한다. 새로 정의할 인터페이스는 모든 확장 대상 인터페이스의 속성에 자신의 속성을 더한 타입을 갖는다. 32 | 33 | ```typescript 34 | interface ElectricDevice { 35 | voltage: number; 36 | } 37 | interface SquareShape { 38 | width: number; 39 | height: number; 40 | } 41 | interface Laptop extends ElectricDevice, SquareShape { 42 | color: string; 43 | } 44 | const macbook15: Laptop = { voltage: 220, width: 30, height: 21; color: 'white' }; 45 | ``` 46 | 47 | 이 때, 확장 대상인 인터페이스 중 여러 인터페이스가 같은 이름의 속성을 가질 수 있다. 그런 경우, **이름이 겹치는 속성의 타입은 모든 확장 대상 인터페이스에서 같아야 한다**. 만약 같은 이름의 속성이 다른 타입을 갖는 경우엔 타입 에러가 발생한다. 48 | 49 | ```typescript 50 | interface BeverageLover { 51 | favoriteDrink: string; 52 | } 53 | interface BeerLover { 54 | favoriteDrink: 'beer'; 55 | } 56 | interface CoolPerson extends BeverageLover, BeerLover { 57 | name: string; 58 | } 59 | // error TS2320: 60 | // Interface 'CoolPerson' cannot simultaneously extend types 'BeverageLover' and 'BeerLover'. 61 | // Named property 'favoriteDrink' of types 'BeverageLover' and 'BeerLover' are not identical. 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /04-interface-and-class/indexable-types.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 동적인 색인을 표현하는 색인 가능 타입에 대해 다룬다. 3 | --- 4 | 5 | # 4.2 색인 가능 타입 6 | 7 | 앞서 다룬 예시들은 모두 코드 작성 시점에 속성 이름이 알려져 있었다. 하지만 **코드의 실행 시점에서만 알 수 있는 이름의 동적 속성을 갖는 타입**은 어떻게 표시해야 할까? 8 | 9 | ```typescript 10 | const users: = [ 11 | { name: '안희종', height: 176, favoriteLanguage: 'TypeScript' }, 12 | { name: '이방인', height: 42 } 13 | ]; 14 | interface NameHeightMap { 15 | // ?? 16 | } 17 | const nameHeightMap: NameHeightMap = {}; 18 | users.map(user => { 19 | nameHeightMap[user.name] = user.height; 20 | }); 21 | console.log(userHeightMap) // { '안희종': 176, 'Stranger': 42 } 22 | ``` 23 | 24 | 위 코드의 `nameHeightMap`은 **임의의 유저 목록**을 받아 유저의 이름을 키로, 유저의 신장을 값으로 갖는 매핑이다. 이 때 이 객체의 키들은 임의의 유저 이름이므로 코드를 작성하는 시점에는 모든 가능한 키를 나열하는 것이 불가능하다. 예를 들어 위 타입을 아래와 같이 정의한다고 해 보자. 25 | 26 | ```typescript 27 | interface NameHeightMap { 28 | 안희종: number; 29 | 이방인: number; 30 | } 31 | ``` 32 | 33 | 이 경우 이후 `users` 값에 `{ name: '뉴페이스', height: 777 }` 등의 유저가 추가되는 경우를 제대로 처리하지 못 할 것이다. 또한 실제로는 `users` 와 같은 정보를 실행 시간에 서버로부터 얻어오는 등의 경우가 많은데, 이런 경우 역시 커버할 수 없다. 이럴 때 필요한 것이 바로 **색인 가능 타입**\(indexable type\)이다. 34 | 35 | ### **색인 시그니쳐** 36 | 37 | 색인 가능 타입을 이용해 색인 가능한\(indexable\) 객체의 타입을 정의할 수 있다. 색인 가능 타입을 정의하기 위해서는 색인에 접근할 때 사용하는 기호인 대괄호\(`[]`\)를 이용해 객체의 색인 시그니쳐\(index signature\)를 적어줘야 한다. 38 | 39 | 예를 들어 위의 `NameHeightMap` 인터페이스는 색인 가능 타입을 사용해 아래와 같이 적을 수 있다. 40 | 41 | ```typescript 42 | interface NameHeightMap { 43 | [userName: string]: number | undefined; 44 | } 45 | ``` 46 | 47 | 위 정의는 다음과 같이 읽는다. 48 | 49 | * `NameHeightMap` 타입의 값을 50 | * 임의의 `string` 타입 값 `userName` 으로 색인한 값 \(`[userName: string]` → 인덱스 시그니쳐\) 51 | * 즉 `nameHeightMap[userName]`은 `number` 또는 `undefined` 타입의 값이다. \(`: number | undefined`\) 52 | 53 | 위 예제에선 색인된 값이 `number` 가 아닌 `number | undefined` 타입을 가지는 것에 유의하라. `nameHeightMap`이 모든 문자열을 키로 갖고 있다는 보장이 없으므로 `nameHeightMap['없는 유저']` 따위의 값은 `undefined` 일 수 있기 때문이다. 54 | 55 | 이 경우 색인된 값을 `number` 타입의 값으로 사용하고 싶다면 먼저 `undefined`인지 여부를 체크해줘야 한다. 56 | 57 | ```typescript 58 | const h = nameHeightMap['안희종']; // 이 시점에서 h의 타입은 number | undefined 59 | if (h !== undefined) { 60 | // 이 시점에서 h의 타입은 number 61 | console.log(h.toString()); // ok 62 | } 63 | ``` 64 | 65 | ### **색인과 타입** 66 | 67 | 색인의 타입으로는 **문자열 또는 숫자**만이 사용 가능하다. 이 때 주의해야 할 점은 만약 문자열 색인과 숫자 색인이 모두 존재하는 경우, **숫자로 색인 된 값의 타입은 문자열로 색인 된 값 타입의 서브타입이어야 한다**는 것이다. 68 | 69 | 즉, 아래 예제에서 `B`는 `A`의 서브타입이어야 한다. 70 | 71 | ```typescript 72 | inteface Mixed { 73 | [stringIndex: string]: A; 74 | [numberIndex: number]: B; 75 | } 76 | ``` 77 | 78 | 이 때 “`B`가 `A`의 서브타입이다”는 말의 의미는 “`B` 타입의 모든 값은 `A` 타입에도 속한다” 정도로 이해할 수 있다. 예를 들어, 모든 정수를 나타내는 타입 `Int`와 모든 숫자를 나타내는 타입 `Num`이 존재한다고 하자. 모든 정수는 숫자이므로 \(즉 `Int` 타입의 모든 값을 `Num` 타입의 값으로도 사용할 수 있으므로\) `Int`는 `Num`의 서브타입이다. 79 | 80 | 이러한 제약이 존재하는 이유는 **자바스크립트 색인의 동작 방식** 때문이다. 자바스크립트 코드에서 객체의 색인에 접근할 때, 내부적으로는 색인의 `toString()` 메소드를 호출해 문자열로 변형된 값을 색인으로 사용한다. 예를 들어 `1.toString() === '1'` 이므로 `obj[1]` 이라고 적은 코드는 실제로는 `obj['1']`와 동일하다. 81 | 82 | 이 때, 만약 다음 `ErrorProne` 타입과 같이 숫자로 색인 된 값의 타입\(`boolean`\)이 문자열로 색인 된 타입\(`number`\)의 서브타입이 아닌 경우가 허용된다고 가정해보자. 83 | 84 | ```typescript 85 | interface ErrorProne { 86 | [str: string]: number; 87 | [num: number]: boolean; 88 | } 89 | let errorProne: ErrorProne = { 90 | 'abc': 3, 91 | 3: true 92 | }; 93 | errorProne[3]; 94 | ``` 95 | 96 | 가장 아래 줄을 보면, `3`이라는 색인은 숫자 타입이므로 타입 시스템은 `errorProne[3]`의 타입이 `boolean`일 것이라 추측할 것이다. 하지만 위에서 언급한 색인의 동작 방식에 의해 실제로 해당 값은 `errorProne['3']`과 같고, 이는 문자열 색인으로 접근한 `number` 타입의 값이다. 타입 시스템이 알고 있는 정보\(`boolean`\)와 실제 상황\(`number`\) 이 달라지는 것이다. 97 | 98 | 따라서 타입스크립트는 이런 코드를 작성하는 것을 허용하지 않고, `error TS2413: Numeric index type 'boolean' is not assignable to string index type 'number'.` 와 같은 에러를 발생시킨다. 숫자 색인으로 접근한 타입 `boolean`을 문자열 색인으로 접근한 타입 `number`에 할당할 수 없다는 의미다. 99 | 100 | 비슷한 이유로, 문자열 색인 시그니처가 존재한다면 그 외 모든 속성의 값 타입은 문자열 색인으로 접근한 값의 타입의 서브타입이여야 한다. 모든 속성 접근은 \(`user.name === user['name']` 이므로\) 결국 문자열 색인 접근의 특수한 케이스이기 때문이다. 아래와 같은 선언은 타입 에러를 발생시킨다. 101 | 102 | ```typescript 103 | interface User { 104 | [randomProp: string]: number; 105 | name: string; 106 | } 107 | // error TS2411: Property 'name' of type 'string' is not assignable to string index type 'number'. 108 | ``` 109 | 110 | ### **읽기 전용 색인** 111 | 112 | 색인 역시 읽기 전용으로 선언할 수 있다. 객체 타입, 인터페이스에서의 `readonly`의 동작과 마찬가지로 `readonly`로 선언된 색인의 값은 재할당이 불가능하다. 113 | 114 | ```typescript 115 | interface ReadonlyNameHeightMap { 116 | readonly [name: string]: height; 117 | } 118 | const m: ReadonlyNameHeightMap = { '안희종': 176 }; 119 | m['안희종'] = 177; // error TS2542: Index signature in type 'ReadonlyNameHeightMap' only permits reading. 120 | ``` 121 | 122 | ### **색인 가능 타입의 사용예** 123 | 124 | 색인 가능 타입을 사용하는 가장 간단하면서도 유용한 인터페이스 중 하나로 `Array` 인터페이스를 꼽을 수 있다. 만약 색인 가능 타입이 없이 `T` 타입의 원소를 갖는 `Array` 인터페이스를 작성한다면 대략 아래와 같은 식으로 모든 색인에 대한 타입을 일일이 정의해야 할 것이다. 125 | 126 | ```typescript 127 | interface Array { 128 | length: number; 129 | 0?: T; 130 | 1?: T; 131 | /* ... */ 132 | Number.MAX_SAFE_INTEGER?: T; 133 | /* 메소드 정의 */ 134 | } 135 | ``` 136 | 137 | 인덱스 타입을 이용하면 위 코드를 다음처럼 간결하게 대체할 수 있다. 138 | 139 | ```typescript 140 | interface Array { 141 | length: number; 142 | [index: number]?: T; 143 | /* 메소드 정의 */ 144 | } 145 | ``` 146 | 147 | -------------------------------------------------------------------------------- /04-interface-and-class/interface-basics.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 인터페이스의 기초적인 개념과 용례를 알아본다. 3 | --- 4 | 5 | # 4.1 인터페이스 기초 6 | 7 | `interface` 키워드를 사용해 값이 특정한 형태\(shape\)를 갖도록 제약할 수 있다. 인터페이스를 정의하는 기본적인 문법은 객체 타입의 그것과 유사하다. 8 | 9 | ```typescript 10 | interface User { 11 | name: string; 12 | height: number; 13 | } 14 | ``` 15 | 16 | 또한 객체 타입에서와 비슷하게 인터페이스의 속성을 **읽기 전용 속성** 또는 **선택 속성**으로 정의할 수 있다. 17 | 18 | ```typescript 19 | interface User { 20 | name: string; 21 | readonly height: number; 22 | favoriteLanguage?: string; 23 | } 24 | const author: User = { name: '안희종', height: 176 }; // ok 25 | author.height = 183; // error TS2540: Cannot assign to 'height' because it is a constant or a read-only property. 26 | ``` 27 | 28 | ### **함수 인터페이스** 29 | 30 | 인터페이스를 이용해 함수 타입을 표현할 수 있다. 함수 타입의 표현을 위해선 호출 시그니쳐\(call signature\)를 제공해야 하는데, 함수 타입 정의와 유사한 아래 문법을 사용한다. 31 | 32 | ```typescript 33 | (매개변수1 이름: 매개변수1 타입, 매개변수2 이름: 매개변수2 타입, ...): 반환 타입 34 | ``` 35 | 36 | 예를 들어, `User` 타입의 값 `user`를 받아 이름을 반환하는 함수 인터페이스를 다음과 같이 적을 수 있다. 37 | 38 | ```typescript 39 | interface GetUserName { 40 | (user: User): string; 41 | } 42 | const getUserName: GetUserName = function (user) { 43 | return user.name; 44 | }; 45 | ``` 46 | 47 | 이 때 실제 함수 정의와 인터페이스에서의 매개변수 이름은 꼭 같을 필요는 없다. 즉 위 코드에서 아래처럼 매개변수명을 `user`가 아닌 `u`로 바꾸어 써도 매개변수의 타입 순서만 맞는다면 에러는 발생하지 않는다. 48 | 49 | ```typescript 50 | const getUserName: GetUserName = function (u) { 51 | return u.name; 52 | }; 53 | ``` 54 | 55 | ### **하이브리드 타입** 56 | 57 | 자바스크립트에서는 jQuery의 `$`과 같이 호출 가능한\(callable\) 동시에 추가적으로 여러 속성을 갖는 객체가 존재할 수 있다. 이런 객체의 타입을 표현하기 위해서 호출 시그니쳐와 속성 타입 정의를 동시에 적을 수 있다. 타입스크립트 공식 문서의 `Counter` 예제를 살펴보자. 58 | 59 | ```typescript 60 | interface Counter { 61 | (start: number): string; 62 | interval: number; 63 | reset(): void; 64 | } 65 | function getCounter(): Counter { 66 | let counter = function (start: number) { }; 67 | counter.interval = 123; 68 | counter.reset = function () { }; 69 | return counter; 70 | } 71 | let c = getCounter(); 72 | c(10); 73 | c.reset(); 74 | c.interval = 5.0; 75 | ``` 76 | 77 | 위의 `Counter` 타입의 값은 함수로서 호출 할 수 있고, 따라서 호출 시그니쳐를 갖는다. 한편, 이 인터페이스는 추가적으로 `interval`과 `reset` 이라는 속성을 가진다. 따라서 인터페이스는 해당 속성의 타입 정보 또한 포함한다. 78 | 79 | 이렇게 호출 시그니쳐와 속성 타입을 동시에 갖는 인터페이스를 하이브리드 타입\(hybrid type\)이라 부른다. 80 | 81 | ### **제너릭 인터페이스** 82 | 83 | 인터페이스 이름 뒤에 타입 변수 정의를 붙여 제너릭 인터페이스\(generic interface\)를 정의할 수 있다. 예를 들어, 서버로부터 받은 임의의 응답을 나타내는 `Response` 인터페이스를 아래와 같이 정의할 수 있다. 84 | 85 | ```typescript 86 | interface MyResponse { 87 | data: Data; 88 | status: number; 89 | ok: boolean; 90 | /* ... */ 91 | } 92 | inteface User { 93 | name: string; 94 | readonly height: number; 95 | /* ... */ 96 | } 97 | const user: MyReponse = await getUserApiCall(userId); 98 | user.name; // 타입 시스템은 user.name이 string임을 알 수 있다. 99 | ``` 100 | 101 | 함수 인터페이스의 정의에도 제너릭을 사용 할 수 있다. 이 경우 타입 변수는 매개변수의 앞에 적는다. 102 | 103 | ```typescript 104 | interface GetData { 105 | (response: MyResponse): Data; 106 | } 107 | ``` 108 | 109 | ### **타입 별칭과의 차이** 110 | 111 | 타입에 새로운 이름을 붙이는 수단이라는 점에서 인터페이스와 앞서 살펴본 타입 별칭은 비슷한 점이 많다. 하지만 두 개념 사이엔 다음과 같은 차이점이 있다. 112 | 113 | * 타입 별칭을 이용해서 기본 타입, 배열과 튜플, 유니온 타입 등에 새로운 이름을 붙일 수 있다 \(`type UUID = string`\). 인터페이스로는 해당 타입을 표현하는 것이 불가능하다. 114 | * 타입 별칭은 실제로는 새 타입을 생성하지 않는다. 따라서 `type User = { name: string; }` 타입과 관련된 타입 에러가 발생했을 시 에러 메시지는 `User` 대신 `{ name: string; }` 를 보여준다. 한편 인터페이스는 실제로 새 타입을 생성하고, `interface User { name: string; }` 과 관련된 에러 메시지에는 `User` 가 등장한다. 115 | * 인터페이스는 곧 다룰 `extends` 키워드를 이용해 확장할 수 있는 반면, 타입 별칭의 경우는 그런 수단을 제공하지 않는다. 116 | 117 | 이런 차이점 때문에 타입스크립트 공식 문서는 가능한 경우 항상 타입 별칭보다 인터페이스를 사용할 것을 권장한다. **기본적으로 인터페이스로 표현할 수 있는 모든 타입은 인터페이스로 표현하고, 기본 타입에 새로운 이름을 붙이고 싶거나 유니온 타입을 명명하고 싶은 경우 등 인터페이스의 능력 밖인 부분에서만 타입 별칭을 사용하라**. 118 | 119 | -------------------------------------------------------------------------------- /04-interface-and-class/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: '타입스크립트에서 가장 중요하고 자주 쓰이는 두 추상화 수단, 인터페이스와 클래스에 대해 다룬다.' 3 | --- 4 | 5 | # 4.0 들어가며 6 | 7 | 3장에서 다룬 내용만으로도 간단한 프로그램은 큰 어려움 없이 작성할 수 있다. 하지만 프로젝트의 규모가 커짐에 따라 코드를 더 일관적으로 구조화할 수단이 필요하다. 4장에서는 타입스크립트가 코드의 구조화를 위해 제공하는 대표적인 두 가지 수단인 인터페이스와 클래스에 대해 다룬다. 8 | 9 | **인터페이스**\(interface\)를 통해 값이 따라야 할 제약을 타입으로 표현 할 수 있다. 인터페이스 타입을 통해 값의 형태\(shape\)를, 즉 값이 어떤 멤버를 가져야 하고 각 멤버의 타입은 어때야 하는지를 서술할 수 있다. 10 | 11 | **클래스**\(class\)를 이용해 객체 지향 프로그래밍 언어와 비슷한 방식으로 코드를 구조화 할 수 있다. 타입스크립트의 클래스는 ES6에 추가된 클래스 문법의 확장으로, 접근 제어자 등의 유용한 추가 기능을 제공한다. 12 | 13 | -------------------------------------------------------------------------------- /04-interface-and-class/outro.md: -------------------------------------------------------------------------------- 1 | # 4.8 맺으며 2 | 3 | 4장에서는 타입스크립트가 제공하는 두 가지 대표적 코드 구조화 수단, 인터페이스와 클래스에 대해 살펴보았다. 인터페이스를 이용해 값의 형태가 따라야 할 제약을 표현하고, 클래스를 이용해 복잡한 개념에 연관된 값과 메소드를 깔끔하게 묶는 법을 다루었다. 또한 클래스 및 인터페이스 확장을 통해 재사용성을 증가시키고, 인터페이스 구현을 사용해 클래스의 형태를 제약하는 방법에 대해서도 배웠다. 4 | 5 | 3장과 4장을 마친 지금, 우리는 타입스크립트의 타입 시스템에 어떤 형태의 타입들이 존재하는지 알게 되었다. 다음으로는 이러한 타입들 사이의 관계에 대해 다룬다. 5장의 주제는 타입의 호환성이다. 즉, 서로 다른 타입이 어떤 경우엔 호환 가능하고 또 그렇지 않은지, 어떤 타입의 값을 다른 타입의 값에 할당하는 일이 허용되는 경우는 언제인지 등의 내용에 대해 다룬다. 6 | 7 | -------------------------------------------------------------------------------- /05-type-compatibility/6-4.md: -------------------------------------------------------------------------------- 1 | # 들어가며 \(6월 4일 공개\) 2 | 3 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 4 | 5 | -------------------------------------------------------------------------------- /05-type-compatibility/classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트가 클래스 간의 호환성을 판단하는 법에 대해 다룬다. 3 | --- 4 | 5 | # 5.4 클래스의 호환성 6 | 7 | 클래스의 호환성 비교는 기본적으로 객체 호환성 비교와 비슷하게 진행된다. 이 때 **스태틱 멤버 및 생성자는 호환성 비교에 영향을 주지 않는다**는 점에 주의해야 한다. 예를 들어, 다음 코드에서 이루어지는 두 할당은 두 클래스의 생성자 타입 시그니처가 다름에도 문제 없이 진행된다. 8 | 9 | ```typescript 10 | class Animal { 11 | feet: number; 12 | constructor(name: string, numFeet: number) { } 13 | } 14 | 15 | class Size { 16 | feet: number; 17 | constructor(numFeet: number) { } 18 | } 19 | 20 | let a: Animal; 21 | let s: Size; 22 | a = s; // ok 23 | s = a; // ok 24 | ``` 25 | 26 | **private 및 protected 멤버** 27 | 28 | `public` 멤버를 비교할 때에는 객체 속성을 비교할 때와 마찬가지로 이름이 같은지, 타입이 호환 되는지만 따진다. 하지만 `private` 멤버와 `protected` 멤버는 조금 특별하게 처리된다. `private` 및 `protected` 속성은 이름이 같다고 해도 다른 클래스로부터 정의된 멤버라면 호환이 불가능하다. 29 | 30 | 아래 예시를 보자. 31 | 32 | ```typescript 33 | class FacebookUser { 34 | constructor (id: string, private password: string) {} 35 | } 36 | 37 | class TwitterUser { 38 | constructor (id: string, private password: string) {} 39 | } 40 | 41 | let twitterUser: TwitterUser; 42 | let facebookUser: FacebookUser; 43 | twitterUser = facebookUser; 44 | ``` 45 | 46 | `TwitterUser` 타입과 `FacebookUser` 타입은 모두 `private password: string` 멤버를 갖는다. 비록 이름은 같지만 이 두 속성은 서로 다른 클래스에서 정의된 `private` 멤버다. 따라서 위와 같은 할당을 시도한다면 다음 타입 에러가 발생한다. 47 | 48 | ```typescript 49 | // error TS2322: Type 'FacebookUser' is not assignable to type 'TwitterUser'. 50 | // Types have separate declarations of a private property 'password'. 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /05-type-compatibility/enums.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트가 열거형 간의 호환성을 판단하는 법에 대해 다룬다. 3 | --- 4 | 5 | # 5.6 열거형의 호환성 6 | 7 | 열거형의 호환성은 객체 타입이 연관된 경우에 비해 상당히 간단하다. 다른 열거형으로부터 유래된 값끼리는 호환되지 않는다. 8 | 9 | ```typescript 10 | enum Status { Ready, Waiting } 11 | enum Color { Red, Blue, Green } 12 | let status: Status = Status.Ready; 13 | status = Color.Green; // error 14 | ``` 15 | 16 | 숫자 열거형 값은 `number`에, 문자열 열거형 값은 `string`에 할당 가능하다. 17 | 18 | ```typescript 19 | enum MyEnum { 20 | Zero, 21 | One = 1, 22 | Name = '안희종' 23 | } 24 | const zero: number = MyEnum.Zero; 25 | const one: number = MyEnum.One; 26 | const name: string = MyEnum.Name; 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /05-type-compatibility/functions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트가 함수 타입 간의 호환성을 판단하는 법에 대해 다룬다. 3 | --- 4 | 5 | # 5.3 함수 타입의 호환성 6 | 7 | ### 매개변수 수가 같은 경우 8 | 9 | 함수 타입 간의 호환성을 판단하려 할 때, 가장 간단한 경우는 두 함수의 매개변수의 갯수가 같은 경우다. 이 때, 할당을 받는 함수의 타입을 `Target`, 할당하려는 함수의 타입을 `Source`라 하자. 10 | 11 | ```typescript 12 | let source: Source; 13 | const target: Target = source; 14 | ``` 15 | 16 | 위 코드가 허용되는지, 즉 `Target`이 `Source`에 할당 가능한지를 보기 위해선 다음 두 질문에 답해야 한다. 17 | 18 | * `Target`과 `Source`의 모든 매개변수 타입에 대해, `Source`의 매개변수 타입이 `Target`의 매개변수 타입에 할당 가능한가? 19 | * `Target`의 반환 타입이 `Source`의 반환 타입에 할당 가능한가? 20 | 21 | 두 질문에 대한 답이 모두 “예”라면, `Target`은 `Source`에 할당 가능하다. 22 | 23 | {% hint style="info" %} 24 | 직관적으로는, 선택 매개변수와 필수 매개변수는 호환성을 판단할 때 다르게 취급되는 게 맞아 보인다. 하지만 타입스크립트에선 매개변수가 선택 매개변수인지 필수 매개변수인지는 함수 타입의 호환성 판단에 아무런 영향을 주지 않는다. 25 | {% endhint %} 26 | 27 | #### **할당 가능한 경우** 28 | 29 | 먼저 할당 가능한 경우를 살펴보자. 30 | 31 | ```typescript 32 | type Sum = (sumFirst: number, sumSecond: number) => number; 33 | type Multiply = (mulFirst: number, mulSecond: number) => number; 34 | ``` 35 | 36 | * 모든 매개변수 타입은 `number`로, 서로 할당 가능하다. 37 | * `Multiply`의 반환 타입인 `number`는 `Sum`의 반환 타입인 `number`에 할당 가능하다. 38 | 39 | 따라서 `Sum`은 `Multiply`에 할당 가능하다. 40 | 41 | ```typescript 42 | const sum: Sum (sumFirst: number, sumSecond: number) => { 43 | return sumFirst + sumSecond; 44 | }; 45 | const multiply: Multiply = sum; // ok 46 | ``` 47 | 48 | #### **할당 불가능한 경우** 49 | 50 | 다음으로는 할당이 불가능한 예제를 살펴보자. 51 | 52 | ```typescript 53 | interface Animal { animalProp: string }; 54 | interface Dog extends Animal { dogProp: number }; 55 | 56 | let f = (animal: Animal) => animal.animalProp; 57 | let g = (dog: Dog) => { doSomething(dog.dogProp) }; 58 | 59 | f = g; 60 | ``` 61 | 62 | * 할당받는 함수의 매개변수 타입 `Animal`은 할당하는 함수의 매개변수 타입 `Dog`에 할당 불가능하다. 63 | 64 | 만족되지 않는 기준이 있으므로 `g`는 `f`에 할당할 수 없다. 65 | 66 | {% hint style="info" %} 67 | 이 때, 이 기준을 만족해야 하는 이유는 뭘까? 즉, `f`의 매개변수 타입인 `Animal` 이 `g`의 매개변수 타입인 `Dog`에 할당 불가능하면 왜 `g`를 `f`에 할당하지 못해야 할까? 68 | 69 | 해당 할당을 허용하는 경우를 생각해보자. 만약 `f = g` 의 할당을 허용한다면 다음과 같이 `Animal` 타입의 인자를 넘길 수 있을 것이다. 70 | 71 | ```typescript 72 | const cat: Animal = { animalProp: 'cute' }; 73 | f(cat); // 컴파일러는 통과 시켜 줌 74 | ``` 75 | 76 | 하지만 `g` 는 인자가 `Dog` 일 것이라 가정하고, `Dog` 에만 존재하는 속성 `dogProp` 에 접근하고 있다. 따라서 런타임 에러가 발생할 수 있다. 이런 상황을 막기 위해 타입스크립트는 이 경우 `f = g` 와 같은 할당을 금지한다. 77 | {% endhint %} 78 | 79 | ### **매개변수 수가 다른 경우** 80 | 81 | 매개변수 수가 같은 두 함수의 호환성을 비교하는 법에 대해 살펴봤다. 이번엔 매개변수의 수가 다른 경우엔 상황이 어떻게 달라지는지를 다음 두 함수 타입을 통해 살펴보자. 82 | 83 | ```typescript 84 | type Login = (id: string) => Response; 85 | type LoginWithToken = (id: string, token: string) => Response; 86 | ``` 87 | 88 | #### **할당하는 함수의 매개변수 수가 더 많은 경우** 89 | 90 | 아래 코드에서 할당하는 함수인 `loginWithToken`은 할당받는 함수 `login`에 비해 `token: string` 이라는 매개변수를 추가적으로 갖고 있다. 91 | 92 | ```typescript 93 | const loginWithToken: LoginWithToken = (id: string, token: string) => { /* ... */ }; 94 | const login: Login = loginWithToken; 95 | ``` 96 | 97 | 이런 경우는 할당이 **불가능**하다. 만약 이 할당을 허용한다고 생각해보자. 프로그래머는 이 함수를 다음과 같은 식으로 호출할 것이다. 98 | 99 | ```typescript 100 | login('myId'); 101 | ``` 102 | 103 | 이는 `loginWithToken` 함수를 `token` 인자 없이 호출하는 셈이다. `loginWithToken` 함수 내에서 `token`을 `string` 타입이라 생각하고 사용했다면, `string`이 필요한 자리에 `undefined` 값이 넘어와서 런타임 에러가 발생할 것이다. 따라서 이런 할당은 허용되지 않는다. 104 | 105 | #### **할당받는 함수의 매개변수 수가 더 많은 경우** 106 | 107 | 아래 코드에서 할당하는 함수인 `login`은 할당받는 함수인 `loginWithToken`에 비해 매개변수 수가 하나 모자라다. 108 | 109 | ```typescript 110 | const login: Login = (id: string) => { /* ... */ }; 111 | const loginWithToken: LoginWithToken = login; 112 | ``` 113 | 114 | 이런 경우, **초과 매개변수는 무시된다**. 그리고 매개변수 수가 같을 때와 동일한 알고리즘으로 호환성을 판단한다. 위의 경우, 초과 매개변수인 `token: string` 을 제외하고 첫 번째 매개변수는 동일한 타입을 가지므로 할당은 문제 없이 진행된다. 115 | 116 | -------------------------------------------------------------------------------- /05-type-compatibility/generics.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트가 제너릭 타입 간의 호환성을 판단하는 법에 대해 다룬다. 3 | --- 4 | 5 | # 5.5 제너릭의 호환성 6 | 7 | 제너릭의 호환성은 기본적으로 객체의 호환성과 비슷하게 동작한다. 이 때 크게 두 가지 경우의 수가 있는데, 모든 타입 변수가 어떤 타입인지 알려진 경우와 그렇지 않은 경우이다. 공식 예제와 함께 살펴보자. 8 | 9 | ### **모든 타입 변수가 어떤 타입인지 알려진 경우** 10 | 11 | ```typescript 12 | interface NotEmpty { 13 | data: T; 14 | } 15 | 16 | let x: NotEmpty; 17 | let y: NotEmpty; 18 | ``` 19 | 20 | 위의 예제에서 x와 y는 각각 `NotEmpty`와 `NotEmpty` 타입을 가진다. 하지만 이 경우, 제너릭 인터페이스의 정의를 잘 보면 21 | 22 | * `NotEmpty` 는 `{ data: number }` 로 23 | * `NotEmpty`은 `{ data: string }` 으로 24 | 25 | 고쳐 씀으로써 타입 변수를 완전히 제거할 수 있다. 따라서 `NotEmpty`가 `NotEmpty`에 할당 가능한지 여부를 판단하는 일은 객체 타입간의 할당 여부를 판단하는 일과 다를 바 없다. 26 | 27 | 이 경우, `number`는 `string`에 할당 불가능하므로 아래와 같은 할당을 시도한다면 타입 에러가 날 것이다. 28 | 29 | ```typescript 30 | y = x; // 타입 에러 31 | ``` 32 | 33 | ### **어떤 타입인지 알려지지 않은 타입 변수가 있는 경우** 34 | 35 | 제너릭 타입의 호환성을 판단하는 시점에 타입 변수가 알려져 있지 않은 경우도 존재한다. 36 | 37 | ```typescript 38 | const identity = function(x: T): T { 39 | // ... 40 | }; 41 | 42 | const reverse = function(y: U): U { 43 | // ... 44 | }; 45 | ``` 46 | 47 | 이 때, `identity`와 `reverse` 함수의 타입에는 타입 변수가 남아 있다. 이럴 때에는 **아직 남아 있는 타입 변수를 모두 `any` 타입으로 대체하고 호환성을 판단**한다. 예를 들어 아래와 같은 할당은 허용된다. 48 | 49 | ```typescript 50 | identity = reverse; 51 | ``` 52 | 53 | 타입 변수 `T`를 `any`로 대체한 `(x: any) => any` 와 타입 변수 `U`를 `any`로 대체한 `(y: any) => any` 는 서로 할당 가능한 타입이기 때문이다. 54 | 55 | -------------------------------------------------------------------------------- /05-type-compatibility/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | 다양한 타입 간의 호환성(compatibility), 보다 구체적으로는 타입 간의 할당 가능성(assignability)에 대해 다루어 4 | 본다. 5 | --- 6 | 7 | # 5.0 들어가며 8 | 9 | 타입 시스템의 역할은 결국 ‘어떤 자리에 들어가면 안 되는 값이 들어가 있는’ 상황을 찾아내는 것이다. 타입 시스템은 이런 상황을 발생시키는 코드를 보고하기 위해 타입 오류를 사용한다. 그렇다면 타입 시스템은 어떤 값이 어떤 자리에 들어가도 되는지 아닌지를 어떻게 판단할까? 다르게 표현하자면, **타입 시스템은 서로 다른 타입의 호환 가능성을 어떻게 판단할까?** 10 | 11 | 이 장에서는 기본 타입에서부터 시작해 제너릭, 클래스를 비롯한 복잡한 타입들까지, 타입의 호환성 비교가 어떻게 이루어지는지 하나씩, 구체적으로 알아볼 것이다. 타입 호환성을 판단하는 원리를 이해함으로써, 앞으로 타입스크립트 코드를 짜며 마주칠 에러 메시지를 더 잘 이해하고 보다 나은 설계를 할 수 있을 것이다. 12 | 13 | -------------------------------------------------------------------------------- /05-type-compatibility/objects.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트가 객체 타입 간의 호환성을 판단하는 법에 대해 다룬다. 3 | --- 4 | 5 | # 5.2 객체 타입의 호환성 6 | 7 | 다음 두 인터페이스를 보자. 8 | 9 | ```typescript 10 | interface User { 11 | name: string; 12 | height: number; 13 | } 14 | interface Pet { 15 | name: string; 16 | species?: string; 17 | } 18 | const user: User = { name: '안희종', age: 176 }; 19 | const puppy: Pet = { name: '해피' }; 20 | ``` 21 | 22 | `user`의 타입은 `User`이고, `puppy`의 타입은 `Pet`이다. 이 때 아래 코드를 실행했을 때 어떤 결과가 나올까? 23 | 24 | ```typescript 25 | const pet2: Pet = user; // ?? 26 | const user2: User = pet; // ?? 27 | ``` 28 | 29 | 즉, `User`는 `Pet`에 할당 가능할까? 또 반대로 `Pet`은 `User`에 할당 가능할까? 30 | 31 | ### **두 가지 기준** 32 | 33 | 타입스크립트가 객체 타입 사이의 할당 가능성을 판단하는 기준은 사실 꽤 간단하다. 타입 `A`와 `B`에 대해, `A`가 `B`에 할당 가능하려면 다음 두 기준을 만족해야 한다. 34 | 35 | * `B` 타입의 모든 필수 멤버에 대해, `A` 에도 같은 이름의 멤버가 존재하는가? 36 | * `B` 타입과 `A` 타입에 동시에 존재하는 멤버 `m`에 대해, `A.m` 의 타입을 `M`, `B.m`의 타입을 `M'`라 하자. 이 때, 모든 `m`에 대해서 `M`이 `M'`에 할당 가능한가? 37 | 38 | 이 때, 두 질문에 대한 답이 **모두** “예”라면 `A`는 `B`에 할당 가능하다. 반대로 하나라도 만족되지 않으면, `A`는 `B`에 할당 불가능하다. 예시를 들어 살펴보자. 39 | 40 | #### **User는** **Pet에 할당 가능한가?** 41 | 42 | * `Pet`의 필수 멤버는 `name` 하나다. 같은 이름의 멤버가 `User`에도 존재한다. 43 | * `Pet`의 `name` 멤버 타입은 `string` 이다\(`type M = string`\). `User` 의 `name` 멤버 타입 또한 그렇다\(`type M' = string`\). 둘은 같은 타입이므로 당연히 서로 할당 가능하다. 44 | 45 | 두 조건이 모두 만족되므로, `User`는 `Pet`에 할당 가능하다. 이 때 두 가지 눈여겨 볼 점이 있다. 46 | 47 | * `Pet`에는 `User`의 `height: number` 속성이 존재하지 않지만 이는 무관하다. **할당 가능성을 따질 때엔 할당을 받는 쪽의 타입만이 중요하다**. 48 | * 선택 속성으로 정의된 `species?: string` 은 없어도 무방하다. 49 | 50 | #### **Pet은** **User에 할당 가능한가?** 51 | 52 | * `User`의 필수 멤버는 `name`과 `height` 둘이다. 이 때, `Pet` 타입엔 `height` 멤버가 존재하지 않는다. 53 | 54 | 따라서 두 번째 조건은 볼 것도 없이, `Pet`은 `User`에 할당 불가능하다. `const user2: User = pet` 과 같은 시도는 타입 에러를 발생시킨다. 55 | 56 | ### **구조적 타입 시스템** 57 | 58 | 이렇듯, 타입스크립트에서는 두 타입의 구조\(structure\)만을 비교하여 호환성을 결정한다. 어떤 타입이 다른 타입이 갖는 멤버를 전부 가지고 있다면 그걸로 충분하며, 두 타입이 호환되는 타입이라는 명시적인 표시는 필요하지 않다. 이렇게 동작하는 타입 시스템을 **구조적 타입 시스템**\(structural type system\)이라 부른다. 59 | 60 | 반대되는 개념으로는 C++, Java 등의 언어가 채택한 노미널 타입 시스템\(nominal type system\)이 있다. 노미널 타입 시스템을 갖는 언어에서는 특정 키워드를 통해 서로 호환 가능하다고 명시적으로 표현 된 타입 간의 할당만이 허용된다. 61 | 62 | ### **객체 리터럴과 과잉 속성 검사** 63 | 64 | 다음 코드를 보자. 65 | 66 | ```typescript 67 | interface Color { 68 | R: number; 69 | G: number; 70 | B: number; 71 | } 72 | 73 | const white: Color = { 74 | R: 255, 75 | G: 255, 76 | B: 255, 77 | A: 1 78 | }; 79 | ``` 80 | 81 | 변수 `white`는 `Color` 타입을 갖는다. 그리고 `white`에 할당하려는 객체는 `R`, `G`, `B` 세 멤버를 모두 갖고 있고, 세 멤버의 모두 `number` 타입다. 따라서 위에서 살펴본 구조적 타입 검사에 의하면 이 할당에는 아무런 문제가 없어야 한다. 82 | 83 | 하지만 실제로 위의 할당은 에러가 발생한다. 에러 메시지는 다음과 같다. 84 | 85 | ```typescript 86 | error TS2322: Type '{ R: number; G: number; B: number; A: number; }' is not assignable to type 'Color'. 87 | Object literal may only specify known properties, and 'A' does not exist in type 'Color'. 88 | ``` 89 | 90 | 객체 리터럴은 알려진 속성\(known property\)만을 가질 수 있는데, `Color` 타입에는 `A` 속성이 존재하지 않으므로 할당이 불가능하다는 메시지이다. 하지만 분명 할당을 받는 쪽의 타입만이 중요하고, 추가적인 멤버를 갖는건 상관 없다고 하지 않았나? 이게 어찌 된 일일까? 91 | 92 | 이러한 현상이 발생하는 이유는 우리가 할당하는 값이 변수나 표현식이 아닌 **객체 리터럴**이기 때문이다. 객체 리터럴을 할당하는 경우에는 그 리터럴이 알려지지 않은 속성\(unknown property\), 즉 할당 받는 타입에 존재하지 않는 속성을 포함한다면 타입 에러가 발생한다. 93 | 94 | 실제로 위의 코드를 아래와 같이 객체 리터럴이 아닌 변수를 할당하도록 바꾸면 에러는 사라진다. 95 | 96 | ```typescript 97 | interface Color { 98 | R: number; 99 | G: number; 100 | B: number; 101 | } 102 | 103 | const someColor = { 104 | R: 255, 105 | G: 255, 106 | B: 255, 107 | A: 1 108 | }; 109 | const white: Color = someColor; 110 | ``` 111 | 112 | 이렇게 객체 리터럴에 대해서만 알려지지 않은 속성은 없는지 추가적으로 시행하는 검사를 과잉 속성 검사\(excess property checking\)라 부른다. 113 | 114 | #### **과잉 속성 검사가 존재하는 이유** 115 | 116 | 과잉 속성 검사는 프로그래머의 실수를 막기 위해 존재한다. 어떤 타입의 값에 객체 리터럴을 직접 할당하는 경우, 만약 해당 타입에 정의되지 않은 멤버는 오타 등의 실수로 인해 존재할 확률이 높다고 가정하는 것이다. 공식 문서에서 빌려온 아래 예제를 보자. 117 | 118 | ```typescript 119 | interface SquareConfig { 120 | width?: number; 121 | color?: string; 122 | } 123 | const squareConfig: SquareConfig = { 124 | width: 100, 125 | colour: red 126 | }; 127 | ``` 128 | 129 | 구조적 타입 시스템의 원칙에 따르면 위 할당에는 문제가 없다. 하지만 할당 시점에 딱 한 번만 사용될 객체 리터럴에 `colour`라는 알려지지 않은 속성이 존재한다면, `color`를 타이핑하려다 오타가 난 경우가 대부분일 것이다. 과잉 속성 검사를 시행해서 이런 흔하게 예상되는 오류를 컴파일러가 잡아 줄 수 있다. 130 | 131 | -------------------------------------------------------------------------------- /05-type-compatibility/outro.md: -------------------------------------------------------------------------------- 1 | # 5.7 맺으며 2 | 3 | 5장에서는 여러 타입의 호환성을 판단하는 방법에 대해 알아보았다. 가장 먼저 구조적 타입 시스템의 원리에 기초해서 객체 타입의 호환성을 판단하는 기준에 대해 다루었다. 또한 함수 타입, 클래스, 그리고 제너릭 등의 다양한 타입의 호환성이 객체 타입의 그것과 어떤 공통점이 있고 어떤 차이점이 있는지도 살펴 보았다. 마지막으로 열거형의 호환성을 판단하는 법을 배웠다. 4 | 5 | 다음 6장에서는 타입 시스템에 관한 보다 심화된 내용을 다루어 본다. 먼저 앞부분에서는 타입스크립트가 프로그램이 제공하는 정보로부터 타입을 어떻게 좁혀 나가는지\(narrowing\) 살펴 볼 것이다. 또한, 타입 추론과 타입 단언이 어떤 개념인지, 타입은 어떤 의미를 갖는지 등에 대해서도 다룬다. 6 | 7 | -------------------------------------------------------------------------------- /05-type-compatibility/primitive-types.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트가 기본 타입 간의 호환성을 판단하는 법에 대해 다룬다. 3 | --- 4 | 5 | # 5.1 기본 타입의 호환성 6 | 7 | 타입 간의 할당 가능성 판단은 결국 다음 질문에 답하는 것과 같다. 8 | 9 | > 두 가지 다른 타입 `A`와 `B`에 대해, 모든 `A` 타입의 값을 `B` 타입의 값으로도 취급할 수 있는가? 10 | 11 | 다음 코드를 보자. 12 | 13 | ```typescript 14 | type OneDigitOdd = 1 | 3 | 5 | 7 | 9; 15 | const three: OneDigitOdd = 3; 16 | const num: number = three; 17 | ``` 18 | 19 | 위의 코드는 `number` 타입의 값에 `OneDigitOdd` 타입의 값을 할당한다. 이 때, `OneDigitOdd` 타입이 가질 수 있는 값인 `1`, `3`, `5`, `7`, `9` 는 모두 `number` 에 속한다. `OneDigitOdd` 타입의 모든 값이 `number` 타입의 값이기도 하므로 위의 코드는 오류 없이 실행 된다. 즉, `OneDigitOdd` 타입은 `number` 타입에 **할당 가능**\(assignable\)하다. 20 | 21 | 반면 아래의 코드를 보자. 22 | 23 | ```typescript 24 | const four: number = 4; 25 | const oneDigitOdd: OneDigitOdd = four; 26 | // error TS2322: Type 'number' is not assignable to type 'OneDigitOdd'. 27 | ``` 28 | 29 | `number` 타입은 `1`, `3`, `5`, `7`, `9` 이외의 다른 값 또한 가질 수 있다. 예를 들어, `const four: number = 4`는 `OneDigitOdd` 타입의 값으로 허용되지 않는다. `number` 타입의 값이지만 `OneDigitOdd` 값으로 취급할 수 없는 값이 존재하므로, `number` 타입은 `OneDigitOdd` 타입에 **할당 불가능**하다. 30 | 31 | 위에서 보았듯, 기본적인 타입 간의 할당 가능 여부를 판단하는 건 간단하다. 그렇다면 보다 복잡한 타입 사이의 호환성은 어떻게 판단할 수 있을지 살펴보자. 32 | 33 | -------------------------------------------------------------------------------- /06-type-system-deepdive/6-18.md: -------------------------------------------------------------------------------- 1 | # 들어가며 \(6월 18일 공개\) 2 | 3 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 4 | 5 | -------------------------------------------------------------------------------- /06-type-system-deepdive/disjoint-union-type.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 유니온 타입의 특수한 경우인 서로소 유니온 타입의 의미와 쓸모에 대해 다룬다. 3 | --- 4 | 5 | # 6.5 서로소 유니온 타입 6 | 7 | 앞에서 타입을 집합으로 바라보는 관점을, 또 그러한 관점에서 유니온 타입은 합집합을 표현하는 수단임을 배웠다. 이번엔 그러한 유니온 타입을 사용할 때 유용한 패턴에 대해 다루어 보자. 8 | 9 | ### 겹치지 않는 타입으로 이루어진 유니온 타입 10 | 11 | ```typescript 12 | interface Employee { 13 | department: string; 14 | salary: number; 15 | } 16 | interface Boss { 17 | kind: boolean; 18 | } 19 | type CompanyMember = Employee | Boss; 20 | ``` 21 | 22 | 회사의 멤버는 직원 또는 사장이라고 정의했다. 이 때 유니온 타입을 구성하는 `Employee`와 `Boss` 각각을 브랜치\(branch\)라 부른다. 다음 객체를 보자. 이 객체는 어떤 브랜치에 속하는가? 23 | 24 | ```typescript 25 | const whoAmI: CompanyMember = { 26 | kind: true, 27 | salary: 777, 28 | department; 'everywhere', 29 | }; 30 | ``` 31 | 32 | 정답은 ‘둘 다’다.타입스크립트는 구조적 타입 시스템을 가지므로, `whoAmI` 객체는 `Employee` 타입으로 볼 수도 있고 `Boss` 타입으로 볼 수도 있다. 다르게 표현하면, `whoAmI`는 `Employee` 집합과 `Boss` 집합의 교집합에 속한다. 33 | 34 | 그렇다면 만약 `Employee`와 `Boss` 타입을 다음처럼 정의한다면 어떻게 될까? 각각의 브랜치, 즉 유니온 타입을 구성하는 타입마다 고유한 리터럴 타입 속성을 추가하는 것이다. 35 | 36 | ```typescript 37 | interface Employee { 38 | type: 'Employee'; 39 | department: string; 40 | salary: number; 41 | } 42 | 43 | interface Boss { 44 | type: 'Boss'; 45 | kind: boolean; 46 | } 47 | 48 | interface CompanyMember = Employee | Boss; 49 | ``` 50 | 51 | 위 정의에서 두 인터페이스는 서로 다른 리터럴 타입의 `type` 속성을 갖는다. `'Boss'` 타입이면서 `'Employee'` 타입인 문자열은 없다. 따라서 동시에 `Employee` 타입이면서 `Boss` 타입인 값은 존재할 수 없다. 즉 `Employee` 집합과 `Boss` 집합의 교집합은 공집합이다. 52 | 53 | ```typescript 54 | type EmployeeAndBoss = Employee & Boss; // 불가능하다. 따라서 never 타입. 55 | ``` 56 | 57 | 이렇게 **브랜치 간에 겹치는 부분이 없는** 유니온 타입을 **서로소 유니온 타입**\(disjoint union type\)이라 부른다. “서로소”는 교집합이 없는 집합 사이의 관계를 의미하는 “서로소 집합”에서 따온 용어다. 58 | 59 | {% hint style="info" %} 60 | 이러한 타입은 ‘서로소 유니온 타입’ 이외에도 여러가지 다른 이름을 갖고 있다. 61 | 62 | 먼저 위의 `type` 속성처럼, 특정 속성을 통해 값이 속하는 브랜치를 식별할 수 있다는 이유로 **식별 가능한 유니온**\(discriminated union type\)또는 **태그된 유니온**\(tagged union\)이라는 이름을 갖는다. 브랜치를 식별하기 위해 쓰이는 `type` 속성은 식별자\(discriminator\) 또는 태그\(tag\)라 불린다. 63 | 64 | 서로소 유니온 타입의 또 다른 이름으로 **합 타입**\(sum type\)이 있다. 다음 코드를 보자. `Bool` 타입은 2개의 값, `Num` 타입은 3개의 값을 갖는다. 65 | 66 | ```typescript 67 | type Bool = true | false; 68 | type Num = 1 | 2 | 3; 69 | ``` 70 | 71 | 이 때 아래와 같이 정의한 서로소 유니온 타입 `SumType`은 몇 개의 값을 가질까? 72 | 73 | ```typescript 74 | type SumType = { type: 'bool', value: Bool } | { type: 'num', value: Num }; 75 | ``` 76 | 77 | 두 브랜치에 동시에 속하는 값이 없으므로 `SumType`은 2 + 3 = 5 개의 값을 갖는다. 합 타입이라는 이름은 이렇듯 각 브랜치가 갖는 값의 수를 합친 만큼의 값을 갖는 타입이라는 데에서 유래했다. 78 | {% endhint %} 79 | 80 | 서로소 유니온 타입 패턴을 사용해 **서로 겹치지 않는** 여러 경우 중 하나인 타입을 표현할 수 있다. 이 패턴은 매우 유용한데, 실제로 프로그래밍을 하면서 마주치는 많은 상황이 이러한 경우에 해당하기 때문이다. 81 | 82 | * 네트워크 요청: 요청은 성공하거나 실패하겠지만, 성공한 동시에 실패할 수는 없다. 83 | * 데이터베이스에서 특정 아이디를 갖는 컬럼을 쿼리: 해당 컬럼이 있거나 없지만, 있으면서 없을 수는 없다. 84 | * 어떤 파일명을 갖는 파일 내용을 읽어오는 경우: 해당 파일이 있거나 없지만, 있으면서 없을 수는 없다. 85 | 86 | 그 외에도 여러가지 선택지 중 하나의 결과를 갖는 대부분의 상황을 서로소 유니온 타입으로 표현할 수 있다. 87 | 88 | ### **switch를 이용한 패턴 매칭** 89 | 90 | 서로소 유니온 타입의 브랜치 사이에 겹치는 값이 없다. 이 성질과 패턴 매칭을 사용하면 간결하고 직관적인 코드를 짤 수 있다. 이런 장점 때문에 하스켈, 스칼라, 러스트 등의 함수형 프로그래밍 언어는 서로소 유니온 타입과 패턴 매칭을 매우 적극적으로 사용한다. 91 | 92 | 아쉽게도 타입스크립트는 언어 차원에서 패턴 매칭 문법을 제공하지 않는다. 하지만 `switch`-`case` 제어 구조와 타입 좁히기의 힘을 빌려 비슷한 효과를 흉내낼 수 있다. `kind` 속성을 식별자로 갖는 다음 서로소 유니온 타입을 생각해보자. 93 | 94 | ```typescript 95 | interface Square { 96 | kind: "square"; 97 | size: number; 98 | } 99 | interface Rectangle { 100 | kind: "rectangle"; 101 | width: number; 102 | height: number; 103 | } 104 | interface Circle { 105 | kind: "circle"; 106 | radius: number; 107 | } 108 | type Shape = Square | Rectangle | Circle; 109 | ``` 110 | 111 | 이 때 `Shape` 타입의 값을 받아 면적을 반환하는 함수를 다음과 같이 구현할 수 있다. 112 | 113 | ```typescript 114 | function area(s: Shape): number { 115 | switch (s.kind) { 116 | case "square": { 117 | // s의 타입 Square로 좁혀짐 118 | return s.size * s.size; 119 | } 120 | case "rectangle": { 121 | // s의 타입 Rectangle로 좁혀짐 122 | return s.height * s.width; 123 | } 124 | case "circle": { 125 | // s의 타입 Circle로 좁혀짐 126 | return Math.PI * s.radius ** 2; 127 | } 128 | } 129 | } 130 | ``` 131 | 132 | 타입스크립트의 타입 시스템은 `s.kind`를 식별자로 사용해 각각의 `case` 문 내에서 `s`의 타입을 성공적으로 좁혀낼 수 있다. 또한, 세 가지 `case`문을 통해 가능한 모든 경우를 이미 처리했음을 알고 있으므로 `default` 브랜치가 없이도 이 함수의 반환 타입이 `number`라는 사실을 이해한다. 133 | 134 | 다음과 같이 프로그래머가 실수로 한 브랜치를 처리하지 않는 경우를 생각해보자. 135 | 136 | ```typescript 137 | function incompleteArea(s: Shape): number { 138 | switch (s.kind) { 139 | case "square": { 140 | // s의 타입 Square로 좁혀짐 141 | return s.size * s.size; 142 | } 143 | case "rectangle": { 144 | // s의 타입 Rectangle로 좁혀짐 145 | return s.height * s.width; 146 | } 147 | } 148 | } 149 | ``` 150 | 151 | 위 코드는 다음 에러를 발생시킨다. `number` 타입을 반환해야 하는데 `undefined` 를 반환하는\(즉 반환문이 없는\) 경우가 있으므로 무언가 잘못되었음을 알려주는 것이다. 152 | 153 | ```typescript 154 | error TS2366: Function lacks ending return statement and return type does not include 'undefined'. 155 | ``` 156 | 157 | 그 뿐만이 아니다. 타입스크립트는 실수로 식별자 이름에 오타를 내는 경우에도 그런 식별자는 존재하지 않는다는 사실을 지적해 줄 수 있다. 158 | 159 | ```typescript 160 | function areaWithTypo(s: Shape): number { 161 | switch (s.kind) { 162 | case "square": { 163 | // s의 타입 Square로 좁혀짐 164 | return s.size * s.size; 165 | } 166 | case "rectangl": { 167 | // 오타 발생 168 | return s.height * s.width; 169 | } 170 | case "circle": { 171 | // s의 타입 Circle로 좁혀짐 172 | return Math.PI * s.radius ** 2; 173 | } 174 | } 175 | } 176 | // error TS2678: Type '"rectangl"' is not comparable to type '"square" | "circle" | "rectangle"'. 177 | ``` 178 | 179 | {% hint style="info" %} 180 | 이런 식으로 타입스크립트가 모든 브랜치가 올바르게 처리되었는지 수행하는 검사를 완전함 검사\(exhaustiveness check\)라 부른다. 181 | {% endhint %} 182 | 183 | 이렇듯, 서로소 유니온 타입과 `switch`-`case`를 사용해 언어가 지원하지 않는 패턴매칭을 흉내 낼 수 있다. 실제로 이 패턴은 redux를 비롯해 여러 인기 있는 라이브러리에서 쓰인다. 184 | 185 | {% hint style="info" %} 186 | 사실 [자바스크립트에 패턴 매칭을 도입하려는 프로포절](https://github.com/tc39/proposal-pattern-matching)이 존재한다. 2018년 3월 현재 아직까지는 stage 0에 머물러 있지만, 만약 이 프로포절이 살아남는다면 몇 년 후에는 자바스크립트와 타입스크립트에서도 언어 차원에서 지원되는 패턴 매칭을 사용할 수 있을지 모른다. 187 | {% endhint %} 188 | 189 | -------------------------------------------------------------------------------- /06-type-system-deepdive/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 6장에서는 타입스크립트의 타입 시스템에 대해 심도있게 다룬다. 3 | --- 4 | 5 | # 6.0 들어가며 6 | 7 | 이번 장에서는 타입 시스템이 동작하는 방식에 대해 보다 심도 있게 다룰 것이다. 가장 먼저, 타입스크립트가 코드 상의 여러 정보를 사용해 타입을 ‘**좁혀 나가는**’ 과정, 그리고 타입 표기 없이도 **타입 추론**을 사용해 타입에 대한 정보를 얻는 법에 대해 배운다. 다음으로 다룰 내용은 **타입 단언**을 사용해 컴파일러가 가진 타입 정보를 임의로 덮어쓰는 법이다. 마지막으로는 **타입을 집합으로 바라보는 관점**에 대해 알아본다. 8 | 9 | 타입 좁히기와 타입 추론은 프로그래머에게 가해지는 타입 표기의 부담을 줄임으로서 보다 간결한 코드 작성을 가능케 한다. 타입 단언은 외부 라이브러리 사용이나 빠른 프로토타이핑 등의 이유로 타입 검사를 빠르게 건너뛰고 싶은 상황에서 유용할 것이다. 또한 집합으로서 타입을 바라보는 관점은 타입스크립트 뿐만 아니라 정적 타입 언어를 익힐 때 큰 도움이 될 것이다. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /06-type-system-deepdive/outro.md: -------------------------------------------------------------------------------- 1 | # 6.6 맺으며 2 | 3 | 먼저 타입스크립트가 프로그램을 분석해 더 넓은 타입을 더 좁은 타입으로 좁힐 수 있음을 배웠다. 이 때 타입을 좁힐 수 있게 도와주는 타입 가드는 크게 두 종류가 있었다. 여러 제어 구조에 기반한 제어 구조 분석, 그리고 사용자가 제공하는 사용자 정의 타입 가드가 그것이다. 4 | 5 | 타입스크립트가 프로그래머의 명시적인 타입 표기 없이도 값의 타입을 추론해 내는 과정, 즉 타입 추론에 대해서도 다루었다. 이 때 타입 추론은 최적의 공통 타입을 찾는 방식으로 이루어진다. 또한, 할당 받는 값 뿐만 아니라 할당하는 값 또한 추론의 대상이 되며, 이렇게 추론된 타입은 문맥 상의 타입이라 부른다. 6 | 7 | 타입 시스템에게 특정 타입 정보를 강요할 수 있는 타입 단언도 알아보았다. `any`를 사용한 타입 단언으로 사실상 타입 검사를 완전히 건너 뛸 수 있다. 거듭 강조하건데, 타입 단언은 강력한 도구인만큼 꼭 필요하지 않은 상황에서 남용해서는 안 된다. 8 | 9 | 마지막으로, 프로그래밍 언어의 타입을 마치 수학의 집합처럼 바라볼 수 있었다. 그 관점에서 유니온 타입과 인터섹션 타입이 각각 합집합, 교집합의 역할을 한다는 것 또한 배웠다. 유니온 타입과 리터럴 타입을 종합해 서로소 유니온 타입을 구현하고 `switch-case`로 패턴 매칭을 흉내내는 고급 패턴 또한 알아보았다. 10 | 11 | 6장에서 다룬 타입 시스템이 동작하는 방식, 그리고 타입의 의의에 대한 이해는 시스템이 복잡해질수록 더욱 빛을 발하는 지식이다. 다음 행선지는 타입스크립트에 존재하는 고급 타입들이다. 12 | 13 | -------------------------------------------------------------------------------- /06-type-system-deepdive/type-assertion.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입 단언을 통해 컴파일러에게 특정 타입 정보의 사용을 강제할 수 있다. 3 | --- 4 | 5 | # 6.3 타입 단언 6 | 7 | 타입스크립트 컴파일러는 타입 표기, 타입 좁히기와 타입 추론 등의 기법을 이용해 값의 타입을 판단한다. 하지만 때로는 컴파일러가 가진 정보를 무시하고 프로그래머가 원하는 임의의 타입을 값에 할당하고 싶을 수 있다. 이럴 때 필요한 것이 바로 **타입 단언**\(type assertion\)이다. 8 | 9 | ### 타입 단언 문법 10 | 11 | `value as Type` 문법을 사용해 값 `value`를 `Type` 타입으로 단언할 수 있다. 이 표현은 다음과 같은 의미를 갖는다. 12 | 13 | > `value`가 어떤 타입인지는 내가 가장 잘 알아. 책임은 프로그래머인 내가 질테니, 네가 갖고 있는 정보는 다 무시하고 `value`를 `Type` 타입의 값이라 생각하고 진행해. 14 | 15 | 일례로 아래 코드는 `Animal` 타입의 값을 `Fish`로 단언한다. 원래대로라면 `Dog`와 `Insect` 타입에는 `swim` 메소드가 없다는 에러가 났겠지만, 타입 단언으로 인해 컴파일러는 `animal`을 `Fish` 타입으로 해석하고, 타입 에러 없이 컴파일된다. 16 | 17 | ```typescript 18 | interface Dog { 19 | legs: 4; 20 | bark(): void; 21 | } 22 | 23 | interface Insect { 24 | legs: number; 25 | creepy: boolean; 26 | } 27 | 28 | interface Fish { 29 | swim(): void; 30 | } 31 | 32 | type Animal = Dog | Insect | Fish; 33 | 34 | function doSomethingWithAnimal(animal: Animal) { 35 | (animal as Fish).swim(); 36 | } 37 | ``` 38 | 39 | 주의할 점은 타입 단언은 타입 에러를 없애줄 뿐 런타임 에러를 막아주지 않는다는 점이다. 오히려 그 반대인데, 컴파일 타입에 잡을 수 있는 에러를 없앰으로서 원래대로면 생기지 않았을 런타임 에러를 발생시킬 수 있다. 실제로 위 함수는 런타임에 `Dog` 혹은 `Insect` 타입 값을 받으면 터질 것이다. 40 | 41 | ### **any** **타입 단언** 42 | 43 | 3장에서 타입스크립트의 비상 탈출구인 `any` 타입에 대해 다루었다. 값을 `any` 타입으로 단언함으로써 특정 값에 대한 타입 검사를 사실상 완전히 무효화할 수 있다. 44 | 45 | ```typescript 46 | (3 as any).substr(0, 3); 47 | ``` 48 | 49 | 위 코드는 실제로 실행한다면 런타임 에러가 발생하지만, 타입 검사는 통과한다. 번거로운 타입 검사를 피할 수 있지만, any를 사용한 타입 단언은 어쩔 수 없는 경우를 제외하곤 피하는 것이 좋다. 타입스크립트를 사용하는 근본적인 이유는 런타임에 발생할 에러를 컴파일 타임에 방지하기 위해서인데, any를 사용한 타입 단언은 그 의도에 정확히 반하기 때문이다. 50 | 51 | ### **다중 단언** 52 | 53 | 타입 단언은 여러번 겹쳐 사용할 수 있다. 54 | 55 | ```typescript 56 | const wowSuchAny = (((42 as any) as any) as any) as any); 57 | ``` 58 | 59 | 이러한 다중 단언은 호환되지 않는 것이 명백한 타입으로의 단언을 가능케 한다. 아래와 같은 타입 단언에선 타입 에러가 발생한다. `Dog` 타입을 `Insect`로 취급할 수 없다는 것을 컴파일러가 알기 때문이다. 60 | 61 | ```typescript 62 | interface Dog { 63 | legs: 4; 64 | bark(): void; 65 | } 66 | 67 | interface Insect { 68 | legs: number; 69 | creepy: boolean; 70 | } 71 | 72 | const dog: Dog = { legs: 4, bark() { console.log('bark') } }; 73 | const insect: Insect = dog as Insect; 74 | // error TS2352: Type 'Dog' cannot be converted to type 'Insect'. 75 | // Property 'creepy' is missing in type 'Dog'. 76 | ``` 77 | 78 | 하지만 이러한 제약은 `any`로 한 번 타입 단언을 한 뒤, 그 값을 다시 `Insect`로 단언함으로서 피해갈 수 있다. 일단 `any` 타입으로 취급된 그 값은 모든 타입에 할당 가능하기 때문이다. 79 | 80 | ```typescript 81 | const insect2: Insect = (dog as any) as Insect; // ok 82 | ``` 83 | 84 | 다시 한 번 강조하지만, 타입 단언이 막아주는 건 타입 에러 뿐이다. 절대 런타임 오류가 나지 않을 것이라는 확신이 있거나 런타임 에러가 나도 상관 없는 상황이 아니라면 이런 식으로 호환되지 않는 타입을 `any`를 거쳐 단언하는 일은 피하는게 좋다. 85 | 86 | -------------------------------------------------------------------------------- /06-type-system-deepdive/type-inference.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 컴파일러는 타입 추론을 통해 명시적인 타입 표기 없이도 타입 정보를 이해할 수 있다. 3 | --- 4 | 5 | # 6.2 타입 추론 6 | 7 | 지금까지의 이 책의 모든 코드 예제는 타입을 명시적으로 표기해줬다. 하지만 3장의 도입부에서 언급했듯, 타입스크립트는 타입 추론을 지원한다. 프로그래머가 타입 표기를 적지 않아도 값의 타입을 추론해 낼 수 있는 것이다. 다음은 타입 표기를 명시적으로 적어준 버전이다. 8 | 9 | ```typescript 10 | let x: number = 3; 11 | ``` 12 | 13 | 하지만 위의 코드에서 타입 표기를 없애더라도 타입스크립트는 `3`이라는 값으로부터 `x`가 `number` 타입일 것이라 추론해낸다. 14 | 15 | ```typescript 16 | let x = 3; // number 타입으로 추론 17 | ``` 18 | 19 | 한 편, `let` 대신 `const` 를 사용하면 어떨까? 타입스크립트는 이처럼 재할당이 불가능한 경우에는 변수의 타입을 보다 구체적인 숫자 리터럴 타입으로 추론한다. 이러한 속성은 인터페이스나 클래스의 `readonly` 속성에도 비슷하게 적용된다. 20 | 21 | ```typescript 22 | const x = 3; // 3 타입으로 추론 23 | ``` 24 | 25 | 물론 의도한 타입이 할당하는 값만으로는 드러나지 않는 경우엔 여전히 타입 표기가 필요하다. 예를 들어 아래의 코드에서 타입 표기 없이 컴파일러가 `1 | 3` 이라는 타입을 추론하긴 불가능 할 것이다. 26 | 27 | ```typescript 28 | const oneOrThree: 1 | 3 = 3; 29 | ``` 30 | 31 | ### **최적의 공통 타입** 32 | 33 | 하나의 값에 대한 타입 추론은 단순하다. 그렇다면 여러 값이 연관된 타입을 추론할 때는 어떨까? 다음 코드를 보자. 34 | 35 | ```typescript 36 | interface Animal { 37 | legs: number; 38 | } 39 | 40 | interface Dog extends Animal { 41 | bark(): void; 42 | } 43 | 44 | interface Cat extends Animal { 45 | meow(): void; 46 | } 47 | 48 | let dog: Dog; 49 | let cat: Cat; 50 | const dogAndCat = [dog, cat]; // ?? 51 | ``` 52 | 53 | `dogAndCat`은 `Dog` 타입의 원소와 `Cat` 타입의 원소를 갖는 배열이다. 이러한 배열의 타입은 어떻게 추론해야 할까? 54 | 55 | 이런 상황에서 타입스크립트는 **최적 공통 타입**\(best common type\)이란 접근법을 사용한다. 원리는 간단한데, 모든 가능한 타입의 유니온 타입을 사용하는 것이다. 예를 들어 위 `dogAndCat`의 타입을 `Array` 으로 추론하는 식이다. 56 | 57 | 이 때 두 인터페이스 모두 `Animal` 인터페이스를 호환받았으니 `Array`로 추론하면 되는 것 아닐지 궁금해 할 수 있다. 그렇게 동작하지 않는 이유를 이해하기 위해 다음 예제를 보자. 58 | 59 | ```typescript 60 | interface Camel extends Animal { 61 | humps: number; 62 | } 63 | 64 | function getSoundFunction(dogOrCat: Dog | Cat) { 65 | if ('meow' in dogOrCat) { 66 | return dogOrCat.meow; 67 | } else { 68 | return dogOrCat.bark; 69 | } 70 | } 71 | ``` 72 | 73 | 타입스크립트는 최적 공통 타입에 따라 `dogAndCat`를 `Array` 타입으로 추론한다. 따라서 다음 코드는 실제로 타입 검사를 통과한다. 아래 코드는 실제로 문제가 생길 여지가 없으므로 이는 바람직한 동작이다. 74 | 75 | ```typescript 76 | dogAndCat.map(dogOrCat => getSoundFunction(dogOrCat)); 77 | ``` 78 | 79 | 하지만 만약 배열의 타입을 `Array`로 추론한다면 어떻게 될지 생각해보자. `getSoundFunction` 함수는 `Camel` 타입을 인자로 받지 않는다. 그 때문에, `dogAndCat` 내에는 `Camel` 타입 값이 존재하지 않음에도 위의 코드에선 타입 에러가 날 것이다. 80 | 81 | 이런 불편함을 막고자, 최적 공통 타입에서는 타입 추론에 사용된 값들의 타입\(이 경우엔 `Dog`와 `Cat`\)만을 재료로 사용한다. `dogAndCat`이 `Array` 타입을 갖길 원한다면 타입 추론에 의존하는 대신 명시적으로 타입 표기를 해 주면 된다. 82 | 83 | ### **문맥 상의 타입** 84 | 85 | 할당이 일어날 때, 타입 추론은 할당 받는 값\(왼쪽 항\)의 타입 뿐만 아니라 할당하는 값\(오른쪽 항\)의 타입에 대해서도 일어난다. 이렇게 추론된 타입을 문맥 상의 타입\(contextual type\)이라 부른다. 86 | 87 | ```typescript 88 | window.onmousedown = function(mouseEvent) { 89 | console.log(mouseEvent.a); 90 | }; 91 | ``` 92 | 93 | 이 때, `Window` 인터페이스의 `onmousedown` 속성은 아래와 같이 정의되어 있다. 94 | 95 | ```typescript 96 | interface MouseEvent { 97 | /* ... */ 98 | /* button 속성 없음! */ 99 | } 100 | 101 | interface Window { 102 | /* ... */ 103 | onmousedown: (event: MouseEvent) => void; 104 | } 105 | ``` 106 | 107 | 따라서 타입스크립트는 우변의 함수가 `(event: MouseEvent) => void` 타입일 것이라고 추론한다. 이 때 함수 내부에서 `event.a` 속성에 접근하는데, `a` 속성은 `MouseEvent` 타입에 존재하지 않으므로 타입 에러가 발생한다. 108 | 109 | 만약 타입 표기가 주어졌다면 문맥 상의 타입은 무시된다. 예를 들어, 다음과 같이 `mouseEvent` 매개변수의 타입을 표기해주면 위의 에러는 사라진다. 110 | 111 | ```typescript 112 | window.onmousedown = function(mouseEvent: any) { 113 | console.log(mouseEvent.a); 114 | }; 115 | ``` 116 | 117 | -------------------------------------------------------------------------------- /06-type-system-deepdive/type-narrowing.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 타입스크립트가 타입을 보다 좁은 타입으로 좁히는 다양한 상황에 대해 알아본다. 3 | --- 4 | 5 | # 6.1 타입 좁히기 6 | 7 | 앞서 유니온 타입을 통해 **여러 경우 중 하나인 타입**을 표현할 수 있음을 배웠다. `A | B` 는 `A`타입 **이거나** `B`타입인 타입을 나타낸다. 객체의 선택 속성 역시 유니온 타입의 특수한 경우라고 생각할 수 있다. 선택 속성 `prop?: T`에서 `prop`은 `undefined` **이거나** T인 타입이고, 이는 `prop: T | undefined` 와 유사하기 때문이다. 8 | 9 | {% hint style="info" %} 10 | `prop?: T` 와 `prop: T | undefined` 는 정확히 동일한 의미는 아니다. 후자는 `prop` 이라는 이름의 속성이 존재할 것을 보장하지만, 전자는 그렇지 않다. 아래 예제를 보면 그 차이가 쉽게 이해 될 것이다. 11 | 12 | ```typescript 13 | interface OptionalProp { 14 | prop?: number; 15 | } 16 | interface UnionProp { 17 | prop: number | undefined; 18 | } 19 | const optional: OptionalProp = {}; // ok 20 | const union: UnionProp = {}; 21 | // error TS2322: Type '{}' is not assignableto type 'UnionProp'. 22 | // Property 'prop' is missing in type '{}'. 23 | ``` 24 | 25 | 다만 지금 다룰 타입 좁히기라는 주제에 있어서는 선택 속성을 유니온 타입으로 보는 것에 큰 무리는 없다. 26 | {% endhint %} 27 | 28 | 하지만 프로그램의 로직에 의해 값이 여러 경우의 수 중 어떤 타입을 가지는지, 또 어떤 타입은 확실히 아닌지가 명백해지는 상황들이 있다. 다음 코드를 보자. 29 | 30 | ```typescript 31 | interface Person { 32 | favoriteLanguage?: string; 33 | } 34 | 35 | function isFavoriteLangScript(p: Person): boolean { 36 | // A 37 | if (p.favoriteLanguage === undefined) { 38 | return false; 39 | } 40 | 41 | //B 42 | const lowerCased = p.favoriteLanguage.toLowerCase(); 43 | return lowerCased.includes('script'); 44 | } 45 | ``` 46 | 47 | `A` 시점에서 `p.favoriteLanguage`의 타입은 당연히 `string | undefined` 다. 하지만 `B`에서는 어떨까? 48 | 49 | 함수 내의 `if` 문을 보면, `p.favoriteLanguage`가 `undefined` 라면 함수는 이른 반환\(early return\)을 수행한다. 이 경우에는 실행 흐름이 `B`에 도달하기 전에 함수를 빠져나간다. 따라서 실행 흐름이 `B`에 도달했다면 `p.favoriteLanguage`는 `string` 타입일 수 밖에 없다. 타입스크립트는 이러한 상황을 이해하여 위의 `if` 문 이후부터는 `p.favoriteLanguage`의 타입을 `string` 으로 인식한다. 50 | 51 | 이런 식으로 보다 넓은\(더 많은 경우의 수를 갖는\) 타입을 더 좁은\(더 적은 경우의 수를 갖는\) 타입으로 재정의하는 행위를 **타입 좁히기**\(type narrowing\)라 부른다. 위의 예제에서는 `if` 문을 통해 `string | undefined` 라는 넓은 타입으로부터 `string` 이라는 좁은 타입으로의 타입 좁히기가 일어났다. 52 | 53 | 타입 좁히기가 없다면 불필요한 런타임 검사를 계속 해야 하고, 결과적으로 유니온 타입을 사용하기가 훨씬 불편해질 것이다. 예를 들어 위 예제 코드에서 타입 좁히기가 일어나지 않으면 어떻게 될까? 아래와 같이 `B` 이후로도 `p.favoriteLanguage`를 사용하려 할 때마다 `undefined` 여부를 체크해줘야 할 것이다. 54 | 55 | ```typescript 56 | function isFavoriteLangScript(p: Person): boolean { 57 | if (p.favoriteLanguage === undefined) { 58 | return false; 59 | } 60 | 61 | // String 타입의 메소드에 접근하기 위해 체크가 필요 62 | if (p.favoriteLanguage !== undefined) { 63 | const lowerCased = p.favoriteLanguage.toLowerCase(); 64 | return lowerCased.includes('script'); 65 | } 66 | // 반환 타입을 맞추기 위해 적어줘야 함 67 | return false; 68 | } 69 | ``` 70 | 71 | ### **타입 가드** 72 | 73 | 특정 스코프 내에서 값의 타입을 좁히는, 즉 타입 좁히기를 유발하는 표현을 **타입 가드**\(type guard\)라 부른다. 타입 가드는 크게 두 종류로 나뉜다. 한 종류는 **제어 흐름 분석**\(control flow analysis\)을 통해 타입을 좁히는 가드들이다. 그리고 다른 하나는 프로그래머가 값을 어떤 타입으로 좁혀야 하는지 직접 명시할 수 있는 수단인 **사용자 정의 타입 가드**\(user defined type guard\)다. 74 | 75 | ### **제어 흐름 분석** 76 | 77 | 기본적으로 비동기 실행 코드를 제외하면 코드는 위에서 아래로 차례대로 실행된다. 대부분의 프로그래밍 언어는 특정 조건이 만족될 때에만 코드를 실행하거나 같은 코드를 여러번 실행하는 식으로 순차적 실행을 벗어난 실행을 가능하게 하는 제어 구조\(control structure\)를 제공한다. 자바스크립트와 타입스크립트 역시 예외는 아니며, 이 언어들의 대표적인 제어 구조는 다음과 같다. 78 | 79 | * `if`, `else if`, `else` 80 | * `while`, `for` 81 | * `switch`, `case` 82 | * `break`, `continue` 83 | * `return` 84 | 85 | 컴파일러는 이런 제어 구조로부터 코드의 특정 시점에서 프로그램이 갖는 상태에 대한 정보를 얻어낼 수 있다. 그리고 컴파일러는 이러한 정보를 이용해 제어 흐름 분석\(control flow analysis\)를 진행해 특정 값의 타입을 좁혀낼 수 있다. 제어 흐름 분석을 가능케 하는 타입 가드들엔 어떤 종류가 있고, 타입이 어떻게 좁혀지는지 살펴보자. 86 | 87 | #### **undefined / null 과의 비교** 88 | 89 | `undefined` 또는 `null`과의 비교는 각각 대응하는 타입에 대한 타입 가드로 동작한다. 아래 예제에서는 `if` 문에서의 null 체크가 타입 가드로 동작한다. 90 | 91 | ```typescript 92 | interface Animal { 93 | ownerName: string | null; 94 | } 95 | 96 | function getOwnerName(animal: Animal): string { 97 | if (animal.ownerName === null) { 98 | return 'wildness'; 99 | } else { 100 | // animal.ownerName 타입은 string 101 | return animal.ownerName; 102 | } 103 | } 104 | ``` 105 | 106 | 앞서 살펴본 `Person.favoriteLanguage` 예제 역시 `undefined` 와의 비교가 타입 가드로 동작한 경우다. 107 | 108 | #### **리터럴 타입과의 비교** 109 | 110 | 리터럴 타입과의 비교 또한 타입 가드로 동작한다. 아래 예제를 보자. 111 | 112 | ```typescript 113 | interface TeamLeader { 114 | type: 'leader'; 115 | leadingSince: Date; 116 | } 117 | 118 | interface Newcomer { 119 | type: 'newcomer'; 120 | major: string; 121 | } 122 | 123 | type Employee = TeamLeader | NewComer; 124 | 125 | function doSomething(employee: Employee) { 126 | switch (employee.type) { 127 | case 'leader': { 128 | // employee는 TeamLeader 타입 129 | return employee.leadingSince; 130 | } 131 | case 'newcomer': { 132 | // employee는 Newcomer 타입 133 | return employee.major; 134 | } 135 | default: { 136 | // employee는 never 타입 137 | return null; 138 | } 139 | } 140 | } 141 | ``` 142 | 143 | 리터럴 타입인 `employee.type`을 기반으로 `switch`-`case`를 통해 각 브랜치에서 `employee`의 타입을 좁힐 수 있었다. 앞의 두 케이스가 가능한 모든 케이스를 처리했으므로 `default` 브랜치에서 `employee`는 `never` 타입이 되는데, 이 역시 타입 좁히기 덕분이다. 144 | 145 | {% hint style="info" %} 146 | 위와 같은 식으로 리터럴 타입 식별자\(discriminator\)를 갖는 유니온 타입을 서로소 유니온 타입이라 부른다. 서로소 유니온 타입에 대해서는 이 장의 끝부분에서 다시 자세히 다룬다. 147 | {% endhint %} 148 | 149 | #### **typeof 연산자** 150 | 151 | `typeof` 연산자는 하나의 인자를 받아 해당 인자의 타입을 나타내는 문자열을 반환한다. `typeof`의 반환값과 문자열을 비교한 결과를 타입 가드로 사용할 수 있다. 공식 문서의 예제를 보자. 152 | 153 | ```typescript 154 | function padLeft(value: string, padding: string | number) { 155 | if (typeof padding === "number") { 156 | return Array(padding + 1).join(" ") + value; 157 | } 158 | 159 | if (typeof padding === "string") { 160 | return padding + value; 161 | } 162 | 163 | throw new Error(`Expected string or number, got '${padding}'.`); 164 | } 165 | ``` 166 | 167 | {% hint style="info" %} 168 | `typeof` 연산자는 보통의 프로그래머의 예상과는 다르게 동작한다는 점을 주의해야 한다. 흔히 쓰이는 값의 타입과 `typeof` 연산자의 반환값을 보면 아래 표와 같다. 169 | 170 | 하위 호환성 이슈로 인해 `typeof null`은 `"null"`이 아닌 `"object"` 다. 또한 `typeof [] === "array"` 일 것이란 예상과 달리 배열을 나타내는 별도의 반환값은 존재하지 않는다. 이런 혼란스러운 동작 탓에 일반적으로 `typeof` 타입 가드는 `boolean`, `string`, `number`, `symbol` 등 단순한 타입에 대해서만 사용하는 것이 권장된다. 171 | {% endhint %} 172 | 173 | | 타입 | `typeof` 반환값 | 174 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | 175 | | Undefined | "undefined" | 176 | | Null | "object" | 177 | | Boolean | "boolean" | 178 | | Number | "number" | 179 | | String | "string" | 180 | | Symbol | "symbol" | 181 | | Function | "function" | 182 | | 그 외 모든 객체 | "object" | 183 | 184 | #### **instanceof** **연산자** 185 | 186 | instanceof 연산자는 값과 생성자를 받아 해당 값의 프로토타입 체인에 해당 생성자가 등장하는지를 확인한다. ES6 클래스 역시 내부적으로는 프로토타입 체인에 기반해 돌아가므로, 클래스의 인스턴스 여부도 instanceof를 이용해 확인할 수 있다. 공식 예제를 보자. 187 | 188 | ```typescript 189 | interface Padder { 190 | getPaddingString(): string; 191 | } 192 | 193 | class SpaceRepeatingPadder implements Padder { 194 | constructor(private numSpaces: number) { } 195 | getPaddingString() { 196 | return Array(this.numSpaces + 1).join(" "); 197 | } 198 | } 199 | 200 | class StringPadder implements Padder { 201 | constructor(private value: string) { } 202 | getPaddingString() { 203 | return this.value; 204 | } 205 | } 206 | 207 | function getRandomPadder() { 208 | return Math.random() < 0.5 ? 209 | new SpaceRepeatingPadder(4) : 210 | new StringPadder(" "); 211 | } 212 | 213 | // 이 시점에선 'SpaceRepeatingPadder | StringPadder' 214 | let padder: Padder = getRandomPadder(); 215 | 216 | if (padder instanceof SpaceRepeatingPadder) { 217 | padder; // SpaceRepeatingPadder 로 좁혀짐 218 | } 219 | 220 | if (padder instanceof StringPadder) { 221 | padder; // StringPadder 로 좁혀짐 222 | } 223 | ``` 224 | 225 | #### **in 연산자** 226 | 227 | `in` 연산자는 객체에 특정 속성이 존재하는지 여부를 확인할 때 사용된다. 228 | 229 | ```typescript 230 | const obj = { a: 123 }; 231 | console.log('a' in obj); // true 232 | console.log('b' in obj); // false 233 | ``` 234 | 235 | `in` 연산자의 결과 역시 역시 타입 가드로 쓸 수 있다. 236 | 237 | ```typescript 238 | interface Dog { 239 | legs: 4; 240 | bark(): void; 241 | } 242 | 243 | interface Insect { 244 | legs: number; 245 | creepy: boolean; 246 | } 247 | 248 | interface Fish { 249 | swim(): void; 250 | } 251 | 252 | type Animal = Dog | Insect | Fish; 253 | 254 | function doSomethingWithAnimal(animal: Animal) { 255 | if ('legs' in animal) { 256 | // animal은 Dog | Insect 타입 257 | console.log(animal.legs); 258 | } else { 259 | // animal은 Fish 타입 260 | animal.swim(); 261 | } 262 | } 263 | ``` 264 | 265 | ### **사용자 정의 타입 가드** 266 | 267 | 지금까지는 타입스크립트 언어에 내장된 제어 흐름에 기반하여 동작하는 타입 가드를 살펴보았다. 그와 다르게 프로그래머가 직접 임의의 기준을 사용해 타입 가드를 정의할 수도 있다. 이러한 타입 가드를 사용자 정의 타입 가드라 부른다. 268 | 269 | 사용자 정의 타입 가드는 `value is Type` 형태의 반환 타입을 갖는 **함수**로 정의한다. 예를 들어 아래와 같이 `isFish` 사용자 정의 타입 가드를 정의해서 위의 `doSomethingWithAnimal` 함수를 고쳐 쓸 수 있다. 270 | 271 | ```typescript 272 | function isFish(animal: Animal): animal is Fish { 273 | if ('legs' in animal) { 274 | return false; 275 | } 276 | return true; 277 | } 278 | 279 | function doSomethingWithAnimal(animal: Animal) { 280 | if (isFish(animal)) { 281 | // animal은 Fish 타입 282 | animal.swim(); 283 | } else { 284 | // animal은 Dog | Insect 타입 285 | console.log(animal.legs); 286 | } 287 | } 288 | ``` 289 | 290 | {% hint style="info" %} 291 | 초기 버전의 타입스크립트는 타입 시스템의 힘이 강력하지 않았고, 제어 흐름 분석에 기반한 타입 좁히기가 거의 이루어지지 않았다. 때문에 사용자 정의 타입 가드를 사용해야 하는 경우가 많았다. 하지만 꾸준히 발전을 거듭한 오늘날의 타입스크립트에선 타입 좁히기가 똑똑하게 이루어지고, 대부분의 사용례는 위에서 다룬 내장 타입 가드로도 충분히 커버할 수 있다. 292 | {% endhint %} 293 | 294 | -------------------------------------------------------------------------------- /06-type-system-deepdive/types-as-sets.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: '프로그래밍의 타입이 수학의 집합과 공유하는 성질, 그리고 타입을 집합으로 바라보는 관점에 대해 배운다.' 3 | --- 4 | 5 | # 6.4 집합으로서의 타입 6 | 7 | 타입스크립트 문법으로부터 잠시 쉬어가는 시간을 갖자. 이 장에서 다루는 내용은 언뜻 보아선 타입스크립트와 동떨어진 듯 보일 수 있다. 하지만 일단 이해하고 나면 타입스크립트 뿐만 아니라 다른 여러 정적 타입 언어들을 이해하는 데에 큰 도움이 될 것이다. 8 | 9 | ### **타입은 집합이다** 10 | 11 | 프로그래밍 언어에서의 타입이란 뭘까? 비슷한 성격의 값들을 모아 놓은 무언가? 지켜야 할 규칙? 우리는 타입에 대해 자주 이야기하지만 정작 타입라는 개념이 갖는 함의에 대해서는 잘 이야기하지 않는다. 타입을 이해하기 위한 다양한 수단 중 상대적으로 쉽고 직관적인 개념이 있다. 바로 집합이다. 12 | 13 | 프로그래밍에서의 **타입**은 수학에서의 **집합**과 매우 많은 특징을 공유한다. 보다 과격하게 표현하자면, **타입은 집합이다**. 실제로 타입스크립트의 **값과 타입의 관계**는 집합의 **원소와 집합의 관계**와 굉장히 유사하다. 타입\(집합\)이란 결국 값\(원소\)들의 모임이 아닌가! 14 | 15 | 어떤 값 `value`가 타입 `T`에 속한다는 사실을 원소 `value`가 집합 `T`에 속한다\(`value ∈ T`\)는 사실에 대응해 생각해보자. 예를 들어, `true` 라는 원소가 `Boolean` 이란 집합에 속한다는 식으로 말이다. 이런 관점으로 타입스크립트의 내장 타입 `number` 와 `type Binary = 0 | 1` 두 타입을 바라보면 아래와 같은 대응 관계들이 성립한다. 16 | 17 | | 값과 타입 | 원소와 집합 | 18 | | --- | --- | --- | --- | --- | --- | 19 | | 값 `0`은 타입 `number`에 속한다. | 원소 `0`이 집합 `number`에 속한다. | 20 | | 값은 여러 타입에 속할 수 있다. 예를 들어, `0`은 `number`와 `Binary` 두 타입에 모두 속한다. | 한 원소는 여러 집합에 속할 수 있다. | 21 | | `Binary` 타입의 모든 값\(`0`, `1`\)은 `number` 타입의 값이기도 하다. 즉 `Binary`는 `number`의 서브타입이다. | `Binary`의 모든 원소는 `number`의 원소이기도 하다. 즉 `Binary`는 `number`의 부분집합이다. | 22 | | 이미 존재하는 두 타입을 이용해 더 복잡한 타입을 만들 수 있다. `{ binary: Binary, num: number }` | 두 집합을 사용해 새로운 집합을 정의할 수 있다. `{ (binary, num) | binary ∈ Binary, num ∈ number }` | 23 | | 어떤 값도 가질 수 없는 타입 `never`가 존재한다. `never`는 모든 타입의 서브타입이다. | 원소가 없는 공집합 `Ø`가 존재한다. `Ø`는 모든 집합의 부분집합이다. | 24 | 25 | 이 내용을 보다 일반적으로 확장해보자. 26 | 27 | * 어떤 프로그래밍 언어 L로 쓰인 프로그램이 가질 수 있는 모든 값의 집합을 V라 부르자. 28 | * 이때, **V의 원소 중 특정한 조건을 만족하는 값**을 모아 이 집합을 **L에서의 타입 T**라고 정의할 수 있다. 29 | * 이렇게 타입을 집합으로서 바라볼 때, 아래와 같은 대응 관계가 성립한다. 30 | 31 | | 원소와 집합 | 값과 타입 | 32 | | --- | --- | --- | --- | --- | --- | 33 | | 원소 `x`가 집합 `S`에 속한다. \(`x ∈ S`\) | 값 `x`는 `S` 타입에 속한다. \(혹은 `S` 타입을 값 `x`에 할당할 수 있다.\) | 34 | | 한 원소는 여러 집합에 속할 수 있다. | 한 값은 여러 타입에 속할 수 있다. | 35 | | 집합 `T`가 집합 `S`의 부분집합이다. \(`T ⊂ S`\) | 타입 `T`가 타입 `S`의 서브타입이다. | 36 | | 조건 제시법을 이용해 기존에 존재하는 집합으로부터 새로운 집합을 만들어낼 수 있다. \(`S’ = { (x, y) | x ∈ S, y ∈ S }`\) | 기존 타입의 정의로부터 새로운 타입을 만들어낼 수 있다\(객체 타입, 유니온 타입, ...\) | 37 | | 모든 집합의 부분집합인 공집합 `Ø`이 존재한다. | 모든 타입의 서브타입인 바닥 타입_bottom type_이 존재한다 \(혹은 존재할 수 있다\). | 38 | 39 | 이런 대응 관계에서 알 수 있듯이, 프로그래밍의 타입은 수학의 집합에, 값은 원소에 대응한다. **타입은 값의 모음, 즉 집합인 것이다**. 40 | 41 | ### **유니온 타입은 합집합이다** 42 | 43 | 위에서 타입이 집합과 매우 유사한 개념임을 알 수 있었다. 이제 이런 관점에서 유니온 타입을 한 번 바라보자. 44 | 45 | ```typescript 46 | type Union = A | B; 47 | ``` 48 | 49 | 위 코드가 갖는 의미는 다음과 같이 풀어쓸 수 있다. 50 | 51 | * `A` 타입의 모든 값은 `Union` 타입의 값이다. 52 | * `B` 타입의 모든 값은 `Union` 타입의 값이다. 53 | 54 | 그리고 이는 집합의 관점에서 다음과 같이 해석할 수 있다. 55 | 56 | * 집합 `A`의 모든 원소는 집합 `Union`의 원소이다. 57 | * 집합 `B`의 모든 원소는 집합 `Union`의 원소이다. 58 | 59 | 친숙하게 느껴지지 않는가? 이는 곧 **합집합**의 정의이다. 즉, 유니온 타입 `Union`은 타입 시스템에서 합집합 `A ∪ B` 를 표현하는 수단이다! 유니온 타입이 합집합이라는 의미의 유니온\(union\)이라는 이름을 가진 건 우연이 아니다. 60 | 61 | ### **인터섹션 타입은 교집합이다** 62 | 63 | 다음으로는 인터섹션 타입은 어떤 의미를 지닐지 생각해보자. 눈치 빠른 독자라면 벌써 눈치 챘을 것이다. 64 | 65 | ```typescript 66 | type Intersection = A & B; 67 | ``` 68 | 69 | 위 코드를 풀어쓰면 다음과 같다. 70 | 71 | * `Intersection` 타입의 모든 값은 `A` 타입의 값이다. 72 | * `Intersection` 타입의 모든 값은 `B` 타입의 값이다. 73 | 74 | 마찬가지로 이를 집합의 관점에서 해석해보자. 75 | 76 | * 집합 `Intersection`의 모든 원소는 집합 `A`의 원소이다. 77 | * 집합 `Intersection`의 모든 원소는 집합 `B`의 원소이다. 78 | 79 | 맞다. 이는 교집합 `A ∩ B`의 정의이다. 인터섹션 타입은 타입 시스템이 여러 타입 간의 교집합을 표현하는 수단이다. 짐작했겠지만, 인터섹션\(intersection\)은 수학에서 교집합이라는 의미를 갖는다. 80 | 81 | ### **차집합 = ???** 82 | 83 | 합집합과 교집합을 다루었으니 자연스레 ‘그럼 차집합은 어떻게 나타내지?’하는 질문이 들 수 있다. 84 | 85 | 집합 `A`에 대한 집합 `B`의 차집합 `B - A`는 다음 조건을 만족하는 집합으로 정의한다. 86 | 87 | * 집합 `A`에는 속하고 집합 `B`에는 속하지 않는 모든 원소는 집합 `B - A`의 원소이다. 88 | * 집합 `A`와 `B`에 동시에 속하는 모든 원소는 집합 `B - A`의 원소가 아니다. 89 | 90 | 타입스크립트 2.8에 추가된 `Exclude` 제너릭을 이용해 차집합을 나타낼 수 있다. 91 | 92 | * `B` 타입에 할당 불가능한 `A` 타입의 값은 모두 `Exclude`에 할당 가능하다. 93 | * `A & B` 타입의 값은 모두 `Exclude`에 할당 불가능하다. 94 | 95 | `Exclude` 제너릭에 대해선 7장에서 보다 자세히 다룰 것이다. 96 | 97 | -------------------------------------------------------------------------------- /07-advanced-types/7-2.md: -------------------------------------------------------------------------------- 1 | # 들어가며 \(7월 2일 공개\) 2 | 3 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 4 | 5 | -------------------------------------------------------------------------------- /07-advanced-types/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 7월 2일 공개 예정. 3 | --- 4 | 5 | # 들어가며 \(7월 2일 공개\) 6 | 7 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 8 | 9 | -------------------------------------------------------------------------------- /08-modules-and-namespaces/7-16.md: -------------------------------------------------------------------------------- 1 | # 들어가며 \(7월 16일 공개\) 2 | 3 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 4 | 5 | -------------------------------------------------------------------------------- /08-modules-and-namespaces/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 7월 16일 공개 예정. 3 | --- 4 | 5 | # 들어가며 \(7월 16일 공개\) 6 | 7 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 8 | 9 | -------------------------------------------------------------------------------- /09-real-world-projects/7-30.md: -------------------------------------------------------------------------------- 1 | # 들어가며 \(7월 30일 공개\) 2 | 3 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 4 | 5 | -------------------------------------------------------------------------------- /09-real-world-projects/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 7월 30일 공개 예정. 3 | --- 4 | 5 | # 들어가며 \(7월 30일 공개\) 6 | 7 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 8 | 9 | -------------------------------------------------------------------------------- /10-libraries/8-13.md: -------------------------------------------------------------------------------- 1 | # 들어가며 \(8월 13일 공개\) 2 | 3 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 4 | 5 | -------------------------------------------------------------------------------- /10-libraries/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 8월 13일 공개 예정. 3 | --- 4 | 5 | # 들어가며 \(8월 13일 공개\) 6 | 7 | ### [메일링 리스트 가입하고 새 챕터 공개 받아보기.](https://mailchi.mp/413644e9615c/ts-for-jsdev) 8 | 9 | -------------------------------------------------------------------------------- /10-libraries/outro.md: -------------------------------------------------------------------------------- 1 | # 맺으며 2 | 3 | 연재가 완료된 후에 적을 예정. 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 자바스크립트 개발자를 위한 타입스크립트 입문서입니다. 3 | --- 4 | 5 | # 자바스크립트 개발자를 위한 타입스크립트 6 | 7 | 저는 [안희종이라고 합니다](http://ahnheejong.name). 2018년 5월 현재 [토스](https://toss.im/)에서 웹 프론트엔드 개발자로 일하고 있습니다. 웹이라는 플랫폼에 큰 애정을 갖고 있으며, 웹에서 수많은 멋진 일들을 가능하게 해주는 언어인 자바스크립트를 참 좋아합니다. 자바스크립트는 그 난해함으로 악명이 높습니다. 그리고 자바스크립트가 받는 비난 중 일부는 \(사실 다수는\) 분명 사실입니다. 적어도 복잡하고 거대하면서도 정교하게 동작하는 애플리케이션 개발을 염두에 두고 개발된 언어는 분명 아니지요. 8 | 9 | 타입스크립트는 그런 자바스크립트의 단점을 보완하기 위한 많은 시도 중 하나입니다. 사실 그 중 가장 돋보이는 시도지요. 타입스크립트는 저와 다른 많은 자바스크립트 프로그래머가 사랑하는 자바스크립트의 장점을 대부분 살리면서도 훨씬 더 안정적이고 즐거운 대규모 애플리케이션 개발이 가능할 수 있다는 것을 보여줍니다. 그래서 저는 타입스크립트를 참 좋아합니다. 10 | 11 | 『자바스크립트 개발자를 위한 타입스크립트』는 타입스크립트 입문서입니다. 하지만 프로그래밍을 처음 시작하는 독자가 읽을 것을 염두에 두고 쓰진 않았습니다. 저와 같이 자바스크립트를 좋아하는 분들, 또 꼭 애정까진 갖고 있지 않더라도 자바스크립트를 매일 개발하는데 사용하는 분들께 타입스크립트가 왜 좋은 도구이고, 이 좋은 타입스크립트를 어떻게 알아가면 좋을지 소개하는 것을 목표로 적었습니다. 12 | 13 | 이 책은 현재 베타 버전으로 공개된 상태입니다. 타입스크립트의 기본을 소개하는 4장까지의 선공개를 시작으로, 매 2주마다 한 장\(chapter\)씩 공개해 나갈 생각입니다. 아래 메일링 리스트에 가입해주시면 격주 간격으로 발행될 새 장 공개를 받아보실 수 있습니다. 책의 내용과 무관한 스팸은 보내지 않을테니 걱정하지 않으셔도 됩니다. 14 | 15 | ## [**메일링 리스트 가입하기**](https://mailchi.mp/413644e9615c/ts-for-jsdev) 16 | 17 | 책을 웹으로 무료 공개하는 일의 가장 큰 장점은 빠르게 의견을 수렴하고 반영할 수 있단 점이라 생각합니다. 바라는 점이 있다면 이 책이 많은 분들께 도움이 되었으면 좋겠고, 그리고 여러분과 함께 이 책을 더 나은, 더욱 도움이 되는 자료로 만들고 싶습니다. 의견이나 오류 정정, 원하는 사항 등을 [깃허브 저장소](https://github.com/heejongahn/ts-for-jsdev)에 이슈로 달아주시면 여력이 닿는 한 최대한 수렴할 수 있도록 노력하겠습니다. 18 | 19 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [자바스크립트 개발자를 위한 타입스크립트](README.md) 4 | * [들어가며](intro.md) 5 | 6 | ## 01 타입스크립트 소개 7 | 8 | * [1.0 들어가며](01-introducing-typescript/intro.md) 9 | * [1.1 정적 타입 분석](01-introducing-typescript/static-type-analysis.md) 10 | * [1.2 왜 타입스크립트인가](01-introducing-typescript/why-typescript.md) 11 | * [1.3 타입스크립트의 구성요소](01-introducing-typescript/elements-of-typescript.md) 12 | * [1.4 타입스크립트의 역사](01-introducing-typescript/history-of-typescript.md) 13 | 14 | ## 02 ECMAScript 15 | 16 | * [2.0 ECMAScript](02-ecmascript/intro.md) 17 | * [2.1 블록 수준 스코프](02-ecmascript/block-level-scope/README.md) 18 | * [2.1.1 let을 이용한 선언](02-ecmascript/block-level-scope/declaration-using-let.md) 19 | * [2.2.2 const를 이용한 선언](02-ecmascript/block-level-scope/declaration-using-const.md) 20 | * [2.2.3 스코프 베스트 프랙티스](02-ecmascript/block-level-scope/scope-best-practice.md) 21 | * [2.2 객체와 배열](02-ecmascript/object-and-array/README.md) 22 | * [2.2.1 비구조화 할당](02-ecmascript/object-and-array/destructuring-assignment.md) 23 | * [2.2.2 나머지 연산자와 전개 연산자](02-ecmascript/object-and-array/rest-and-spread-operator.md) 24 | * [2.2.3 객체 리터럴 변경사항](02-ecmascript/object-and-array/object-literal-diff.md) 25 | * [2.3 함수](02-ecmascript/function/README.md) 26 | * [2.3.1 기본 매개변수](02-ecmascript/function/default-parameter.md) 27 | * [2.3.2 화살표 함수](02-ecmascript/function/arrow-functions.md) 28 | * [2.4 템플릿 리터럴](02-ecmascript/template-literal/README.md) 29 | * [2.4.1 멀티라인 문자열](02-ecmascript/template-literal/multiline-string.md) 30 | * [2.4.2 문자열 치환](02-ecmascript/template-literal/string-substitution.md) 31 | * [2.5 원소 순회](02-ecmascript/element-enumeration/README.md) 32 | * [2.5.1 forEach 메소드](02-ecmascript/element-enumeration/foreach.md) 33 | * [2.5.2 for-of 문법](02-ecmascript/element-enumeration/for-of.md) 34 | * [2.5.3 이터레이터 프로토콜](02-ecmascript/element-enumeration/iterator-protocol.md) 35 | * [2.5.4 이터러블 프로토콜](02-ecmascript/element-enumeration/iterable-protocol.md) 36 | * [2.6 비동기 처리](02-ecmascript/handling-asynchronous/README.md) 37 | * [2.6.1 프로미스](02-ecmascript/handling-asynchronous/promises.md) 38 | * [2.6.2 Async / Await](02-ecmascript/handling-asynchronous/async-await.md) 39 | * [2.7 맺으며](02-ecmascript/outro.md) 40 | 41 | ## 03 타입스크립트 기초 문법 42 | 43 | * [3.0 타입스크립트 기초 문법](03-basic-grammar/intro.md) 44 | * [3.1 기본 타입](03-basic-grammar/primitive-types.md) 45 | * [3.2 배열과 튜플](03-basic-grammar/array-and-tuple.md) 46 | * [3.3 객체](03-basic-grammar/object.md) 47 | * [3.4 타입 별칭](03-basic-grammar/type-alias.md) 48 | * [3.5 함수](03-basic-grammar/function.md) 49 | * [3.6 제너릭](03-basic-grammar/generics.md) 50 | * [3.7 유니온 타입](03-basic-grammar/union-type.md) 51 | * [3.8 인터섹션 타입](03-basic-grammar/intersection-type.md) 52 | * [3.9 열거형](03-basic-grammar/enums.md) 53 | 54 | ## 04 인터페이스와 클래스 55 | 56 | * [4.0 들어가며](04-interface-and-class/intro.md) 57 | * [4.1 인터페이스 기초](04-interface-and-class/interface-basics.md) 58 | * [4.2 색인 가능 타입](04-interface-and-class/indexable-types.md) 59 | * [4.3 인터페이스 확장](04-interface-and-class/extending-interfaces.md) 60 | * [4.4 클래스](04-interface-and-class/classes.md) 61 | * [4.5 클래스 확장](04-interface-and-class/extending-classes.md) 62 | * [4.6 클래스 심화](04-interface-and-class/class-advanced/README.md) 63 | * [4.6.1 스태틱 멤버](04-interface-and-class/class-advanced/static-members.md) 64 | * [4.6.2 접근 제어자](04-interface-and-class/class-advanced/access-specifiers.md) 65 | * [4.6.3 접근자](04-interface-and-class/class-advanced/accessors.md) 66 | * [4.6.4 추상 클래스](04-interface-and-class/class-advanced/abstract-classes.md) 67 | * [4.7 인터페이스와 클래스의 관계](04-interface-and-class/connecting-interface-and-class.md) 68 | * [4.8 맺으며](04-interface-and-class/outro.md) 69 | 70 | ## 05 타입의 호환성 71 | 72 | * [5.0 들어가며](05-type-compatibility/intro.md) 73 | * [5.1 기본 타입의 호환성](05-type-compatibility/primitive-types.md) 74 | * [5.2 객체 타입의 호환성](05-type-compatibility/objects.md) 75 | * [5.3 함수 타입의 호환성](05-type-compatibility/functions.md) 76 | * [5.4 클래스의 호환성](05-type-compatibility/classes.md) 77 | * [5.5 제너릭의 호환성](05-type-compatibility/generics.md) 78 | * [5.6 열거형의 호환성](05-type-compatibility/enums.md) 79 | * [5.7 맺으며](05-type-compatibility/outro.md) 80 | 81 | ## 06 타입 시스템 심화 82 | 83 | * [6.0 들어가며](06-type-system-deepdive/intro.md) 84 | * [6.1 타입 좁히기](06-type-system-deepdive/type-narrowing.md) 85 | * [6.2 타입 추론](06-type-system-deepdive/type-inference.md) 86 | * [6.3 타입 단언](06-type-system-deepdive/type-assertion.md) 87 | * [6.4 집합으로서의 타입](06-type-system-deepdive/types-as-sets.md) 88 | * [6.5 서로소 유니온 타입](06-type-system-deepdive/disjoint-union-type.md) 89 | * [6.6 맺으며](06-type-system-deepdive/outro.md) 90 | 91 | ## 07 고급 타입 92 | 93 | * [들어가며 \(7월 2일 공개\)](07-advanced-types/intro.md) 94 | 95 | ## 08 모듈과 네임스페이스 96 | 97 | * [들어가며 \(7월 16일 공개\)](08-modules-and-namespaces/intro.md) 98 | 99 | ## 09 실제 프로젝트에서 사용하기 100 | 101 | * [들어가며 \(7월 30일 공개\)](09-real-world-projects/intro.md) 102 | 103 | ## 10 유용한 라이브러리 소개 104 | 105 | * [들어가며 \(8월 13일 공개\)](10-libraries/intro.md) 106 | * [맺으며](10-libraries/outro.md) 107 | 108 | ## 부록 II : 자바스크립트 언어 생태계 109 | 110 | * [ECMAScript 언어 표준과 TC39](appendix-ii-js-ecosystem/ecmascript-tc39.md) 111 | * [TC39 프로세스](appendix-ii-js-ecosystem/tc39-process.md) 112 | * [실제 예시 - Array.prototype.includes](appendix-ii-js-ecosystem/example-array.prototype.includes.md) 113 | * [타입스크립트와 ECMAScript](appendix-ii-js-ecosystem/typescript-and-ecmascript.md) 114 | 115 | -------------------------------------------------------------------------------- /appendix-ii-js-ecosystem/ecmascript-tc39.md: -------------------------------------------------------------------------------- 1 | # ECMAScript 언어 표준과 TC39 2 | 3 | 자바스크립트의 역사를 극단적으로 간략히 서술해보자면 다음과 같다. 자바스크립트는 1990년대 중반, \(지금은 사라진\) Netscape Navigator 브라우저에 동적인 부분을 더하기 위한 목적으로 Brianden Eich에 의해 개발되었다. 인기를 끔에 따라 Microsoft는 \(저작권 이슈를 피하기 위해\) JScript라 이름 붙인 자바스크립트의 방언을 개발했다. JScript는 1996년 8월, IE 3.0에 포함되어 배포되었다. 4 | 5 | 이후 Netscape는 언어의 표준화 및 명세화를 위해 자바스크립트를 [Ecma 인터내셔널\(Ecma International\)](https://www.ecma-international.org/)에 제출했다. Netscape와 Microsoft 등 이해당사자 간의 이름을 둘러싼 합의 끝에 ECMAScript라는 이름과 [ECMA-262](https://tc39.github.io/ecma262/), “ECMAScript 언어 표준\(ECMAScript Language Specification\)”이 탄생했다. 오늘날 우리가 말하는 자바스크립트는 ECMA-262을 만족하는 구현체를 가리킨다. Ecma 인터내셔널의 여러 기술 의원회\(Technial Comittee, 이하 TC\) 중 TC39 라는 의원회가 이 명세를 관리한다. 6 | 7 | ### **ECMAScript 언어 표준** 8 | 9 | 1997년 6월에 발표된 첫 번째 판\(edition\)을 시작으로 2017년 12월 현재까지 ECMA-262의 총 8개 판이 존재한다. 각 판은 판 번호 또는 발표된 연도를 따라 이름붙여진다. 예를 들어, **2015**년에 발표된 **6**판의 경우 **ECMAScript2015** 또는 **ECMAScript6** 이라 \(또는 ECMAScript를 ES로 줄여 ES2015 또는 ES6이라\) 불리운다. 10 | 11 | 그 중 약 9년만의 메이저 버전 업이 될 예정이었던 ES4는 굉장히 많고 복잡한 변경사항을 포함하고 있었다. 그 복잡도와 하위 호환성을 파괴하는 변경사항은 많은 논쟁을 야기했고, 결국 컨센서스를 불러일으키는데 실패한 ES4는 **기각되었다**. 비록 4판은 받아들여지지 않았지만, 당시 제안되었던 기능 중 다수는 이후 점진적으로 표준에 추가되었다. 12 | 13 | ### **Technical Comittee 39** 14 | 15 | Ecma 인터내셔널의 여러 기술 의원회\(Technial Comittee, 이하 TC\) 중 ECMA-262 명세의 관리는 TC39 위원회가 맡고 있다. TC39의 구성원 목록은 Mozilla, Google, Apple, Microsoft 등의 메이저 브라우저 벤더를 비롯해 Facebook, Twitter 등의 다양한 단체로 이루어져 있다. 대부분의 구성원이 표준을 올바르게 구현해야 할 책임을 갖는, 언어 표준의 변화에 직접적으로 영향을 받는 단체다. 엄밀히는 기업/단체가 구성원이지만, ‘TC39 구성원’ 이라는 용어가 해당 기업/단체가 보낸 대리인을 지칭하는 경우도 많다. 16 | 17 | TC39는 정기적으로 만나 회의를 진행하는데, 회의록은 모두 [웹상에 공개된다](https://github.com/tc39/tc39-notes). **TC39에서 내리는 결정이 단순 다수결이 아닌 컨센서스 체제로 이루어진다**는 점은 주목할 만 하다. 대부분이 동의하는 안이라도 한 명이 강하게 거부권을 행사한다면 섣불리 결정을 내리기보단 합의를 이루어내기 위해 더 시간을 갖고 논의를 해 보는 식이다. TC39의 모든 의사 결정이 구성원 대다수의 이해관계와 직결되어 있다는 점을 감안했을 때, 이러한 정책은 이례적이다. 18 | 19 | -------------------------------------------------------------------------------- /appendix-ii-js-ecosystem/example-array.prototype.includes.md: -------------------------------------------------------------------------------- 1 | # 실제 예시 - Array.prototype.includes 2 | 3 | TC39 프로세스가 구체적으로 어떻게 진행되는지 좀 더 구체적인 감각을 갖기 위해, 실제 예시를 들어 살펴보자. 오늘의 초대 손님은 `Array.prototype.includes` 메소드다. 이 메소드는 이름이 암시하듯 배열에 어떤 원소가 들어있는지 검사한다. 4 | 5 | 매우 흔한 요구사항인 것을 감안하면 놀랍게도 이 메소드는 2016년이 되어서야 표준 – ECMAScript 2016 – 에 추가되었다. 이 프러포절의 챔피언은 Google의 Domenic Denicola이 맡았고, [2014년 4월 9일 회의](http://tc39.github.io/tc39-notes/2014-04_apr-9.html#45-arrayprototypecontains)에서 처음 언급되었다. 6 | 7 | ## **0단계: 허수아비 \(stage 0: strawman\)** 8 | 9 | 앞서 언급된 2014년 4월 9일 회의록에서 Rick Waldron은 ES6에 추가될 예정인 String.prototype.contains 와 비슷한 메소드 Array.prototype.contains 를 배열 프로토타입에 추가하는 것이 어떻겠냐는 제안을 제시한다. 메일링 리스트에서 논의되던 이 프러포절은 이로서 0단계 프러포절이 된다. 10 | 11 | ## **1단계: 제안 \(stage 1: proposal\)** 12 | 13 | 이 프러포절은 [2014년 7월 31일 회의](http://tc39.github.io/tc39-notes/2014-07_jul-31.html)에서 다음으로 등장한다. 동작에 대한 간단한 논의 \(인자로 받는 게 배열의 원소여야 하는지 서브 배열이어야 하는지\)와 네이밍에 대한 논의 후에 1단계 승격이 결정된다. 이후 논의에서 상대적으로 간단한 제안인 만큼 프로세스를 TC39 회의 밖에서 비동기적으로 진행하고 싶다는 의견이 나오는데, Allen Wirfs-Brock와 Mark Miller에 의해 기각된다. 14 | 15 | [1단계 프러포절로 승격된 시점의 저장소](https://github.com/tc39/Array.prototype.includes/tree/4fafe65eaa57e6da65ecbe48aa3978b199087645)를 보면, 이 프러포절이 필요한 이유와 API, 그리고 많이들 궁금해 할 법한 질문과 사용 예시 모두가 `README.md` 에 정리되어 있으며, 데모 구현 또한 저장소에 포함되어 있다. 승격에 필요한 요건의 체크리스트는 [6번 이슈](https://github.com/tc39/Array.prototype.includes/issues/6)에서 볼 수 있다. 16 | 17 | {% hint style="info" %} 18 | 이후 이 제안은 [2014년 11월 18일 회의](http://tc39.github.io/tc39-notes/2014-11_nov-18.html#44-arrayprototypecontains-breaks-mootools)의 안건으로 올라온다. 이 안건에서는 `Array.prototype.contains` 가 표준에 포함되면 이를 구현한 브라우저에서 [Mootools](https://mootools.net/)라는 라이브러리를 사용하고 있는 기존 웹사이트가 깨질 것이라는 문제가 제기된다. 19 | 20 | 당일 챔피언이 도착하고 [이어진 논의](http://tc39.github.io/tc39-notes/2014-11_nov-18.html#51--44-arrayprototypecontains-and-stringprototypecontains)에서 결국 `String.prototype.contains` 와 `Array.prototype.contains` 의 메소드 명을 `includes` 로 변경하는 결정이 난다. 회의 전에 이루어진 [관련 대화 쓰레드](https://esdiscuss.org/topic/having-a-non-enumerable-array-prototype-contains-may-not-be-web-compatible)를 읽어보면 설령 유용한 새 기능을 포기하더라도 \(그리고 그것이 TC39나 브라우저의 잘못으로 인한 상황이 아니더라도\) 이미 존재하는 웹을 깨트리지 않겠다는 참여자들의 의지를 엿볼 수 있어 흥미롭다. 21 | {% endhint %} 22 | 23 | ## **2단계: 초고 \(stage 2: draft\)** 24 | 25 | 이름을 바꾸기로 결정한 논의의 이틀 후인 [2014년 11월 20일](http://tc39.github.io/tc39-notes/2014-11_nov-20.html#55-arrayprototypeincludes-proposal-to-move-to-stage-2), 이 프러포절을 2단계로 승격시키는 안건이 올라오고, 통과된다. 1단계와 마찬가지로 [2단계로 승격된 시점의 저장소](https://github.com/tc39/Array.prototype.includes/tree/6e3b78c927aeda20b9d40e81303f9d44596cd904)와 [체크리스트를 담은 이슈](https://github.com/tc39/Array.prototype.includes/issues/10)를 깃허브 저장소에서 확인할 수 있다. 26 | 27 | 저장소를 확인해보면 스펙 초안은 사실 1단계가 되는 시점에서 이미 준비되어 있다. 2단계의 또다른 요구사항인 \(예를 들어 V8에서 `--harmony-array-includes` 등의 플래그에 의해 제어되는\) 실험적인 구현이 완성된 시점에서 안건에 올라온 것으로 보인다. 앞으로 패치가 좀 더 있을 예정이므로 3단계로 올릴 수는 없다는 내용이 언급된다. 28 | 29 | ## **3단계: 후보 \(stage 3: candidate\)** 30 | 31 | 2단계로 승격된 지 여덟 달이 지난 [2015년 7월 28일](http://tc39.github.io/tc39-notes/2015-07_july-28.html#6i-advance-arrayprototypeincludes-to-stage-3), 이 제안은 3단계로 승격된다. \([저장소](https://github.com/tc39/Array.prototype.includes/tree/5c3538772881d6efefb6c328d2c6ac0f8fe5170a), [이슈](https://github.com/tc39/Array.prototype.includes/issues/12)\) 32 | 33 | 각종 엣지 케이스들에 대한 디자인 문제를 해결한 후 명세 문서가 완전히 정리된 상태다. 또한 지명된 리뷰어와 ECMAScript 편집자의 승인도 받은 것을 볼 수 있다. 실제로 명세를 담고 있는 [spec.html 파일의 히스토리](https://github.com/tc39/Array.prototype.includes/commits/master/spec.html)를 보면 3단계로 승격된 이후에는 명세를 적은 마크업의 문법 버전 변경을 제외하면 변경 사항이 거의 전무하다. 34 | 35 | ## **4단계: 완료됨 \(stage 4: finished\)** 36 | 37 | [2015년 11월 17일 회의](http://tc39.github.io/tc39-notes/2015-11_nov-17.html#arrayprototypeincludes)에서 Array.prototype.includes 메소드 프러포절은 최종적으로 4단계로 승격된다. 이 프러포절은 2016년 6월에 발표된 ECMAScript 2016 표준에 거듭제곱 연산자\(\*\*\)와 함께 언어의 표준 기능으로 포함되어 배포된다. 38 | 39 | -------------------------------------------------------------------------------- /appendix-ii-js-ecosystem/tc39-process.md: -------------------------------------------------------------------------------- 1 | # TC39 프로세스 2 | 3 | {% hint style="info" %} 4 | 이하 **1단계: 제안** 단계와 구분하기 위해 앞으로 0단계에서 4단계에 이르기까지, 아직 표준에 편입되지도, 명시적으로 거절되지도 않은 제안을 통틀어 **프러포절**이라 칭한다. 5 | {% endhint %} 6 | 7 | ECMA-262 표준에 새로운 명세를 추가하기 위한 과정은 공식적으로 명문화되어 있다. [TC39 프로세스](https://tc39.github.io/process-document/)라고도 불리는 이 과정은 0단계부터 4단계까지 총 5개의 단계로 나누어져 있으며, 각 단계로의 승급을 위한 명시적인 조건들이 존재한다. 해당 조건을 만족한 이후 위원회의 동의를 얻은 프러포절만이 다음 단계로 넘어간다. 물론 그 과정에서 최종적으로 표준에 편입되지 않기로 결정되어 버려지는 제안들도 존재한다. 8 | 9 | 모든 프러포절은 [TC39의 깃허브 단체 계정](https://github.com/tc39)의 [proposals 저장소](https://github.com/tc39/proposals)에 등재된다. 2017.12.19 기준, 0단계의 경우 stage-0-proposals.md 파일에서, 4단계에 이르러 표준에 편입된 프러포절은 finished-proposals.md 파일에서 확인할 수 있다. 활성화된 프러포절\(active proposal\)이라 불리는 1-3단계 프러포절의 경우 README.md에 등재되어 있다. 대부분의 프러포절은 별도의 저장소를 갖고 있고, 그 곳에선 해당 프러포절을 어떻게 발전시킬지, 어떤 어려움이 예상되는지 등의 다양한 논의가 이루어진다. 10 | 11 | ## **0단계: 허수아비 \(stage 0: strawman\)** 12 | 13 | TC39 프로세스에 프러포절을 내놓기 위해선 기본적으로 별다른 제약이 없다. 라이센스 관련 조항에 동의하고 TC39의 컨트리뷰터로 등록한 누구라도 프러포절을 내놓을 수 있다. 해당 프러포절 중 어떤 경로로든 TC39의 회의의 안건으로 상정되고 앞서 언급된 0단계 문서에 등재되면 0단계 제안이 된다. 14 | 15 | ## **1단계: 제안 \(stage 1: proposal\)** 16 | 17 | 1단계에 들어오기 위해선 가장 먼저 **챔피언**\(champion\)을 구해야 한다. 챔피언이란 해당 제안을 책임지고 다음 단계로 끌고 나아갈 TC39 구성원을 일컫는다. 또한, 1단계 프러포절은 **풀고자 하는 문제**와 **하이 레벨 API** 및 **잠재적 장애물**을 제시해야 한다. 구현상으로는 폴리필, 데모 등을 필요로 한다. 18 | 19 | 위에서 살펴보았듯 0단계는 기본적으로 TC39 회의에서 안건으로 논의되어야 한다는 것 이외에는 제약이 전무하다시피 하다. 어떤 프러포절이 **1단계에 진입한다는 것은 본격적으로 위원회 수준에서 시간과 노력을 투자해 해당 프러포절에 관해 논의할 의사를 표명한 것**으로 해석할 수 있다. 1단계 제안은 추후 단계를 거치며 많은 부분 변화할 수 있다. 20 | 21 | ## **2단계: 초고 \(stage 2: draft\)** 22 | 23 | **2단계에 올라오기 위해선 ECMAScript 표준의 형식 언어\(formal description\)로 작성 된 형식적인 서술\(formal description\) 초안이 필요하다**. 이 초안은 만약 프러포절이 실제로 표준에 편입 될 경우 사용할 명세의 초기 버전이다. 2단계까지는 앞으로 해야 할 일 등을 TODO 마크 등으로 표시해 놓는 등의 일부 불완전한 명세가 허용된다. 또한 실험적인\(플래그에 의해 켜지고 꺼지는\) 구현이 요구된다. 24 | 25 | 어떤 프러포절이 2단계에 올라왔다는 것은 의원회가 이 프러포절을 발전시켜 궁극적으로는 표준에 포함시키고자 하는 의지가 있다고 해석할 수 있다. 2단계 이후로는 상대적으로 적은 변경만이 허용된다. 26 | 27 | ## **3단계: 후보 \(stage 3: candidate\)** 28 | 29 | 3단계 프러포절은 대부분 완성에 가깝고, 구현 주체나 사용자들로부터 피드백을 좀 더 받아보는 일만이 남은 상태다. 3단계에 들어오기 위해서는 2단계의 초안과는 다르게 빈칸 없이 문법, 동작, 그리고 API까지 모든 부분이 기술되어 있도록 마무리 된 명세가 필요하다. 30 | 31 | **3단계까지 올라온 프러포절은 이후 구현상 심각한 문제가 발견되지 않는 이상 변경이 허용되지 않는다**. 32 | 33 | 이 시점에서는 실제로 ECMA-262 표준에 편입시키고자 하는 해당 표준의 명세가 거의 마무리 된 상태여야 한다. 34 | 35 | ## **4단계: 완료됨 \(stage 4: finished\)** 36 | 37 | 마지막 4단계는 모든 단계를 거치고 마침내 제안이 수락되고 다음 표준에 포함되어 발표되기만을 기다리는 단계이다. 3단계의 프러포절이 ECMA-262의 단위 테스트 슈트인 Test262에 관련 테스트가 작성되고, 최소 2개 이상의 구현이 제공되는 등의 까다로운 추가 조건을 모두 만족하면 마침내 4단계로 올라올 수 있다. 38 | 39 | **4단계까지 올라온 프러포절은 별다른 이변이 없는 이상 다가오는 새 표준에 포함되어 발표된다**. 2015년을 기점으로 매년 6월 새로운 ECMAScript 표준이 발표되는데, 당해 3월 전까지 4단계를 달성하고 3월 회의에서 최종 승인된 제안들이 새 표준에 포함된다. 40 | 41 | 각 과정에 대한 보다 자세한 설명은 공식 문서에 기재되어 있다. 또한, 언제든지 앞서 언급한 proposals 저장소에서 현재 진행중인 모든 ECMAScript 표준에 관한 프러포절과 그 상태를 확인할 수 있다. 42 | 43 | -------------------------------------------------------------------------------- /appendix-ii-js-ecosystem/typescript-and-ecmascript.md: -------------------------------------------------------------------------------- 1 | # 타입스크립트와 ECMAScript 2 | 3 | 이렇게 ECMA-262 표준과 TC39, 그리고 TC39 프로세스에 대해 다루어보았다. 개발자와 사용자 커뮤니티, 그리고 TC39 위원회를 비롯한 수많은 이들의 노력 덕에 ECMAScript는 하루가 달리 빠르게 발전하고 있다. 하지만 브라우저 서포트를 비롯한 다양한 문제로, 최신 기능들을 실제로 사용하기 위해선 babel 등의 트랜스파일링 과정을 거쳐야 하는 경우가 많다. 4 | 5 | 타입스크립트는 ES 표준으로 지정된 대부분의 최신 기능을 지원함에 따라, 타입스크립트 사용자는 추가적인 단계를 하나 생략할 수 있다. 뿐만 아니라 타입스크립트는 async iterator 를 비롯해 아직 표준에 편입되지 않았지만 많은 이들이 유용하다고 판단하는 프러포절까지 발빠르게 채용하곤 한다. 덕분에 타입스크립트 사용자는 외부 도구 없이도 최신 명세를 이용한 생산성 높은 개발이 가능하다. 6 | 7 | 타입스크립트는 자바스크립트와 다른 언어지만, 자바스크립트의 상위 집합을 표방하고 있다. 자바스크립트 생태계가 어떻게 발전하는지 파악하고 어느 방향으로 나아가는지 주목하는 일은 타입스크립트의 다음 발자취를 예측하는 데에도 분명 도움이 될 것이다. 8 | 9 | -------------------------------------------------------------------------------- /intro.md: -------------------------------------------------------------------------------- 1 | # 들어가며 2 | 3 | {% hint style="info" %} 4 | 이 페이지를 시작으로, 본문에서는 평어체를 사용합니다. 5 | {% endhint %} 6 | 7 | 2012년 말 처음으로 릴리스 된 이후로부터, 타입스크립트는 하루가 다르게 더 큰 인기를 얻으며 또 꾸준히 발전해오고 있다. 그리고 그 성장세는 날이 갈수록 가팔라지고 있다. 8 | 9 | 2017년 말 진행된 자바스크립트 관련 서베이 “[The State of JavaScript 2017](https://stateofjs.com/)” 설문에는 총 28,000여 명이 참여했다. 그 중 단 350명만이 ‘타입스크립트에 대해 들어보지 못 했다’고 답했다. 타입스크립트를 사용해 본 경험이 있는 9,345명 중 약 85%인 7,968명은 ‘다시 사용할 의사가 있다’고 답했으며, 타입스크립트라는 이름을 들어만 본 14,009명 중 약 63%인 8,796명이 ‘배우고 싶다’고 답했다. 10 | 11 | 이 짤막하지만 인상적인 결과에서 보이듯, 타입스크립트는 사용 해 본 사람과 그렇지 않은 사람 모두에게 인기 있는 기술이다. 특히 사용 경험이 있는 사람 중 대다수가 재사용할 의사가 있다 밝힌 점은 무척 고무적이다. 타입스크립트의 세계에 발을 디디는 데에 이 책이 유용한 안내서가 되길 바란다. 12 | 13 | ## 대상 독자 14 | 15 | 이 책은 타입스크립트 입문서이지만, 프로그래밍 입문서는 아니다. 16 | 17 | 프로그래밍 경험이 전혀 없는 사람은 이 책의 대상 독자가 아니다. 또한, 자바스크립트를 이용해 프로그램을 짜 본 적이 전혀 없는 사람 역시 이 책의 대상 독자가 아니다. ECMAScript 언어 명세를 속속들이 알 필요까진 없지만, 만약 자바스크립트 기반 프로젝트를 진행해 본 적이 전혀 없다면 먼저 자바스크립트와 익숙해 진 후에 이 책으로 돌아오기를 추천한다. 18 | 19 | 자바스크립트를 사용한 개발을 하면서 프로젝트가 크고 복잡해짐에 따라 ‘어떻게 하면 더 안정적이고 생산적인 개발이 가능할지’에 대한 고민이 생긴 독자에게 도움이 될 것을 기대하고 썼다. 타입스크립트, 또는 페이스북의 [Flow](https://flow.org/) 등에 대해 듣고 흥미를 갖게 되었다면 더할 나위 없겠지만, 자바스크립트에 정적 타이핑을 도입하는 기술들에 대해 전혀 들어본 바가 없어도 무방하다. 20 | 21 | ## 책의 목표 22 | 23 | 이 책은 실용적인 타입스크립트 입문서를 지향한다. 24 | 25 | 꼭 책의 시작부터 끝까지 순서대로 읽어 내려갈 필요는 없다. 독자의 수준이나 상황에 따라 뛰어넘어도 무방한 부분에 대해서는 따로 명시하는 것을 목표로 한다. 널리 쓰이는 용어는 가급적 그대로 사용하는 것을 기조로 하며, 지나치게 세세한 부분까지 설명하느라 필요 이상으로 장황하게 늘어놓는 일을 지양하려 노력했다. 26 | 27 | 사족으로 개인적인 바람을 덧붙이자면, 이 책이 기술 서적 중 수명이 긴 편에 속하는 책으로 남기를 바라는 마음으로 썼다. 빠르게 발전하는 기술을 다루는 글은 대부분 기술의 변화와 함께 빠른 속도로 쓸모 없어진다. 그럼에도 다른 맥락, 다른 장소에서도 유용한 지혜를 담은 책들은 독자들의 기억 속에서 오래도록 버티곤 한다. 28 | 29 | 누군가에게 이 책이 그런 존재로 남는다면 더할 나위 없겠다. 30 | 31 | ## 책의 구성 32 | 33 | 이 책을 완독했을 때, 독자는 다음과 같은 지식을 얻게 될 것이다. 34 | 35 | * 최신 자바스크립트 기능 36 | * 자바스크립트 언어 표준의 발전 과정 37 | * 타입스크립트로 실제 프로젝트를 구성하고 배포하는 방법 38 | * 타입스크립트의 기본 개념 및 문법에 대한 탄탄한 이해 39 | * 타입스크립트의 타입 시스템에 대한 깊이 있는 이해 40 | * 타입스크립트 생태계에 대한 이해 41 | 42 | 앞으로 펼쳐질 책의 구성은 다음과 같다. 책의 진행을 따라가며 자연스레 점차 깊고 넓게 들여다보는 식으로 짜여 있으니, 당장 이 소개가 이해가 가지 않아도 당황하거나 겁먹을 이유는 없다. 43 | 44 | 먼저 1장부터 4장까지는 타입스크립트의 기초에 대해 소개한다. 45 | 46 | * 1장에선 타입스크립트가 무엇인지 소개한다. 타입스크립트란 무엇인지 알아보고, 그 역사와 더불어 왜 타입스크립트를 배워야 하는지에 대한 근거를 제시한다. 47 | * 2장에선 타입스크립트의 기반이 되는 언어, 자바스크립트의 최신 명세에 추가된 여러 유용한 기능들을 살펴본다. 타입스크립트를 쌓아올린 기반이 되는 자바스크립트를 더 잘 이해함으로서 더 나은 타입스크립트 프로그래머가 될 수 있다. 48 | * 3장에선 타입스크립트의 기초적인 문법과 타입 시스템에 대해 소개한다. 3장을 끝낸 시점에서 독자는 간단한 타입스크립트 코드를 읽고 그 의미를 이해할 수 있다. 49 | * 4장에선 타입스크립트에서 가장 중요하고 자주 쓰이는 두 추상화 수단, 인터페이스와 클래스에 대해 다룬다. 이 둘은 실생활에서 자주 맞닥뜨리게 될 복잡한 자료 구조의 형태를 기술하기 위한 든든한 동반자가 될 것이다. 50 | 51 | 5장부터 7장까지는 타입스크립트의 타입 시스템, 그리고 보다 복잡한 사용예에 유용한 고급 타입에 대해 다룬다. 52 | 53 | * 5장에선 타입스크립트가 여러 다른 타입 간의 타입 호환성을 어떻게 판단하는지에 대해 살펴본다. 54 | * 6장에선 타입스크립트의 타입 시스템이 내부적으로 어떻게 동작하는지에 대해 좀 더 깊숙히 들여다본다. 타입 좁히기, 타입 추론 등의 주제를 다룬다. 55 | * 7장에선 보다 복잡한 문제를 해결할 때에 알아두면 도움이 될 각종 고급 타입에 대해 다룬다. 데코레이터, 매핑된 타입, 조건부 타입 등이 등장한다. 56 | 57 | 8장에서 10장 까지는 실제로 타입스크립트를 프로젝트에서 사용하기 위해 알아야 할 모듈, 프로젝트 설정법과 유용한 라이브러리에 대해 다룬다. 58 | 59 | * 8장에선 타입스크립트의 모듈, 그리고 네임스페이스에 대해 다룬다. 기본 개념과 함께 모듈 리졸빙이 어떻게 일어나는지, 앰비언트 타입의 의의 등에 대해 이야기한다. 60 | * 9장에선 실제 프로젝트에 타입스크립트를 도입할 때 알아야 할 프로젝트 초기 설정 및 작업 흐름에 대해 다룬다. 61 | * 10장에선 마지막으로 타입스크립트와 함께 사용하기 유용한 라이브러리 및 도구를 소개한다. 62 | 63 | 부록은 아래와 같이 이루어져 있다. 64 | 65 | * <부록 I : 추가적으로 공부할 자료>에선 이 책의 내용을 기반으로, 공부를 더 이어나갈 때에 도움이 될만한 추천 자료를 소개한다. 66 | * <부록 II에선 자바스크립트 언어 표준>에서 ECMA-262와 그 표준이 발전하는 과정인 TC39 프로세스에 대해 다룬다. 67 | * <부록 III : Webpack – 자바스크립트 모듈 번들러>에서는 최근 가장 널리 사용되는 자바스크립트 모듈 번들러, 웹팩에 대해 다룬다. 68 | * <부록 IV : 타입스크립트 모듈 작성하기>에선 타입스크립트 모듈 사용하는 입장이 아니라 만들어 배포하는 입장일 때 필요한 작업들에 대해 간단히 소개한다. 69 | 70 | ## 사후 지원 71 | 72 | 오탈자 제보, 책의 내용에 관련된 질문 등은 [깃헙 저장소](https://github.com/heejongahn/ts-for-jsdev)의 이슈를 생성하시거나, [heejongahn@gmail.com](mailto:heejongahn@gmail.com)으로 메일을 보내주실 것을 부탁드립니다. 73 | 74 | --------------------------------------------------------------------------------