Repository: gibbok/typescript-book
Branch: main
Commit: 16d6d2bc5306
Files: 374
Total size: 1.6 MB
Directory structure:
gitextract_xft80fat/
├── .github/
│ └── workflows/
│ ├── compile.sh
│ ├── lint.sh
│ └── validate-content.yml
├── .gitignore
├── .vscode/
│ └── settings.json
├── FUNDING.yml
├── LICENSE.MD
├── README-it_IT.md
├── README-pt_BR.md
├── README-sv_SE.md
├── README-zh_CN.md
├── README.md
├── downloads/
│ ├── typescript-book-it_IT.epub
│ ├── typescript-book-pt_BR.epub
│ ├── typescript-book-sv_SE.epub
│ ├── typescript-book-zh_CN.epub
│ └── typescript-book.epub
├── tools/
│ ├── .markdownlint.json
│ ├── .nvmrc
│ ├── .prettierrc
│ ├── .vscode/
│ │ ├── launch.json
│ │ └── settings.json
│ ├── Makefile
│ ├── README.md
│ ├── check_toc.py
│ ├── compile.ts
│ ├── config.ts
│ ├── format.ts
│ ├── i18n.ts
│ ├── lint.ts
│ ├── make-books.sh
│ ├── make-website-content.py
│ ├── package.json
│ ├── remove_skip_empty_lines.sh
│ ├── test-md/
│ │ ├── README-zh_CN.md
│ │ └── README.md
│ ├── tsconfig.json
│ ├── utilities-add-translation/
│ │ ├── README.md
│ │ ├── concatenate_language_files.py
│ │ ├── create_language_files.py
│ │ ├── split_markdown.py
│ │ ├── verify_concatenation.py
│ │ └── verify_concatenation_detailed.py
│ ├── utils.ts
│ └── verify_codeblocks.py
└── website/
├── .gitignore
├── .nvmrc
├── .prettierrc
├── .vscode/
│ ├── extensions.json
│ └── launch.json
├── README.md
├── astro.config.mjs
├── package.json
├── public/
│ ├── cookie-banner/
│ │ ├── silktide-consent-manager.css
│ │ └── silktide-consent-manager.js
│ ├── cookie_policy.html
│ └── google184c8848cff38265.html
├── src/
│ ├── components/
│ │ ├── Hero.astro
│ │ └── RightSidebarBanner.astro
│ ├── content/
│ │ ├── config.ts
│ │ ├── docs/
│ │ │ ├── book/
│ │ │ │ ├── about-the-author.md
│ │ │ │ ├── any-type.md
│ │ │ │ ├── assignments.md
│ │ │ │ ├── built-in-type-primitives.md
│ │ │ │ ├── class.md
│ │ │ │ ├── common-built-in-js-objects.md
│ │ │ │ ├── conditional-types.md
│ │ │ │ ├── control-flow-analysis.md
│ │ │ │ ├── differences-between-type-and-interface.md
│ │ │ │ ├── discriminated-unions.md
│ │ │ │ ├── distributive-conditional-types.md
│ │ │ │ ├── downloads-and-website.md
│ │ │ │ ├── enums.md
│ │ │ │ ├── erased-structural-types.md
│ │ │ │ ├── exhaustiveness-checking.md
│ │ │ │ ├── exploring-the-type-system.md
│ │ │ │ ├── extending-types.md
│ │ │ │ ├── fixed-length-tuple.md
│ │ │ │ ├── generics.md
│ │ │ │ ├── getting-started-with-typescript.md
│ │ │ │ ├── index-signatures.md
│ │ │ │ ├── infer-type-inference-in-conditional-types.md
│ │ │ │ ├── interface-and-type.md
│ │ │ │ ├── intersection-types.md
│ │ │ │ ├── introduction.md
│ │ │ │ ├── literal-inference.md
│ │ │ │ ├── literal-types.md
│ │ │ │ ├── mapped-type-modifiers.md
│ │ │ │ ├── mapped-types.md
│ │ │ │ ├── merging-and-extension.md
│ │ │ │ ├── named-tuple-type-labeled.md
│ │ │ │ ├── namespacing.md
│ │ │ │ ├── narrowing.md
│ │ │ │ ├── never-type.md
│ │ │ │ ├── object-types.md
│ │ │ │ ├── optional-properties.md
│ │ │ │ ├── others.md
│ │ │ │ ├── overloads.md
│ │ │ │ ├── predefined-conditional-types.md
│ │ │ │ ├── primitive-types.md
│ │ │ │ ├── readonly-properties.md
│ │ │ │ ├── strictnullchecks.md
│ │ │ │ ├── symbols.md
│ │ │ │ ├── table-of-contents.md
│ │ │ │ ├── template-union-types.md
│ │ │ │ ├── the-concise-typescript-book.md
│ │ │ │ ├── the-never-type.md
│ │ │ │ ├── translations.md
│ │ │ │ ├── triple-slash-directives.md
│ │ │ │ ├── tuple-type-anonymous.md
│ │ │ │ ├── type-annotations.md
│ │ │ │ ├── type-from-func-return.md
│ │ │ │ ├── type-from-module.md
│ │ │ │ ├── type-from-value.md
│ │ │ │ ├── type-indexing.md
│ │ │ │ ├── type-manipulation.md
│ │ │ │ ├── type-predicates.md
│ │ │ │ ├── typescript-introduction.md
│ │ │ │ ├── union-type.md
│ │ │ │ ├── unknown-type.md
│ │ │ │ └── void-type.md
│ │ │ ├── index.mdx
│ │ │ ├── it-it/
│ │ │ │ ├── book/
│ │ │ │ │ ├── about-the-author.md
│ │ │ │ │ ├── any-type.md
│ │ │ │ │ ├── assignments.md
│ │ │ │ │ ├── built-in-type-primitives.md
│ │ │ │ │ ├── class.md
│ │ │ │ │ ├── common-built-in-js-objects.md
│ │ │ │ │ ├── conditional-types.md
│ │ │ │ │ ├── control-flow-analysis.md
│ │ │ │ │ ├── differences-between-type-and-interface.md
│ │ │ │ │ ├── discriminated-unions.md
│ │ │ │ │ ├── distributive-conditional-types.md
│ │ │ │ │ ├── downloads-and-website.md
│ │ │ │ │ ├── enums.md
│ │ │ │ │ ├── erased-structural-types.md
│ │ │ │ │ ├── exhaustiveness-checking.md
│ │ │ │ │ ├── exploring-the-type-system.md
│ │ │ │ │ ├── extending-types.md
│ │ │ │ │ ├── fixed-length-tuple.md
│ │ │ │ │ ├── generics.md
│ │ │ │ │ ├── getting-started-with-typescript.md
│ │ │ │ │ ├── index-signatures.md
│ │ │ │ │ ├── infer-type-inference-in-conditional-types.md
│ │ │ │ │ ├── interface-and-type.md
│ │ │ │ │ ├── intersection-types.md
│ │ │ │ │ ├── introduction.md
│ │ │ │ │ ├── literal-inference.md
│ │ │ │ │ ├── literal-types.md
│ │ │ │ │ ├── mapped-type-modifiers.md
│ │ │ │ │ ├── mapped-types.md
│ │ │ │ │ ├── merging-and-extension.md
│ │ │ │ │ ├── named-tuple-type-labeled.md
│ │ │ │ │ ├── namespacing.md
│ │ │ │ │ ├── narrowing.md
│ │ │ │ │ ├── never-type.md
│ │ │ │ │ ├── object-types.md
│ │ │ │ │ ├── optional-properties.md
│ │ │ │ │ ├── others.md
│ │ │ │ │ ├── overloads.md
│ │ │ │ │ ├── predefined-conditional-types.md
│ │ │ │ │ ├── primitive-types.md
│ │ │ │ │ ├── readonly-properties.md
│ │ │ │ │ ├── strictnullchecks.md
│ │ │ │ │ ├── symbols.md
│ │ │ │ │ ├── table-of-contents.md
│ │ │ │ │ ├── template-union-types.md
│ │ │ │ │ ├── the-concise-typescript-book.md
│ │ │ │ │ ├── the-never-type.md
│ │ │ │ │ ├── translations.md
│ │ │ │ │ ├── triple-slash-directives.md
│ │ │ │ │ ├── tuple-type-anonymous.md
│ │ │ │ │ ├── type-annotations.md
│ │ │ │ │ ├── type-from-func-return.md
│ │ │ │ │ ├── type-from-module.md
│ │ │ │ │ ├── type-from-value.md
│ │ │ │ │ ├── type-indexing.md
│ │ │ │ │ ├── type-manipulation.md
│ │ │ │ │ ├── type-predicates.md
│ │ │ │ │ ├── typescript-introduction.md
│ │ │ │ │ ├── union-type.md
│ │ │ │ │ ├── unknown-type.md
│ │ │ │ │ └── void-type.md
│ │ │ │ └── index.mdx
│ │ │ ├── pt-br/
│ │ │ │ ├── book/
│ │ │ │ │ ├── about-the-author.md
│ │ │ │ │ ├── any-type.md
│ │ │ │ │ ├── assignments.md
│ │ │ │ │ ├── built-in-type-primitives.md
│ │ │ │ │ ├── class.md
│ │ │ │ │ ├── common-built-in-js-objects.md
│ │ │ │ │ ├── conditional-types.md
│ │ │ │ │ ├── control-flow-analysis.md
│ │ │ │ │ ├── differences-between-type-and-interface.md
│ │ │ │ │ ├── discriminated-unions.md
│ │ │ │ │ ├── distributive-conditional-types.md
│ │ │ │ │ ├── downloads-and-website.md
│ │ │ │ │ ├── enums.md
│ │ │ │ │ ├── erased-structural-types.md
│ │ │ │ │ ├── exhaustiveness-checking.md
│ │ │ │ │ ├── exploring-the-type-system.md
│ │ │ │ │ ├── extending-types.md
│ │ │ │ │ ├── fixed-length-tuple.md
│ │ │ │ │ ├── generics.md
│ │ │ │ │ ├── getting-started-with-typescript.md
│ │ │ │ │ ├── index-signatures.md
│ │ │ │ │ ├── infer-type-inference-in-conditional-types.md
│ │ │ │ │ ├── interface-and-type.md
│ │ │ │ │ ├── intersection-types.md
│ │ │ │ │ ├── introduction.md
│ │ │ │ │ ├── literal-inference.md
│ │ │ │ │ ├── literal-types.md
│ │ │ │ │ ├── mapped-type-modifiers.md
│ │ │ │ │ ├── mapped-types.md
│ │ │ │ │ ├── merging-and-extension.md
│ │ │ │ │ ├── named-tuple-type-labeled.md
│ │ │ │ │ ├── namespacing.md
│ │ │ │ │ ├── narrowing.md
│ │ │ │ │ ├── never-type.md
│ │ │ │ │ ├── object-types.md
│ │ │ │ │ ├── optional-properties.md
│ │ │ │ │ ├── others.md
│ │ │ │ │ ├── overloads.md
│ │ │ │ │ ├── predefined-conditional-types.md
│ │ │ │ │ ├── primitive-types.md
│ │ │ │ │ ├── readonly-properties.md
│ │ │ │ │ ├── strictnullchecks.md
│ │ │ │ │ ├── symbols.md
│ │ │ │ │ ├── table-of-contents.md
│ │ │ │ │ ├── template-union-types.md
│ │ │ │ │ ├── the-concise-typescript-book.md
│ │ │ │ │ ├── the-never-type.md
│ │ │ │ │ ├── translations.md
│ │ │ │ │ ├── triple-slash-directives.md
│ │ │ │ │ ├── tuple-type-anonymous.md
│ │ │ │ │ ├── type-annotations.md
│ │ │ │ │ ├── type-from-func-return.md
│ │ │ │ │ ├── type-from-module.md
│ │ │ │ │ ├── type-from-value.md
│ │ │ │ │ ├── type-indexing.md
│ │ │ │ │ ├── type-manipulation.md
│ │ │ │ │ ├── type-predicates.md
│ │ │ │ │ ├── typescript-introduction.md
│ │ │ │ │ ├── union-type.md
│ │ │ │ │ ├── unknown-type.md
│ │ │ │ │ └── void-type.md
│ │ │ │ └── index.mdx
│ │ │ ├── sv-se/
│ │ │ │ ├── book/
│ │ │ │ │ ├── about-the-author.md
│ │ │ │ │ ├── any-type.md
│ │ │ │ │ ├── assignments.md
│ │ │ │ │ ├── built-in-type-primitives.md
│ │ │ │ │ ├── class.md
│ │ │ │ │ ├── common-built-in-js-objects.md
│ │ │ │ │ ├── conditional-types.md
│ │ │ │ │ ├── control-flow-analysis.md
│ │ │ │ │ ├── differences-between-type-and-interface.md
│ │ │ │ │ ├── discriminated-unions.md
│ │ │ │ │ ├── distributive-conditional-types.md
│ │ │ │ │ ├── downloads-and-website.md
│ │ │ │ │ ├── enums.md
│ │ │ │ │ ├── erased-structural-types.md
│ │ │ │ │ ├── exhaustiveness-checking.md
│ │ │ │ │ ├── exploring-the-type-system.md
│ │ │ │ │ ├── extending-types.md
│ │ │ │ │ ├── fixed-length-tuple.md
│ │ │ │ │ ├── generics.md
│ │ │ │ │ ├── getting-started-with-typescript.md
│ │ │ │ │ ├── index-signatures.md
│ │ │ │ │ ├── infer-type-inference-in-conditional-types.md
│ │ │ │ │ ├── interface-and-type.md
│ │ │ │ │ ├── intersection-types.md
│ │ │ │ │ ├── introduction.md
│ │ │ │ │ ├── literal-inference.md
│ │ │ │ │ ├── literal-types.md
│ │ │ │ │ ├── mapped-type-modifiers.md
│ │ │ │ │ ├── mapped-types.md
│ │ │ │ │ ├── merging-and-extension.md
│ │ │ │ │ ├── named-tuple-type-labeled.md
│ │ │ │ │ ├── namespacing.md
│ │ │ │ │ ├── narrowing.md
│ │ │ │ │ ├── never-type.md
│ │ │ │ │ ├── object-types.md
│ │ │ │ │ ├── optional-properties.md
│ │ │ │ │ ├── others.md
│ │ │ │ │ ├── overloads.md
│ │ │ │ │ ├── predefined-conditional-types.md
│ │ │ │ │ ├── primitive-types.md
│ │ │ │ │ ├── readonly-properties.md
│ │ │ │ │ ├── strictnullchecks.md
│ │ │ │ │ ├── symbols.md
│ │ │ │ │ ├── table-of-contents.md
│ │ │ │ │ ├── template-union-types.md
│ │ │ │ │ ├── the-concise-typescript-book.md
│ │ │ │ │ ├── the-never-type.md
│ │ │ │ │ ├── translations.md
│ │ │ │ │ ├── triple-slash-directives.md
│ │ │ │ │ ├── tuple-type-anonymous.md
│ │ │ │ │ ├── type-annotations.md
│ │ │ │ │ ├── type-from-func-return.md
│ │ │ │ │ ├── type-from-module.md
│ │ │ │ │ ├── type-from-value.md
│ │ │ │ │ ├── type-indexing.md
│ │ │ │ │ ├── type-manipulation.md
│ │ │ │ │ ├── type-predicates.md
│ │ │ │ │ ├── typescript-introduction.md
│ │ │ │ │ ├── union-type.md
│ │ │ │ │ ├── unknown-type.md
│ │ │ │ │ └── void-type.md
│ │ │ │ └── index.mdx
│ │ │ └── zh-cn/
│ │ │ ├── book/
│ │ │ │ ├── about-the-author.md
│ │ │ │ ├── any-type.md
│ │ │ │ ├── assignments.md
│ │ │ │ ├── built-in-type-primitives.md
│ │ │ │ ├── class.md
│ │ │ │ ├── common-built-in-js-objects.md
│ │ │ │ ├── conditional-types.md
│ │ │ │ ├── control-flow-analysis.md
│ │ │ │ ├── differences-between-type-and-interface.md
│ │ │ │ ├── discriminated-unions.md
│ │ │ │ ├── distributive-conditional-types.md
│ │ │ │ ├── downloads-and-website.md
│ │ │ │ ├── enums.md
│ │ │ │ ├── erased-structural-types.md
│ │ │ │ ├── exhaustiveness-checking.md
│ │ │ │ ├── exploring-the-type-system.md
│ │ │ │ ├── extending-types.md
│ │ │ │ ├── fixed-length-tuple.md
│ │ │ │ ├── generics.md
│ │ │ │ ├── getting-started-with-typescript.md
│ │ │ │ ├── index-signatures.md
│ │ │ │ ├── infer-type-inference-in-conditional-types.md
│ │ │ │ ├── interface-and-type.md
│ │ │ │ ├── intersection-types.md
│ │ │ │ ├── introduction.md
│ │ │ │ ├── literal-inference.md
│ │ │ │ ├── literal-types.md
│ │ │ │ ├── mapped-type-modifiers.md
│ │ │ │ ├── mapped-types.md
│ │ │ │ ├── merging-and-extension.md
│ │ │ │ ├── named-tuple-type-labeled.md
│ │ │ │ ├── namespacing.md
│ │ │ │ ├── narrowing.md
│ │ │ │ ├── never-type.md
│ │ │ │ ├── object-types.md
│ │ │ │ ├── optional-properties.md
│ │ │ │ ├── others.md
│ │ │ │ ├── overloads.md
│ │ │ │ ├── predefined-conditional-types.md
│ │ │ │ ├── primitive-types.md
│ │ │ │ ├── readonly-properties.md
│ │ │ │ ├── strictnullchecks.md
│ │ │ │ ├── symbols.md
│ │ │ │ ├── table-of-contents.md
│ │ │ │ ├── template-union-types.md
│ │ │ │ ├── the-concise-typescript-book.md
│ │ │ │ ├── the-never-type.md
│ │ │ │ ├── translations.md
│ │ │ │ ├── triple-slash-directives.md
│ │ │ │ ├── tuple-type-anonymous.md
│ │ │ │ ├── type-annotations.md
│ │ │ │ ├── type-from-func-return.md
│ │ │ │ ├── type-from-module.md
│ │ │ │ ├── type-from-value.md
│ │ │ │ ├── type-indexing.md
│ │ │ │ ├── type-manipulation.md
│ │ │ │ ├── type-predicates.md
│ │ │ │ ├── typescript-introduction.md
│ │ │ │ ├── union-type.md
│ │ │ │ ├── unknown-type.md
│ │ │ │ └── void-type.md
│ │ │ └── index.mdx
│ │ └── i18n/
│ │ └── zh-cn.json
│ ├── env.d.ts
│ └── styles/
│ └── custom.css
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/compile.sh
================================================
#!/bin/bash
set -e
echo "Compiling all code snippets ..."
cd tools
npm ci
npm run compile
================================================
FILE: .github/workflows/lint.sh
================================================
#!/bin/bash
set -e
echo "Linting Markdown and Code files ..."
cd tools
npm ci
npm run lint
npm run lint:md
================================================
FILE: .github/workflows/validate-content.yml
================================================
name: validate-content
run-name: ${{ github.actor }} validate content
on: [pull_request]
jobs:
lint-content:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run lint script
run: bash ${GITHUB_WORKSPACE}/.github/workflows/lint.sh
compile-snippets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run compile script
run: bash ${GITHUB_WORKSPACE}/.github/workflows/compile.sh
================================================
FILE: .gitignore
================================================
node_modules
temp
!.vscode
__pycache__/
================================================
FILE: .vscode/settings.json
================================================
{
"editor.formatOnSave": false,
}
================================================
FILE: FUNDING.yml
================================================
github: gibbok
buy_me_a_coffee: simonepoggiali
custom: ["https://www.paypal.com/donate/?business=QW82ZS956XLFY&no_recurring=0¤cy_code=EUR"]
================================================
FILE: LICENSE.MD
================================================
# Licence
The Concise TypeScript Book © by Simone Poggiali is licensed under Attribution 4.0 International.
For more information visit:
================================================
FILE: README-it_IT.md
================================================
# The Concise TypeScript Book
The Concise TypeScript Book offre una panoramica completa e concisa delle funzionalità di TypeScript. Questo libro offre spiegazioni chiare che coprono tutti gli aspetti dell'ultima versione del linguaggio, dal suo potente sistema di tipi alle funzionalità avanzate. Che siate principianti o sviluppatori esperti, questo libro è una risorsa preziosa per migliorare la vostra comprensione e competenza in TypeScript.
Questo libro è completamente gratuito e open source.
Credo che un'istruzione tecnica di alta qualità debba essere accessibile a tutti, ed è per questo che mantengo questo libro gratuito e aperto.
Se il libro ti ha aiutato a risolvere un bug, a comprendere un concetto ostico o a progredire nella tua carriera, ti prego di considerare di sostenere il mio lavoro pagando quanto vuoi (prezzo suggerito: 15 euro) o sponsorizzando un caffè. Il tuo supporto mi aiuta a mantenere i contenuti aggiornati e ad ampliarli con nuovi esempi e spiegazioni più approfondite.
[](https://www.buymeacoffee.com/simonepoggiali)
[](https://www.paypal.com/donate/?business=QW82ZS956XLFY&no_recurring=0¤cy_code=EUR)
## Traduzioni
Questo libro è stato tradotto in diverse lingue, tra cui:
[Cinese](https://github.com/gibbok/typescript-book/blob/main/README-zh_CN.md)
[Italiano](https://github.com/gibbok/typescript-book/blob/main/README-it_IT.md)
[Portoghese (Brasile)](https://github.com/gibbok/typescript-book/blob/main/README-pt_BR.md)
[Svedese](https://github.com/gibbok/typescript-book/blob/main/README-sv_SE.md)
## Download e sito web
Puoi anche scaricare la versione Epub:
[https://github.com/gibbok/typescript-book/tree/main/downloads](https://github.com/gibbok/typescript-book/tree/main/downloads)
È disponibile una versione online su:
[https://gibbok.github.io/typescript-book](https://gibbok.github.io/typescript-book)
## Indice
- [The Concise TypeScript Book](#the-concise-typescript-book)
- [Traduzioni](#traduzioni)
- [Download e sito web](#download-e-sito-web)
- [Indice](#indice)
- [Introduzione](#introduzione)
- [Informazioni sull'autore](#informazioni-sullautore)
- [Introduzione a TypeScript](#introduzione-a-typescript)
- [Cos'è TypeScript?](#cosè-typescript)
- [Perché TypeScript?](#perché-typescript)
- [TypeScript e JavaScript](#typescript-e-javascript)
- [Generazione di codice TypeScript](#generazione-di-codice-typescript)
- [JavaScript moderno ora (Downleveling)](#javascript-moderno-ora-downleveling)
- [Per iniziare con TypeScript](#per-iniziare-con-typescript)
- [Installazione](#installazione)
- [Configurazione](#configurazione)
- [File di configurazione TypeScript](#file-di-configurazione-typescript)
- [target](#target)
- [lib](#lib)
- [strict](#strict)
- [module](#module)
- [moduleResolution](#moduleresolution)
- [esModuleInterop](#esmoduleinterop)
- [jsx](#jsx)
- [skipLibCheck](#skiplibcheck)
- [files](#files)
- [include](#include)
- [exclude](#exclude)
- [importHelpers](#importhelpers)
- [Consigli per la migrazione a TypeScript](#consigli-per-la-migrazione-a-typescript)
- [Esplorazione del sistema di tipi](#esplorazione-del-sistema-di-tipi)
- [Il servizio di linguaggio TypeScript](#il-servizio-di-linguaggio-typescript)
- [Tipizzazione Strutturale](#tipizzazione-strutturale)
- [Regole fondamentali di confronto di TypeScript](#regole-fondamentali-di-confronto-di-typescript)
- [Tipi come insiemi](#tipi-come-insiemi)
- [Allargamento di tipo](#allargamento-di-tipo)
- [Const](#const)
- [Modificatore Const sui parametri di tipo](#modificatore-const-sui-parametri-di-tipo)
- [Asserzione Const](#asserzione-const)
- [Annotazione di tipo esplicita](#annotazione-di-tipo-esplicita)
- [Restringimento dei tipi](#restringimento-dei-tipi)
- [Condizioni](#condizioni)
- [Generazione o restituzione](#generazione-o-restituzione)
- [Unione Discriminata](#unione-discriminata)
- [Protezioni di tipo definite dall'utente](#protezioni-di-tipo-definite-dallutente)
- [Tipi primitivi](#tipi-primitivi)
- [string](#string)
- [Inferenza letterale](#inferenza-letterale)
- [strictNullChecks](#strictnullchecks)
- [Enumerazioni](#enumerazioni)
- [Enumerazioni numeriche](#enumerazioni-numeriche)
- [Enum String](#enum-string)
- [Enum Constant](#enum-constant)
- [Mapping inverso](#mapping-inverso)
- [Enum ambientali](#enum-ambientali)
- [Membri calcolati e costanti](#membri-calcolati-e-costanti)
- [Restringimento](#restringimento)
- [protezioni di tipo typeof](#protezioni-di-tipo-typeof)
- [Restringimento di veridicità](#restringimento-di-veridicità)
- [Restringimento di uguaglianza](#restringimento-di-uguaglianza)
- [Restringimento dell'operatore "in"](#restringimento-delloperatore-in)
- [Restringimento instanceof](#restringimento-instanceof)
- [Assegnazioni](#assegnazioni)
- [Analisi del flusso di controllo](#analisi-del-flusso-di-controllo)
- [Tipo da Valore](#tipo-da-valore)
- [Tipo da Ritorno Funzione](#tipo-da-ritorno-funzione)
- [Tipo da modulo](#tipo-da-modulo)
- [Tipi mappati](#tipi-mappati)
- [Modificatori di tipo mappati](#modificatori-di-tipo-mappati)
- [Tipi condizionali](#tipi-condizionali)
- [Tipi condizionali distributivi](#tipi-condizionali-distributivi)
- [infer Inferenza di tipo nei tipi condizionali](#infer-inferenza-di-tipo-nei-tipi-condizionali)
- [Tipi Condizionali Predefiniti](#tipi-condizionali-predefiniti)
- [Tipi di unione di template](#tipi-di-unione-di-template)
- [Tipo Any](#tipo-any)
- [Tipo Unknown](#tipo-unknown)
- [Tipo Void](#tipo-void)
- [Tipo Never](#tipo-never)
- [Interfaccia e tipo](#interfaccia-e-tipo)
- [Sintassi comune](#sintassi-comune)
- [Tipi di base](#tipi-di-base)
- [Oggetti e interfacce](#oggetti-e-interfacce)
- [Modificatori di accesso](#modificatori-di-accesso)
- [Get e Set](#get-e-set)
- [Accessori automatici nelle classi](#accessori-automatici-nelle-classi)
- [this](#this)
- [Proprietà dei parametri](#proprietà-dei-parametri)
- [Classi astratte](#classi-astratte)
- [Con i generici](#con-i-generici)
- [Decoratori](#decoratori)
- [Decoratori di classe](#decoratori-di-classe)
- [Decoratore di proprietà](#decoratore-di-proprietà)
- [Ereditarietà](#ereditarietà)
- [Statiche](#statiche)
- [Inizializzazione delle proprietà](#inizializzazione-delle-proprietà)
- [Sovraccarico dei metodi](#sovraccarico-dei-metodi)
- [Generici](#generici)
- [Tipo generico](#tipo-generico)
- [Classi generiche](#classi-generiche)
- [Vincoli generici](#vincoli-generici)
- [Restringimento contestuale generico](#restringimento-contestuale-generico)
- [Tipi strutturali cancellati](#tipi-strutturali-cancellati)
- [Namespace](#namespace)
- [Simboli](#simboli)
- [Direttive con tripla barra](#direttive-con-tripla-barra)
- [Manipolazione dei tipi](#manipolazione-dei-tipi)
- [Creazione di tipi da tipi](#creazione-di-tipi-da-tipi)
- [Tipi di accesso indicizzati](#tipi-di-accesso-indicizzati)
- [Tipi di utilità](#tipi-di-utilità)
- [Awaited\](#awaitedt)
- [Partial\](#partialt)
- [Required\](#requiredt)
- [Readonly\](#readonlyt)
- [Record\](#recordk-t)
- [Pick\](#pickt-k)
- [Omit\](#omitt-k)
- [Exclude\](#excludet-u)
- [Extract\](#extractt-u)
- [NonNullable\](#nonnullablet)
- [Parameters\](#parameterst)
- [ConstructorParameters\](#constructorparameterst)
- [ReturnType\](#returntypet)
- [InstanceType\](#instancetypet)
- [ThisParameterType\](#thisparametertypet)
- [OmitThisParameter\](#omitthisparametert)
- [ThisType\](#thistypet)
- [Uppercase\](#uppercaset)
- [Lowercase\](#lowercaset)
- [Capitalize\](#capitalizet)
- [Uncapitalize\](#uncapitalizet)
- [NoInfer\](#noinfert)
- [Altri](#altri)
- [Gestione degli errori e delle eccezioni](#gestione-degli-errori-e-delle-eccezioni)
- [Classi Mixin](#classi-mixin)
- [Funzionalità del linguaggio asincrono](#funzionalità-del-linguaggio-asincrono)
- [Iteratori e Generatori](#iteratori-e-generatori)
- [Riferimento JSDoc di TsDocs](#riferimento-jsdoc-di-tsdocs)
- [@types](#types)
- [JSX](#jsx-1)
- [Moduli ES6](#moduli-es6)
- [Operatore di elevamento a potenza ES7](#operatore-di-elevamento-a-potenza-es7)
- [L'istruzione for-await-of](#listruzione-for-await-of)
- [Nuova meta-proprietà target](#nuova-meta-proprietà-target)
- [Espressioni di importazione dinamica](#espressioni-di-importazione-dinamica)
- ["tsc –watch"](#tsc-watch)
- [Operatore di asserzione non nullo](#operatore-di-asserzione-non-nullo)
- [Dichiarazioni predefinite](#dichiarazioni-predefinite)
- [Concatenamento opzionale](#concatenamento-opzionale)
- [Operatore di coalescenza nullo](#operatore-di-coalescenza-nullo)
- [Tipi letterali di template](#tipi-letterali-di-template)
- [Sovraccarico di funzioni](#sovraccarico-di-funzioni)
- [Tipi ricorsivi](#tipi-ricorsivi)
- [Tipi condizionali ricorsivi](#tipi-condizionali-ricorsivi)
- [Supporto per i moduli ECMAScript in Node](#supporto-per-i-moduli-ecmascript-in-node)
- [Funzioni di asserzione](#funzioni-di-asserzione)
- [Tipi di tupla variadici](#tipi-di-tupla-variadici)
- [Tipi boxed](#tipi-boxed)
- [Covarianza e Controvarianza in TypeScript](#covarianza-e-controvarianza-in-typescript)
- [Annotazioni di varianza opzionali per i parametri di tipo](#annotazioni-di-varianza-opzionali-per-i-parametri-di-tipo)
- [Firme di indice con pattern di stringhe modello](#firme-di-indice-con-pattern-di-stringhe-modello)
- [Operatore `satisfies`](#operatore-satisfies)
- [Importazioni ed esportazioni solo per tipo](#importazioni-ed-esportazioni-solo-per-tipo)
- [Dichiarazione using e Gestione Risorse Esplicita](#dichiarazione-using-e-gestione-risorse-esplicita)
- [dichiarazione await using](#dichiarazione-await-using)
- [Attributi di importazione](#attributi-di-importazione)
## Introduzione
Benvenuti a The Concise TypeScript Book! Questa guida vi fornirà le conoscenze essenziali e le competenze pratiche per uno sviluppo TypeScript efficace. Scoprite i concetti e le tecniche chiave per scrivere codice pulito e robusto. Che siate principianti o sviluppatori esperti, questo libro rappresenta sia una guida completa che un pratico riferimento per sfruttare la potenza di TypeScript nei vostri progetti.
Questo libro tratta TypeScript 5.2.
## Informazioni sull'autore
Simone Poggiali è uno Staff Engineer esperto, con una passione per la scrittura di codice di livello professionale fin dagli anni '90. Nel corso della sua carriera internazionale, ha contribuito a numerosi progetti per un'ampia gamma di clienti, dalle startup alle grandi organizzazioni. Aziende di spicco come HelloFresh, Siemens, O2, Leroy Merlin e Snowplow hanno beneficiato della sua competenza e dedizione.
È possibile contattare Simone Poggiali sulle seguenti piattaforme:
* LinkedIn: [https://www.linkedin.com/in/simone-poggiali](https://www.linkedin.com/in/simone-poggiali)
* GitHub: [https://github.com/gibbok](https://github.com/gibbok)
* X.com: [https://x.com/gibbok_coding](https://x.com/gibbok_coding)
* Email: gibbok.coding📧gmail.com
Elenco completo dei collaboratori: [https://github.com/gibbok/typescript-book/graphs/contributors](https://github.com/gibbok/typescript-book/graphs/contributors)
## Introduzione a TypeScript
### Cos'è TypeScript?
TypeScript è un linguaggio di programmazione fortemente tipizzato basato su JavaScript. È stato originariamente progettato da Anders Hejlsberg nel 2012 ed è attualmente sviluppato e gestito da Microsoft come progetto open source.
TypeScript si compila in JavaScript e può essere eseguito in qualsiasi runtime JavaScript (ad esempio, un browser o Node.js su un server).
Supporta diversi paradigmi di programmazione, come la programmazione funzionale, generica, imperativa e orientata agli oggetti, ed è un linguaggio compilato (transpilato) che viene convertito in JavaScript prima dell'esecuzione.
### Perché TypeScript?
TypeScript è un linguaggio fortemente tipizzato che aiuta a prevenire errori di programmazione comuni ed evitare determinati tipi di errori di runtime prima dell'esecuzione del programma.
Un linguaggio fortemente tipizzato consente allo sviluppatore di specificare vari vincoli e comportamenti del programma nelle definizioni dei tipi di dati, facilitando la verifica della correttezza del software e la prevenzione dei difetti. Questo è particolarmente utile nelle applicazioni su larga scala.
Alcuni dei vantaggi di TypeScript:
* Tipizzazione statica, facoltativamente fortemente tipizzata
* Inferenza di tipo
* Accesso alle funzionalità di ES6 ed ES7
* Compatibilità multipiattaforma e multibrowser
* Supporto degli strumenti con IntelliSense
### TypeScript e JavaScript
TypeScript è scritto in file `.ts` o `.tsx`, mentre i file JavaScript sono scritti in file `.js` o `.jsx`.
I file con estensione `.tsx` o `.jsx` possono contenere l'estensione di sintassi JavaScript JSX, utilizzata in React per lo sviluppo dell'interfaccia utente.
TypeScript è un superset tipizzato di JavaScript (ECMAScript 2015) in termini di sintassi. Tutto il codice JavaScript è codice TypeScript valido, ma il contrario non è sempre vero.
Ad esempio, si consideri una funzione in un file JavaScript con estensione `.js`, come la seguente:
```typescript
const sum = (a, b) => a + b;
```
La funzione può essere convertita e utilizzata in TypeScript modificando l'estensione del file in `.ts`. Tuttavia, se la stessa funzione è annotata con tipi TypeScript, non può essere eseguita in alcun runtime JavaScript senza compilazione. Il seguente codice TypeScript genererà un errore di sintassi se non compilato:
```typescript
const sum = (a: number, b: number): number => a + b;
```
TypeScript è stato progettato per rilevare possibili eccezioni che possono verificarsi in fase di runtime durante la compilazione, consentendo allo sviluppatore di definire l'intento con annotazioni di tipo. Inoltre, TypeScript può anche rilevare problemi se non viene fornita alcuna annotazione di tipo. Ad esempio, il seguente frammento di codice non specifica alcun tipo TypeScript:
```typescript
const items = [{ x: 1 }, { x: 2 }];
const result = items.filter(item => item.y);
```
In questo caso, TypeScript rileva un errore e segnala:
```text
La proprietà 'y' non esiste sul tipo '{ x: number; }'.
```
Il sistema di tipi di TypeScript è ampiamente influenzato dal comportamento runtime di JavaScript. Ad esempio, l'operatore di addizione (+), che in JavaScript può eseguire sia la concatenazione di stringhe che l'addizione numerica, è modellato allo stesso modo in TypeScript:
```typescript
const result = '1' + 1; // Il risultato è di tipo stringa
```
Il team di TypeScript ha deliberatamente deciso di segnalare come errori l'utilizzo insolito di JavaScript. Ad esempio, si consideri il seguente codice JavaScript valido:
```typescript
const result = 1 + true; // In JavaScript, il risultato è uguale a 2
```
Tuttavia, TypeScript genera un errore:
```text
L'operatore '+' non può essere applicato ai tipi 'number' e 'boolean'.
```
Questo errore si verifica perché TypeScript applica rigorosamente la compatibilità di tipo e, in questo caso, identifica un'operazione non valida tra un numero e un valore booleano.
### Generazione di codice TypeScript
Il compilatore TypeScript ha due responsabilità principali: il controllo degli errori di tipo e la compilazione in JavaScript. Questi due processi sono indipendenti l'uno dall'altro. I tipi non influenzano l'esecuzione del codice in un runtime JavaScript, poiché vengono completamente cancellati durante la compilazione. TypeScript può comunque generare codice JavaScript anche in presenza di errori di tipo.
Ecco un esempio di codice TypeScript con un errore di tipo:
```typescript
const add = (a: number, b: number): number => a + b;
const result = add('x', 'y'); // L'argomento di tipo 'string' non è assegnabile al parametro di tipo 'number'.
```
Tuttavia, può comunque produrre un output JavaScript eseguibile:
```typescript
'use strict';
const add = (a, b) => a + b;
const result = add('x', 'y'); // xy
```
Non è possibile controllare i tipi TypeScript in fase di esecuzione. Ad esempio:
```typescript
interface Animal {
name: string;
}
interface Dog extends Animal {
bark: () => void;
}
interface Cat extends Animal {
meow: () => void;
}
const makeNoise = (animal: Animal) => {
if (animal instanceof Dog) {
// 'Dog' si riferisce solo a un tipo, ma qui viene utilizzato come valore.
// ...
}
};
```
Poiché i tipi vengono cancellati dopo la compilazione, non è possibile eseguire questo codice in JavaScript. Per riconoscere i tipi a runtime, dobbiamo usare un altro meccanismo. TypeScript offre diverse opzioni, una delle quali è la "tagged union". Ad esempio:
```typescript
interface Dog {
kind: 'dog'; // Tagged union
bark: () => void;
}
interface Cat {
kind: 'cat'; // Tagged union
meow: () => void;
}
type Animal = Dog | Cat;
const makeNoise = (animal: Animal) => {
if (animal.kind === 'dog') {
animal.bark();
} else {
animal.meow();
}
};
const dog: Dog = {
kind: 'dog',
bark: () => console.log('bark'),
};
makeNoise(dog);
```
La proprietà "kind" è un valore che può essere utilizzato in fase di esecuzione per distinguere gli oggetti in JavaScript.
È anche possibile che un valore in fase di esecuzione abbia un tipo diverso da quello dichiarato nella dichiarazione di tipo. Ad esempio, se lo sviluppatore ha interpretato erroneamente un tipo API e lo ha annotato in modo errato.
TypeScript è un superset di JavaScript, quindi la parola chiave "class" può essere utilizzata come tipo e valore in fase di esecuzione.
```typescript
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
constructor(
public name: string,
public bark: () => void
) {
super(name);
}
}
class Cat extends Animal {
constructor(
public name: string,
public meow: () => void
) {
super(name);
}
}
type Mammal = Dog | Cat;
const makeNoise = (mammal: Mammal) => {
if (mammal instanceof Dog) {
mammal.bark();
} else {
mammal.meow();
}
};
const dog = new Dog('Fido', () => console.log('bark'));
makeNoise(dog);
```
In JavaScript, una "classe" ha una proprietà "prototype" e l'operatore "instanceof" può essere utilizzato per verificare se la proprietà prototype di un costruttore appare in qualsiasi punto della catena di prototipi di un oggetto.
TypeScript non ha alcun effetto sulle prestazioni di runtime, poiché tutti i tipi verranno cancellati. Tuttavia, TypeScript introduce un certo overhead in fase di compilazione.
### JavaScript moderno ora (Downleveling)
TypeScript può compilare codice per qualsiasi versione rilasciata di JavaScript a partire da ECMAScript 3 (1999). Ciò significa che TypeScript può transpilare codice dalle funzionalità JavaScript più recenti a versioni precedenti, un processo noto come Downleveling. Questo consente l'utilizzo di JavaScript moderno mantenendo la massima compatibilità con gli ambienti di runtime più vecchi.
È importante notare che durante la transpilazione a una versione precedente di JavaScript, TypeScript potrebbe generare codice che potrebbe comportare un sovraccarico di prestazioni rispetto alle implementazioni native.
Ecco alcune delle funzionalità di JavaScript moderno che possono essere utilizzate in TypeScript:
* Moduli ECMAScript al posto delle callback "define" in stile AMD o delle istruzioni "require" di CommonJS.
* Classi al posto dei prototipi.
* Dichiarazione di variabili utilizzando "let" o "const" al posto di "var".
* Ciclo "for-of" o ".forEach" al posto del tradizionale ciclo "for".
* Funzioni freccia al posto delle espressioni di funzione.
* Assegnazione destrutturata. \* Nomi abbreviati di proprietà/metodi e nomi di proprietà calcolate.
* Parametri di funzione predefiniti.
Sfruttando queste moderne funzionalità di JavaScript, gli sviluppatori possono scrivere codice più espressivo e conciso in TypeScript.
## Per iniziare con TypeScript
### Installazione
Visual Studio Code offre un eccellente supporto per il linguaggio TypeScript, ma non include il compilatore TypeScript. Per installare il compilatore TypeScript, è possibile utilizzare un gestore di pacchetti come npm o yarn:
```shell
npm install typescript --save-dev
```
oppure
```shell
yarn add typescript --dev
```
Assicurarsi di eseguire il commit del file di lock generato per garantire che ogni membro del team utilizzi la stessa versione di TypeScript.
Per eseguire il compilatore TypeScript, è possibile utilizzare i seguenti comandi:
```shell
npx tsc
```
oppure
```shell
yarn tsc
```
Si consiglia di installare TypeScript a livello di progetto anziché globale, poiché garantisce un processo di build più prevedibile. Tuttavia, per occasioni particolari, è possibile utilizzare il seguente comando:
```shell
npx tsc
```
oppure installarlo globalmente:
```shell
npm install -g typescript
```
Se si utilizza Microsoft Visual Studio, è possibile ottenere TypeScript come pacchetto in NuGet per i progetti MSBuild. Nella console di Gestione Pacchetti di NuGet, eseguire il seguente comando:
```shell
Install-Package Microsoft.TypeScript.MSBuild
```
Durante l'installazione di TypeScript, vengono installati due eseguibili: "tsc" come compilatore TypeScript e "tsserver" come server autonomo TypeScript. Il server autonomo contiene il compilatore e i servizi linguistici che possono essere utilizzati da editor e IDE per fornire il completamento intelligente del codice.
Inoltre, sono disponibili diversi transpiler compatibili con TypeScript, come Babel (tramite un plugin) o swc. Questi transpiler possono essere utilizzati per convertire il codice TypeScript in altri linguaggi o versioni di destinazione.
### Configurazione
TypeScript può essere configurato utilizzando le opzioni della CLI di tsc o un file di configurazione dedicato chiamato tsconfig.json, posizionato nella radice del progetto.
Per generare un file tsconfig.json precompilato con le impostazioni consigliate, è possibile utilizzare il seguente comando:
```shell
tsc --init
```
Quando si esegue il comando `tsc` localmente, TypeScript compilerà il codice utilizzando la configurazione specificata nel file tsconfig.json più vicino.
Ecco alcuni esempi di comandi CLI che vengono eseguiti con le impostazioni predefinite:
```shell
tsc main.ts // Compila un file specifico (main.ts) in JavaScript
tsc src/*.ts // Compila tutti i file .ts nella cartella 'src' in JavaScript
tsc app.ts util.ts --outfile index.js // Compila due file TypeScript (app.ts e util.ts) in un singolo file JavaScript (index.js)
```
### File di configurazione TypeScript
Un file tsconfig.json viene utilizzato per configurare il compilatore TypeScript (tsc). Solitamente, viene aggiunto alla radice del progetto, insieme al file `package.json`.
Note:
* tsconfig.json accetta commenti anche se è in formato json.
* Si consiglia di utilizzare questo file di configurazione al posto delle opzioni della riga di comando.
Al seguente link potete trovare la documentazione completa e il relativo schema:
[https://www.typescriptlang.org/tsconfig](https://www.typescriptlang.org/tsconfig)
[https://www.typescriptlang.org/tsconfig/](https://www.typescriptlang.org/tsconfig/)
Di seguito è riportato un elenco delle configurazioni più comuni e utili:
#### target
La proprietà "target" viene utilizzata per specificare in quale versione di JavaScript ECMAScript TypeScript deve emettere/compilare. Per i browser moderni, ES6 è una buona opzione, mentre per i browser più vecchi si consiglia ES5.
#### lib
La proprietà "lib" viene utilizzata per specificare quali file di libreria includere in fase di compilazione. TypeScript include automaticamente le API per le funzionalità specificate nella proprietà "target", ma è possibile omettere o selezionare librerie specifiche per esigenze particolari. Ad esempio, se si lavora su un progetto server, è possibile escludere la libreria "DOM", utile solo in un ambiente browser.
#### strict
La proprietà "strict" offre garanzie più solide e migliora la sicurezza dei tipi. Si consiglia di includere sempre questa proprietà nel file tsconfig.json del progetto. Abilitando la proprietà "strict", TypeScript può:
* Emettere codice utilizzando "use strict" per ogni file sorgente.
* Considerare "null" e "undefined" nel processo di controllo dei tipi.
* Disabilitare l'utilizzo del tipo "any" quando non sono presenti annotazioni di tipo.
* Generare un errore sull'utilizzo dell'espressione "this", che altrimenti implicherebbe il tipo "any".
#### module
La proprietà "module" imposta il sistema di moduli supportato dal programma compilato. Durante l'esecuzione, un caricatore di moduli viene utilizzato per individuare ed eseguire le dipendenze in base al sistema di moduli specificato.
I caricatori di moduli più comuni utilizzati in JavaScript sono Node.js CommonJS per le applicazioni lato server e RequireJS per i moduli AMD nelle applicazioni web basate su browser. TypeScript può generare codice per vari sistemi di moduli, tra cui UMD, System, ESNext, ES2015/ES6 ed ES2020.
Nota: il sistema di moduli deve essere scelto in base all'ambiente di destinazione e al meccanismo di caricamento dei moduli disponibile in tale ambiente.
#### moduleResolution
La proprietà "moduleResolution" specifica la strategia di risoluzione dei moduli. Utilizzare "node" per il codice TypeScript moderno, la strategia "classic" viene utilizzata solo per le vecchie versioni di TypeScript (precedenti alla 1.6).
#### esModuleInterop
La proprietà "esModuleInterop" consente l'importazione predefinita dai moduli CommonJS che non sono stati esportati utilizzando la proprietà "default". Questa proprietà fornisce uno shim per garantire la compatibilità nel codice JavaScript emesso. Dopo aver abilitato questa opzione, possiamo usare `import MyLibrary from "my-library"` invece di `import * as MyLibrary from "my-library"`.
#### jsx
La proprietà "jsx" si applica solo ai file .tsx utilizzati in ReactJS e controlla il modo in cui i costrutti JSX vengono compilati in JavaScript. Un'opzione comune è "preserve", che compilerà in un file .jsx mantenendo invariato il codice JSX, in modo che possa essere passato a diversi strumenti come Babel per ulteriori trasformazioni.
#### skipLibCheck
La proprietà "skipLibCheck" impedisce a TypeScript di controllare il tipo di tutti i pacchetti di terze parti importati. Questa proprietà riduce il tempo di compilazione di un progetto. TypeScript controllerà comunque il codice rispetto alle definizioni di tipo fornite da questi pacchetti.
#### files
La proprietà "files" indica al compilatore un elenco di file che devono essere sempre inclusi nel programma.
#### include
La proprietà "include" indica al compilatore un elenco di file che si desidera includere. Questa proprietà consente schemi di tipo glob, come "\*_" per qualsiasi sottodirectory, "_" per qualsiasi nome di file e "?" per caratteri opzionali.
#### exclude
La proprietà "exclude" indica al compilatore un elenco di file che non devono essere inclusi nella compilazione. Questo può includere file come "node_modules" o file di test.
Nota: tsconfig.json consente commenti.
### importHelpers
TypeScript utilizza codice helper durante la generazione di codice per determinate funzionalità JavaScript avanzate o di livello inferiore. Per impostazione predefinita, questi helper vengono duplicati nei file che li utilizzano. L'opzione `importHelpers` importa invece questi helper dal modulo `tslib`, rendendo l'output JavaScript più efficiente.
### Consigli per la migrazione a TypeScript
Per progetti di grandi dimensioni, si consiglia di adottare una transizione graduale in cui TypeScript e codice JavaScript coesisteranno inizialmente. Solo i progetti di piccole dimensioni possono essere migrati a TypeScript in un'unica soluzione.
Il primo passo di questa transizione è introdurre TypeScript nel processo di build chain. Questo può essere fatto utilizzando l'opzione del compilatore "allowJs", che consente ai file .ts e .tsx di coesistere con i file JavaScript esistenti. Poiché TypeScript tornerà al tipo "any" per una variabile quando non riesce a dedurre il tipo dai file JavaScript, si consiglia di disabilitare "noImplicitAny" nelle opzioni del compilatore all'inizio della migrazione.
Il secondo passaggio consiste nell'assicurarsi che i test JavaScript funzionino insieme ai file TypeScript, in modo da poterli eseguire durante la conversione di ciascun modulo. Se si utilizza Jest, si può valutare l'utilizzo di `ts-jest`, che consente di testare i progetti TypeScript con Jest.
Il terzo passaggio consiste nell'includere le dichiarazioni di tipo per le librerie di terze parti nel progetto. Queste dichiarazioni sono disponibili in bundle o su DefinitelyTyped. È possibile cercarle utilizzando [https://www.typescriptlang.org/dt/search](https://www.typescriptlang.org/dt/search) e installarle tramite:
```shell
npm install --save-dev @types/package-name
```
or
```shell
yarn add --dev @types/package-name
```
Il quarto passaggio consiste nel migrare modulo per modulo con un approccio bottom-up, seguendo il grafo delle dipendenze partendo dalle foglie. L'idea è di iniziare a convertire i moduli che non dipendono da altri moduli. Per visualizzare i grafici delle dipendenze, è possibile utilizzare lo strumento "madge".
I moduli candidati ideali per queste conversioni iniziali sono funzioni di utilità e codice relativo ad API o specifiche esterne. È possibile generare automaticamente definizioni di tipo TypeScript da contratti Swagger, schemi GraphQL o JSON da includere nel progetto.
Quando non sono disponibili specifiche o schemi ufficiali, è possibile generare tipi da dati grezzi, come JSON restituiti da un server. Tuttavia, si consiglia di generare tipi da specifiche anziché da dati per evitare di perdere casi limite.
Durante la migrazione, evitare il refactoring del codice e concentrarsi solo sull'aggiunta di tipi ai moduli.
Il quinto passaggio consiste nell'abilitare "noImplicitAny", che garantirà che tutti i tipi siano noti e definiti, offrendo una migliore esperienza TypeScript per il progetto.
Durante la migrazione, è possibile utilizzare la direttiva `@ts-check`, che abilita il controllo dei tipi TypeScript in un file JavaScript. Questa direttiva fornisce una versione semplificata del controllo dei tipi e può essere utilizzata inizialmente per identificare problemi nei file JavaScript. Quando `@ts-check` è incluso in un file, TypeScript tenterà di dedurre le definizioni utilizzando commenti in stile JSDoc. Tuttavia, si consiglia di utilizzare le annotazioni JSDoc solo in una fase molto precoce della migrazione.
Si consiglia di mantenere il valore predefinito di `noEmitOnError` nel file tsconfig.json su false. Questo consentirà di generare codice sorgente JavaScript anche se vengono segnalati errori.
## Esplorazione del sistema di tipi
### Il servizio di linguaggio TypeScript
Il servizio di linguaggio TypeScript, noto anche come tsserver, offre diverse funzionalità come la segnalazione degli errori, la diagnostica, la compilazione al salvataggio, la ridenominazione, il passaggio alla definizione, gli elenchi di completamento, la guida alle firme e altro ancora. Viene utilizzato principalmente dagli ambienti di sviluppo integrati (IDE) per fornire supporto IntelliSense. Si integra perfettamente con Visual Studio Code ed è utilizzato da strumenti come Conquer of Completion (Coc).
Gli sviluppatori possono sfruttare un'API dedicata e creare plugin di servizi linguistici personalizzati per migliorare l'esperienza di modifica di TypeScript. Questo può essere particolarmente utile per implementare funzionalità di linting speciali o abilitare il completamento automatico per un linguaggio di template personalizzato.
Un esempio di plugin personalizzato reale è "TypeScript-styled-plugin", che fornisce la segnalazione degli errori di sintassi e il supporto IntelliSense per le proprietà CSS nei componenti con stile.
Per ulteriori informazioni e guide rapide, è possibile consultare il Wiki ufficiale di TypeScript su GitHub: [https://github.com/microsoft/TypeScript/wiki/](https://github.com/microsoft/TypeScript/wiki/)
### Tipizzazione Strutturale
TypeScript si basa su un sistema di tipi strutturale. Ciò significa che la compatibilità e l'equivalenza dei tipi sono determinate dalla struttura o definizione effettiva del tipo, piuttosto che dal suo nome o dal punto di dichiarazione, come nei sistemi di tipi nominativi come C# o C.
Il sistema di tipi strutturale di TypeScript è stato progettato sulla base del funzionamento del sistema di tipizzazione dinamica di JavaScript durante l'esecuzione.
L'esempio seguente è codice TypeScript valido. Come si può osservare, "X" e "Y" hanno lo stesso membro "a", anche se hanno nomi di dichiarazione diversi. I tipi sono determinati dalle loro strutture e, in questo caso, poiché le strutture sono le stesse, sono compatibili e validi.
```typescript
type X = {
a: string;
};
type Y = {
a: string;
};
const x: X = { a: 'a' };
const y: Y = x; // Valido
```
### Regole fondamentali di confronto di TypeScript
Il processo di confronto di TypeScript è ricorsivo ed è eseguito su tipi annidati a qualsiasi livello.
Un tipo "X" è compatibile con "Y" se "Y" ha almeno gli stessi membri di "X".
```typescript
type X = {
a: string;
};
const y = { a: 'A', b: 'B' }; // Valido, poiché ha almeno gli stessi membri di X
const r: X = y;
```
I parametri delle funzioni vengono confrontati in base al tipo, non al nome:
```typescript
type X = (a: number) => void;
type Y = (a: number) => void;
let x: X = (j: number) => undefined;
let y: Y = (k: number) => undefined;
y = x; // Valido
x = y; // Valido
```
I tipi restituiti dalla funzione devono essere gli stessi:
```typescript
type X = (a: number) => undefined;
type Y = (a: number) => number;
let x: X = (a: number) => undefined;
let y: Y = (a: number) => 1;
y = x; // Non valido
x = y; // Non valido
```
Il tipo di ritorno di una funzione sorgente deve essere un sottotipo del tipo di ritorno di una funzione target:
```typescript
let x = () => ({ a: 'A' });
let y = () => ({ a: 'A', b: 'B' });
x = y; // Valido
y = x; // Il membro non valido b è mancante
```
È consentito ignorare i parametri della funzione, come è prassi comune in JavaScript, ad esempio utilizzando "Array.prototype.map()":
```typescript
[1, 2, 3].map((element, _index, _array) => element + 'x');
```
Pertanto, le seguenti dichiarazioni di tipo sono completamente valide:
```typescript
type X = (a: number) => undefined;
type Y = (a: number, b: number) => undefined;
let x: X = (a: number) => undefined;
let y: Y = (a: number) => undefined; // Parametro b mancante
y = x; // Valido
```
Tutti i parametri opzionali aggiuntivi del tipo sorgente sono validi:
```typescript
type X = (a: number, b?: number, c?: number) => undefined;
type Y = (a: number) => undefined;
let x: X = a => undefined;
let y: Y = a => undefined;
y = x; // Valido
x = y; //Valido
```
Tutti i parametri opzionali del tipo destinazione senza parametri corrispondenti nel tipo sorgente sono validi e non costituiscono un errore:
```typescript
type X = (a: number) => undefined;
type Y = (a: number, b?: number) => undefined;
let x: X = a => undefined;
let y: Y = a => undefined;
y = x; // Valido
x = y; // Valido
```
Il parametro rest viene trattato come una serie infinita di parametri opzionali:
```typescript
type X = (a: number, ...rest: number[]) => undefined;
let x: X = a => undefined; //valido
```
Le funzioni con overload sono valide se la firma di overload è compatibile con la firma della sua implementazione:
```typescript
function x(a: string): void;
function x(a: string, b: number): void;
function x(a: string, b?: number): void {
console.log(a, b);
}
x('a'); // Valido
x('a', 1); // Valido
function y(a: string): void; // Non valido, non compatibile con la firma dell'implementazione
function y(a: string, b: number): void;
function y(a: string, b: number): void {
console.log(a, b);
}
y('a');
y('a', 1);
```
Il confronto dei parametri della funzione ha esito positivo se i parametri sorgente e destinazione sono assegnabili a supertipi o sottotipi (bivarianza).
```typescript
// Supertipo
class X {
a: string;
constructor(value: string) {
this.a = value;
}
}
// Sottotipo
class Y extends X {}
// Sottotipo
class Z extends X {}
type GetA = (x: X) => string;
const getA: GetA = x => x.a;
// La bivarianza accetta supertipi
console.log(getA(new X('x'))); // Valido
console.log(getA(new Y('Y'))); // Valido
console.log(getA(new Z('z'))); // Valido
```
Gli enum sono confrontabili e validi con i numeri e viceversa, ma il confronto di valori Enum di tipi Enum diversi non è valido.
```typescript
enum X {
A,
B,
}
enum Y {
A,
B,
C,
}
const xa: number = X.A; // Valido
const ya: Y = 0; // Valido
X.A === Y.A; // Non valido
```
Le istanze di una classe sono soggette a un controllo di compatibilità per i loro membri privati e protetti:
```typescript
class X {
public a: string;
constructor(value: string) {
this.a = value;
}
}
class Y {
private a: string;
constructor(value: string) {
this.a = value;
}
}
let x: X = new Y('y'); // Non valido
```
Il controllo di confronto non tiene conto della diversa gerarchia di ereditarietà, ad esempio:
```typescript
class X {
public a: string;
constructor(value: string) {
this.a = value;
}
}
class Y extends X {
public a: string;
constructor(value: string) {
super(value);
this.a = value;
}
}
class Z {
public a: string;
constructor(value: string) {
this.a = value;
}
}
let x: X = new X('x');
let y: Y = new Y('y');
let z: Z = new Z('z');
x === y; // Valido
x === z; // Valido anche se z proviene da una gerarchia di ereditarietà diversa
```
I generici vengono confrontati utilizzando le loro strutture in base al tipo risultante dopo l'applicazione del parametro generico; solo il risultato finale viene confrontato come tipo non generico.
```typescript
interface X {
a: T;
}
let x: X = { a: 1 };
let y: X = { a: 'a' };
x === y; // Non valido poiché l'argomento tipo è utilizzato nella struttura finale
```
```typescript
interface X {}
const x: X = 1;
const y: X = 'a';
x === y; // Valido poiché l'argomento tipo non è utilizzato nella struttura finale
```
Quando i generici non hanno il loro argomento tipo specificato, tutti gli argomenti non specificati vengono trattati come tipi con "any":
```typescript
type X = (x: T) => T;
type Y = (y: K) => K;
let x: X = x => x;
let y: Y = y => y;
x = y; // Valido
```
Ricorda:
```typescript
let a: number = 1;
let b: number = 2;
a = b; // Valido, tutto è assegnabile a se stesso
let c: any;
c = 1; // Valido, tutti i tipi sono assegnabili a qualsiasi
let d: unknown;
d = 1; // Valido, tutti i tipi sono assegnabili a sconosciuto
let e: unknown;
let e1: unknown = e; // Valido, sconosciuto è assegnabile solo a se stesso e a qualsiasi
let e2: any = e; // Valido
let e3: number = e; // Non valido
let f: never;
f = 1; // Non valido, nulla è assegnabile a never
let g: void;
let g1: any;
g = 1; // Non valido, void non è assegnabile a o da nulla, tranne qualsiasi
g = g1; // Valido
```
Si noti che quando "strictNullChecks" è abilitato, "null" e "undefined" vengono trattati in modo simile a "void"; in caso contrario, sono simili a "never".
### Tipi come insiemi
In TypeScript, un tipo è un insieme di possibili valori. Questo insieme è anche definito dominio del tipo. Ogni valore di un tipo può essere visto come un elemento di un insieme. Un tipo stabilisce i vincoli che ogni elemento dell'insieme deve soddisfare per essere considerato membro di quell'insieme.
Il compito principale di TypeScript è controllare e verificare se un insieme è un sottoinsieme di un altro.
TypeScript supporta vari tipi di insiemi:
| Termine di insieme | TypeScript | Note |
| ------------------------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Insieme vuoto | never | "never" non contiene nulla |
| Insieme di un singolo elemento | undefined / null / tipo letterale | |
| Insieme finito | boolean / union | |
| Insieme infinito | string / number / object | |
| Insieme universale | any / unknown | Ogni elemento è un membro di "any" e ogni insieme è un suo sottoinsieme / "unknown" è una controparte di tipo sicuro di "any" |
Ecco alcuni esempi:
| TypeScript | Termine di insieme | Esempio |
| -------------- | --------------------------- | --------------------------------------------------------------------------------- |
| never | ∅ (insieme vuoto) | const x: never = 'x'; // Errore: il tipo 'string' non è assegnabile al tipo 'never' |
| | |
| Tipo letterale | Insieme di elementi singoli | type X = 'X'; |
| | | type Y = 7; |
| | |
| Valore assegnabile a T | Valore ∈ T (membro di) | type XY = 'X' \| 'Y'; |
| | | const x: XY = 'X'; |
| | |
| T1 assegnabile a T2 | T1 ⊆ T2 (sottoinsieme di) | type XY = 'X' \| 'Y'; |
| | | const x: XY = 'X'; |
| | | const j: XY = 'J'; // Il tipo '"J"' non è assegnabile al tipo 'XY'. |
| | | |
| T1 extends T2 | T1 ⊆ T2 (sottoinsieme di) | type X = 'X' extends string ? true : false; |
| | |
| T1 \| T2 | T1 ∪ T2 (unione) | type XY = 'X' \| 'Y'; |
| | | type JK = 1 \| 2; |
| | |
| T1 & T2 | T1 ∩ T2 (intersezione) | type X = \{ a: string \} |
| | | type Y = \{ b: string \} |
| | | type XY = X & Y |
| | | const x: XY = \{ a: 'a', b: 'b' \} |
| | |
| unknown | Insieme universale | const x: unknown = 1 |
Un'unione (T1 | T2) crea un insieme più ampio (entrambi):
```typescript
type X = {
a: string;
};
type Y = {
b: string;
};
type XY = X | Y;
const r: XY = { a: 'a', b: 'x' }; // Valido
```
Un'intersezione (T1 e T2) crea un insieme più ristretto (solo condiviso):
```typescript
type X = {
a: string;
};
type Y = {
a: string;
b: string;
};
type XY = X & Y;
const r: XY = { a: 'a' }; // Non valido
const j: XY = { a: 'a', b: 'b' }; // Valido
```
La parola chiave `extends` potrebbe essere considerata un "sottoinsieme di" in questo contesto. Imposta un vincolo per un tipo. L'extends utilizzato con un generico, considera il generico come un insieme infinito e lo vincola a un tipo più specifico. Si noti che ``extends` non ha nulla a che fare con la gerarchia in senso OOP (questo concetto non esiste in TypeScript).
TypeScript funziona con insiemi e non ha una gerarchia rigida; infatti, come nell'esempio seguente, due tipi potrebbero sovrapporsi senza che uno dei due sia un sottotipo dell'altro (TypeScript considera la struttura e la forma degli oggetti).
```typescript
interface X {
a: string;
}
interface Y extends X {
b: string;
}
interface Z extends Y {
c: string;
}
const z: Z = { a: 'a', b: 'b', c: 'c' };
interface X1 {
a: string;
}
interface Y1 {
a: string;
b: string;
}
interface Z1 {
a: string;
b: string;
c: string;
}
const z1: Z1 = { a: 'a', b: 'b', c: 'c' };
const r: Z1 = z; // Valido
```
### Assegnare un tipo: Dichiarazioni di tipo e asserzioni di tipo
Un tipo può essere assegnato in diversi modi in TypeScript:
#### Dichiarazione di tipo
Nell'esempio seguente, utilizziamo x: X (": Type") per dichiarare un tipo per la variabile x.
```typescript
type X = {
a: string;
};
// Dichiarazione di tipo
const x: X = {
a: 'a',
};
```
Se la variabile non è nel formato specificato, TypeScript segnalerà un errore. Ad esempio:
```typescript
type X = {
a: string;
};
const x: X = {
a: 'a',
b: 'b', // Errore: il letterale dell'oggetto può specificare solo proprietà note
};
```
#### Asserzione di tipo
È possibile aggiungere un'asserzione utilizzando la parola chiave `as`. Questo indica al compilatore che lo sviluppatore ha maggiori informazioni su un tipo e silenzia eventuali errori.
Ad esempio:
```typescript
type X = {
a: string;
};
const x = {
a: 'a',
b: 'b',
} as X;
```
Nell'esempio precedente, si asserisce che l'oggetto x abbia il tipo X utilizzando la parola chiave as. Questo informa il compilatore TypeScript che l'oggetto è conforme al tipo specificato, anche se ha una proprietà aggiuntiva b non presente nella definizione del tipo.
Le asserzioni di tipo sono utili in situazioni in cui è necessario specificare un tipo più specifico, soprattutto quando si lavora con il DOM. Ad esempio:
```typescript
const myInput = document.getElementById('my_input') as HTMLInputElement;
```
Qui, l'asserzione di tipo come HTMLInputElement viene utilizzata per indicare a TypeScript che il risultato di getElementById deve essere trattato come un HTMLInputElement.
Le asserzioni di tipo possono anche essere utilizzate per rimappare le chiavi, come mostrato nell'esempio seguente con letterali di template:
```typescript
type J = {
[Property in keyof Type as `prefix_${string &
Property}`]: () => Type[Property];
};
type X = {
a: string;
b: number;
};
type Y = J;
```
In questo esempio, il tipo `J` utilizza un tipo mappato con un letterale template per rimappare le chiavi di Tipo. Crea nuove proprietà con un "prefisso\_" aggiunto a ciascuna chiave e i valori corrispondenti sono funzioni che restituiscono i valori delle proprietà originali.
È importante notare che quando si utilizza un'asserzione di tipo, TypeScript non eseguirà controlli di proprietà eccessivi. Pertanto, è generalmente preferibile utilizzare una Dichiarazione di Tipo quando la struttura dell'oggetto è nota in anticipo.
#### Dichiarazioni Ambientali
Le dichiarazioni Ambientali sono file che descrivono i tipi per il codice JavaScript e hanno un formato di nome file come `.d.ts.`. Di solito vengono importate e utilizzate per annotare librerie JavaScript esistenti o per aggiungere tipi a file JS esistenti nel progetto.
Molti tipi di librerie comuni sono disponibili all'indirizzo:
[https://github.com/DefinitelyTyped/DefinitelyTyped/](https://github.com/DefinitelyTyped/DefinitelyTyped/)
e possono essere installate tramite:
```shell
npm install --save-dev @types/library-name
```
Per le dichiarazioni di ambiente definite, è possibile importarle utilizzando il riferimento "tripla barra":
```typescript
///
```
È possibile utilizzare le dichiarazioni di ambiente anche all'interno di file JavaScript utilizzando `// @ts-check`.
La parola chiave `declare` abilita le definizioni di tipo per il codice JavaScript esistente senza importarlo, fungendo da segnaposto per i tipi da un altro file o a livello globale.
### Controllo delle proprietà e controllo delle proprietà in eccesso
TypeScript si basa su un sistema di tipi strutturale, ma il controllo delle proprietà in eccesso è una proprietà di TypeScript che gli consente di verificare se un oggetto possiede esattamente le proprietà specificate nel tipo.
Il controllo delle proprietà in eccesso viene eseguito, ad esempio, quando si assegnano letterali di oggetto a variabili o quando li si passa come argomenti alla proprietà in eccesso di una funzione.
```typescript
type X = {
a: string;
};
const y = { a: 'a', b: 'b' };
const x: X = y; // Valido perché tipizzazione strutturale
const w: X = { a: 'a', b: 'b' }; // Non valido perché controllo delle proprietà in eccesso
```
### Tipi deboli
Un tipo è considerato debole quando contiene solo un insieme di proprietà completamente opzionali:
```typescript
type X = {
a?: string;
b?: string;
};
```
TypeScript considera un errore assegnare qualsiasi cosa a un tipo debole quando non c'è sovrapposizione, ad esempio, il seguente codice genera un errore:
```typescript
type Options = {
a?: string;
b?: string;
};
const fn = (options: Options) => undefined;
fn({ c: 'c' }); // Non valido
```
Sebbene non sia consigliato, se necessario, è possibile bypassare questo controllo utilizzando l'asserzione di tipo:
```typescript
type Options = {
a?: string;
b?: string;
};
const fn = (options: Options) => undefined;
fn({ c: 'c' } as Options); // Valido
```
Oppure aggiungendo `unknown` alla firma dell'indice del tipo debole:
```typescript
type Options = {
[prop: string]: unknown;
a?: string;
b?: string;
};
const fn = (options: Options) => undefined;
fn({ c: 'c' }); // Valido
```
### Controllo rigoroso dei letterali di oggetto (freschezza)
Il controllo rigoroso dei letterali di oggetto, a volte chiamato "freschezza", è una funzionalità di TypeScript che aiuta a individuare proprietà in eccesso o con errori di ortografia che altrimenti passerebbero inosservate nei normali controlli di tipo strutturale.
Quando si crea un letterale di oggetto, il compilatore TypeScript lo considera "fresco". Se il letterale di oggetto viene assegnato a una variabile o passato come parametro, TypeScript genererà un errore se il letterale di oggetto specifica proprietà che non esistono nel tipo di destinazione.
Tuttavia, la "freschezza" scompare quando un letterale di oggetto viene ampliato o viene utilizzata un'asserzione di tipo.
Ecco alcuni esempi per illustrare:
```typescript
type X = { a: string };
type Y = { a: string; b: string };
let x: X;
x = { a: 'a', b: 'b' }; // Controllo di freschezza: Assegnazione non valida
var y: Y;
y = { a: 'a', bx: 'bx' }; // Controllo di freschezza: Assegnazione non valida
const fn = (x: X) => console.log(x.a);
fn(x);
fn(y); // Allargamento: Nessun errore, strutturalmente compatibile con il tipo
fn({ a: 'a', bx: 'b' }); // Controllo di aggiornamento: argomento non valido
let c: X = { a: 'a' };
let d: Y = { a: 'a', b: '' };
c = d; // Allargamento: nessun controllo di aggiornamento
```
### Inferenza di tipo
TypeScript può inferire i tipi quando non viene fornita alcuna annotazione durante:
* Inizializzazione delle variabili.
* Inizializzazione dei membri.
* Impostazione dei valori predefiniti per i parametri.
* Tipo di ritorno della funzione.
Ad esempio:
```typescript
let x = 'x'; // Il tipo inferito è una stringa
```
Il compilatore TypeScript analizza il valore o l'espressione e ne determina il tipo in base alle informazioni disponibili.
### Inferenze più avanzate
Quando si utilizzano più espressioni nell'inferenza di tipo, TypeScript cerca i "tipi più comuni". Ad esempio:
```typescript
let x = [1, 'x', 1, null]; // Il tipo dedotto è: (string | number | null)[]
```
Se il compilatore non riesce a trovare i tipi comuni migliori, restituisce un tipo unione. Ad esempio:
```typescript
let x = [new RegExp('x'), new Date()]; // Il tipo inferito è: (RegExp | Date)[]
```
TypeScript utilizza la "tipizzazione contestuale" basata sulla posizione della variabile per inferire i tipi. Nell'esempio seguente, il compilatore sa che `e` è di tipo `MouseEvent` grazie al tipo di evento `click` definito nel file lib.d.ts, che contiene dichiarazioni ambientali per vari costrutti JavaScript comuni e il DOM:
```typescript
window.addEventListener('click', function (e) {}); // Il tipo inferito di e è MouseEvent
```
### Allargamento di tipo
L'allargamento di tipo è il processo in cui TypeScript assegna un tipo a una variabile inizializzata quando non è stata fornita alcuna annotazione di tipo. Consente il passaggio da tipi stretti a più ampi, ma non viceversa. Nell'esempio seguente:
```typescript
let x = 'x'; // TypeScript inferisce come stringa, un tipo ampio
let y: 'y' | 'x' = 'y'; // Il tipo y è un'unione di tipi letterali
y = x; // Il tipo non valido 'string' non è assegnabile al tipo '"x" | "y"'.
```
TypeScript assegna `string` a `x` in base al singolo valore fornito durante l'inizializzazione (`x`); questo è un esempio di ampliamento.
TypeScript fornisce modi per controllare il processo di ampliamento, ad esempio utilizzando "const".
### Const
L'utilizzo della parola chiave `const` durante la dichiarazione di una variabile produce un'inferenza di tipo più ristretta in TypeScript.
Ad esempio:
```typescript
const x = 'x'; // TypeScript deduce il tipo di x come 'x', un tipo più ristretto
let y: 'y' | 'x' = 'y';
y = x; // Valido: il tipo di x viene dedotto come 'x'
```
Utilizzando `const` per dichiarare la variabile x, il suo tipo viene ristretto allo specifico valore letterale 'x'. Poiché il tipo di x viene ristretto, può essere assegnato alla variabile y senza errori.
Il motivo per cui il tipo può essere dedotto è che le variabili `const` non possono essere riassegnate, quindi il loro tipo può essere ristretto a un tipo letterale specifico, in questo caso, il tipo letterale 'x'.
#### Modificatore Const sui parametri di tipo
Dalla versione 5.0 di TypeScript, è possibile specificare l'attributo `const` su un parametro di tipo generico. Questo consente di dedurre il tipo più preciso possibile. Vediamo un esempio senza usare `const`:
```typescript
function identity(value: T) {
// Nessuna costante qui
return value;
}
const values = identity({ a: 'a', b: 'b' }); // Il tipo inferito è: { a: string; b: string; }
```
Come puoi vedere, le proprietà `a` e `b` vengono inferite con un tipo `string`.
Ora, vediamo la differenza con la versione `const`:
```typescript
function identity(value: T) {
// Utilizzo del modificatore const sui parametri di tipo
return value;
}
const values = identity({ a: 'a', b: 'b' }); // Il tipo inferito è: { a: "a"; b: "b"; }
```
Ora possiamo vedere che le proprietà `a` e `b` vengono dedotte come `const`, quindi `a` e `b` vengono trattate come stringhe letterali anziché come semplici tipi `string`.
#### Asserzione Const
Questa funzionalità consente di dichiarare una variabile con un tipo letterale più preciso in base al suo valore di inizializzazione, indicando al compilatore che il valore deve essere trattato come un letterale immutabile. Ecco alcuni esempi:
Su una singola proprietà:
```typescript
const v = {
x: 3 as const,
};
v.x = 3;
```
Su un intero oggetto:
```typescript
const v = {
x: 1,
y: 2,
} as const;
```
Questo può essere particolarmente utile quando si definisce il tipo per una tupla:
```typescript
const x = [1, 2, 3]; // number[]
const y = [1, 2, 3] as const; // Tupla di readonly [1, 2, 3]
```
### Annotazione di tipo esplicita
Possiamo essere specifici e passare un tipo, nell'esempio seguente la proprietà `x` è di tipo `number`:
```typescript
const v = {
x: 1, // Inferred type: number (widening)
};
v.x = 3; // Valido
```
Possiamo rendere l'annotazione di tipo più specifica utilizzando un'unione di tipi letterali:
```typescript
const v: { x: 1 | 2 | 3 } = {
x: 1, // x è ora un'unione di tipi letterali: 1 | 2 | 3
};
v.x = 3; // Valido
v.x = 100; // Non valido
```
### Restringimento dei tipi
Il restringimento dei tipi è il processo in TypeScript in cui un tipo generico viene ridotto a un tipo più specifico. Ciò si verifica quando TypeScript analizza il codice e determina che determinate condizioni o operazioni possono perfezionare le informazioni sul tipo.
Il restringimento dei tipi può avvenire in diversi modi, tra cui:
#### Condizioni
Utilizzando istruzioni condizionali, come `if` o `switch`, TypeScript può restringere il tipo in base al risultato della condizione. Ad esempio:
```typescript
let x: number | undefined = 10;
if (x !== undefined) {
x += 100; // Il tipo è number, che è stato ristretto dalla condizione
}
```
#### Generazione o restituzione
Generare un errore o restituire un'istruzione in anticipo da un branch può essere utilizzato per aiutare TypeScript a restringere un tipo. Ad esempio:
```typescript
let x: number | undefined = 10;
if (x === undefined) {
throw 'error';
}
x += 100;
```
Altri modi per restringere i tipi in TypeScript includono:
* Operatore `instanceof`: utilizzato per verificare se un oggetto è un'istanza di una classe specifica.
* Operatore `in`: utilizzato per verificare se una proprietà esiste in un oggetto.
* Operatore `typeof`: utilizzato per verificare il tipo di un valore in fase di esecuzione.
* Funzioni integrate come `Array.isArray()`: utilizzate per verificare se un valore è un array.
#### Unione Discriminata
L'utilizzo di una "Unione Discriminata" è un pattern in TypeScript in cui un "tag" esplicito viene aggiunto agli oggetti per distinguere i diversi tipi all'interno di un'unione. Questo pattern è anche definito "unione con tag". Nell'esempio seguente, il "tag" è rappresentato dalla proprietà "type":
```typescript
type A = { type: 'type_a'; value: number };
type B = { type: 'type_b'; value: string };
const x = (input: A | B): string | number => {
switch (input.type) {
case 'type_a':
return input.value + 100; // il tipo è A
case 'type_b':
return input.value + 'extra'; // il tipo è B
}
};
```
#### Protezioni di tipo definite dall'utente
Nei casi in cui TypeScript non sia in grado di determinare un tipo, è possibile scrivere una funzione di supporto nota come "protezione di tipo definita dall'utente". Nell'esempio seguente, utilizzeremo un predicato di tipo per restringere il tipo dopo aver applicato un determinato filtro:
```typescript
const data = ['a', null, 'c', 'd', null, 'f'];
const r1 = data.filter(x => x != null); // Il tipo è (string | null)[], TypeScript non è riuscito a dedurre correttamente il tipo
const isValid = (item: string | null): item is string => item !== null; // Protezione personalizzata del tipo
const r2 = data.filter(isValid); // Il tipo ora è corretto string[], utilizzando la protezione del tipo predicato siamo riusciti a restringere il tipo
```
## Tipi primitivi
TypeScript supporta 7 tipi primitivi. Un tipo di dati primitivo si riferisce a un tipo che non è un oggetto e non ha metodi associati. In TypeScript, tutti i tipi primitivi sono immutabili, il che significa che i loro valori non possono essere modificati una volta assegnati.
### string
Il tipo primitivo `string` memorizza dati testuali e il valore è sempre racchiuso tra virgolette doppie o singole.
```typescript
const x: string = 'x';
const y: string = 'y';
```
Le stringhe possono estendersi su più righe se racchiuse dal carattere di apice inverso (`):
```typescript
let sentence: string = `xxx,
yyy`;
```
### boolean
Il tipo di dati `boolean` in TypeScript memorizza un valore binario, `true` o `false`.
```typescript
const isReady: boolean = true;
```
### number
Un tipo di dati `number` in TypeScript è rappresentato da un valore in virgola mobile a 64 bit. Un tipo di dati `number` può rappresentare numeri interi e frazioni.
TypeScript supporta anche i sistemi di numerazione esadecimale, binario e ottale, ad esempio:
```typescript
const decimal: number = 10;
const hexadecimal: number = 0xa00d; // L'esadecimale inizia con 0x
const binary: number = 0b1010; // Il binario inizia con 0b
const octal: number = 0o633; // L'ottale inizia con 0o
```
### bigInt
Un `bigInt` rappresenta valori numerici molto grandi (253 – 1) e non possono essere rappresentati con un `number`.
Un `bigInt` può essere creato chiamando la funzione integrata `BigInt()` o aggiungendo `n` alla fine di qualsiasi letterale numerico intero:
```typescript
const x: bigint = BigInt(9007199254740991);
const y: bigint = 9007199254740991n;
```
Note:
* I valori `bigInt` non possono essere combinati con `number` e non possono essere utilizzati con `Math` integrato, devono essere forzati allo stesso tipo.
* I valori `bigInt` sono disponibili solo se la configurazione di destinazione è ES2020 o superiore.
### Simbolo
I simboli sono identificatori univoci che possono essere utilizzati come chiavi di proprietà negli oggetti per evitare conflitti di denominazione.
```typescript
type Obj = {
[sym: symbol]: number;
};
const a = Symbol('a');
const b = Symbol('b');
let obj: Obj = {};
obj[a] = 123;
obj[b] = 456;
console.log(obj[a]); // 123
console.log(obj[b]); // 456
```
### null e undefined
I tipi `null` e `undefined` rappresentano entrambi nessun valore o l'assenza di qualsiasi valore.
Il tipo `undefined` indica che il valore non è assegnato o inizializzato o indica un'assenza involontaria di valore.
Il tipo `null` indica che sappiamo che il campo non ha un valore, quindi il valore non è disponibile, e indica un'assenza intenzionale di valore.
### Array
Un `array` è un tipo di dati che può memorizzare più valori dello stesso tipo o meno. Può essere definito utilizzando la seguente sintassi:
```typescript
const x: string[] = ['a', 'b'];
const y: Array = ['a', 'b'];
const j: Array = ['a', 1, 'b', 2]; // Unione
```
TypeScript supporta array di sola lettura utilizzando la seguente sintassi:
```typescript
const x: readonly string[] = ['a', 'b']; // Modificatore di sola lettura
const y: ReadonlyArray = ['a', 'b'];
const j: ReadonlyArray = ['a', 1, 'b', 2];
j.push('x'); // Non valido
```
TypeScript supporta tuple e tuple di sola lettura:
```typescript
const x: [string, number] = ['a', 1];
const y: readonly [string, number] = ['a', 1];
```
### any
Il tipo di dati ``any` rappresenta letteralmente un valore "qualsiasi", ed è il valore predefinito quando TypeScript non può dedurre il tipo o non è specificato.
Quando si utilizza `any`, il compilatore TypeScript salta il controllo del tipo, quindi non c'è sicurezza di tipo quando si utilizza `any`. In genere, non utilizzare `any` per silenziare il compilatore quando si verifica un errore, ma concentrarsi sulla correzione dell'errore, poiché utilizzando `any` è possibile interrompere i contratti e perdere i vantaggi del completamento automatico di TypeScript.
Il tipo `any` potrebbe essere utile durante una migrazione graduale da JavaScript a TypeScript, in quanto può silenziare il compilatore.
Per i nuovi progetti, utilizzare la configurazione TypeScript `noImplicitAny`, che consente a TypeScript di generare errori quando viene utilizzato o dedotto `any`.
Il tipo `any` è solitamente fonte di errori che possono mascherare problemi reali con i tipi. Evitatelo il più possibile.
## Annotazioni di tipo
Sulle variabili dichiarate usando `var`, `let` e `const`, è possibile aggiungere facoltativamente un tipo:
```typescript
const x: number = 1;
```
TypeScript esegue un buon lavoro nell'inferenza dei tipi, soprattutto quando si tratta di tipi semplici, quindi queste dichiarazioni nella maggior parte dei casi non sono necessarie.
Sulle funzioni è possibile aggiungere annotazioni di tipo ai parametri:
```typescript
function sum(a: number, b: number) {
return a + b;
}
```
Il seguente è un esempio che utilizza una funzione anonima (la cosiddetta funzione lambda):
```typescript
const sum = (a: number, b: number) => a + b;
```
Queste annotazioni possono essere evitate quando è presente un valore predefinito per un parametro:
```typescript
const sum = (a = 10, b: number) => a + b;
```
Le annotazioni del tipo di ritorno possono essere aggiunte alle funzioni:
```typescript
const sum = (a = 10, b: number): number => a + b;
```
Questo è utile soprattutto per le funzioni più complesse, poiché scrivere esplicitamente il tipo di ritorno prima di un'implementazione può aiutare a pensare meglio alla funzione.
In genere, si consiglia di annotare le firme dei tipi, ma non le variabili locali del corpo, e di aggiungere i tipi sempre ai letterali degli oggetti.
## Proprietà facoltative
Un oggetto può specificare Proprietà facoltative aggiungendo un punto interrogativo `?` alla fine del nome della proprietà:
```typescript
type X = {
a: number;
b?: number; // Facoltativo
};
```
È possibile specificare un valore predefinito quando una proprietà è facoltativa"
```typescript
type X = {
a: number;
b?: number;
};
const x = ({ a, b = 100 }: X) => a + b;
```
## Proprietà di sola lettura
È possibile impedire la scrittura su una proprietà utilizzando il modificatore `readonly`, che assicura che la proprietà non possa essere riscritta ma non fornisce alcuna garanzia di immutabilità totale:
```typescript
interface Y {
readonly a: number;
}
type X = {
readonly a: number;
};
type J = Readonly<{
a: number;
}>;
type K = {
readonly [index: number]: string;
};
```
## Firme di indice
In TypeScript possiamo usare come firma di indice `string`, `number` e `symbol`:
```typescript
type K = {
[name: string | number]: string;
};
const k: K = { x: 'x', 1: 'b' };
console.log(k['x']);
console.log(k[1]);
console.log(k['1']); // Stesso risultato di k[1]
```
Si noti che JavaScript converte automaticamente un indice con `number` in un indice con `string`, quindi `k[1]` o `k["1"]` restituiscono lo stesso valore.
## Estensione dei tipi
È possibile estendere un'`interfaccia` (copiare membri da un altro tipo):
```typescript
interface X {
a: string;
}
interface Y extends X {
b: string;
}
```
È anche possibile estendere da più tipi:
```typescript
interface A {
a: string;
}
interface B {
b: string;
}
interface Y extends A, B {
y: string;
}
```
La parola chiave `extends` funziona solo su interfacce e classi; per i tipi utilizzare un'intersezione:
```typescript
type A = {
a: number;
};
type B = {
b: number;
};
type C = A & B;
```
È possibile estendere un tipo utilizzando un'inferenza, ma non viceversa:
```typescript
type A = {
a: string;
};
interface B extends A {
b: string;
}
```
## Tipi letterali
Un tipo letterale è un singolo insieme di elementi di un tipo collettivo; definisce un valore molto preciso che è una primitiva JavaScript.
I tipi letterali in TypeScript sono numeri, stringhe e booleani.
Esempio di letterali:
```typescript
const a = 'a'; // Stringa tipo letterale
const b = 1; // Numeric literal type
const c = true; // Boolean literal type
```
I tipi letterali stringa, numerico e booleano vengono utilizzati nell'unione, nella protezione dei tipi e negli alias di tipo.
Nell'esempio seguente, è possibile vedere un alias di tipo unione. `O` è costituito solo dai valori specificati, nessun'altra stringa è valida:
```typescript
type O = 'a' | 'b' | 'c';
```
## Inferenza letterale
L'inferenza letterale è una funzionalità di TypeScript che consente di dedurre il tipo di una variabile o di un parametro in base al suo valore.
Nell'esempio seguente possiamo vedere che TypeScript considera `x` un tipo letterale in quanto il valore non può essere modificato in seguito, mentre `y` viene dedotto come stringa in quanto può essere modificato in seguito.
```typescript
const x = 'x'; // Literal type of 'x', because this value cannot be changed
let y = 'y'; // Type string, because we can change this value
```
Nell'esempio seguente possiamo vedere che `o.x` è stato dedotto come `string` (e non come un letterale di `a`), poiché TypeScript considera che il valore possa essere modificato in qualsiasi momento successivo.
```typescript
type X = 'a' | 'b';
let o = {
x: 'a', // Questa è una stringa più ampia
};
const fn = (x: X) => `${x}-foo`;
console.log(fn(o.x)); // L'argomento di tipo 'string' non è assegnabile al parametro di tipo 'X'
```
Come puoi vedere, il codice genera un errore quando si passa `o.x` a `fn`, poiché X è un tipo più ristretto.
Possiamo risolvere questo problema utilizzando l'asserzione di tipo `const` o il tipo `X`:
```typescript
let o = {
x: 'a' as const,
};
```
oppure:
```typescript
let o = {
x: 'a' as X,
};
```
## strictNullChecks
`strictNullChecks` è un'opzione del compilatore TypeScript che impone un controllo null rigoroso. Quando questa opzione è abilitata, variabili e parametri possono essere assegnati a `null` o `undefined` solo se sono stati dichiarati esplicitamente di quel tipo utilizzando l'unione `null` | `undefined`. Se una variabile o un parametro non viene dichiarato esplicitamente come nullable, TypeScript genererà un errore per prevenire potenziali errori di runtime.
## Enumerazioni
In TypeScript, un `enum` è un insieme di valori costanti denominati.
```typescript
enum Colore {
Rosso = '#ff0000',
Verde = '#00ff00',
Blu = '#0000ff',
}
```
Gli enum possono essere definiti in diversi modi:
### Enumerazioni numeriche
In TypeScript, un enum numerico è un enum in cui a ogni costante viene assegnato un valore numerico, a partire da 0 per impostazione predefinita.
```typescript
enum Size {
Small, // il valore inizia da 0
Medium,
Large,
}
```
È possibile specificare valori personalizzati assegnandoli esplicitamente:
```typescript
enum Size {
Small = 10,
Medium,
Large,
}
console.log(Size.Medium); // 11
```
### Enum String
In TypeScript, un enum String è un enum in cui a ogni costante viene assegnato un valore stringa.
```typescript
enum Language {
English = 'EN',
Spanish = 'ES',
}
```
Nota: TypeScript consente l'utilizzo di enum eterogenei in cui stringhe e membri numerici possono coesistere.
### Enum Constant
Un enum Constant in TypeScript è un tipo speciale di enum in cui tutti i valori sono noti in fase di compilazione e vengono inlineati ovunque venga utilizzato l'enum, con conseguente maggiore efficienza del codice.
```typescript
const enum Language {
English = 'EN',
Spanish = 'ES',
}
console.log(Language.English);
```
Verrà compilato in:
```typescript
console.log('EN' /* Language.English */);
```
Note:
Gli enum costanti hanno valori hardcoded, che cancellano l'enum, il che può essere più efficiente nelle librerie autonome, ma generalmente non è auspicabile. Inoltre, gli enum costanti non possono avere membri calcolati.
### Mapping inverso
In TypeScript, i mapping inversi negli enum si riferiscono alla possibilità di recuperare il nome del membro dell'enum dal suo valore. Per impostazione predefinita, i membri dell'enum hanno mapping in avanti dal nome al valore, ma i mapping inversi possono essere creati impostando esplicitamente i valori per ciascun membro. I mapping inversi sono utili quando è necessario cercare un membro dell'enum in base al suo valore o quando è necessario iterare su tutti i membri dell'enum. Si noti che solo i membri dell'enum numerico genereranno mapping inversi, mentre i membri dell'enum stringa non generano alcun mapping inverso.
Il seguente enum:
```typescript
enum Grade {
A = 90,
B = 80,
C = 70,
F = 'fail',
}
```
Compila in:
```javascript
"use strict";
var Grade;
(function (Grade) {
Grade[(Grade["A"] = 90)] = "A";
Grade[(Grade["B"] = 80)] = "B";
Grade[(Grade["C"] = 70)] = "C";
Grade["F"] = "fail";
})(Grade || (Grade = {}));
```
Pertanto, la mappatura dei valori alle chiavi funziona per i membri enum numerici, ma non per i membri enum stringa:
```typescript
enum Grade {
A = 90,
B = 80,
C = 70,
F = 'fail',
}
const myGrade = Grade.A;
console.log(Grade[myGrade]); // A
console.log(Grade[90]); // A
const failGrade = Grade.F;
console.log(failGrade); // fail
console.log(Grade[failGrade]); // Element ha implicitamente un tipo 'any' perché l'espressione indice non è di tipo 'number'.
```
### Enum ambientali
Un enum ambientale in TypeScript è un tipo di Enum definito in un file di dichiarazione (\*.d.ts) senza un'implementazione associata. Permette di definire un set di costanti denominate che possono essere utilizzate in modo sicuro tra file diversi senza dover importare i dettagli di implementazione in ogni file.
### Membri calcolati e costanti
In TypeScript, un membro calcolato è un membro di un Enum il cui valore è calcolato in fase di esecuzione, mentre un membro costante è un membro il cui valore è impostato in fase di compilazione e non può essere modificato in fase di esecuzione. I membri calcolati sono consentiti negli Enum normali, mentre i membri costanti sono consentiti sia negli enum normali che in quelli costanti.
```typescript
// Membri costanti
enum Color {
Red = 1,
Green = 5,
Blue = Red + Green,
}
console.log(Color.Blue); // 6 generazioni in fase di compilazione
```
```typescript
// Membri calcolati
enum Color {
Red = 1,
Green = Math.pow(2, 2),
Blue = Math.floor(Math.random() * 3) + 1,
}
console.log(Color.Blue); // numero casuale generato in fase di esecuzione
```
Gli enum sono indicati da unioni che comprendono i loro tipi di membri. I valori di ciascun membro possono essere determinati tramite espressioni costanti o non costanti, con i membri che possiedono valori costanti a cui vengono assegnati tipi letterali. Per illustrare, si consideri la dichiarazione del tipo E e dei suoi sottotipi E.A, E.B ed E.C. In questo caso, E rappresenta l'unione E.A | E.B | E.C.
```typescript
const identity = (value: number) => value;
enum E {
A = 2 * 5, // Letterale numerico
B = 'bar', // Letterale stringa
C = identity(42), // Calcolato opaco
}
console.log(E.C); //42
```
## Restringimento
Il restringimento di TypeScript è il processo di perfezionamento del tipo di una variabile all'interno di un blocco condizionale. Questo è utile quando si lavora con tipi union, in cui una variabile può avere più di un tipo.
TypeScript riconosce diversi modi per restringere il tipo:
### protezioni di tipo typeof
Il type guard typeof è uno specifico type guard in TypeScript che controlla il tipo di una variabile in base al suo tipo JavaScript predefinito.
```typescript
const fn = (x: number | string) => {
if (typeof x === 'number') {
return x + 1; // x è un numero
}
return -1;
};
```
### Restringimento di veridicità
Il restringimento di veridicità in TypeScript funziona verificando se una variabile è vera o falsa, per restringerne di conseguenza il tipo.
```typescript
const toUpperCase = (name: string | null) => {
if (name) {
return name.toUpperCase();
} else {
return null;
}
};
```
### Restringimento di uguaglianza
Il restringimento di uguaglianza in TypeScript funziona verificando se una variabile è uguale o meno a un valore specifico, per restringerne di conseguenza il tipo.
Viene utilizzato insieme alle istruzioni `switch` e agli operatori di uguaglianza come `===`, `!==`, `==` e `!=` per restringere i tipi.
```typescript
const checkStatus = (status: 'success' | 'error') => {
switch (status) {
case 'success':
return true;
case 'error':
return null;
}
};
```
### Restringimento dell'operatore `in`
Il restringimento dell'operatore `in` in TypeScript è un modo per restringere il tipo di una variabile in base all'esistenza di una proprietà all'interno del tipo della variabile.
```typescript
type Dog = {
name: string;
breed: string;
};
type Cat = {
name: string;
likesCream: boolean;
};
const getAnimalType = (pet: Dog | Cat) => {
if ('breed' in pet) {
return 'dog';
} else {
return 'cat';
}
};
```
### Restringimento instanceof
L'operatore di restringimento `instanceof` in TypeScript è un modo per restringere il tipo di una variabile in base alla sua funzione costruttore, verificando se un oggetto è un'istanza di una determinata classe o interfaccia.
```typescript
class Square {
constructor(public width: number) {}
}
class Rectangle {
constructor(
public width: number,
public height: number
) {}
}
function area(shape: Square | Rectangle) {
if (shape instanceof Square) {
return shape.width * shape.width;
} else {
return shape.width * shape.height;
}
}
const square = new Square(5);
const rectangle = new Rectangle(5, 10);
console.log(area(square)); // 25
console.log(area(rectangle)); // 50
```
## Assegnazioni
Il restringimento TypeScript tramite assegnazioni è un modo per restringere il tipo di una variabile in base al valore assegnato. Quando a una variabile viene assegnato un valore, TypeScript ne deduce il tipo in base al valore assegnato e restringe il tipo della variabile in modo che corrisponda al tipo dedotto.
```typescript
let value: string | number;
value = 'hello';
if (typeof value === 'string') {
console.log(value.toUpperCase());
}
value = 42;
if (typeof value === 'number') {
console.log(value.toFixed(2));
}
```
## Analisi del flusso di controllo
L'analisi del flusso di controllo in TypeScript è un modo per analizzare staticamente il flusso di codice per dedurre i tipi di variabili, consentendo al compilatore di restringere i tipi di tali variabili secondo necessità, in base ai risultati dell'analisi.
Prima di TypeScript 4.4, l'analisi del flusso di codice veniva applicata solo al codice all'interno di un'istruzione if, ma da TypeScript 4.4 può essere applicata anche alle espressioni condizionali e agli accessi alle proprietà discriminanti referenziati indirettamente tramite variabili const.
Ad esempio:
```typescript
const f1 = (x: unknown) => {
const isString = typeof x === 'string';
if (isString) {
x.length;
}
};
const f2 = (
obj: { kind: 'foo'; foo: string } | { kind: 'bar'; bar: number }
) => {
const isFoo = obj.kind === 'foo';
if (isFoo) {
obj.foo;
} else {
obj.bar;
}
};
```
Alcuni esempi in cui il restringimento non avviene:
```typescript
const f1 = (x: unknown) => {
let isString = typeof x === 'string';
if (isString) {
x.length; // Errore, nessun restringimento perché isString non è costante
}
};
const f6 = (
obj: { kind: 'foo'; foo: string } | { kind: 'bar'; bar: number }
) => {
const isFoo = obj.kind === 'foo';
obj = obj;
if (isFoo) {
obj.foo; // Errore, nessun restringimento perché obj è assegnato nel corpo della funzione
}
};
```
Note: Nelle espressioni condizionali vengono analizzati fino a cinque livelli di indirezione.
## Predicati di tipo
I predicati di tipo in TypeScript sono funzioni che restituiscono un valore booleano e vengono utilizzate per restringere il tipo di una variabile a un tipo più specifico.
```typescript
const isString = (value: unknown): value is string => typeof value === 'string';
const foo = (bar: unknown) => {
if (isString(bar)) {
console.log(bar.toUpperCase());
} else {
console.log('not a string');
}
};
```
## Unioni Discriminate
Le unioni Discriminate in TypeScript sono un tipo di unione che utilizza una proprietà comune, nota come discriminante, per restringere l'insieme dei tipi possibili per l'unione.
```typescript
type Square = {
kind: 'square'; // Discriminante
size: number;
};
type Circle = {
kind: 'circle'; // Discriminante
radius: number;
};
type Shape = Square | Circle;
const area = (shape: Shape) => {
switch (shape.kind) {
case 'square':
return Math.pow(shape.size, 2);
case 'circle':
return Math.PI * Math.pow(shape.radius, 2);
}
};
const square: Square = { kind: 'square', size: 5 };
const circle: Circle = { kind: 'circle', radius: 2 };
console.log(area(square)); // 25
console.log(area(circle)); // 12.566370614359172
```
## Il tipo never
Quando una variabile viene ristretta a un tipo che non può contenere alcun valore, il compilatore TypeScript dedurrà che la variabile deve essere del tipo `never`. Questo perché il tipo `never` rappresenta un valore che non può mai essere prodotto.
```typescript
const printValue = (val: string | number) => {
if (typeof val === 'string') {
console.log(val.toUpperCase());
} else if (typeof val === 'number') {
console.log(val.toFixed(2));
} else {
// val ha il tipo never qui perché non può essere altro che una stringa o un numero
const neverVal: never = val;
console.log(`Valore imprevisto: ${neverVal}`);
}
};
```
## Controllo di esaustività
Il controllo di esaustività è una funzionalità di TypeScript che garantisce che tutti i possibili casi di unione discriminata vengano gestiti in un'istruzione `switch` o in un'istruzione `if`.
```typescript
type Direction = 'up' | 'down';
const move = (direction: Direction) => {
switch (direction) {
case 'up':
console.log("Spostamento verso l'alto");
break;
case 'down':
console.log('Spostamento verso il basso');
break;
default:
const exhaustiveCheck: never = direction;
console.log(exhaustiveCheck); // Questa riga non verrà mai eseguita
}
};
```
Il tipo `never` viene utilizzato per garantire che il caso predefinito sia esaustivo e che TypeScript generi un errore se un nuovo valore viene aggiunto al tipo Direction senza essere gestito nell'istruzione switch.
## Tipi di oggetto
In TypeScript, i tipi di oggetto descrivono la forma di un oggetto. Specificano i nomi e i tipi delle proprietà dell'oggetto, nonché se tali proprietà sono obbligatorie o facoltative.
In TypeScript, è possibile definire i tipi di oggetto in due modi principali:
interface, che definisce la forma di un oggetto specificando i nomi, i tipi e l'opzionalità delle sue proprietà.
```typescript
interface User {
name: string;
age: number;
email?: string;
}
```
Un alias di tipo, simile a un'interfaccia, definisce la forma di un oggetto. Tuttavia, può anche creare un nuovo tipo personalizzato basato su un tipo esistente o su una combinazione di tipi esistenti. Ciò include la definizione di tipi unione, tipi intersezione e altri tipi complessi.
```typescript
type Point = {
x: number;
y: number;
};
```
È anche possibile definire un tipo in modo anonimo:
```typescript
const sum = (x: { a: number; b: number }) => x.a + x.b;
console.log(sum({ a: 5, b: 1 }));
```
## Tipo di tupla (anonimo)
Un tipo di tupla è un tipo che rappresenta un array con un numero fisso di elementi e i relativi tipi. Un tipo di tupla impone un numero specifico di elementi e i rispettivi tipi in un ordine fisso. I tipi di tupla sono utili quando si desidera rappresentare una raccolta di valori con tipi specifici, dove la posizione di ciascun elemento nell'array ha un significato specifico.
```typescript
type Point = [number, number];
```
## Tipo di tupla denominato (etichettato)
I tipi di tupla possono includere etichette o nomi opzionali per ciascun elemento. Queste etichette servono per migliorare la leggibilità e facilitare l'utilizzo degli strumenti e non influiscono sulle operazioni che è possibile eseguire con esse.
```typescript
type T = string;
type Tuple1 = [T, T];
type Tuple2 = [a: T, b: T];
type Tuple3 = [a: T, T]; // Tupla con nome più Tupla anonima
```
## Tupla a lunghezza fissa
Una tupla a lunghezza fissa è un tipo specifico di tupla che impone un numero fisso di elementi di tipi specifici e non consente alcuna modifica alla lunghezza della tupla una volta definita.
Le tuple a lunghezza fissa sono utili quando è necessario rappresentare una raccolta di valori con un numero specifico di elementi e tipi specifici e si desidera garantire che la lunghezza e i tipi della tupla non possano essere modificati inavvertitamente.
```typescript
const x = [10, 'hello'] as const;
x.push(2); // Errore
```
## Tipo Unione
Un Tipo Unione è un tipo che rappresenta un valore che può essere di diversi tipi. I Tipi Unione sono indicati con il simbolo `|` tra ogni tipo possibile.
```typescript
let x: string | number;
x = 'hello'; // Valido
x = 123; // Valido
```
## Tipi Intersezione
Un Tipo Intersezione è un tipo che rappresenta un valore che ha tutte le proprietà di due o più tipi. I Tipi Intersezione sono indicati con il simbolo `&` tra ogni tipo.
```typescript
type X = {
a: string;
};
type Y = {
b: string;
};
type J = X & Y; // Intersezione
const j: J = {
a: 'a',
b: 'b',
};
```
## Indicizzazione dei tipi
L'indicizzazione dei tipi si riferisce alla capacità di definire tipi che possono essere indicizzati da una chiave non nota in anticipo, utilizzando una firma di indice per specificare il tipo per le proprietà che non sono dichiarate esplicitamente.
```typescript
type Dictionary = {
[key: string]: T;
};
const myDict: Dictionary = { a: 'a', b: 'b' };
console.log(myDict['a']); // Restituisce un
```
## Tipo da Valore
In TypeScript, il tipo da valore si riferisce all'inferenza automatica di un tipo da un valore o da un'espressione tramite inferenza di tipo.
```typescript
const x = 'x'; // TypeScript inferisce 'x' come una stringa letterale con 'const' (immutabile), ma lo amplia a 'string' con 'let' (riassegnabile).
```
## Tipo da Ritorno Funzione
Il tipo da Ritorno Funzione si riferisce alla possibilità di inferire automaticamente il tipo di ritorno di una funzione in base alla sua implementazione. Ciò consente a TypeScript di determinare il tipo del valore restituito dalla funzione senza annotazioni di tipo esplicite.
```typescript
const add = (x: number, y: number) => x + y; // TypeScript può dedurre che il tipo restituito dalla funzione sia un numero
```
## Tipo da modulo
Il tipo da modulo si riferisce alla possibilità di utilizzare i valori esportati di un modulo per dedurne automaticamente il tipo. Quando un modulo esporta un valore con un tipo specifico, TypeScript può utilizzare tali informazioni per dedurre automaticamente il tipo di quel valore quando viene importato in un altro modulo.
```typescript
// calc.ts
export const add = (x: number, y: number) => x + y;
// index.ts
import { add } from 'calc';
const r = add(1, 2); // r è un numero
```
## Tipi mappati
I tipi mappati in TypeScript consentono di creare nuovi tipi basati su un tipo esistente trasformando ciascuna proprietà tramite una funzione di mappatura. Mappando i tipi esistenti, è possibile creare nuovi tipi che rappresentano le stesse informazioni in un formato diverso. Per creare un tipo mappato, si accede alle proprietà di un tipo esistente utilizzando l'operatore `keyof` e quindi le si modifica per produrre un nuovo tipo.
Nell'esempio seguente:
```typescript
type MyMappedType = {
[P in keyof T]: T[P][];
};
type MyType = {
foo: string;
bar: number;
};
type MyNewType = MyMappedType;
const x: MyNewType = {
foo: ['hello', 'world'],
bar: [1, 2, 3],
};
```
Definiamo MyMappedType per mappare le proprietà di T, creando un nuovo tipo con ogni proprietà come array del suo tipo originale. In questo modo, creiamo MyNewType per rappresentare le stesse informazioni di MyType, ma con ogni proprietà come array.
## Modificatori di tipo mappati
I modificatori di tipo mappati in TypeScript consentono la trasformazione delle proprietà all'interno di un tipo esistente:
* `readonly` o `+readonly`: questo rende una proprietà nel tipo mappato di sola lettura.
* `-readonly`: questo consente a una proprietà nel tipo mappato di essere modificabile.
* `?`: questo designa una proprietà nel tipo mappato come facoltativa.
Esempi:
```typescript
type ReadOnly = { readonly [P in keyof T]: T[P] }; // Tutte le proprietà contrassegnate come di sola lettura
type Mutable = { -readonly [P in keyof T]: T[P] }; // Tutte le proprietà contrassegnate come modificabili
type MyPartial = { [P in keyof T]?: T[P] }; // Tutte le proprietà contrassegnate come facoltative
```
## Tipi condizionali
I tipi condizionali sono un modo per creare un tipo che dipende da una condizione, in cui il tipo da creare viene determinato in base al risultato della condizione. Sono definiti utilizzando la parola chiave `extends` e un operatore ternario per scegliere condizionatamente tra due tipi.
```typescript
type IsArray = T extends any[] ? true : false;
const myArray = [1, 2, 3];
const myNumber = 42;
type IsMyArrayAnArray = IsArray; // Type true
type IsMyNumberAnArray = IsArray; // Type false
```
## Tipi condizionali distributivi
I tipi condizionali distributivi sono una funzionalità che consente di distribuire un tipo su un'unione di tipi, applicando una trasformazione a ciascun membro dell'unione individualmente.
Questo può essere particolarmente utile quando si lavora con tipi mappati o tipi di ordine superiore.
```typescript
type Nullable = T extends any ? T | null : never;
type NumberOrBool = number | boolean;
type NullableNumberOrBool = Nullable; // number | boolean | null
```
## infer Inferenza di tipo nei tipi condizionali
La parola chiave `infer` viene utilizzata nei tipi condizionali per inferire (estrarre) il tipo di un parametro generico da un tipo che dipende da esso. Questo consente di scrivere definizioni di tipo più flessibili e riutilizzabili.
```typescript
type ElementType = T extends (infer U)[] ? U : never;
type Numbers = ElementType; // number
type Strings = ElementType; // string
```
## Tipi Condizionali Predefiniti
In TypeScript, i Tipi Condizionali Predefiniti sono tipi condizionali integrati forniti dal linguaggio. Sono progettati per eseguire trasformazioni di tipo comuni in base alle caratteristiche di un dato tipo.
`Exclude`: questo tipo rimuove da Type tutti i tipi assegnabili a ExcludedType.
`Extract`: questo tipo estrae da Union tutti i tipi assegnabili a Type.
`NonNullable`: questo tipo rimuove null e undefined da Type.
`ReturnType`: questo tipo estrae il tipo di ritorno di un Type di funzione.
`Parameters`: questo tipo estrae i tipi di parametro di un Type di funzione.
`Required`: Questo tipo rende obbligatorie tutte le proprietà in Type.
`Partial`: Questo tipo rende facoltative tutte le proprietà in Type.
`Readonly`: Questo tipo rende di sola lettura tutte le proprietà in Type.
## Tipi di unione di template
I tipi di unione di template possono essere utilizzati per unire e manipolare il testo all'interno del sistema di tipi, ad esempio:
```typescript
type Status = 'active' | 'inactive';
type Products = 'p1' | 'p2';
type ProductId = `id-${Products}-${Status}`; // "id-p1-active" | "id-p1-inactive" | "id-p2-active" | "id-p2-inactive"
```
## Tipo Any
Il tipo `any` è un tipo speciale (supertipo universale) che può essere utilizzato per rappresentare qualsiasi tipo di valore (primitive, oggetti, array, funzioni, errori, simboli). Viene spesso utilizzato in situazioni in cui il tipo di un valore non è noto in fase di compilazione, o quando si lavora con valori provenienti da API o librerie esterne che non dispongono di tipi TypeScript.
Utilizzando il tipo `any`, si indica al compilatore TypeScript che i valori devono essere rappresentati senza alcuna limitazione. Per massimizzare la sicurezza dei tipi nel codice, tieni presente quanto segue:
* Limitare l'utilizzo di `any` a casi specifici in cui il tipo è realmente sconosciuto.
* Non restituire `any` da una funzione, poiché ciò indebolisce la sicurezza del tipo nel codice che lo utilizza.
* Invece di `any`, utilizzare `@ts-ignore` se è necessario silenziare il compilatore.
```typescript
let value: any;
value = true; // Valido
value = 7; // Valido
```
## Tipo Unknown
In TypeScript, il tipo `unknown` rappresenta un valore di tipo sconosciuto. A differenza del tipo `any`, che consente qualsiasi tipo di valore, `unknown` richiede un controllo o un'asserzione di tipo prima di poter essere utilizzato in un modo specifico, quindi non sono consentite operazioni su un `unknown` senza prima aver effettuato un'asserzione o aver limitato il campo a un tipo più specifico.
Il tipo `unknown` è assegnabile solo a qualsiasi tipo e il tipo `unknown` stesso è un'alternativa type-safe ad `any`.
```typescript
let value: unknown;
let value1: unknown = value; // Valido
let value2: any = value; // Valido
let value3: boolean = value; // Non valido
let value4: number = value; // Non valido
```
```typescript
const add = (a: unknown, b: unknown): number | undefined =>
typeof a === 'number' && typeof b === 'number' ? a + b : undefined;
console.log(add(1, 2)); // 3
console.log(add('x', 2)); // non definito
```
## Tipo Void
Il tipo `void` viene utilizzato per indicare che una funzione non restituisce un valore.
```typescript
const sayHello = (): void => {
console.log('Hello!');
};
```
## Tipo Never
Il tipo `never` rappresenta valori che non si verificano mai. Viene utilizzato per indicare funzioni o espressioni che non restituiscono mai né generano errori.
Ad esempio, un ciclo infinito:
```typescript
const infiniteLoop = (): never => {
while (true) {
// fai qualcosa
}
};
```
Generazione di un errore:
```typescript
const throwError = (message: string): never => {
throw new Error(message);
};
```
Il tipo `never` è utile per garantire la sicurezza dei tipi e rilevare potenziali errori nel codice. Aiuta TypeScript ad analizzare e dedurre tipi più precisi se utilizzato in combinazione con altri tipi e istruzioni di controllo del flusso, ad esempio:
```typescript
type Direction = 'up' | 'down';
const move = (direction: Direction): void => {
switch (direction) {
case 'up':
// sposta verso l'alto
break;
case 'down':
// sposta verso il basso
break;
default:
const exhaustiveCheck: never = direction;
throw new Error(`Unhandled direction: ${exhaustiveCheck}`);
}
};
```
## Interfaccia e tipo
### Sintassi comune
In TypeScript, le interfacce definiscono la struttura degli oggetti, specificando i nomi e i tipi di proprietà o metodi che un oggetto deve avere. La sintassi comune per definire un'interfaccia in TypeScript è la seguente:
```typescript
interface InterfaceName {
property1: Type1;
// ...
method1(arg1: ArgType1, arg2: ArgType2): ReturnType;
// ...
}
```
Analogamente per la definizione del tipo:
```typescript
type TypeName = {
property1: Type1;
// ...
method1(arg1: ArgType1, arg2: ArgType2): ReturnType;
// ...
};
```
`interface InterfaceName` o `type TypeName`: Definisce il nome dell'interfaccia.
`property1`: `Type1`: Specifica le proprietà dell'interfaccia insieme ai tipi corrispondenti. È possibile definire più proprietà, ciascuna separata da un punto e virgola.
`method1(arg1: ArgType1, arg2: ArgType2): ReturnType;`: Specifica i metodi dell'interfaccia. I metodi sono definiti con i loro nomi, seguiti da un elenco di parametri tra parentesi e dal tipo di ritorno. È possibile definire più metodi, ciascuno separato da un punto e virgola.
Esempio di interfaccia:
```typescript
interface Person {
name: string;
age: number;
greet(): void;
}
```
Esempio di tipo:
```typescript
type TypeName = {
property1: string;
method1(arg1: string, arg2: string): string;
};
```
In TypeScript, i tipi vengono utilizzati per definire la forma dei dati e applicare il controllo dei tipi. Esistono diverse sintassi comuni per la definizione dei tipi in TypeScript, a seconda del caso d'uso specifico. Ecco alcuni esempi:
### Tipi di base
```typescript
let myNumber: number = 123; // number type
let myBoolean: boolean = true; // boolean type
let myArray: string[] = ['a', 'b']; // array di stringhe
let myTuple: [string, number] = ['a', 123]; // tupla
```
### Oggetti e interfacce
```typescript
const x: { name: string; age: number } = { name: 'Simon', age: 7 };
```
### Tipi di unione e intersezione
```typescript
type MyType = string | number; // Union type
let myUnion: MyType = 'hello'; // Can be a string
myUnion = 123; // Or a number
type TypeA = { name: string };
type TypeB = { age: number };
type CombinedType = TypeA & TypeB; // Intersection type
let myCombined: CombinedType = { name: 'John', age: 25 }; // Object with name and age properties
```
## Primitive di tipo predefinite
TypeScript dispone di diverse primitive di tipo predefinite che possono essere utilizzate per definire variabili, parametri di funzione e tipi restituiti:
* `number`: rappresenta valori numerici, inclusi numeri interi e numeri in virgola mobile.
* `string`: rappresenta dati testuali.
* `boolean`: rappresenta valori logici, che possono essere true o false.
* `null`: rappresenta l'assenza di un valore.
* `undefined`: rappresenta un valore che non è stato assegnato o non è stato definito.
* `symbol`: rappresenta un identificatore univoco. I simboli vengono in genere utilizzati come chiavi per le proprietà degli oggetti.
* `bigint`: rappresenta numeri interi con precisione arbitraria.
* `any`: rappresenta un tipo dinamico o sconosciuto. Le variabili di tipo any possono contenere valori di qualsiasi tipo e ignorano il controllo del tipo. \* `void`: rappresenta l'assenza di qualsiasi tipo. È comunemente usato come tipo di ritorno di funzioni che non restituiscono alcun valore.
* `never`: rappresenta un tipo per valori che non si verificano mai. È tipicamente usato come tipo di ritorno di funzioni che generano un errore o entrano in un ciclo infinito.
## Oggetti JavaScript predefiniti comuni
TypeScript è un superset di JavaScript e include tutti gli oggetti JavaScript predefiniti comunemente usati. Un elenco completo di questi oggetti è disponibile sul sito web di documentazione di Mozilla Developer Network (MDN):
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects)
Ecco un elenco di alcuni oggetti JavaScript predefiniti comunemente utilizzati:
* Function
* Object
* Boolean
* Error
* Number
* BigInt
* Math
* Date
* String
* RegExp
* Array
* Map
* Set
* Promise
* Intl
## Sovraccarichi
Gli overload di funzione in TypeScript consentono di definire più firme di funzione per un singolo nome di funzione, consentendo di definire funzioni che possono essere chiamate in più modi. Ecco un esempio:
```typescript
// Sovraccarichi
function sayHi(name: string): string;
function sayHi(names: string[]): string[];
// Implementazione
function sayHi(name: unknown): unknown {
if (typeof name === 'string') {
return `Ciao, ${name}!`;
} else if (Array.isArray(name)) {
return name.map(name => `Ciao, ${name}!`);
}
throw new Error('Valore non valido');
}
sayHi('xx'); // Valido
sayHi(['aa', 'bb']); // Valido
```
Ecco un altro esempio di utilizzo di overload di funzione all'interno di una `class`:
```typescript
class Greeter {
message: string;
constructor(message: string) {
this.message = message;
}
// Sovraccarichi
sayHi(name: string): string;
sayHi(name: string[]): ReadonlyArray;
// Implementazione
sayHi(name: unknown): unknown {
if (typeof name === 'string') {
return `${this.message}, ${name}!`;
} else if (Array.isArray(name)) {
return name.map(name => `${this.message}, ${name}!`);
}
throw new Error('value is invalid');
}
}
console.log(new Greeter('Hello').sayHi('Simon'));
```
## Unione ed estensione
Merging ed estensione si riferiscono a due concetti diversi relativi all'utilizzo di tipi e interfacce.
Merging consente di combinare più dichiarazioni con lo stesso nome in un'unica definizione, ad esempio quando si definisce un'interfaccia con lo stesso nome più volte:
```typescript
interface X {
a: string;
}
interface X {
b: number;
}
const person: X = {
a: 'a',
b: 7,
};
```
L'estensione si riferisce alla possibilità di estendere o ereditare da tipi o interfacce esistenti per crearne di nuovi. È un meccanismo per aggiungere proprietà o metodi aggiuntivi a un tipo esistente senza modificarne la definizione originale. Esempio:
```typescript
interface Animal {
name: string;
eat(): void;
}
interface Bird extends Animal {
sing(): void;
}
const dog: Bird = {
name: 'Bird 1',
eat() {
console.log('Eating');
},
sing() {
console.log('Singing');
},
};
```
## Differenze tra tipo e interfaccia
Unione delle dichiarazioni (aumento):
Le interfacce supportano l'unione delle dichiarazioni, il che significa che è possibile definire più interfacce con lo stesso nome e TypeScript le unirà in un'unica interfaccia con le proprietà e i metodi combinati. D'altra parte, i tipi non supportano l'unione delle dichiarazioni. Questo può essere utile quando si desidera aggiungere funzionalità extra o personalizzare i tipi esistenti senza modificare le definizioni originali o correggere tipi mancanti o errati.
```typescript
interface A {
x: string;
}
interface A {
y: string;
}
const j: A = {
x: 'xx',
y: 'yy',
};
```
Estensione di altri tipi/interfacce:
Sia i tipi che le interfacce possono estendere altri tipi/interfacce, ma la sintassi è diversa. Con le interfacce, si utilizza la parola chiave `extends` per ereditare proprietà e metodi da altre interfacce. Tuttavia, un'interfaccia non può estendere un tipo complesso come un tipo unione.
```typescript
interface A {
x: string;
y: number;
}
interface B extends A {
z: string;
}
const car: B = {
x: 'x',
y: 123,
z: 'z',
};
```
Per i tipi, si utilizza l'operatore & per combinare più tipi in un unico tipo (intersezione).
```typescript
interface A {
x: string;
y: number;
}
type B = A & {
j: string;
};
const c: B = {
x: 'x',
y: 123,
j: 'j',
};
```
Tipi di unione e intersezione:
I tipi sono più flessibili quando si tratta di definire tipi di unione e intersezione. Con la parola chiave `type`, è possibile creare facilmente tipi di unione utilizzando l'operatore `|` e tipi di intersezione utilizzando l'operatore `&`. Sebbene le interfacce possano anche rappresentare tipi di unione indirettamente, non dispongono di supporto integrato per i tipi di intersezione.
```typescript
type Department = 'dep-x' | 'dep-y'; // Unione
type Person = {
name: string;
age: number;
};
type Employee = {
id: number;
department: Department;
};
type EmployeeInfo = Person & Employee; // Intersezione
```
Esempio con interfacce:
```typescript
interface A {
x: 'x';
}
interface B {
y: 'y';
}
type C = A | B; // Unione di interfacce
```
## Classe
### Sintassi comune della classe
La parola chiave `class` viene utilizzata in TypeScript per definire una classe. Di seguito è riportato un esempio:
```typescript
class Person {
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public sayHi(): void {
console.log(`Ciao, mi chiamo ${this.name} e ho ${this.age} anni.`);
}
}
```
La parola chiave `class` viene utilizzata per definire una classe denominata "Person".
La classe ha due proprietà private: name di tipo `string` ed age di tipo `number`.
Il costruttore viene definito utilizzando la parola chiave `constructor`. Accetta name ed age come parametri e li assegna alle proprietà corrispondenti.
La classe ha un metodo `public` denominato sayHi che registra un messaggio di saluto.
Per creare un'istanza di una classe in TypeScript, è possibile utilizzare la parola chiave `new` seguita dal nome della classe, seguito da parentesi `()`. Ad esempio:
```typescript
const myObject = new Person('John Doe', 25);
myObject.sayHi(); // Output: Ciao, mi chiamo John Doe e ho 25 anni.
```
### Costruttore
I costruttori sono metodi speciali all'interno di una classe che vengono utilizzati per inizializzare le proprietà dell'oggetto quando viene creata un'istanza della classe.
```typescript
class Person {
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Ciao, mi chiamo ${this.name} e ho ${this.age} anni.`);
}
}
const john = new Person('Simon', 17);
john.sayHello();
```
È possibile sovraccaricare un costruttore utilizzando la seguente sintassi:
```typescript
type Sex = 'm' | 'f';
class Person {
name: string;
age: number;
sex: Sex;
constructor(name: string, age: number, sex?: Sex);
constructor(name: string, age: number, sex: Sex) {
this.name = name;
this.age = age;
this.sex = sex ?? 'm';
}
}
const p1 = new Person('Simon', 17);
const p2 = new Person('Alice', 22, 'f');
```
In TypeScript, è possibile definire più overload del costruttore, ma è possibile avere una sola implementazione che deve essere compatibile con tutti gli overload; questo si può ottenere utilizzando un parametro opzionale.
```typescript
class Person {
name: string;
age: number;
constructor();
constructor(name: string);
constructor(name: string, age: number);
constructor(name?: string, age?: number) {
this.name = name ?? 'Sconosciuto';
this.age = age ?? 0;
}
displayInfo() {
console.log(`Nome: ${this.name}, Età: ${this.age}`);
}
}
const person1 = new Person();
person1.displayInfo(); // Nome: unknown, Età: 0
const person2 = new Person('John');
person2.displayInfo(); // Nome: John, Età: 0
const person3 = new Person('Jane', 25);
person3.displayInfo(); // Nome: Jane, Età: 25
```
### Costruttori privati e protetti
In TypeScript, i costruttori possono essere contrassegnati come privati o protetti, il che ne limita l'accessibilità e l'utilizzo.
Costruttori privati:
possono essere chiamati solo all'interno della classe stessa. I costruttori privati vengono spesso utilizzati in scenari in cui si desidera applicare un pattern singleton o limitare la creazione di istanze a un metodo factory all'interno della classe.
Costruttori protetti:
I costruttori protetti sono utili quando si desidera creare una classe base che non deve essere istanziata direttamente, ma può essere estesa tramite sottoclassi.
```typescript
class BaseClass {
protected constructor() {}
}
class DerivedClass extends BaseClass {
private value: number;
constructor(value: number) {
super();
this.value = value;
}
}
// Il tentativo di istanziare direttamente la classe base genererà un errore.
// const baseObj = new BaseClass(); // Errore: il costruttore della classe 'BaseClass' è protetto.
// Crea un'istanza della classe derivata
const derivedObj = new DerivedClass(10);
```
### Modificatori di accesso
I modificatori di accesso `private`, `protected` e `public` vengono utilizzati per controllare la visibilità e l'accessibilità dei membri della classe, come proprietà e metodi, nelle classi TypeScript. Questi modificatori sono essenziali per applicare l'incapsulamento e stabilire limiti per l'accesso e la modifica dello stato interno di una classe.
Il modificatore `private` limita l'accesso al membro della classe solo all'interno della classe contenitore.
Il modificatore `protected` consente l'accesso al membro della classe all'interno della classe contenitore e delle sue classi derivate.
Il modificatore `public` fornisce accesso illimitato al membro della classe, consentendone l'accesso da qualsiasi luogo.
### Get e Set
Getter e setter sono metodi speciali che consentono di definire un comportamento personalizzato di accesso e modifica per le proprietà della classe. Consentono di incapsulare lo stato interno di un oggetto e di fornire logica aggiuntiva durante l'ottenimento o l'impostazione dei valori delle proprietà.
In TypeScript, getter e setter sono definiti utilizzando rispettivamente le parole chiave `get` e `set`. Ecco un esempio:
```typescript
class MyClass {
private _myProperty: string;
constructor(value: string) {
this._myProperty = value;
}
get myProperty(): string {
return this._myProperty;
}
set myProperty(value: string) {
this._myProperty = value;
}
}
```
### Accessori automatici nelle classi
TypeScript versione 4.9 aggiunge il supporto per auto-accessor, una funzionalità ECMAScript di prossima uscita. Assomigliano alle proprietà di classe, ma sono dichiarate con la parola chiave "accessor".
```typescript
class Animal {
accessor name: string;
constructor(name: string) {
this.name = name;
}
}
```
Gli auto-accessor vengono "de-sugared" in accessor privati `get` e `set`, che operano su una proprietà inaccessibile.
```typescript
class Animal {
#__name: string;
get name() {
return this.#__name;
}
set name(value: string) {
this.#__name = name;
}
constructor(name: string) {
this.name = name;
}
}
```
### this
In TypeScript, la parola chiave `this` si riferisce all'istanza corrente di una classe all'interno dei suoi metodi o costruttori. Permette di accedere e modificare le proprietà e i metodi della classe. dall'interno del proprio ambito.
Fornisce un modo per accedere e manipolare lo stato interno di un oggetto all'interno dei propri metodi.
```typescript
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
public introduce(): void {
console.log(`Ciao, mi chiamo ${this.name}.`);
}
}
const person1 = new Person('Alice');
person1.introduce(); // Ciao, mi chiamo Alice.
```
### Proprietà dei parametri
Le proprietà dei parametri consentono di dichiarare e inizializzare le proprietà della classe direttamente all'interno dei parametri del costruttore, evitando codice boilerplate, ad esempio:
```typescript
class Person {
constructor(
private name: string,
public age: number
) {
// Le parole chiave "private" e "public" nel costruttore
// dichiarano e inizializzano automaticamente le proprietà della classe corrispondenti.
}
public introduce(): void {
console.log(`Ciao, mi chiamo ${this.name} e ho ${this.age} anni.`);
}
}
const person = new Person('Alice', 25);
person.introduce();
```
### Classi astratte
Le classi astratte sono utilizzate in TypeScript principalmente per l'ereditarietà, poiché forniscono un modo per definire proprietà e metodi comuni che possono essere ereditati dalle sottoclassi.
Questo è utile quando si desidera definire un comportamento comune e imporre alle sottoclassi di implementare determinati metodi. Forniscono un modo per creare una gerarchia di classi in cui la classe base astratta fornisce un'interfaccia condivisa e funzionalità comuni per le sottoclassi.
```typescript
abstract class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
abstract makeSound(): void;
}
class Cat extends Animal {
makeSound(): void {
console.log(`${this.name} miagola.`);
}
}
const cat = new Cat('Whiskers');
cat.makeSound(); // Output: Whiskers miagola.
```
### Con i generici
Le classi con i generici consentono di definire classi riutilizzabili che possono funzionare con tipi diversi.
```typescript
class Container {
private item: T;
constructor(item: T) {
this.item = item;
}
getItem(): T {
return this.item;
}
setItem(item: T): void {
this.item = item;
}
}
const container1 = new Container(42);
console.log(container1.getItem()); // 42
const container2 = new Container('Hello');
container2.setItem('World');
console.log(container2.getItem()); // Mondo
```
### Decoratori
I decoratori forniscono un meccanismo per aggiungere metadati, modificare il comportamento, convalidare o estendere la funzionalità dell'elemento di destinazione. Sono funzioni che vengono eseguite in fase di esecuzione. È possibile applicare più decoratori a una dichiarazione.
I decoratori sono funzionalità sperimentali e gli esempi seguenti sono compatibili solo con TypeScript versione 5 o successive che utilizzano ES6.
Per le versioni di TypeScript precedenti alla 5, dovrebbero essere abilitati utilizzando la proprietà `experimentalDecorators` nel file `tsconfig.json` o utilizzando `--experimentalDecorators` nella riga di comando (ma l'esempio seguente non funzionerà).
Alcuni dei casi d'uso comuni per i decoratori includono:
* Monitoraggio delle modifiche delle proprietà.
* Monitoraggio delle chiamate ai metodi.
* Aggiunta di proprietà o metodi aggiuntivi.
* Validazione in fase di esecuzione.
* Serializzazione e deserializzazione automatica.
* Registrazione.
* Autorizzazione e autenticazione.
* Protezione dagli errori.
Nota: i decoratori per la versione 5 non consentono parametri di decorazione.
Tipi di decoratori:
#### Decoratori di classe
I decoratori di classe sono utili per estendere una classe esistente, ad esempio aggiungendo proprietà o metodi o raccogliendo istanze di una classe. Nell'esempio seguente, aggiungiamo un metodo `toString` che converte la classe in una rappresentazione in formato stringa.
```typescript
type Constructor = new (...args: any[]) => T;
function toString(
Value: Class,
context: ClassDecoratorContext
) {
return class extends Value {
constructor(...args: any[]) {
super(...args);
console.log(JSON.stringify(this));
console.log(JSON.stringify(context));
}
};
}
@toString
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
return 'Ciao,' + this.name;
}
}
const person = new Person('Simon');
/* Log:
{"name":"Simon"}
{"kind":"class","name":"Person"}
*/
```
#### Decoratore di proprietà
I decoratori di proprietà sono utili per modificare il comportamento di una proprietà, ad esempio cambiando i valori di inizializzazione. Nel codice seguente, abbiamo uno script che imposta una proprietà in modo che sia sempre in maiuscolo:
```typescript
function upperCase(
target: undefined,
context: ClassFieldDecoratorContext
) {
return function (this: T, value: string) {
return value.toUpperCase();
};
}
class MyClass {
@upperCase
prop1 = 'hello!';
}
console.log(new MyClass().prop1); // Log: HELLO!
```
#### Decoratore di metodo
I decoratori di metodo consentono di modificare o migliorare il comportamento dei metodi. Di seguito è riportato un esempio di un semplice logger:
```typescript
function log(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<
This,
(this: This, ...args: Args) => Return
>
) {
const methodName = String(context.name);
function replacementMethod(this: This, ...args: Args): Return {
console.log(`LOG: Accesso al metodo '${methodName}'.`);
const result = target.call(this, ...args);
console.log(`LOG: Uscita dal metodo '${methodName}'.`);
return result;
}
return replacementMethod;
}
class MyClass {
@log
sayHello() {
console.log('Ciao!');
}
}
new MyClass().sayHello();
```
Registra:
```shell
LOG: Accesso al metodo 'sayHello'.
Ciao!
LOG: Uscita dal metodo 'sayHello'.
```
#### Decoratori Getter e Setter
I decoratori Getter e Setter consentono di modificare o migliorare il comportamento degli accessor di classe. Sono utili, ad esempio, per convalidare le assegnazioni di proprietà. Ecco un semplice esempio di decoratore getter:
```typescript
function range(min: number, max: number) {
return function (
target: (this: This) => Return,
context: ClassGetterDecoratorContext
) {
return function (this: This): Return {
const value = target.call(this);
if (value < min || value > max) {
throw 'Invalid';
}
Object.defineProperty(this, context.name, {
value,
enumerable: true,
});
return value;
};
};
}
class MyClass {
private _value = 0;
constructor(value: number) {
this._value = value;
}
@range(1, 100)
get getValue(): number {
return this._value;
}
}
const obj = new MyClass(10);
console.log(obj.getValue); // Valido: 10
const obj2 = new MyClass(999);
console.log(obj2.getValue); // Throw: Invalid!
```
#### Metadati del decoratore
I metadati del decoratore semplificano il processo per i decoratori di applicare e utilizzare i metadati in qualsiasi classe. Possono accedere a una nuova proprietà metadati sull'oggetto contesto, che può fungere da chiave sia per le primitive che per gli oggetti.
Le informazioni sui metadati sono accessibili sulla classe tramite `Symbol.metadata`.
I metadati possono essere utilizzati per vari scopi, come il debug, la serializzazione o l'iniezione di dipendenze con i decoratori.
```typescript
//@ts-ignore
Symbol.metadata ??= Symbol('Symbol.metadata'); // Simple polyfill
type Context =
| ClassFieldDecoratorContext
| ClassAccessorDecoratorContext
| ClassMethodDecoratorContext; // Il contesto contiene la proprietà metadati: DecoratorMetadata
function setMetadata(_target: any, context: Context) {
// Imposta l'oggetto metadati con un valore primitivo
context.metadata[context.name] = true;
}
class MyClass {
@setMetadata
a = 123;
@setMetadata
accessor b = 'b';
@setMetadata
fn() {}
}
const metadata = MyClass[Symbol.metadata]; // Ottieni informazioni sui metadati
console.log(JSON.stringify(metadata)); // {"bar":true,"baz":true,"foo":true}
```
### Ereditarietà
L'ereditarietà si riferisce al meccanismo mediante il quale una classe può ereditare proprietà e metodi da un'altra classe, nota come classe base o superclasse. La classe derivata, chiamata anche classe figlia o sottoclasse, può estendere e specializzare le funzionalità della classe base aggiungendo nuove proprietà e metodi o sovrascrivendo quelli esistenti.
```typescript
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): void {
console.log("L'animale emette un suono");
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
speak(): void {
console.log('Woof! Woof!');
}
}
// Crea un'istanza della classe base
const animal = new Animal('Animale generico');
animal.speak(); // L'animale emette un suono
// Crea un'istanza della classe derivata
const dog = new Dog('Max', 'Labrador');
dog.speak(); // Woof! Bau!"
```
TypeScript non supporta l'ereditarietà multipla nel senso tradizionale, ma consente invece l'ereditarietà da una singola classe base.
TypeScript supporta più interfacce. Un'interfaccia può definire un contratto per la struttura di un oggetto e una classe può implementare più interfacce. Questo consente a una classe di ereditare comportamento e struttura da più fonti.
```typescript
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
class FlyingFish implements Flyable, Swimmable {
fly() {
console.log('Flying...');
}
swim() {
console.log('Swimming...');
}
}
const flyingFish = new FlyingFish();
flyingFish.fly();
flyingFish.swim();
```
La parola chiave `class` in TypeScript, simile a JavaScript, è spesso definita "syntactic sugar". È stata introdotta in ECMAScript 2015 (ES6) offre una sintassi più familiare per la creazione e l'utilizzo di oggetti in modalità basata sulle classi. Tuttavia, è importante notare che TypeScript, essendo un superset di JavaScript, alla fine si compila in JavaScript, che rimane fondamentalmente basato sui prototipi.
### Statiche
TypeScript ha membri statici. Per accedere ai membri statici di una classe, è possibile utilizzare il nome della classe seguito da un punto, senza dover creare un oggetto.
```typescript
class OfficeWorker {
static memberCount: number = 0;
constructor(private name: string) {
OfficeWorker.memberCount++;
}
}
const w1 = new OfficeWorker('James');
const w2 = new OfficeWorker('Simon');
const total = OfficeWorker.memberCount;
console.log(total); // 2
```
### Inizializzazione delle proprietà
Esistono diversi modi per Inizializza le proprietà per una classe in TypeScript:
Inline:
Nell'esempio seguente, questi valori iniziali verranno utilizzati quando verrà creata un'istanza della classe.
```typescript
class MyClass {
property1: string = 'default value';
property2: number = 42;
}
```
Nel costruttore:
```typescript
class MyClass {
property1: string;
property2: number;
constructor() {
this.property1 = 'default value';
this.property2 = 42;
}
}
```
Utilizzo dei parametri del costruttore:
```typescript
class MyClass {
constructor(
private property1: string = 'default value',
public property2: number = 42
) {
// Non è necessario assegnare esplicitamente i valori alle proprietà.
}
log() {
console.log(this.property2);
}
}
const x = new MyClass();
x.log();
```
### Sovraccarico dei metodi
Il sovraccarico dei metodi consente a una classe di avere più metodi con lo stesso nome ma tipi di parametri diversi o un numero diverso di parametri. Questo ci permette di chiamare un metodo in modi diversi in base agli argomenti passati.
```typescript
class MyClass {
add(a: number, b: number): number; // Firma di sovraccarico 1
add(a: string, b: string): string; // Firma di sovraccarico 2
add(a: number | string, b: number | string): number | string {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
if (typeof a === 'string' && typeof b === 'string') {
return a.concat(b);
}
throw new Error('Argomenti non validi');
}
}
const r = new MyClass();
console.log(r.add(10, 5)); // Log 15
```
## Generici
I generici consentono di creare componenti e funzioni riutilizzabili che possono funzionare con più tipi. Con i generici, è possibile parametrizzare tipi, funzioni e interfacce, consentendo loro di operare su tipi diversi senza doverli specificare esplicitamente in anticipo.
I generici consentono di rendere il codice più flessibile e riutilizzabile.
### Tipo generico
Per definire un tipo generico, si utilizzano le parentesi angolari (`<>`) per specificare i parametri di tipo, ad esempio:
```typescript
function identity(arg: T): T {
return arg;
}
const a = identity('x');
const b = identity(123);
const getLen = (data: ReadonlyArray) => data.length;
const len = getLen([1, 2, 3]);
```
### Classi generiche
I generici possono essere applicati anche alle classi, in questo modo possono lavorare con più tipi utilizzando parametri di tipo. Questo è utile per creare definizioni di classe riutilizzabili che possono operare su diversi tipi di dati mantenendo la sicurezza dei tipi.
```typescript
class Container {
private item: T;
constructor(item: T) {
this.item = item;
}
getItem(): T {
return this.item;
}
}
const numberContainer = new Container(123);
console.log(numberContainer.getItem()); // 123
const stringContainer = new Container('hello');
console.log(stringContainer.getItem()); // ciao
```
### Vincoli generici
I parametri generici possono essere vincolati utilizzando la parola chiave `extends` seguita da un tipo o un'interfaccia che il parametro di tipo deve soddisfare.
Nell'esempio seguente, T deve contenere una `length` appropriata per essere valido:
```typescript
const printLen = (value: T): void => {
console.log(value.length);
};
printLen('Ciao'); // 5
printLen([1, 2, 3]); // 3
printLen({ length: 10 }); // 10
printLen(123); // Non valido
```
Una caratteristica interessante di generic introdotta nella versione 3.4 RC è l'inferenza di tipo di funzione di ordine superiore, che ha introdotto argomenti di tipo generico propagati:
```typescript
declare function pipe(
ab: (...args: A) => B,
bc: (b: B) => C
): (...args: A) => C;
declare function list(a: T): T[];
declare function box(x: V): { value: V };
const listBox = pipe(list, box); // (a: T) => { value: T[] }
const boxList = pipe(box, list); // (x: V) => { value: V }[]
```
Questa funzionalità consente una programmazione più semplice, sicura e senza punti, comune nella programmazione funzionale.
### Restringimento contestuale generico
Il restringimento contestuale per i generici è il meccanismo di TypeScript che consente al compilatore di restringere il tipo di un parametro generico in base al contesto in cui viene utilizzato. È utile quando si lavora con tipi generici in istruzioni condizionali:
```typescript
function process(value: T): void {
if (typeof value === 'string') {
// Il valore viene ristretto al tipo 'string'
console.log(value.length);
} else if (typeof value === 'number') {
// Il valore viene ristretto al tipo 'number'
console.log(value.toFixed(2));
}
}
process('hello'); // 5
process(3.14159); // 3.14
```
## Tipi strutturali cancellati
In TypeScript, gli oggetti non devono necessariamente corrispondere a un tipo specifico ed esatto. Ad esempio, se creiamo un oggetto che soddisfa i requisiti di un'interfaccia, possiamo utilizzare quell'oggetto nei punti in cui l'interfaccia è richiesta, anche se non esiste una connessione esplicita tra i due.
Esempio:
```typescript
type NameProp1 = {
prop1: string;
};
function log(x: NameProp1) {
console.log(x.prop1);
}
const obj = {
prop2: 123,
prop1: 'Origin',
};
log(obj); // Valido
```
## Namespace
In TypeScript, gli spazi dei nomi vengono utilizzati per organizzare il codice in contenitori logici, prevenendo collisioni di nomi e fornendo un modo per raggruppare il codice correlato.
L'utilizzo delle parole chiave `export` consente l'accesso allo spazio dei nomi nei moduli "esterni".
```typescript
export namespace MyNamespace {
export interface MyInterface1 {
prop1: boolean;
}
export interface MyInterface2 {
prop2: string;
}
}
const a: MyNamespace.MyInterface1 = {
prop1: true,
};
```
## Simboli
I simboli sono un tipo di dati primitivo che rappresenta un valore immutabile la cui unicità globale è garantita per tutta la durata del programma.
I simboli possono essere utilizzati come chiavi per le proprietà degli oggetti e forniscono un modo per creare proprietà non enumerabili.
```typescript
const key1: symbol = Symbol('key1');
const key2: symbol = Symbol('key2');
const obj = {
[key1]: 'value 1',
[key2]: 'value 2',
};
console.log(obj[key1]); // valore 1
console.log(obj[key2]); // valore 2
```
In WeakMaps e WeakSet, i simboli sono ora consentiti come chiavi.
## Direttive con tripla barra
Le direttive con tripla barra sono commenti speciali che forniscono istruzioni al compilatore su come elaborare un file. Queste direttive iniziano con tre barre consecutive (`///`) e sono in genere posizionate all'inizio di un file TypeScript e non hanno alcun effetto sul comportamento in fase di esecuzione.
Le direttive con tripla barra vengono utilizzate per fare riferimento a dipendenze esterne, specificare il comportamento di caricamento dei moduli, abilitare/disabilitare determinate funzionalità del compilatore e altro ancora. Alcuni esempi:
Riferimento a un file di dichiarazione:
```typescript
///
```
Indicare il formato del modulo:
```typescript
///
```
Abilitare le opzioni del compilatore, nell'esempio seguente, in modalità strict:
```typescript
///
```
## Manipolazione dei tipi
### Creazione di tipi da tipi
È possibile creare nuovi tipi componendo, manipolando o trasformando tipi esistenti.
Tipi di intersezione (`&`):
Consentono di combinare più tipi in un unico tipo:
```typescript
type A = { foo: number };
type B = { bar: string };
type C = A & B; // Intersezione di A e B
const obj: C = { foo: 42, bar: 'hello' };
```
Tipi di unione (`|`):
Consente di definire un tipo che può essere di diversi tipi:
```typescript
type Result = string | number;
const value1: Result = 'hello';
const value2: Result = 42;
```
Tipi mappati:
Consentono di trasformare le proprietà di un tipo esistente per crearne uno nuovo:
```typescript
type Mutable = {
readonly [P in keyof T]: T[P];
};
type Person = {
name: string;
age: number;
};
type ImmutablePerson = Mutable; // Le proprietà diventano di sola lettura
```
Tipi condizionali:
Consentono di creare tipi in base ad alcune condizioni:
```typescript
type ExtractParam = T extends (param: infer P) => any ? P : never;
type MyFunction = (name: string) => number;
type ParamType = ExtractParam; // string
```
### Tipi di accesso indicizzati
In TypeScript è possibile accedere e manipolare i tipi di proprietà all'interno di un altro tipo utilizzando un indice, `Type[Key]`.
```typescript
type Person = {
name: string;
age: number;
};
type AgeType = Person['age']; // number
```
```typescript
type MyTuple = [string, number, boolean];
type MyType = MyTuple[2]; // boolean
```
### Tipi di utilità
Diversi tipi di utilità predefiniti possono essere utilizzati per manipolare i tipi; di seguito è riportato un elenco dei più comuni:
#### Awaited\
Costruisce un tipo che esegue ricorsivamente l'unwrapping dei tipi Promise.
```typescript
type A = Awaited>; // string
```
#### Partial\
Costruisce un tipo con tutte le proprietà di T impostate su optional.
```typescript
type Person = {
name: string;
age: number;
};
type A = Partial; // { name?: string | undefined; age?:number | undefined; }
```
#### Required\
Costruisce un tipo con tutte le proprietà di T impostate su required.
```typescript
type Person = {
name?: string;
age?: number;
};
type A = Required; // { name: string; age:number; }
```
#### Readonly\
Costruisce un tipo con tutte le proprietà di T impostate su readonly.
```typescript
type Person = {
name: string;
age: number;
};
type A = Readonly;
const a: A = { name: 'Simon', age: 17 };
a.name = 'John'; // Non valido
```
#### Record\
Costruisce un tipo con un insieme di proprietà K di tipo T.
```typescript
type Product = {
name: string;
price: number;
};
const products: Record = {
apple: { name: 'Apple', price: 0.5 },
banana: { name: 'Banana', price: 0.25 },
};
console.log(products.apple); // { name: 'Apple', price: 0.5 }
```
#### Pick\
Costruisce un tipo selezionando le proprietà specificate K da T.
```typescript
type Product = {
name: string;
price: number;
};
type Price = Pick; // { price: number; }
```
#### Omit\
Costruisce un tipo omettendo le proprietà specificate K da T.
```typescript
type Product = {
name: string;
price: number;
};
type Name = Omit; // { name: string; }
```
#### Exclude\
Costruisce un tipo escludendo tutti i valori di tipo U da T.
```typescript
type Union = 'a' | 'b' | 'c';
type MyType = Exclude; // b
```
#### Extract\
Costruisce un tipo estraendo tutti i valori di tipo U da T.
```typescript
type Union = 'a' | 'b' | 'c';
type MyType = Extract; // a | c
```
#### NonNullable\
Costruisce un tipo escludendo null e undefined da T.
```typescript
type Union = 'a' | null | undefined | 'b';
type MyType = NonNullable; // 'a' | 'b'
```
#### Parameters\
Estrae i tipi di parametro di una funzione di tipo T.
```typescript
type Func = (a: string, b: number) => void;
type MyType = Parameters; // [a: string, b: number]
```
#### ConstructorParameters\
Estrae i tipi di parametro di una funzione costruttore di tipo T.
```typescript
class Person {
constructor(
public name: string,
public age: number
) {}
}
type PersonConstructorParams = ConstructorParameters; // [name: string, age: number]
const params: PersonConstructorParams = ['John', 30];
const person = new Person(...params);
console.log(person); // Person { name: 'John', age: 30 }
```
#### ReturnType\
Estrae il tipo di ritorno di una funzione di tipo T.
```typescript
type Func = (name: string) => number;
type MyType = ReturnType; // number
```
#### InstanceType\
Estrae il tipo di istanza di una classe di tipo T.
```typescript
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
console.log(`Ciao, mi chiamo ${this.name}!`);
}
}
type PersonInstance = InstanceType;
const person: PersonInstance = new Person('John');
person.sayHello(); // Ciao, mi chiamo John!
```
#### ThisParameterType\
Estrae il tipo del parametro 'this' da una funzione di tipo T.
```typescript
interface Person {
name: string;
greet(this: Person): void;
}
type PersonThisType = ThisParameterType; // Person
```
#### OmitThisParameter\
Rimuove il parametro 'this' da una funzione di tipo T.
```typescript
function capitalize(this: String) {
return this[0].toUpperCase + this.substring(1).toLowerCase();
}
type CapitalizeType = OmitThisParameter; // () => string
```
#### ThisType\
Funge da marcatore per un tipo `this` contestuale.
```typescript
type Logger = {
log: (error: string) => void;
};
let helperFunctions: { [name: string]: Function } & ThisType = {
hello: function () {
this.log('some error'); // Valido poiché "log" è parte di "this".
this.update(); // Non valido
},
};
```
#### Uppercase\
Rendi maiuscolo il nome del tipo di input T.
```typescript
type MyType = Uppercase<'abc'>; // "ABC"
```
#### Lowercase\
Rendi minuscolo il nome del tipo di input T.
```typescript
type MyType = Lowercase<'ABC'>; // "abc"
```
#### Capitalize\
Inserisci in maiuscolo il nome del tipo di input T.
```typescript
type MyType = Capitalize<'abc'>; // "Abc"
```
#### Uncapitalize\
Inserisci in maiuscolo il nome del tipo di input T.
```typescript
type MyType = Uncapitalize<'Abc'>; // "abc"
```
#### NoInfer\
NoInfer è un tipo di utilità progettato per bloccare l'inferenza automatica dei tipi nell'ambito di una funzione generica.
Esempio:
```typescript
// Inferenza automatica dei tipi nell'ambito di una funzione generica.
function fn(x: T[], y: T) {
return x.concat(y);
}
const r = fn(['a', 'b'], 'c'); // Il tipo qui è ("a" | "b" | "c")[]
```
Con NoInfer:
```typescript
// Funzione di esempio che utilizza NoInfer per impedire l'inferenza di tipo
function fn2(x: T[], y: NoInfer) {
return x.concat(y);
}
const r2 = fn2(['a', 'b'], 'c'); // Errore: l'argomento di tipo '"c"' non è assegnabile al parametro di tipo '"a" | "b"'.
```
## Altri
### Gestione degli errori e delle eccezioni
TypeScript consente di rilevare e gestire gli errori utilizzando i meccanismi standard di gestione degli errori JavaScript:
Blocchi Try-Catch-Finally:
```typescript
try {
// Codice che potrebbe generare un errore
} catch (error) {
// Gestisci l'errore
} finally {
// Codice che viene sempre eseguito, finally è facoltativo
}
```
È anche possibile gestire diversi tipi di errore:
```typescript
try {
// Codice che potrebbe generare diversi tipi di errore
} catch (error) {
if (error instanceof TypeError) {
// Gestisci TypeError
} else if (error instanceof RangeError) {
// Gestisci RangeError
} else {
// Gestisci altri errori
}
}
```
Tipi di errore personalizzati:
È possibile specificare errori più specifici estendendo la classe `Error`:
```typescript
class CustomError extends Error {
constructor(message: string) {
super(message);
this.name = 'CustomError';
}
}
throw new CustomError('Questo è un errore personalizzato.');
```
### Classi Mixin
Le classi Mixin consentono di combinare e comporre il comportamento di più classi in un'unica classe. Forniscono un modo per riutilizzare ed estendere le funzionalità senza la necessità di catene di ereditarietà profonde.
```typescript
abstract class Identificabile {
name: string = '';
logId() {
console.log('id:', this.name);
}
}
abstract class Selezionabile {
selected: boolean = false;
select() {
this.selected = true;
console.log('Select');
}
deselect() {
this.selected = false;
console.log('Deselect');
}
}
class MyClass {
constructor() {}
}
// Estendi MyClass per includere il comportamento di Identificabile e Selezionabile
interface MyClass extends Identificabile, Selezionabile {}
// Funzione per applicare i mixin a una classe
function applyMixins(source: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
const descriptor = Object.getOwnPropertyDescriptor(
baseCtor.prototype,
name
);
if (descriptor) {
Object.defineProperty(source.prototype, name, descriptor);
}
});
});
}
// Applica i mixin a MyClass
applyMixins(MyClass, [Identificabile, Selezionabile]);
let o = new MyClass();
o.name = 'abc';
o.logId();
o.select();
```
### Funzionalità del linguaggio asincrono
Essendo TypeScript un superset di JavaScript, integra funzionalità del linguaggio asincrono come:
Promise:
Le promise sono un modo per gestire le operazioni asincrone e i loro risultati utilizzando metodi come `.then()` e `.catch()` per gestire le condizioni di successo e di errore.
Per saperne di più: [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
Async/await:
Le parole chiave Async/await sono un modo per fornire una sintassi più sincrona per lavorare con le promise. La parola chiave `async` viene utilizzata per definire una funzione asincrona, mentre la parola chiave `await` viene utilizzata all'interno di una funzione asincrona per mettere in pausa l'esecuzione finché una Promise non viene risolta o rifiutata.
Per saperne di più:
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)
Le seguenti API sono ben supportate in TypeScript:
API Fetch:
[https://developer.mozilla.org/it/docs/Web/API/Fetch_API](https://developer.mozilla.org/it/docs/Web/API/Fetch_API)
Web Worker:
[https://developer.mozilla.org/it/docs/Web/API/Web_Workers_API](https://developer.mozilla.org/it/docs/Web/API/Web_Workers_API)
Condiviso Worker:
[https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker)
WebSocket:
[https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
### Iteratori e Generatori
Sia gli Iteratori che i Generatori sono ben supportati in TypeScript.
Gli Iteratori sono oggetti che implementano il protocollo Iterator, fornendo un modo per accedere agli elementi di una collezione o sequenza uno alla volta. Si tratta di una struttura che contiene un puntatore all'elemento successivo nell'iterazione. Hanno un metodo `next()` che restituisce il valore successivo nella sequenza insieme a un valore booleano che indica se la sequenza è `completata`.
```typescript
class NumberIterator implements Iterable {
private current: number;
constructor(
private start: number,
private end: number
) {
this.current = start;
}
public next(): IteratorResult {
if (this.current <= this.end) {
const value = this.current;
this.current++;
return { value, done: false };
} else {
return { value: undefined, done: true };
}
}
[Symbol.iterator](): Iterator {
return this;
}
}
const iterator = new NumberIterator(1, 3);
for (const num of iterator) {
console.log(num);
}
```
I generatori sono funzioni speciali definite utilizzando la sintassi `function*` che semplifica la creazione di iteratori. Utilizzano la parola chiave `yield` per definire la sequenza di valori e mettono automaticamente in pausa e riprendono l'esecuzione quando vengono richiesti valori.
I generatori semplificano la creazione di iteratori e sono particolarmente utili per lavorare con sequenze di grandi dimensioni o infinite.
Esempio:
```typescript
function* numberGenerator(start: number, end: number): Generator {
for (let i = start; i <= end; i++) {
yield i;
}
}
const generator = numberGenerator(1, 5);
for (const num of generator) {
console.log(num);
}
```
TypeScript supporta anche iteratori e generatori asincroni.
Per saperne di più:
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator)
### Riferimento JSDoc di TsDocs
Quando si lavora con una base di codice JavaScript, è possibile aiutare TypeScript a dedurre il tipo corretto utilizzando commenti JSDoc con annotazioni aggiuntive per fornire informazioni sul tipo.
Esempio:
```typescript
/**
* Calcola la potenza di un numero dato
* @constructor
* @param {number} base – Il valore base dell'espressione
* @param {number} exponent – Il valore esponente dell'espressione
*/
function power(base: number, exponent: number) {
return Math.pow(base, exponent);
}
power(10, 2); // function power(base: number, exponent: number): number
```
La documentazione completa è disponibile a questo link:
[https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html)
Dalla versione 3.7 è possibile generare definizioni di tipo .d.ts dalla sintassi JavaScript JSDoc.
Ulteriori informazioni sono disponibili qui:
[https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html](https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html)
### @types
I pacchetti nell'organizzazione @types sono convenzioni di denominazione speciali utilizzate per fornire definizioni di tipo per librerie o moduli JavaScript esistenti. Ad esempio, usando:
```shell
npm install --save-dev @types/lodash
```
Installerà le definizioni di tipo di `lodash` nel tuo progetto corrente.
Per contribuire alle definizioni di tipo del pacchetto @types, invia una pull request a [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped).
### JSX
JSX (JavaScript XML) è un'estensione della sintassi del linguaggio JavaScript che consente di scrivere codice simile a HTML all'interno dei file JavaScript o TypeScript. Viene comunemente utilizzato in React per definire la struttura HTML.
TypeScript extends le funzionalità di JSX fornendo il controllo dei tipi e l'analisi statica.
Per utilizzare JSX è necessario impostare l'opzione del compilatore `jsx` nel file `tsconfig.json`. Due opzioni di configurazione comuni:
* "preserve": emette file .jsx con il JSX invariato. Questa opzione indica a TypeScript di mantenere la sintassi JSX così com'è e di non trasformarla durante il processo di compilazione. È possibile utilizzare questa opzione se si dispone di uno strumento separato, come Babel, che gestisce la trasformazione.
* "react": abilita la trasformazione JSX integrata di TypeScript. Verrà utilizzato React.createElement.
Tutte le opzioni sono disponibili qui:
[https://www.typescriptlang.org/tsconfig#jsx](https://www.typescriptlang.org/tsconfig#jsx)
### Moduli ES6
TypeScript supporta ES6 (ECMAScript 2015) e molte versioni successive. Ciò significa che è possibile utilizzare la sintassi ES6, come funzioni freccia, letterali template, classi, moduli, destrutturazione e altro ancora.
Per abilitare le funzionalità ES6 nel progetto, è possibile specificare la proprietà `target` nel file tsconfig.json.
Un esempio di configurazione:
```json
{
"compilerOptions": {
"target": "es6",
"module": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist"
},
"include": ["src"]
}
```
### Operatore di elevamento a potenza ES7
L'operatore di elevamento a potenza (`**`) calcola il valore ottenuto elevando il primo operando alla potenza del secondo operando. Funziona in modo simile a `Math.pow()`, ma con la possibilità aggiuntiva di accettare BigInt come operandi.
TypeScript supporta pienamente questo operatore, utilizzandolo come `target` nel file tsconfig.json `es2016` o versione successiva.
```typescript
console.log(2 ** (2 ** 2)); // 16
```
### L'istruzione for-await-of
Questa è una funzionalità JavaScript completamente supportata in TypeScript che consente di iterare su oggetti iterabili asincroni dalla versione target es2018.
```typescript
async function* asyncNumbers(): AsyncIterableIterator {
yield Promise.resolve(1);
yield Promise.resolve(2);
yield Promise.resolve(3);
}
(async () => {
for await (const num of asyncNumbers()) {
console.log(num);
}
})();
```
### Nuova meta-proprietà target
In TypeScript è possibile utilizzare la meta-proprietà `new.target`, che consente di determinare se una funzione o un costruttore è stato invocato utilizzando l'operatore new. Permette inoltre di rilevare se un oggetto è stato creato a seguito di una chiamata al costruttore.
```typescript
class Parent {
constructor() {
console.log(new.target); // Registra la funzione costruttore utilizzata per creare un'istanza
}
}
class Child extends Parent {
constructor() {
super();
}
}
const parentX = new Parent(); // [Function: Parent]
const child = new Child(); // [Function: Child]
```
### Espressioni di importazione dinamica
È possibile caricare i moduli in modo condizionale o caricarli in modo differito su richiesta utilizzando la proposta ECMAScript per l'importazione dinamica, supportata in TypeScript.
La sintassi per le espressioni di importazione dinamica in TypeScript è la seguente:
```typescript
async function renderWidget() {
const container = document.getElementById('widget');
if (container !== null) {
const widget = await import('./widget'); // Importazione dinamica
widget.render(container);
}
}
renderWidget();
```
### "tsc –watch"
Questo comando avvia un compilatore TypeScript con il parametro `--watch`, con la possibilità di ricompilare automaticamente i file TypeScript ogni volta che vengono modificati.
```shell
tsc --watch
```
A partire dalla versione 4.9 di TypeScript, il monitoraggio dei file si basa principalmente sugli eventi del file system, ricorrendo automaticamente al polling se non è possibile stabilire un watcher basato sugli eventi.
### Operatore di asserzione non nullo
L'operatore di asserzione non nullo (Postfix !), noto anche come asserzione di assegnazione definita, è una funzionalità di TypeScript che consente di affermare che una variabile o una proprietà non è nulla o indefinita, anche se l'analisi statica dei tipi di TypeScript suggerisce che potrebbe esserlo. Con questa funzionalità è possibile rimuovere qualsiasi controllo esplicito.
```typescript
type Person = {
name: string;
};
const printName = (person?: Person) => {
console.log(`Name is ${person!.name}`);
};
```
### Dichiarazioni predefinite
Le dichiarazioni predefinite vengono utilizzate quando a una variabile o a un parametro viene assegnato un valore predefinito. Ciò significa che se non viene fornito alcun valore per quella variabile o parametro, verrà utilizzato il valore predefinito.
```typescript
function greet(name: string = 'Anonymous'): void {
console.log(`Ciao, ${name}!`);
}
greet(); // Ciao, Anonymous!
greet('John'); // Ciao, John!
```
### Concatenamento opzionale
L'operatore di concatenamento opzionale `?.` funziona come il normale operatore punto (`.`) per accedere a proprietà o metodi. Tuttavia, gestisce in modo elegante i valori nulli o indefiniti terminando l'espressione e restituendo `undefined`, invece di generare un errore.
```typescript
type Person = {
name: string;
age?: number;
address?: {
street?: string;
city?: string;
};
};
const person: Person = {
name: 'John',
};
console.log(person.address?.city); // indefinito
```
### Operatore di coalescenza nullo
L'operatore di coalescenza nullo `??` restituisce il valore del lato destro se il lato sinistro è `null` o `undefined`; in caso contrario, restituisce il valore del lato sinistro.
```typescript
const foo = null ?? 'foo';
console.log(foo); // foo
const baz = 1 ?? 'baz';
const baz2 = 0 ?? 'baz';
console.log(baz); // 1
console.log(baz2); // 0
```
### Tipi letterali di template
I tipi letterali modello consentono di manipolare i valori stringa a livello di tipo e di generare nuovi tipi stringa basati su quelli esistenti. Sono utili per creare tipi più espressivi e precisi da operazioni basate su stringhe.
```typescript
type Department = 'engineering' | 'hr';
type Language = 'english' | 'spanish';
type Id = `${Department}-${Language}-id`; // "engineering-english-id" | "engineering-spanish-id" | "hr-english-id" | "hr-spanish-id"
```
### Sovraccarico di funzioni
Il sovraccarico di funzioni consente di definire più firme di funzione per lo stesso nome di funzione, ciascuna con tipi di parametro e tipo di ritorno diversi.
Quando si chiama una funzione sovraccaricata, TypeScript utilizza gli argomenti forniti per determinare la firma di funzione corretta:
```typescript
function makeGreeting(name: string): string;
function makeGreeting(names: string[]): string[];
function makeGreeting(person: unknown): unknown {
if (typeof person === 'string') {
return `Ciao ${person}!`;
} else if (Array.isArray(person)) {
return person.map(name => `Ciao, ${name}!`);
}
throw new Error('Impossibile salutare');
}
makeGreeting('Simon');
makeGreeting(['Simone', 'John']);
```
### Tipi ricorsivi
Un tipo ricorsivo è un tipo che può fare riferimento a se stesso. Questo è utile per definire strutture dati che hanno una struttura gerarchica o ricorsiva (annidamento potenzialmente infinito), come liste concatenate, alberi e grafi.
```typescript
type ListNode = {
data: T;
next: ListNode | undefined;
};
```
### Tipi condizionali ricorsivi
È possibile definire relazioni di tipo complesse utilizzando la logica e la ricorsione in TypeScript.
Analizziamole in termini semplici:
Tipi condizionali: consente di definire tipi in base a condizioni booleane:
```typescript
type CheckNumber = T extends number ? 'Number' : 'Not a number';
type A = CheckNumber<123>; // 'Number'
type B = CheckNumber<'abc'>; // 'Not a number'
```
Ricorsione: indica una definizione di tipo che fa riferimento a se stessa all'interno della propria definizione:
```typescript
type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
const data: Json = {
prop1: true,
prop2: 'prop2',
prop3: {
prop4: [],
},
};
```
I tipi condizionali ricorsivi combinano sia la logica condizionale che la ricorsione. Ciò significa che una definizione di tipo può dipendere da se stessa tramite la logica condizionale, creando relazioni di tipo complesse e flessibili.
```typescript
type Flatten = T extends Array ? Flatten : T;
type NestedArray = [1, [2, [3, 4], 5], 6];
type FlattenedArray = Flatten; // 2 | 3 | 4 | 5 | 1 | 6
```
### Supporto per i moduli ECMAScript in Node
Node.js ha aggiunto il supporto per i moduli ECMAScript a partire dalla versione 15.3.0, mentre TypeScript supporta i moduli ECMAScript per Node.js dalla versione 4.7. Questo supporto può essere abilitato utilizzando la proprietà `module` con il valore `nodenext` nel file tsconfig.json. Ecco un esempio:
```json
{
"compilerOptions": {
"module": "nodenext",
"outDir": "./lib",
"declaration": true
}
}
```
Node.js supporta due estensioni di file per i moduli: `.mjs` per i moduli ES e `.cjs` per i moduli CommonJS. Le estensioni di file equivalenti in TypeScript sono `.mts` per i moduli ES e `.cts` per i moduli CommonJS. Quando il compilatore TypeScript trascrive questi file in JavaScript, creerà i file `.mjs` e `.cjs`.
Se desideri utilizzare moduli ES nel tuo progetto, puoi impostare la proprietà `type` su "module" nel file package.json. Questo indica a Node.js di trattare il progetto come un progetto di modulo ES.
Inoltre, TypeScript supporta anche le dichiarazioni di tipo nei file .d.ts. Questi file di dichiarazione forniscono informazioni sul tipo per librerie o moduli scritti in TypeScript, consentendo ad altri sviluppatori di utilizzarli con le funzionalità di controllo del tipo e di completamento automatico di TypeScript.
### Funzioni di asserzione
In TypeScript, le funzioni di asserzione sono funzioni che indicano la verifica di una condizione specifica in base al loro valore di ritorno. Nella loro forma più semplice, una funzione di asserzione esamina un predicato fornito e genera un errore quando il predicato restituisce false.
```typescript
function isNumber(value: unknown): asserts value is number {
if (typeof value !== 'number') {
throw new Error('Not a number');
}
}
```
Oppure può essere dichiarato come espressione di funzione:
```typescript
type AssertIsNumber = (value: unknown) => asserts value is number;
const isNumber: AssertIsNumber = value => {
if (typeof value !== 'number') {
throw new Error('Not a number');
}
};
```
Le funzioni di asserzione condividono alcune somiglianze con le type guard. Le type guard sono state inizialmente introdotte per eseguire controlli in fase di esecuzione e garantire il tipo di un valore all'interno di un ambito specifico.
Nello specifico, una type guard è una funzione che valuta un predicato di tipo e restituisce un valore booleano che indica se il predicato è vero o falso. Questo differisce leggermente dalle funzioni di asserzione, in cui l'intenzione è quella di generare un errore anziché restituire false quando il predicato non è soddisfatto.
Esempio di type guard:
```typescript
const isNumber = (value: unknown): value is number => typeof value === 'number';
```
### Tipi di tupla variadici
I tipi di tupla variadici sono una funzionalità introdotta nella versione 4.0 di TypeScript. Iniziamo a conoscerli ripassando cos'è una tupla:
Un tipo di tupla è un array di lunghezza definita, di cui è noto il tipo di ogni elemento:
```typescript
type Student = [string, number];
const [name, age]: Student = ['Simone', 20];
```
Il termine "variadico" significa indefinito (accetta un numero variabile di argomenti).
Una tupla variadica è un tipo di tupla che ha tutte le proprietà di prima, ma la forma esatta non è ancora definita:
```typescript
type Bar = [boolean, ...T, number];
type A = Bar<[boolean]>; // [booleano, booleano, numero]
type B = Bar<['a', 'b']>; // [boolean, 'a', 'b', number]
type C = Bar<[]>; // [boolean, number]
```
Nel codice precedente possiamo vedere che la forma della tupla è definita dal generico `T` passato.
Le tuple variadiche possono accettare più generici, il che le rende molto flessibili:
```typescript
type Bar = [...T, boolean, ...G];
type A = Bar<[number], [string]>; // [number, boolean, string]
type B = Bar<['a', 'b'], [boolean]>; // ["a", "b", boolean, boolean]
```
Con le nuove tuple variadiche possiamo usare:
* Gli spread nella sintassi dei tipi di tupla ora possono essere generici, quindi possiamo rappresentare operazioni di ordine superiore su tuple e array anche quando non conosciamo i tipi effettivi su cui stiamo operando.
* Gli elementi rimanenti possono trovarsi ovunque in una tupla.
Esempio:
```typescript
type Items = readonly unknown[];
function concat(
arr1: T,
arr2: U
): [...T, ...U] {
return [...arr1, ...arr2];
}
concat([1, 2, 3], ['4', '5', '6']); // [1, 2, 3, "4", "5", "6"]
```
### Tipi boxed
I tipi boxed si riferiscono agli oggetti wrapper utilizzati per rappresentare i tipi primitivi come oggetti. Questi oggetti wrapper forniscono funzionalità e metodi aggiuntivi che non sono disponibili direttamente sui valori primitivi.
Quando si accede a un metodo come `charAt` o `normalize` su una primitiva `string`, JavaScript lo racchiude in un oggetto `String`, chiama il metodo e quindi elimina l'oggetto.
Dimostrazione:
```typescript
const originalNormalize = String.prototype.normalize;
String.prototype.normalize = function () {
console.log(this, typeof this);
return originalNormalize.call(this);
};
console.log('\u0041'.normalize());
```
TypeScript rappresenta questa differenziazione fornendo tipi separati per le primitive e i corrispondenti wrapper di oggetti:
* string => String
* number => Number
* boolean => Boolean
* symbol => Symbol
* bigint => BigInt
I tipi boxed di solito non sono necessari. Evitare di utilizzare tipi boxed e utilizzare invece type per le primitive, ad esempio `string` invece di `String`.
### Covarianza e Controvarianza in TypeScript
Covarianza e Controvarianza vengono utilizzate per descrivere il funzionamento delle relazioni quando si ha a che fare con l'ereditarietà o l'assegnazione di tipi.
Covarianza significa che una relazione di tipo preserva la direzione dell'ereditarietà o dell'assegnazione, quindi se un tipo A è un sottotipo del tipo B, anche un array di tipo A è considerato un sottotipo di un array di tipo B. La cosa importante da notare qui è che la relazione di sottotipo viene mantenuta, il che significa che Covarianza accetta il sottotipo ma non il supertipo.
La controvarianza significa che una relazione di tipo inverte la direzione dell'ereditarietà o dell'assegnazione, quindi se un tipo A è un sottotipo del tipo B, allora un array di tipo B è considerato un sottotipo di un array di tipo A. La relazione di sottotipo è invertita, il che significa che la controvarianza accetta il supertipo ma non il sottotipo.
Note: La bivarianza significa accettare sia il supertipo che il sottotipo.
Esempio: supponiamo di avere uno spazio per tutti gli animali e uno spazio separato solo per i cani.
In covarianza, puoi inserire tutti i cani nello spazio degli animali perché i cani sono un tipo di animale. Ma non puoi inserire tutti gli animali nello spazio dei cani perché potrebbero esserci altri animali mescolati.
In controvarianza, non puoi inserire tutti gli animali nello spazio dei cani perché lo spazio degli animali potrebbe contenere anche altri animali. Tuttavia, puoi inserire tutti i cani nello spazio degli animali perché tutti i cani sono anche animali.
```typescript
// Esempio di covarianza
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
}
let animals: Animal[] = [];
let dogs: Dog[] = [];
// La covarianza consente di assegnare l'array del sottotipo (Dog) all'array del supertipo (Animal)
animals = dogs;
dogs = animals; // Non valido: il tipo 'Animal[]' non è assegnabile al tipo 'Dog[]'
// Esempio di controvarianza
type Feed = (animal: T) => void;
let feedAnimal: Feed = (animal: Animal) => {
console.log(`Nome animale: ${animal.name}`);
};
let feedDog: Feed = (dog: Dog) => {
console.log(`Nome del cane: ${dog.name}, Razza: ${dog.breed}`);
};
// La controvarianza consente di assegnare la callback del supertipo (Animal) alla callback del sottotipo (Dog)
feedDog = feedAnimal;
feedAnimal = feedDog; // Non valido: il tipo 'Feed' non è assegnabile al tipo 'Feed'.
```
In TypeScript, le relazioni di tipo per gli array sono covarianti, mentre le relazioni di tipo per i parametri di funzione sono controvarianti. Ciò significa che TypeScript presenta sia covarianza che controvarianza, a seconda del contesto.
#### Annotazioni di varianza opzionali per i parametri di tipo
A partire da TypeScript 4.7.0, possiamo usare le parole chiave `out` e `in` per specificare l'annotazione di varianza.
Per la covarianza, usare la parola chiave `out`:
```typescript
type AnimalCallback = () => T; // T è covariante in questo caso
```
E per la controvarianza, usare la parola chiave `in`:
```typescript
type AnimalCallback = (value: T) => void; // T è controvariante in questo caso
```
### Firme di indice con pattern di stringhe modello
Le firme di indice con pattern di stringhe modello ci consentono di definire firme di indice flessibili utilizzando pattern di stringhe modello. Questa funzionalità ci consente di creare oggetti che possono essere indicizzati con pattern specifici di chiavi stringa, offrendo maggiore controllo e specificità durante l'accesso e la manipolazione delle proprietà.
TypeScript dalla versione 4.4 consente firme di indice per simboli e pattern di stringhe modello.
```typescript
const uniqueSymbol = Symbol('description');
type MyKeys = `key-${string}`;
type MyObject = {
[uniqueSymbol]: string;
[key: MyKeys]: number;
};
const obj: MyObject = {
[uniqueSymbol]: 'Chiave simbolo univoca',
'key-a': 123,
'key-b': 456,
};
console.log(obj[uniqueSymbol]); // Chiave simbolo univoca
console.log(obj['key-a']); // 123
console.log(obj['key-b']); // 456
```
### Operatore `satisfies`
L'operatore `satisfies` consente di verificare se un dato tipo soddisfa una specifica interfaccia o condizione. In altre parole, garantisce che un tipo abbia tutte le proprietà e i metodi richiesti da una specifica interfaccia. È un modo per garantire che una variabile rientri nella definizione di un tipo.
Ecco un esempio:
```typescript
type Columns = 'name' | 'nickName' | 'attributes';
type User = Record;
// Annotazione del tipo tramite `User`
const user: User = {
name: 'Simone',
nickName: undefined,
attributes: ['dev', 'admin'],
};
// Nelle righe seguenti, TypeScript non sarà in grado di dedurre correttamente
user.attributes?.map(console.log); // La proprietà 'map' non esiste sul tipo 'string | string[]'. La proprietà 'map' non esiste sul tipo 'string'.
user.nickName; // string | string[] | undefined
// Asserzione di tipo tramite `as`
const user2 = {
name: 'Simon',
nickName: undefined,
attributes: ['dev', 'admin'],
} as User;
// Anche in questo caso, TypeScript non sarà in grado di dedurre correttamente
user2.attributes?.map(console.log); // La proprietà 'map' non esiste sul tipo 'string | string[]'. La proprietà 'map' non esiste sul tipo 'string'.
user2.nickName; // string | string[] | undefined
// Utilizzando gli operatori `satisfies` ora possiamo dedurre correttamente i tipi
const user3 = {
name: 'Simon',
nickName: undefined,
attributes: ['dev', 'admin'],
} satisfies User;
user3.attributes?.map(console.log); // TypeScript deduce correttamente: string[]
user3.nickName; // TypeScript deduce correttamente: undefined
```
### Importazioni ed esportazioni solo per tipo
Le importazioni ed esportazioni solo per tipo consentono di importare o esportare tipi senza importare o esportare i valori o le funzioni associati a tali tipi. Questo può essere utile per ridurre le dimensioni del bundle.
Per utilizzare le importazioni solo per tipo, è possibile utilizzare la parola chiave `import type`.
TypeScript consente l'utilizzo di estensioni di file sia di dichiarazione che di implementazione (.ts, .mts, .cts e .tsx) nelle importazioni solo tipo, indipendentemente dalle impostazioni `allowImportingTsExtensions`.
Ad esempio:
```typescript
import type { House } from './house.ts';
```
Sono supportati i seguenti formati:
```typescript
import type T from './mod';
import type { A, B } from './mod';
import type * as Types from './mod';
export type { T };
export type { T } from './mod';
```
### Dichiarazione using e Gestione Risorse Esplicita
Una dichiarazione `using` è un binding immutabile con ambito a blocco, simile a `const`, utilizzato per la gestione delle risorse usa e getta. Quando inizializzato con un valore, il metodo `Symbol.dispose` di quel valore viene registrato e successivamente eseguito all'uscita dall'ambito del blocco che lo racchiude.
Questo si basa sulla funzionalità di Gestione Risorse di ECMAScript, utile per eseguire attività di pulizia essenziali dopo la creazione di oggetti, come la chiusura di connessioni, l'eliminazione di file e il rilascio di memoria.
Note:
* A causa della sua recente introduzione nella versione 5.2 di TypeScript, la maggior parte dei runtime non dispone di supporto nativo. Sono necessari polyfill per: `Symbol.dispose`, `Symbol.asyncDispose`, `DisposableStack`, `AsyncDisposableStack`, `SuppressedError`. \* Inoltre, dovrai configurare il tuo file tsconfig.json come segue:
```json
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022", "esnext.disposable", "dom"]
}
}
```
Esempio:
```typescript
//@ts-ignore
Symbol.dispose ??= Symbol('Symbol.dispose'); // Simple polyfill
const doWork = (): Disposable => {
return {
[Symbol.dispose]: () => {
console.log('disposed');
},
};
};
console.log(1);
{
using work = doWork(); // La risorsa è dichiarata
console.log(2);
} // La risorsa viene eliminata (ad esempio, viene valutata `work[Symbol.dispose]()`)
console.log(3);
```
Il codice registrerà:
```shell
1
2
disposed
3
```
Una risorsa idonea per l'eliminazione deve rispettare l'interfaccia `Disposable`:
```typescript
// lib.esnext.disposable.d.ts
interface Disposable {
[Symbol.dispose](): void;
}
```
Le dichiarazioni `using` registrano le operazioni di eliminazione delle risorse in uno stack, assicurandosi che vengano eliminate nell'ordine inverso rispetto alla dichiarazione:
```typescript
{
using j = getA(),
y = getB();
using k = getC();
} // elimina `C`, poi `B`, poi `A`.
```
È garantito che le risorse vengano eliminate, anche se si verificano codice o eccezioni successive. Questo potrebbe portare alla generazione di un'eccezione durante l'eliminazione, con la possibile soppressione di un'altra. Per conservare le informazioni sugli errori soppressi, è stata introdotta una nuova eccezione nativa, `SuppressedError`.
#### dichiarazione await using
Una dichiarazione `await using` gestisce una risorsa eliminabile in modo asincrono. Il valore deve avere un metodo `Symbol.asyncDispose`, che verrà atteso alla fine del blocco.
```typescript
async function doWorkAsync() {
await using work = doWorkAsync(); // La risorsa viene dichiarata
} // La risorsa viene eliminata (ad esempio, viene valutata `await work[Symbol.asyncDispose]()`)
```
Per una risorsa eliminabile in modo asincrono, deve aderire all'interfaccia `Disposable` o `AsyncDisposable`:
```typescript
// lib.esnext.disposable.d.ts
interface AsyncDisposable {
[Symbol.asyncDispose](): Promise;
}
```
```typescript
//@ts-ignore
Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose'); // Simple polyfill
class DatabaseConnection implements AsyncDisposable {
// Un metodo che viene chiamato quando l'oggetto viene eliminato in modo asincrono
[Symbol.asyncDispose]() {
// Chiude la connessione e restituisce una promessa
return this.close();
}
async close() {
console.log('Chiusura della connessione...');
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Connessione chiusa.');
}
}
async function doWork() {
// Crea una nuova connessione e la elimina in modo asincrono quando esce dall'ambito
await using connection = new DatabaseConnection(); // La risorsa viene dichiarata
console.log('Sto lavorando...');
} // La risorsa viene eliminata (ad esempio, viene valutato `await connection[Symbol.asyncDispose]()`)
doWork();
```
Il codice registra:
```shell
Sto lavorando...
Chiusura della connessione...
Connessione chiusa.
```
Le dichiarazioni `using` e `await using` sono consentite nelle istruzioni: `for`, `for-in`, `for-of`, `for-await-of`, `switch`.
### Attributi di importazione
Gli attributi di importazione di TypeScript 5.3 (etichette per le importazioni) indicano al runtime come gestire i moduli (JSON, ecc.). Questo migliora la sicurezza garantendo importazioni chiare e si allinea con la Content Security Policy (CSP) per un caricamento più sicuro delle risorse. TypeScript garantisce che siano validi, ma lascia che sia il runtime a gestirne l'interpretazione per la gestione di moduli specifici.
Esempio:
```typescript
import config from './config.json' with { type: 'json' };
```
con importazione dinamica:
```typescript
const config = import('./config.json', { with: { type: 'json' } });
```
================================================
FILE: README-pt_BR.md
================================================
# O Livro Conciso de TypeScript
O Livro Conciso de TypeScript fornece uma visão geral abrangente e sucinta dos recursos do TypeScript. Ele oferece explicações claras que abrangem todos os aspectos encontrados na versão mais recente da linguagem, desde o seu poderoso sistema de tipos até recursos avançados. Seja você um iniciante ou um desenvolvedor experiente, este livro é um recurso inestimável para aprimorar sua compreensão e proficiência em TypeScript.
Este livro é completamente Gratuito e de Código Aberto (Open Source).
Acredito que a educação técnica de alta qualidade deve ser acessível a todos, por isso mantenho este livro gratuito e aberto.
Se o livro te ajudou a resolver um bug, entender um conceito difícil ou avançar em sua carreira, considere apoiar meu trabalho pagando quanto quiser (preço sugerido: 15 USD) ou patrocinando um café. Seu apoio me ajuda a manter o conteúdo atualizado e a expandi-lo com novos exemplos e explicações mais profundas.
[](https://www.buymeacoffee.com/simonepoggiali)
[](https://www.paypal.com/donate/?business=QW82ZS956XLFY&no_recurring=0¤cy_code=EUR)
## Traduções
Este livro foi traduzido para várias versões de idiomas, incluindo:
[Chinês](https://github.com/gibbok/typescript-book/blob/main/README-zh_CN.md)
[Italiano](https://github.com/gibbok/typescript-book/blob/main/README-it_IT.md)
[Português (Brasil)](https://github.com/gibbok/typescript-book/blob/main/README-pt_BR.md)
[Sueco](https://github.com/gibbok/typescript-book/blob/main/README-sv_SE.md)
## Downloads e site
Você também pode baixar a versão Epub:
[https://github.com/gibbok/typescript-book/tree/main/downloads](https://github.com/gibbok/typescript-book/tree/main/downloads)
Uma versão online está disponível em:
[https://gibbok.github.io/typescript-book](https://gibbok.github.io/typescript-book)
## Sumário
- [O Livro Conciso de TypeScript](#o-livro-conciso-de-typescript)
- [Traduções](#traduções)
- [Downloads e site](#downloads-e-site)
- [Sumário](#sumário)
- [Introdução](#introdução)
- [Sobre o Autor](#sobre-o-autor)
- [Introdução ao TypeScript](#introdução-ao-typescript)
- [O que é TypeScript?](#o-que-é-typescript)
- [Por que TypeScript?](#por-que-typescript)
- [TypeScript e JavaScript](#typescript-e-javascript)
- [Geração de Código TypeScript](#geração-de-código-typescript)
- [JavaScript Moderno Agora (Downleveling)](#javascript-moderno-agora-downleveling)
- [Começando com TypeScript](#começando-com-typescript)
- [Instalação](#instalação)
- [Configuração](#configuração)
- [Arquivo de Configuração do TypeScript](#arquivo-de-configuração-do-typescript)
- [target](#target)
- [lib](#lib)
- [strict](#strict)
- [module](#module)
- [moduleResolution](#moduleresolution)
- [esModuleInterop](#esmoduleinterop)
- [jsx](#jsx)
- [skipLibCheck](#skiplibcheck)
- [files](#files)
- [include](#include)
- [exclude](#exclude)
- [importHelpers](#importhelpers)
- [Conselhos para Migração para TypeScript](#conselhos-para-migração-para-typescript)
- [Explorando o Sistema de Tipos](#explorando-o-sistema-de-tipos)
- [O Serviço de Linguagem do TypeScript](#o-serviço-de-linguagem-do-typescript)
- [Tipagem Estrutural](#tipagem-estrutural)
- [Regras Fundamentais de Comparação do TypeScript](#regras-fundamentais-de-comparação-do-typescript)
- [Tipos como Conjuntos](#tipos-como-conjuntos)
- [Atribuir um tipo: Declarações de Tipo e Asserções de Tipo](#atribuir-um-tipo-declarações-de-tipo-e-asserções-de-tipo)
- [Declaração de Tipo](#declaração-de-tipo)
- [Asserção de Tipo](#asserção-de-tipo)
- [Declarações de Ambiente (Ambient Declarations)](#declarações-de-ambiente-ambient-declarations)
- [Verificação de Propriedades e Verificação de Excesso de Propriedades](#verificação-de-propriedades-e-verificação-de-excesso-de-propriedades)
- [Tipos Fracos (Weak Types)](#tipos-fracos-weak-types)
- [Verificação Estrita de Objeto Literal (Freshness)](#verificação-estrita-de-objeto-literal-freshness)
- [Inferência de Tipo](#inferência-de-tipo)
- [Inferências Mais Avançadas](#inferências-mais-avançadas)
- [Alargamento de Tipo (Type Widening)](#alargamento-de-tipo-type-widening)
- [Const](#const)
- [Modificador Const em Parâmetros de Tipo](#modificador-const-em-parâmetros-de-tipo)
- [Asserção Const](#asserção-const-const-assertion)
- [Anotação de Tipo Explícita](#anotação-de-tipo-explícita)
- [Estreitamento de Tipo (Type Narrowing)](#estreitamento-de-tipo-type-narrowing)
- [Condições](#condições)
- [Lançando ou retornando](#lançando-ou-retornando)
- [União Discriminada](#união-discriminada)
- [Proteções de Tipo Definidas pelo Usuário (User-Defined Type Guards)](#proteções-de-tipo-definidas-pelo-usuário-user-defined-type-guards)
- [Tipos Primitivos](#tipos-primitivos)
- [string](#string)
- [boolean](#boolean)
- [number](#number)
- [bigInt](#bigint)
- [Symbol](#symbol)
- [null e undefined](#null-e-undefined)
- [Array](#array)
- [any](#any)
- [Anotações de Tipo](#anotações-de-tipo)
- [Propriedades Opcionais](#propriedades-opcionais)
- [Propriedades Somente Leitura (Readonly)](#propriedades-somente-leitura-readonly)
- [Assinaturas de Índice (Index Signatures)](#assinaturas-de-índice-index-signatures)
- [Estendendo Tipos](#estendendo-tipos)
- [Tipos Literais](#tipos-literais)
- [Inferência Literal](#inferência-literal)
- [strictNullChecks](#strictnullchecks)
- [Enums](#enums)
- [Enums numéricos](#enums-numéricos)
- [Enums de string](#enums-de-string)
- [Enums constantes](#enums-constantes)
- [Mapeamento reverso](#mapeamento-reverso)
- [Enums de ambiente](#enums-de-ambiente)
- [Membros computados e constantes](#membros-computados-e-constantes)
- [Estreitamento (Narrowing)](#estreitamento-narrowing)
- [typeof type guards](#typeof-type-guards)
- [Estreitamento de veracidade (Truthiness narrowing)](#estreitamento-de-veracidade-truthiness-narrowing)
- [Estreitamento de igualdade (Equality narrowing)](#estreitamento-de-igualdade-equality-narrowing)
- [Estreitamento com operador In](#estreitamento-com-operador-in)
- [Estreitamento com instanceof](#estreitamento-com-instanceof)
- [Atribuições](#atribuições)
- [Análise de Fluxo de Controle](#análise-de-fluxo-de-controle)
- [Predicados de Tipo](#predicados-de-tipo)
- [Uniões Discriminadas](#uniões-discriminadas)
- [O tipo never](#o-tipo-never)
- [Verificação de exaustividade](#verificação-de-exaustividade)
- [Tipos de Objeto](#tipos-de-objeto)
- [Tipo Tupla (Anônimo)](#tipo-tupla-anônimo)
- [Tipo Tupla Nomeado (Rotulado)](#tipo-tupla-nomeado-rotulado)
- [Tupla de Comprimento Fixo](#tupla-de-comprimento-fixo)
- [Tipo União](#tipo-união)
- [Tipos de Interseção](#tipos-de-interseção)
- [Indexação de Tipo](#indexação-de-tipo)
- [Tipo a partir de Valor](#tipo-a-partir-de-valor)
- [Tipo a partir de Retorno de Função](#tipo-a-partir-de-retorno-de-função)
- [Tipo a partir de Módulo](#tipo-a-partir-de-módulo)
- [Tipos Mapeados](#tipos-mapeados)
- [Modificadores de Tipos Mapeados](#modificadores-de-tipos-mapeados)
- [Tipos Condicionais](#tipos-condicionais-conditional-types)
- [Tipos Condicionais Distributivos](#tipos-condicionais-distributivos)
- [Inferência de tipo infer em Tipos Condicionais](#infer-inferência-de-tipo-em-tipos-condicionais)
- [Tipos Condicionais Predefinidos](#tipos-condicionais-predefinidos)
- [Tipos de União de Template](#tipos-de-união-de-template-template-union-types)
- [Tipo Any](#tipo-any)
- [Tipo Unknown](#tipo-unknown)
- [Tipo Void](#tipo-void)
- [Tipo Never](#tipo-never)
- [Interface e Tipo](#interface-e-type)
- [Sintaxe Comum](#sintaxe-comum)
- [Tipos Básicos](#tipos-básicos)
- [Objetos e Interfaces](#objetos-e-interfaces)
- [Tipos União e Interseção](#tipos-união-e-interseção)
- [Primitivos de Tipo Integrados](#tipos-primitivos-integrados)
- [Objetos JS Integrados Comuns](#objetos-js-integrados-comuns)
- [Sobrecargas](#sobrecargas-overloads)
- [Mesclagem e Extensão](#mesclagem-e-extensão)
- [Diferenças entre Type e Interface](#diferenças-entre-type-e-interface)
- [Classe](#classes)
- [Sintaxe Comum de Classe](#sintaxe-comum-de-classes)
- [Construtor](#construtor)
- [Construtores Privados e Protegidos](#construtores-privados-e-protegidos)
- [Modificadores de Acesso](#modificadores-de-acesso)
- [Get e Set](#get-e-set)
- [Auto-Accessors em Classes](#auto-acessores-em-classes)
- [this](#this)
- [Propriedades de Parâmetro](#propriedades-de-parâmetro)
- [Classes Abstratas](#classes-abstratas)
- [Com Genéricos](#com-genéricos)
- [Decoradores](#decoradores-decorators)
- [Decoradores de Classe](#decoradores-de-classe-class-decorators)
- [Decorador de Propriedade](#decorador- de-propriedade-property-decorator)
- [Decorador de Método](#decorador-de-método-method-decorator)
- [Decoradores de Getter e Setter](#decoradores-de-getter-e-setter)
- [Metadados de Decorador](#metadados-de-decorador-decorator-metadata)
- [Herança](#herança)
- [Estáticos](#estáticos-statics)
- [Inicialização de propriedade](#inicialização-de-propriedade)
- [Sobrecarga de método](#sobrecarga-de-método)
- [Genéricos](#genéricos-generics)
- [Tipo Genérico](#tipo-genérico)
- [Classes Genéricas](#classes-genéricas)
- [Restrições Genéricas](#restrições-genéricas-generic-constraints)
- [Estreitamento contextual genérico](#estreitamento-contextual-genérico)
- [Tipos Estruturais Apagados (Erased Structural Types)](#tipos-estruturais-apagados)
- [Namespacing](#namespacing)
- [Símbolos](#símbolos-symbols)
- [Diretivas de Barra Tripla](#diretivas-triple-slash)
- [Manipulação de Tipos](#manipulação-de-tipos)
- [Criando Tipos a partir de Tipos](#criando-tipos-a-partir-de-tipos)
- [Tipos de Acesso Indexado](#tipos-de-acesso-indexado-indexed-access-types)
- [Tipos Utilitários](#tipos-utilitários-utility-types)
- [Awaited\](#awaitedt)
- [Partial\](#partialt)
- [Required\](#requiredt)
- [Readonly\](#readonlyt)
- [Record\](#recordk-t)
- [Pick\](#pickt-k)
- [Omit\](#omitt-k)
- [Exclude\](#excludet-u)
- [Extract\](#extractt-u)
- [NonNullable\](#nonnullablet)
- [Parameters\](#parameterst)
- [ConstructorParameters\](#constructorparameterst)
- [ReturnType\](#returntypet)
- [InstanceType\](#instancetypet)
- [ThisParameterType\](#thisparametertypet)
- [OmitThisParameter\](#omitthisparametert)
- [ThisType\](#thistypet)
- [Uppercase\](#uppercaset)
- [Lowercase\](#lowercaset)
- [Capitalize\](#capitalizet)
- [Uncapitalize\](#uncapitalizet)
- [NoInfer\](#noinfert)
- [Outros](#outros)
- [Tratamento de Erros e Exceções](#erros-e-tratamento-de-exceções)
- [Classes Mixin](#classes-mixin-mixin-classes)
- [Recursos de Linguagem Assíncronos](#recursos-de-linguagem-assíncronos)
- [Iteradores e Geradores](#iteradores-e-geradores)
- [Referência JSDoc TsDocs](#referência-jsdoc-tsdocs)
- [@types](#types)
- [JSX](#jsx-1)
- [Módulos ES6](#módulos-es6)
- [Operador de Exponenciação ES7](#operador-de-exponenciação-es7)
- [A instrução for-await-of](#a-instrução-for-await-of)
- [Nova meta-propriedade target](#nova-meta-propriedade-target)
- [Expressões de Importação Dinâmica](#expressões-de-importação-dinâmica)
- ["tsc –watch"](#tsc-watch)
- [Operador de Asserção Não-nula](#operador-de-asserção-não-nulo-non-null-assertion-operator)
- [Declarações padronizadas](#declarações-com-valor-padrão-defaulted-declarations)
- [Encadeamento Opcional (Optional Chaining)](#encadeamento-opcional-optional-chaining)
- [Operador de coalescência nula](#operador-de-coalescência-nula-nullish-coalescing-operator)
- [Tipos de Literais de Template](#tipos-de-literal-de-template-template-literal-types)
- [Sobrecarga de função](#sobrecarga-de-função-function-overloading)
- [Tipos Recursivos](#tipos-recursivos)
- [Tipos Condicionais Recursivos](#tipos-condicionais-recursivos)
- [Suporte a Módulo ECMAScript no Node](#suporte-a-módulos-ecmascript-no-node)
- [Funções de Asserção](#funções-de-asserção-assertion-functions)
- [Tipos de Tupla Variádicos](#tipos-de-tupla-variádicos-variadic-tuple-types)
- [Tipos Boxed](#tipos-boxed-boxed-types)
- [Covariância e Contravariância no TypeScript](#covariância-e-contravariância-no-typescript)
- [Anotações de Variância Opcionais para Parâmetros de Tipo](#anotações-de-variância-opcionais-para-parâmetros-de-tipo)
- [Assinaturas de Índice de Padrão de String de Template](#assinaturas-de-índice-de-padrão-de-string-de-template-template-string-pattern-index-signatures)
- [O Operador satisfies](#o-operador-satisfies)
- [Importações e Exportações Apenas de Tipo](#importações-e-exportações-apenas-de-tipo-type-only-imports-and-export)
- [Declaração using e Gerenciamento Explícito de Recursos](#declaração-using-e-gerenciamento-explícito-de-recursos-explicit-resource-management)
- [Declaração await using](#declaração-await-using)
- [Atributos de Importação](#atributos-de-importação-import-attributes)
## Introdução
Bem-vindo ao Livro Conciso de TypeScript! Este guia o equipa com conhecimentos essenciais e habilidades práticas para o desenvolvimento eficaz em TypeScript. Descubra conceitos e técnicas fundamentais para escrever código limpo e robusto. Seja você um iniciante ou um desenvolvedor experiente, este livro serve tanto como um guia abrangente quanto como uma referência prática para aproveitar o poder do TypeScript em seus projetos.
Este livro cobre o TypeScript 5.2.
## Sobre o Autor
Simone Poggiali é um Engenheiro Staff experiente com paixão por escrever código de nível profissional desde os anos 90. Ao longo de sua carreira internacional, contribuiu para inúmeros projetos para uma ampla gama de clientes, de startups a grandes organizações. Empresas notáveis como HelloFresh, Siemens, O2, Leroy Merlin e Snowplow se beneficiaram de sua expertise e dedicação.
Você pode encontrar Simone Poggiali nas seguintes plataformas:
* LinkedIn: [https://www.linkedin.com/in/simone-poggiali](https://www.linkedin.com/in/simone-poggiali)
* GitHub: [https://github.com/gibbok](https://github.com/gibbok)
* X.com: [https://x.com/gibbok_coding](https://x.com/gibbok_coding)
* Email: gibbok.coding📧gmail.com
Lista completa de colaboradores: [https://github.com/gibbok/typescript-book/graphs/contributors](https://github.com/gibbok/typescript-book/graphs/contributors)
## Introdução ao TypeScript
### O que é TypeScript?
TypeScript é uma linguagem de programação fortemente tipada que se baseia no JavaScript. Foi originalmente projetada por Anders Hejlsberg em 2012 e é atualmente desenvolvida e mantida pela Microsoft como um projeto de código aberto.
O TypeScript compila para JavaScript e pode ser executado em qualquer ambiente de execução JavaScript (por exemplo, um navegador ou Node.js em um servidor).
Ele suporta múltiplos paradigmas de programação, como funcional, genérica, imperativa e orientada a objetos, e é uma linguagem compilada (transpilada) que é convertida em JavaScript antes da execução.
### Por que TypeScript?
TypeScript é uma linguagem fortemente tipada que ajuda a prevenir erros comuns de programação e a evitar certos tipos de erros em tempo de execução antes que o programa seja executado.
Uma linguagem fortemente tipada permite ao desenvolvedor especificar várias restrições e comportamentos do programa nas definições de tipos de dados, facilitando a capacidade de verificar a correção do software e prevenir defeitos. Isso é especialmente valioso em aplicações de larga escala.
Alguns dos benefícios do TypeScript:
* Tipagem estática, opcionalmente fortemente tipada
* Inferência de Tipo
* Acesso a recursos ES6 e ES7
* Compatibilidade multiplataforma e entre navegadores
* Suporte de ferramentas com IntelliSense
### TypeScript e JavaScript
Arquivos TypeScript são escritos em arquivos `.ts` ou `.tsx`, enquanto arquivos JavaScript são escritos em `.js` ou `.jsx`.
Arquivos com a extensão `.tsx` ou `.jsx` podem conter a Extensão de Sintaxe JavaScript JSX, que é usada no React para desenvolvimento de UI.
O TypeScript é um superconjunto tipado de JavaScript (ECMAScript 2015) em termos de sintaxe. Todo código JavaScript é código TypeScript válido, mas o inverso nem sempre é verdadeiro.
Por exemplo, considere uma função em um arquivo JavaScript com a extensão `.js`, como a seguinte:
```typescript
const sum = (a, b) => a + b;
```
A função pode ser convertida e usada no TypeScript alterando a extensão do arquivo para `.ts`. No entanto, se a mesma função for anotada com tipos TypeScript, ela não poderá ser executada em nenhum ambiente de execução JavaScript sem compilação. O seguinte código TypeScript produzirá um erro de sintaxe se não for compilado:
```typescript
const sum = (a: number, b: number): number => a + b;
```
O TypeScript foi projetado para detectar possíveis exceções que podem ocorrer em tempo de execução durante o tempo de compilação, fazendo com que o desenvolvedor defina a intenção com anotações de tipo. Além disso, o TypeScript também pode capturar problemas se nenhuma anotação de tipo for fornecida. Por exemplo, o seguinte trecho de código não especifica nenhum tipo TypeScript:
```typescript
const items = [{ x: 1 }, { x: 2 }];
const result = items.filter(item => item.y);
```
Neste caso, o TypeScript detecta um erro e informa:
```text
Property 'y' does not exist on type '{ x: number; }'.
```
O sistema de tipos do TypeScript é amplamente influenciado pelo comportamento de tempo de execução do JavaScript. Por exemplo, o operador de adição (+), que no JavaScript pode realizar a concatenação de strings ou a adição numérica, é modelado da mesma forma no TypeScript:
```typescript
const result = '1' + 1; // Result is of type string
```
A equipe por trás do TypeScript tomou a decisão deliberada de sinalizar o uso incomum do JavaScript como erros. Por exemplo, considere o seguinte código JavaScript válido:
```typescript
const result = 1 + true; // In JavaScript, the result is equal 2
```
No entanto, o TypeScript lança um erro:
```text
Operator '+' cannot be applied to types 'number' and 'boolean'.
```
Este erro ocorre porque o TypeScript impõe estritamente a compatibilidade de tipos e, neste caso, identifica uma operação inválida entre um número e um booleano.
### Geração de Código TypeScript
O compilador TypeScript tem duas responsabilidades principais: verificar se há erros de tipo e compilar para JavaScript. Esses dois processos são independentes um do outro. Os tipos não afetam a execução do código em um ambiente de execução JavaScript, pois são completamente apagados durante a compilação. O TypeScript ainda pode gerar JavaScript mesmo na presença de erros de tipo.
Aqui está um exemplo de código TypeScript com um erro de tipo:
```typescript
const add = (a: number, b: number): number => a + b;
const result = add('x', 'y'); // Argument of type 'string' is not assignable to parameter of type 'number'.
```
No entanto, ele ainda pode produzir uma saída JavaScript executável:
```typescript
'use strict';
const add = (a, b) => a + b;
const result = add('x', 'y'); // xy
```
Não é possível verificar tipos TypeScript em tempo de execução. Por exemplo:
```typescript
interface Animal {
name: string;
}
interface Dog extends Animal {
bark: () => void;
}
interface Cat extends Animal {
meow: () => void;
}
const makeNoise = (animal: Animal) => {
if (animal instanceof Dog) {
// 'Dog' only refers to a type, but is being used as a value here.
// ...
}
};
```
Como os tipos são apagados após a compilação, não há como executar este código em JavaScript. Para reconhecer tipos em tempo de execução, precisamos usar outro mecanismo. O TypeScript fornece várias opções, sendo uma comum a "união tagueada" (tagged union). Por exemplo:
```typescript
interface Dog {
kind: 'dog'; // Tagged union
bark: () => void;
}
interface Cat {
kind: 'cat'; // Tagged union
meow: () => void;
}
type Animal = Dog | Cat;
const makeNoise = (animal: Animal) => {
if (animal.kind === 'dog') {
animal.bark();
} else {
animal.meow();
}
};
const dog: Dog = {
kind: 'dog',
bark: () => console.log('bark'),
};
makeNoise(dog);
```
A propriedade "kind" é um valor que pode ser usado em tempo de execução para distinguir entre objetos em JavaScript.
Também é possível que um valor em tempo de execução tenha um tipo diferente daquele declarado na declaração de tipo. Por exemplo, se o desenvolvedor interpretou mal um tipo de API e o anotou incorretamente.
O TypeScript é um superconjunto do JavaScript, portanto a palavra-chave "class" pode ser usada como um tipo e valor em tempo de execução.
```typescript
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
constructor(
public name: string,
public bark: () => void
) {
super(name);
}
}
class Cat extends Animal {
constructor(
public name: string,
public meow: () => void
) {
super(name);
}
}
type Mammal = Dog | Cat;
const makeNoise = (mammal: Mammal) => {
if (mammal instanceof Dog) {
mammal.bark();
} else {
mammal.meow();
}
};
const dog = new Dog('Fido', () => console.log('bark'));
makeNoise(dog);
```
No JavaScript, uma "classe" tem uma propriedade "prototype", e o operador "instanceof" pode ser usado para testar se a propriedade prototype de um construtor aparece em qualquer lugar na cadeia de protótipos de um objeto.
O TypeScript não tem efeito no desempenho em tempo de execução, pois todos os tipos serão apagados. No entanto, o TypeScript introduz alguma sobrecarga no tempo de compilação.
### JavaScript Moderno Agora (Downleveling)
O TypeScript pode compilar código para qualquer versão lançada do JavaScript desde o ECMAScript 3 (1999). Isso significa que o TypeScript pode transpilar o código dos recursos JavaScript mais recentes para versões mais antigas, um processo conhecido como *Downleveling*. Isso permite o uso do JavaScript moderno, mantendo a compatibilidade máxima com ambientes de execução mais antigos.
É importante notar que durante a transpilação para uma versão mais antiga do JavaScript, o TypeScript pode gerar código que pode incorrer em uma sobrecarga de desempenho em comparação com as implementações nativas.
Aqui estão alguns dos recursos modernos do JavaScript que podem ser usados no TypeScript:
* Módulos ECMAScript em vez de callbacks "define" no estilo AMD ou instruções "require" do CommonJS.
* Classes em vez de protótipos.
* Declaração de variáveis usando "let" ou "const" em vez de "var".
* Loop "for-of" ou ".forEach" em vez do loop "for" tradicional.
* Funções de seta (Arrow functions) em vez de expressões de função.
* Atribuição via desestruturação (Destructuring assignment).
* Nomes de propriedade/método abreviados e nomes de propriedade computados.
* Parâmetros de função padrão.
Ao aproveitar esses recursos modernos do JavaScript, os desenvolvedores podem escrever códigos mais expressivos e concisos no TypeScript.
## Começando com TypeScript
### Instalação
O Visual Studio Code oferece excelente suporte para a linguagem TypeScript, mas não inclui o compilador TypeScript. Para instalar o compilador TypeScript, você pode usar um gerenciador de pacotes como npm ou yarn:
```shell
npm install typescript --save-dev
```
ou
```shell
yarn add typescript --dev
```
Certifique-se de realizar o commit do arquivo de bloqueio (lockfile) gerado para garantir que cada membro da equipe use a mesma versão do TypeScript.
Para executar o compilador TypeScript, você pode usar os seguintes comandos:
```shell
npx tsc
```
ou
```shell
yarn tsc
```
Recomenda-se instalar o TypeScript por projeto em vez de globalmente, pois fornece um processo de construção mais previsível. No entanto, para ocasiões pontuais, você pode usar o seguinte comando:
```shell
npx tsc
```
ou instalá-lo globalmente:
```shell
npm install -g typescript
```
Se você estiver usando o Microsoft Visual Studio, pode obter o TypeScript como um pacote no NuGet para seus projetos MSBuild. No Console do Gerenciador de Pacotes NuGet, execute o seguinte comando:
```shell
Install-Package Microsoft.TypeScript.MSBuild
```
Durante a instalação do TypeScript, dois executáveis são instalados: "tsc" como o compilador TypeScript e "tsserver" como o servidor autônomo do TypeScript. O servidor autônomo contém o compilador e os serviços de linguagem que podem ser utilizados por editores e IDEs para fornecer completamento inteligente de código.
Além disso, existem vários transpiladores compatíveis com TypeScript disponíveis, como Babel (via um plugin) ou swc. Esses transpiladores podem ser usados para converter código TypeScript em outras linguagens ou versões de destino.
### Configuração
O TypeScript pode ser configurado usando as opções da CLI do tsc ou utilizando um arquivo de configuração dedicado chamado tsconfig.json localizado na raiz do projeto.
Para gerar um arquivo tsconfig.json pré-preenchido com as configurações recomendadas, você pode usar o seguinte comando:
```shell
tsc --init
```
Ao executar o comando `tsc` localmente, o TypeScript compilará o código usando a configuração especificada no arquivo tsconfig.json mais próximo.
Aqui estão alguns exemplos de comandos da CLI que rodam com as configurações padrão:
```shell
tsc main.ts // Compila um arquivo específico (main.ts) para JavaScript
tsc src/*.ts // Compila todos os arquivos .ts na pasta 'src' para JavaScript
tsc app.ts util.ts --outfile index.js // Compila dois arquivos TypeScript (app.ts e util.ts) em um único arquivo JavaScript (index.js)
```
### Arquivo de Configuração do TypeScript
Um arquivo tsconfig.json é usado para configurar o Compilador TypeScript (tsc). Geralmente, ele é adicionado à raiz do projeto, junto com o arquivo `package.json`.
Notas:
* tsconfig.json aceita comentários, mesmo estando no formato json.
* É aconselhável usar este arquivo de configuração em vez das opções de linha de comando.
No link a seguir você encontra a documentação completa e seu esquema:
[https://www.typescriptlang.org/tsconfig](https://www.typescriptlang.org/tsconfig)
[https://www.typescriptlang.org/tsconfig/](https://www.typescriptlang.org/tsconfig/)
A seguir, apresentamos uma lista das configurações comuns e úteis:
#### target
A propriedade "target" é usada para especificar qual versão do JavaScript ECMAScript seu TypeScript deve emitir/compilar. Para navegadores modernos, o ES6 é uma boa opção; para navegadores mais antigos, o ES5 é recomendado.
#### lib
A propriedade "lib" é usada para especificar quais arquivos de biblioteca incluir no tempo de compilação. O TypeScript inclui automaticamente APIs para recursos especificados na propriedade "target", mas é possível omitir ou escolher bibliotecas específicas para necessidades particulares. Por exemplo, se você estiver trabalhando em um projeto de servidor, pode excluir a biblioteca "DOM", que é útil apenas em um ambiente de navegador.
#### strict
A propriedade "strict" habilita garantias mais fortes e aumenta a segurança de tipos. É aconselhável incluir sempre esta propriedade no arquivo tsconfig.json do seu projeto. Habilitar a propriedade "strict" permite que o TypeScript possa:
* Emitir código usando "use strict" para cada arquivo de origem.
* Considerar "null" e "undefined" no processo de verificação de tipos.
* Desabilitar o uso do tipo "any" quando não houver anotações de tipo.
* Levantar um erro sobre o uso da expressão "this", que de outra forma implicaria o tipo "any".
#### module
A propriedade "module" define o sistema de módulo suportado para o programa compilado. Durante o tempo de execução, um carregador de módulo é usado para localizar e executar dependências com base no sistema de módulo especificado.
Os carregadores de módulos mais comuns usados no JavaScript são o CommonJS do Node.js para aplicações do lado do servidor e o RequireJS para módulos AMD em aplicações web baseadas em navegador. O TypeScript pode emitir código para vários sistemas de módulos, incluindo UMD, System, ESNext, ES2015/ES6 e ES2020.
Nota: O sistema de módulos deve ser escolhido com base no ambiente de destino e no mecanismo de carregamento de módulos disponível nesse ambiente.
#### moduleResolution
A propriedade "moduleResolution" especifica a estratégia de resolução de módulos. Use "node" para código TypeScript moderno; a estratégia "classic" é usada apenas para versões antigas do TypeScript (antes da 1.6).
#### esModuleInterop
A propriedade "esModuleInterop" permite a importação padrão de módulos CommonJS que não exportaram usando a propriedade "default"; esta propriedade fornece um shim para garantir a compatibilidade no JavaScript emitido. Após habilitar esta opção, podemos usar `import MyLibrary from "my-library"` em vez de `import * as MyLibrary from "my-library"`.
#### jsx
A propriedade "jsx" aplica-se apenas a arquivos .tsx usados no ReactJS e controla como as construções JSX são compiladas em JavaScript. Uma opção comum é "preserve", que compilará para um arquivo .jsx mantendo o JSX inalterado para que ele possa ser passado para diferentes ferramentas, como o Babel, para transformações posteriores.
#### skipLibCheck
A propriedade "skipLibCheck" evitará que o TypeScript verifique os tipos de todos os pacotes de terceiros importados. Esta propriedade reduzirá o tempo de compilação de um projeto. O TypeScript ainda verificará seu código em relação às definições de tipo fornecidas por esses pacotes.
#### files
A propriedade "files" indica ao compilador uma lista de arquivos que devem sempre ser incluídos no programa.
#### include
A propriedade "include" indica ao compilador uma lista de arquivos que gostaríamos de incluir. Esta propriedade permite padrões semelhantes a glob, como "\**" para qualquer subdiretório, "*" para qualquer nome de arquivo e "?" para caracteres opcionais.
#### exclude
A propriedade "exclude" indica ao compilador uma lista de arquivos que não devem ser incluídos na compilação. Isso pode incluir arquivos como "node_modules" ou arquivos de teste.
Nota: tsconfig.json permite comentários.
### importHelpers
O TypeScript usa código auxiliar ao gerar código para certos recursos avançados ou de JavaScript com "down-leveled". Por padrão, esses auxiliares são duplicados nos arquivos que os utilizam. A opção `importHelpers` importa esses auxiliares do módulo `tslib`, tornando a saída do JavaScript mais eficiente.
### Conselhos para Migração para TypeScript
Para projetos grandes, recomenda-se adotar uma transição gradual onde o código TypeScript e JavaScript coexistirão inicialmente. Apenas projetos pequenos podem ser migrados para TypeScript de uma só vez.
O primeiro passo desta transição é introduzir o TypeScript no processo da cadeia de construção. Isso pode ser feito usando a opção de compilador "allowJs", que permite que arquivos .ts e .tsx coexistam com arquivos JavaScript existentes. Como o TypeScript voltará para um tipo "any" para uma variável quando não puder inferir o tipo dos arquivos JavaScript, recomenda-se desabilitar "noImplicitAny" em suas opções de compilador no início da migração.
O segundo passo é garantir que seus testes JavaScript funcionem junto com os arquivos TypeScript, para que você possa executar testes conforme converte cada módulo. Se estiver usando Jest, considere usar o `ts-jest`, que permite testar projetos TypeScript com Jest.
O terceiro passo é incluir declarações de tipo para bibliotecas de terceiros em seu projeto. Essas declarações podem ser encontradas empacotadas ou no DefinitelyTyped. Você pode pesquisar por elas usando [https://www.typescriptlang.org/dt/search](https://www.typescriptlang.org/dt/search) e instalá-las usando:
```shell
npm install --save-dev @types/package-name
```
ou
```shell
yarn add --dev @types/package-name
```
O quarto passo é migrar módulo por módulo com uma abordagem de baixo para cima, seguindo seu Gráfico de Dependências começando pelas folhas. A ideia é começar convertendo Módulos que não dependem de outros Módulos. Para visualizar os gráficos de dependência, você pode usar a ferramenta "madge".
Bons módulos candidatos para essas conversões iniciais são funções utilitárias e código relacionado a APIs ou especificações externas. É possível gerar automaticamente definições de tipo TypeScript a partir de contratos Swagger, GraphQL ou esquemas JSON para serem incluídos em seu projeto.
Quando não houver especificações ou esquemas oficiais disponíveis, você pode gerar tipos a partir de dados brutos, como JSON retornado por um servidor. No entanto, recomenda-se gerar tipos a partir de especificações em vez de dados para evitar perder casos extremos.
Durante a migração, evite a refatoração de código e concentre-se apenas em adicionar tipos aos seus módulos.
O quinto passo é habilitar o "noImplicitAny", que forçará que todos os tipos sejam conhecidos e definidos, proporcionando uma melhor experiência de TypeScript para seu projeto.
Durante a migração, você pode usar a diretiva `@ts-check`, que habilita a verificação de tipos do TypeScript em um arquivo JavaScript. Esta diretiva fornece uma versão flexível de verificação de tipos e pode ser usada inicialmente para identificar problemas em arquivos JavaScript. Quando o `@ts-check` é incluído em um arquivo, o TypeScript tentará deduzir definições usando comentários no estilo JSDoc. No entanto, considere usar anotações JSDoc apenas em um estágio muito inicial da migração.
Considere manter o valor padrão de `noEmitOnError` no seu tsconfig.json como false. Isso permitirá gerar o código-fonte JavaScript mesmo se erros forem relatados.
## Explorando o Sistema de Tipos
### O Serviço de Linguagem do TypeScript
O Serviço de Linguagem do TypeScript, também conhecido como tsserver, oferece vários recursos, como relatório de erros, diagnósticos, compilar ao salvar, renomeação, ir para definição, listas de preenchimento, ajuda de assinatura e muito mais. É usado principalmente por ambientes de desenvolvimento integrados (IDEs) para fornecer suporte ao IntelliSense. Ele se integra perfeitamente ao Visual Studio Code e é utilizado por ferramentas como Conquer of Completion (Coc).
Os desenvolvedores podem aproveitar uma API dedicada e criar seus próprios plugins de serviço de linguagem personalizados para aprimorar a experiência de edição do TypeScript. Isso pode ser particularmente útil para implementar recursos especiais de linting ou habilitar o preenchimento automático para uma linguagem de modelagem personalizada.
Um exemplo de plugin personalizado do mundo real é o "typescript-styled-plugin", que fornece relatórios de erros de sintaxe e suporte IntelliSense para propriedades CSS em componentes estilizados (styled components).
Para mais informações e guias de início rápido, você pode consultar o Wiki oficial do TypeScript no GitHub: [https://github.com/microsoft/TypeScript/wiki/](https://github.com/microsoft/TypeScript/wiki/)
### Tipagem Estrutural
O TypeScript é baseado em um sistema de tipos estrutural. Isso significa que a compatibilidade e a equivalência de tipos são determinadas pela estrutura ou definição real do tipo, em vez de seu nome ou local de declaração, como em sistemas de tipos nominativos como C# ou C++.
O sistema de tipos estrutural do TypeScript foi projetado com base em como o sistema de tipagem dinâmica "duck typing" do JavaScript funciona durante o tempo de execução.
O exemplo a seguir é um código TypeScript válido. Como você pode observar, "X" e "Y" têm o mesmo membro "a", embora tenham nomes de declaração diferentes. Os tipos são determinados por suas estruturas e, neste caso, como as estruturas são as mesmas, eles são compatíveis e válidos.
```typescript
type X = {
a: string;
};
type Y = {
a: string;
};
const x: X = { a: 'a' };
const y: Y = x; // Válido
```
### Regras Fundamentais de Comparação do TypeScript
O processo de comparação do TypeScript é recursivo e executado em tipos aninhados em qualquer nível.
Um tipo "X" é compatível com "Y" se "Y" tiver pelo menos os mesmos membros que "X".
```typescript
type X = {
a: string;
};
const y = { a: 'A', b: 'B' }; // Válido, pois tem pelo menos os mesmos membros que X
const r: X = y;
```
Os parâmetros da função são comparados por tipos, não por seus nomes:
```typescript
type X = (a: number) => void;
type Y = (a: number) => void;
let x: X = (j: number) => undefined;
let y: Y = (k: number) => undefined;
y = x; // Válido
x = y; // Válido
```
Os tipos de retorno da função devem ser os mesmos:
```typescript
type X = (a: number) => undefined;
type Y = (a: number) => number;
let x: X = (a: number) => undefined;
let y: Y = (a: number) => 1;
y = x; // Inválido
x = y; // Inválido
```
O tipo de retorno de uma função de origem deve ser um subtipo do tipo de retorno de uma função de destino:
```typescript
let x = () => ({ a: 'A' });
let y = () => ({ a: 'A', b: 'B' });
x = y; // Válido
y = x; // Inválido, o membro b está faltando
```
Descartar parâmetros de função é permitido, pois é uma prática comum no JavaScript, por exemplo, usando "Array.prototype.map()":
```typescript
[1, 2, 3].map((element, _index, _array) => element + 'x');
```
Portanto, as seguintes declarações de tipo são completamente válidas:
```typescript
type X = (a: number) => undefined;
type Y = (a: number, b: number) => undefined;
let x: X = (a: number) => undefined;
let y: Y = (a: number) => undefined; // Falta o parâmetro b
y = x; // Válido
```
Quaisquer parâmetros opcionais adicionais do tipo de origem são válidos:
```typescript
type X = (a: number, b?: number, c?: number) => undefined;
type Y = (a: number) => undefined;
let x: X = a => undefined;
let y: Y = a => undefined;
y = x; // Válido
x = y; // Válido
```
Quaisquer parâmetros opcionais do tipo de destino sem parâmetros correspondentes no tipo de origem são válidos e não constituem um erro:
```typescript
type X = (a: number) => undefined;
type Y = (a: number, b?: number) => undefined;
let x: X = a => undefined;
let y: Y = a => undefined;
y = x; // Válido
x = y; // Válido
```
O parâmetro rest é tratado como uma série infinita de parâmetros opcionais:
```typescript
type X = (a: number, ...rest: number[]) => undefined;
let x: X = a => undefined; // Válido
```
Funções com sobrecargas são válidas se a assinatura da sobrecarga for compatível com sua assinatura de implementação:
```typescript
function x(a: string): void;
function x(a: string, b: number): void;
function x(a: string, b?: number): void {
console.log(a, b);
}
x('a'); // Válido
x('a', 1); // Válido
function y(a: string): void; // Inválido, não compatível com a assinatura de implementação
function y(a: string, b: number): void;
function y(a: string, b: number): void {
console.log(a, b);
}
y('a');
y('a', 1);
```
A comparação de parâmetros de função é bem-sucedida se os parâmetros de origem e de destino forem atribuíveis a supertipos ou subtipos (bivariância).
```typescript
// Supertipo
class X {
a: string;
constructor(value: string) {
this.a = value;
}
}
// Subtipo
class Y extends X {}
// Subtipo
class Z extends X {}
type GetA = (x: X) => string;
const getA: GetA = x => x.a;
// A bivariância aceita supertipos
console.log(getA(new X('x'))); // Válido
console.log(getA(new Y('Y'))); // Válido
console.log(getA(new Z('z'))); // Válido
```
Enums são comparáveis e válidos com números e vice-versa, mas comparar valores de Enum de diferentes tipos de Enum é inválido.
```typescript
enum X {
A,
B,
}
enum Y {
A,
B,
C,
}
const xa: number = X.A; // Válido
const ya: Y = 0; // Válido
X.A === Y.A; // Inválido
```
Instâncias de uma classe estão sujeitas a uma verificação de compatibilidade para seus membros privados e protegidos:
```typescript
class X {
public a: string;
constructor(value: string) {
this.a = value;
}
}
class Y {
private a: string;
constructor(value: string) {
this.a = value;
}
}
let x: X = new Y('y'); // Inválido
```
A verificação de comparação não leva em consideração as diferentes hierarquias de herança, por exemplo:
```typescript
class X {
public a: string;
constructor(value: string) {
this.a = value;
}
}
class Y extends X {
public a: string;
constructor(value: string) {
super(value);
this.a = value;
}
}
class Z {
public a: string;
constructor(value: string) {
this.a = value;
}
}
let x: X = new X('x');
let y: Y = new Y('y');
let z: Z = new Z('z');
x === y; // Válido
x === z; // Válido mesmo que z seja de uma hierarquia de herança diferente
```
Genéricos são comparados usando suas estruturas baseadas no tipo resultante após a aplicação do parâmetro genérico; apenas o resultado final é comparado como um tipo não genérico.
```typescript
interface X {
a: T;
}
let x: X = { a: 1 };
let y: X = { a: 'a' };
x === y; // Inválido, pois o argumento de tipo é usado na estrutura final
```
```typescript
interface X {}
const x: X = 1;
const y: X = 'a';
x === y; // Válido, pois o argumento de tipo não é usado na estrutura final
```
Quando os genéricos não têm seu argumento de tipo especificado, todos os argumentos não especificados são tratados como tipos com "any":
```typescript
type X = (x: T) => T;
type Y = (y: K) => K;
let x: X = x => x;
let y: Y = y => y;
x = y; // Válido
```
Lembre-se:
```typescript
let a: number = 1;
let b: number = 2;
a = b; // Válido, tudo é atribuível a si mesmo
let c: any;
c = 1; // Válido, todos os tipos são atribuíveis a any
let d: unknown;
d = 1; // Válido, todos os tipos são atribuíveis a unknown
let e: unknown;
let e1: unknown = e; // Válido, unknown só é atribuível a si mesmo e a any
let e2: any = e; // Válido
let e3: number = e; // Inválido
let f: never;
f = 1; // Inválido, nada é atribuível a never
let g: void;
let g1: any;
g = 1; // Inválido, void não é atribuível a nada, exceto any, nem nada é atribuível a ele
g = g1; // Válido
```
Observe que quando "strictNullChecks" está habilitado, "null" e "undefined" são tratados de forma semelhante a "void"; caso contrário, são semelhantes a "never".
### Tipos como Conjuntos
No TypeScript, um tipo é um conjunto de valores possíveis. Este conjunto também é conhecido como o domínio do tipo. Cada valor de um tipo pode ser visto como um elemento em um conjunto. Um tipo estabelece as restrições que cada elemento no conjunto deve satisfazer para ser considerado um membro desse conjunto.
A principal tarefa do TypeScript é verificar se um conjunto é um subconjunto de outro.
O TypeScript suporta vários tipos de conjuntos:
| Termo do conjunto | TypeScript | Notas |
| ---------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Conjunto vazio | never | "never" não contém nada além de si mesmo |
| Conjunto de elemento único | undefined / null / tipo literal | |
| Conjunto finito | boolean / união | |
| Conjunto infinito | string / number / objeto | |
| Conjunto universal | any / unknown | Cada elemento é um membro de "any" e cada conjunto é um subconjunto dele / "unknown" é uma contraparte segura em termos de tipo do "any" |
Aqui estão alguns exemplos:
| TypeScript | Termo do conjunto | Exemplo |
| ----------------------- | -------------------------- | ------------------------------------------------------------------------------------- |
| never | ∅ (conjunto vazio) | const x: never = 'x'; // Erro: O tipo 'string' não pode ser atribuído ao tipo 'never' |
| | |
| Tipo literal | Conjunto de elemento único | type X = 'X'; |
| | | type Y = 7; |
| | |
| Valor atribuível a T | Valor ∈ T (membro de) | type XY = 'X' \| 'Y'; |
| | | const x: XY = 'X'; |
| | |
| T1 atribuível a T2 | T1 ⊆ T2 (subconjunto de) | type XY = 'X' \| 'Y'; |
| | | const x: XY = 'X'; |
| | | const j: XY = 'J'; // O tipo '"J"' não pode ser atribuído ao tipo 'XY'. |
| | | |
| T1 extends T2 | T1 ⊆ T2 (subconjunto de) | type X = 'X' extends string ? true : false; |
| | |
| T1 \| T2 | T1 ∪ T2 (união) | type XY = 'X' \| 'Y'; |
| | | type JK = 1 \| 2; |
| | |
| T1 & T2 | T1 ∩ T2 (interseção) | type X = \{ a: string \} |
| | | type Y = \{ b: string \} |
| | | type XY = X & Y |
| | | const x: XY = \{ a: 'a', b: 'b' \} |
| | |
| unknown | Conjunto universal | const x: unknown = 1 |
Uma união, (T1 | T2), cria um conjunto mais amplo (ambos):
```typescript
type X = {
a: string;
};
type Y = {
b: string;
};
type XY = X | Y;
const r: XY = { a: 'a', b: 'x' }; // Válido
```
Uma interseção, (T1 & T2), cria um conjunto mais estreito (apenas o que é compartilhado):
```typescript
type X = {
a: string;
};
type Y = {
a: string;
b: string;
};
type XY = X & Y;
const r: XY = { a: 'a' }; // Inválido
const j: XY = { a: 'a', b: 'b' }; // Válido
```
A palavra-chave `extends` pode ser considerada como "subconjunto de" neste contexto. Ela define uma restrição para um tipo. O `extends` usado com um genérico trata o genérico como um conjunto infinito e o restringe a um tipo mais específico.
Observe que o `extends` nada tem a ver com hierarquia no sentido de Orientação a Objetos (não existe esse conceito no TypeScript).
O TypeScript trabalha com conjuntos e não possui uma hierarquia estrita; de fato, como no exemplo abaixo, dois tipos podem se sobrepor sem que nenhum seja um subtipo do outro (o TypeScript considera a estrutura, a forma dos objetos).
```typescript
interface X {
a: string;
}
interface Y extends X {
b: string;
}
interface Z extends Y {
c: string;
}
const z: Z = { a: 'a', b: 'b', c: 'c' };
interface X1 {
a: string;
}
interface Y1 {
a: string;
b: string;
}
interface Z1 {
a: string;
b: string;
c: string;
}
const z1: Z1 = { a: 'a', b: 'b', c: 'c' };
const r: Z1 = z; // Válido
```
### Atribuir um tipo: Declarações de Tipo e Asserções de Tipo
Um tipo pode ser atribuído de diferentes maneiras no TypeScript:
#### Declaração de Tipo
No exemplo a seguir, usamos x: X (": Tipo") para declarar um tipo para a variável x.
```typescript
type X = {
a: string;
};
// Declaração de tipo
const x: X = {
a: 'a',
};
```
Se a variável não estiver no formato especificado, o TypeScript relatará um erro. Por exemplo:
```typescript
type X = {
a: string;
};
const x: X = {
a: 'a',
b: 'b', // Erro: O objeto literal só pode especificar propriedades conhecidas
};
```
#### Asserção de Tipo
É possível adicionar uma asserção usando a palavra-chave `as`. Isso informa ao compilador que o desenvolvedor tem mais informações sobre um tipo e silencia quaisquer erros que possam ocorrer.
Por exemplo:
```typescript
type X = {
a: string;
};
const x = {
a: 'a',
b: 'b',
} as X;
```
No exemplo acima, o objeto x é asseverado como tendo o tipo X usando a palavra-chave `as`. Isso informa ao compilador TypeScript que o objeto está em conformidade com o tipo especificado, embora tenha uma propriedade b adicional não presente na definição do tipo.
Asserções de tipo são úteis em situações onde um tipo mais específico precisa ser especificado, especialmente ao trabalhar com o DOM. Por exemplo:
```typescript
const myInput = document.getElementById('my_input') as HTMLInputElement;
```
Aqui, a asserção de tipo `as HTMLInputElement` é usada para dizer ao TypeScript que o resultado de `getElementById` deve ser tratado como um `HTMLInputElement`.
Asserções de tipo também podem ser usadas para mapear chaves novamente, conforme mostrado no exemplo abaixo com literais de template:
```typescript
type J = {
[Property in keyof Type as `prefix_${string &
Property}`]: () => Type[Property];
};
type X = {
a: string;
b: number;
};
type Y = J;
```
Neste exemplo, o tipo `J` usa um tipo mapeado com um literal de template para mapear as chaves de `Type`. Ele cria novas propriedades com um "prefix_" adicionado a cada chave, e seus valores correspondentes são funções que retornam os valores originais da propriedade.
Vale a pena notar que, ao usar uma asserção de tipo, o TypeScript não executará a verificação de excesso de propriedades. Portanto, geralmente é preferível usar uma Declaração de Tipo quando a estrutura do objeto for conhecida antecipadamente.
#### Declarações de Ambiente (Ambient Declarations)
Declarações de ambiente são arquivos que descrevem tipos para código JavaScript; eles têm o formato de nome de arquivo `.d.ts`. Geralmente são importados e usados para anotar bibliotecas JavaScript existentes ou para adicionar tipos a arquivos JS existentes em seu projeto.
Muitos tipos de bibliotecas comuns podem ser encontrados em:
[https://github.com/DefinitelyTyped/DefinitelyTyped/](https://github.com/DefinitelyTyped/DefinitelyTyped/)
e podem ser instalados usando:
```shell
npm install --save-dev @types/nome-da-biblioteca
```
Para suas Declarações de Ambiente definidas, você pode importar usando a referência de "barra tripla":
```typescript
///
```
Você pode usar Declarações de Ambiente até mesmo em arquivos JavaScript usando `// @ts-check`.
A palavra-chave `declare` habilita definições de tipo para código JavaScript existente sem importá-lo, servindo como um marcador para tipos de outro arquivo ou globalmente.
### Verificação de Propriedades e Verificação de Excesso de Propriedades
O TypeScript é baseado em um sistema de tipos estrutural, mas a verificação de excesso de propriedades é um recurso do TypeScript que permite verificar se um objeto tem exatamente as propriedades especificadas no tipo.
A Verificação de Excesso de Propriedades é executada ao atribuir objetos literais a variáveis ou ao passá-los como argumentos para funções, por exemplo.
```typescript
type X = {
a: string;
};
const y = { a: 'a', b: 'b' };
const x: X = y; // Válido por causa da tipagem estrutural
const w: X = { a: 'a', b: 'b' }; // Inválido por causa da verificação de excesso de propriedades
```
### Tipos Fracos (Weak Types)
Um tipo é considerado fraco quando não contém nada além de um conjunto de todas as propriedades opcionais:
```typescript
type X = {
a?: string;
b?: string;
};
```
O TypeScript considera um erro atribuir qualquer coisa a um tipo fraco quando não há sobreposição; por exemplo, o seguinte lança um erro:
```typescript
type Options = {
a?: string;
b?: string;
};
const fn = (options: Options) => undefined;
fn({ c: 'c' }); // Inválido
```
Embora não recomendado, se necessário, é possível ignorar esta verificação usando asserção de tipo:
```typescript
type Options = {
a?: string;
b?: string;
};
const fn = (options: Options) => undefined;
fn({ c: 'c' } as Options); // Válido
```
Ou adicionando `unknown` à assinatura de índice do tipo fraco:
```typescript
type Options = {
[prop: string]: unknown;
a?: string;
b?: string;
};
const fn = (options: Options) => undefined;
fn({ c: 'c' }); // Válido
```
### Verificação Estrita de Objeto Literal (Freshness)
A verificação estrita de objeto literal, às vezes chamada de "freshness", é um recurso do TypeScript que ajuda a capturar propriedades em excesso ou com erro de ortografia que, de outra forma, passariam despercebidas em verificações normais de tipo estrutural.
Ao criar um objeto literal, o compilador TypeScript o considera "fresco" (fresh). Se o objeto literal for atribuído a uma variável ou passado como um parâmetro, o TypeScript lançará um erro se o objeto literal especificar propriedades que não existem no tipo de destino.
No entanto, a "freshness" desaparece quando um objeto literal é alargado ou quando uma asserção de tipo é usada.
Aqui estão alguns exemplos para ilustrar:
```typescript
type X = { a: string };
type Y = { a: string; b: string };
let x: X;
x = { a: 'a', b: 'b' }; // Verificação de Freshness: Atribuição inválida
var y: Y;
y = { a: 'a', bx: 'bx' }; // Verificação de Freshness: Atribuição inválida
const fn = (x: X) => console.log(x.a);
fn(x);
fn(y); // Alargamento (Widening): Sem erros, estruturalmente compatível em termos de tipo
fn({ a: 'a', bx: 'b' }); // Verificação de Freshness: Argumento inválido
let c: X = { a: 'a' };
let d: Y = { a: 'a', b: '' };
c = d; // Alargamento: Sem verificação de Freshness
```
### Inferência de Tipo
O TypeScript pode inferir tipos quando nenhuma anotação é fornecida durante a:
* Inicialização da variável.
* Inicialização de membros.
* Definição de valores padrão para parâmetros.
* Tipo de retorno da função.
Por exemplo:
```typescript
let x = 'x'; // O tipo inferido é string
```
O compilador TypeScript analisa o valor ou expressão e determina seu tipo com base nas informações disponíveis.
### Inferências Mais Avançadas
Quando várias expressões são usadas na inferência de tipo, o TypeScript procura pelos "melhores tipos comuns" (best common types). Por exemplo:
```typescript
let x = [1, 'x', 1, null]; // O tipo inferido é: (string | number | null)[]
```
Se o compilador não conseguir encontrar os melhores tipos comuns, ele retorna um tipo de união. Por exemplo:
```typescript
let x = [new RegExp('x'), new Date()]; // O tipo inferido é: (RegExp | Date)[]
```
O TypeScript utiliza a "tipagem contextual" baseada na localização da variável para inferir tipos. No exemplo a seguir, o compilador sabe que `e` é do tipo `MouseEvent` por causa do tipo de evento `click` definido no arquivo `lib.d.ts`, que contém declarações de ambiente para várias construções JavaScript comuns e o DOM:
```typescript
window.addEventListener('click', function (e) {}); // O tipo inferido de e é MouseEvent
```
### Alargamento de Tipo (Type Widening)
O alargamento de tipo (type widening) é o processo no qual o TypeScript atribui um tipo a uma variável inicializada quando nenhuma anotação de tipo foi fornecida. Ele permite tipos de mais estreitos para mais amplos, mas não o contrário.
No exemplo a seguir:
```typescript
let x = 'x'; // O TypeScript infere como string, um tipo amplo
let y: 'y' | 'x' = 'y'; // o tipo de y é uma união de tipos literais
y = x; // Inválido: O tipo 'string' não pode ser atribuído ao tipo '"x" | "y"'.
```
O TypeScript atribui `string` a `x` com base no valor único fornecido durante a inicialização (`x`); este é um exemplo de alargamento.
O TypeScript fornece maneiras de ter controle sobre o processo de alargamento, por exemplo, usando "const".
### Const
O uso da palavra-chave `const` ao declarar uma variável resulta em uma inferência de tipo mais estreita no TypeScript.
Por exemplo:
```typescript
const x = 'x'; // O TypeScript infere o tipo de x como 'x', um tipo mais estreito
let y: 'y' | 'x' = 'y';
y = x; // Válido: O tipo de x é inferido como 'x'
```
Ao usar `const` para declarar a variável x, seu tipo é estreitado para o valor literal específico 'x'. Como o tipo de x é estreitado, ele pode ser atribuído à variável y sem nenhum erro.
A razão pela qual o tipo pode ser inferido é porque as variáveis `const` não podem ser reatribuídas, portanto seu tipo pode ser estreitado para um tipo literal específico, neste caso, o tipo literal 'x'.
#### Modificador Const em Parâmetros de Tipo
A partir da versão 5.0 do TypeScript, é possível especificar o atributo `const` em um parâmetro de tipo genérico. Isso permite inferir o tipo mais preciso possível. Vejamos um exemplo sem usar `const`:
```typescript
function identity(value: T) {
// Sem const aqui
return value;
}
const values = identity({ a: 'a', b: 'b' }); // O tipo inferido é: { a: string; b: string; }
```
Como você pode ver, as propriedades `a` e `b` são inferidas com o tipo `string`.
Agora, vejamos a diferença com a versão `const`:
```typescript
function identity(value: T) {
// Usando modificador const em parâmetros de tipo
return value;
}
const values = identity({ a: 'a', b: 'b' }); // O tipo inferido é: { a: "a"; b: "b"; }
```
Agora podemos ver que as propriedades `a` e `b` são inferidas como `const`, portanto `a` e `b` são tratados como literais de string em vez de apenas tipos `string`.
#### Asserção Const (Const assertion)
Este recurso permite declarar uma variável com um tipo literal mais preciso baseado em seu valor de inicialização, sinalizando ao compilador que o valor deve ser tratado como um literal imutável. Aqui estão alguns exemplos:
Em uma única propriedade:
```typescript
const v = {
x: 3 as const,
};
v.x = 3;
```
Em um objeto inteiro:
```typescript
const v = {
x: 1,
y: 2,
} as const;
```
Isso pode ser particularmente útil ao definir o tipo para uma tupla:
```typescript
const x = [1, 2, 3]; // number[]
const y = [1, 2, 3] as const; // Tupla de readonly [1, 2, 3]
```
### Anotação de Tipo Explícita
Podemos ser específicos e passar um tipo; no exemplo a seguir, a propriedade `x` é do tipo `number`:
```typescript
const v = {
x: 1, // Tipo inferido: number (alargamento)
};
v.x = 3; // Válido
```
Podemos tornar a anotação de tipo mais específica usando uma união de tipos literais:
```typescript
const v: { x: 1 | 2 | 3 } = {
x: 1, // x agora é uma união de tipos literais: 1 | 2 | 3
};
v.x = 3; // Válido
v.x = 100; // Inválido
```
### Estreitamento de Tipo (Type Narrowing)
O Estreitamento de Tipo (Type Narrowing) é o processo no TypeScript onde um tipo geral é estreitado para um tipo mais específico. Isso ocorre quando o TypeScript analisa o código e determina que certas condições ou operações podem refinar a informação do tipo.
O estreitamento de tipos pode ocorrer de diferentes maneiras, incluindo:
#### Condições
Ao usar instruções condicionais, como `if` ou `switch`, o TypeScript pode estreitar o tipo com base no resultado da condição. Por exemplo:
```typescript
let x: number | undefined = 10;
if (x !== undefined) {
x += 100; // O tipo é number, que foi estreitado pela condição
}
```
#### Lançando ou retornando
Lançar um erro ou retornar cedo de uma ramificação pode ser usado para ajudar o TypeScript a estreitar um tipo. Por exemplo:
```typescript
let x: number | undefined = 10;
if (x === undefined) {
throw 'erro';
}
x += 100;
```
Outras formas de estreitar tipos no TypeScript incluem:
* Operador `instanceof`: Usado para verificar se um objeto é uma instância de uma classe específica.
* Operador `in`: Usado para verificar se uma propriedade existe em um objeto.
* Operador `typeof`: Usado para verificar o tipo de um valor em tempo de execução.
* Funções integradas como `Array.isArray()`: Usadas para verificar se um valor é um array.
#### União Discriminada
O uso de uma "União Discriminada" é um padrão no TypeScript onde uma "tag" explícita é adicionada aos objetos para distinguir entre diferentes tipos dentro de uma união. Este padrão também é conhecido como "união tagueada" (tagged union). No exemplo a seguir, a "tag" é representada pela propriedade "type":
```typescript
type A = { type: 'type_a'; value: number };
type B = { type: 'type_b'; value: string };
const x = (input: A | B): string | number => {
switch (input.type) {
case 'type_a':
return input.value + 100; // o tipo é A
case 'type_b':
return input.value + 'extra'; // o tipo é B
}
};
```
#### Proteções de Tipo Definidas pelo Usuário (User-Defined Type Guards)
Em casos onde o TypeScript não é capaz de determinar um tipo, é possível escrever uma função auxiliar conhecida como "proteção de tipo definida pelo usuário" (user-defined type guard). No exemplo a seguir, utilizaremos um Predicado de Tipo para estreitar o tipo após aplicar certa filtragem:
```typescript
const data = ['a', null, 'c', 'd', null, 'f'];
const r1 = data.filter(x => x != null); // O tipo é (string | null)[], o TypeScript não foi capaz de inferir o tipo corretamente
const isValid = (item: string | null): item is string => item !== null; // Protetor de tipo customizado
const r2 = data.filter(isValid); // O tipo está correto agora string[], ao usar o protetor de tipo predicado conseguimos estreitar o tipo
```
## Tipos Primitivos
O TypeScript suporta 7 tipos primitivos. Um tipo de dado primitivo refere-se a um tipo que não é um objeto e não possui nenhum método associado a ele. No TypeScript, todos os tipos primitivos são imutáveis, o que significa que seus valores não podem ser alterados uma vez que são atribuídos.
### string
O tipo primitivo `string` armazena dados textuais, e o valor é sempre delimitado por aspas duplas ou simples.
```typescript
const x: string = 'x';
const y: string = 'y';
```
As strings podem abranger várias linhas se estiverem rodeadas pelo caractere de crase (`):
```typescript
let sentence: string = `xxx,
yyy`;
```
### boolean
O tipo de dado `boolean` no TypeScript armazena um valor binário, seja `true` ou `false`.
```typescript
const isReady: boolean = true;
```
### number
Um tipo de dado `number` no TypeScript é representado com um valor de ponto flutuante de 64 bits. Um tipo `number` pode representar inteiros e frações.
O TypeScript também suporta hexadecimal, binário e octal, por exemplo:
```typescript
const decimal: number = 10;
const hexadecimal: number = 0xa00d; // Hexadecimal começa com 0x
const binary: number = 0b1010; // Binário começa com 0b
const octal: number = 0o633; // Octal começa com 0o
```
### bigInt
Um `bigInt` representa valores numéricos muito grandes (253 – 1) e que não podem ser representados com um `number`.
Um `bigInt` pode ser criado chamando a função integrada `BigInt()` ou adicionando `n` ao final de qualquer literal numérico inteiro:
```typescript
const x: bigint = BigInt(9007199254740991);
const y: bigint = 9007199254740991n;
```
Notas:
* Valores `bigInt` não podem ser misturados com `number` e não podem ser usados com a função integrada `Math`; eles devem ser coagidos para o mesmo tipo.
* Valores `bigInt` estão disponíveis apenas se a configuração da meta (target) for ES2020 ou superior.
### Symbol
Symbols são identificadores únicos que podem ser usados como chaves de propriedade em objetos para evitar conflitos de nomenclatura.
```typescript
type Obj = {
[sym: symbol]: number;
};
const a = Symbol('a');
const b = Symbol('b');
let obj: Obj = {};
obj[a] = 123;
obj[b] = 456;
console.log(obj[a]); // 123
console.log(obj[b]); // 456
```
### null e undefined
Os tipos `null` e `undefined` representam a ausência de valor.
O tipo `undefined` significa que o valor não foi atribuído ou inicializado, ou indica uma ausência não intencional de valor.
O tipo `null` significa que sabemos que o campo não possui um valor, portanto o valor está indisponível; indica uma ausência intencional de valor.
### Array
Um `array` é um tipo de dado que pode armazenar múltiplos valores do mesmo tipo ou não. Ele pode ser definido usando a seguinte sintaxe:
```typescript
const x: string[] = ['a', 'b'];
const y: Array = ['a', 'b'];
const j: Array = ['a', 1, 'b', 2]; // União
```
O TypeScript suporta arrays somente leitura (readonly) usando a seguinte sintaxe:
```typescript
const x: readonly string[] = ['a', 'b']; // Modificador readonly
const y: ReadonlyArray = ['a', 'b'];
const j: ReadonlyArray = ['a', 1, 'b', 2];
j.push('x'); // Inválido
```
O TypeScript suporta tupla e tupla somente leitura:
```typescript
const x: [string, number] = ['a', 1];
const y: readonly [string, number] = ['a', 1];
```
### any
O tipo de dado `any` representa literalmente "qualquer" valor; é o valor padrão quando o TypeScript não consegue inferir o tipo ou quando este não é especificado.
Ao usar `any`, o compilador TypeScript ignora a verificação de tipo, portanto não há segurança de tipo quando o `any` está sendo usado. Geralmente, não use `any` para silenciar o compilador quando ocorre um erro; em vez disso, concentre-se em corrigir o erro, pois ao usar `any` é possível quebrar contratos e perdemos os benefícios do preenchimento automático do TypeScript.
O tipo `any` pode ser útil durante uma migração gradual de JavaScript para TypeScript, pois pode silenciar o compilador.
Para novos projetos, use a configuração do TypeScript `noImplicitAny`, que permite que o TypeScript emita erros onde `any` é usado ou inferido.
O tipo `any` é geralmente uma fonte de erros que podem mascarar problemas reais com seus tipos. Evite usá-lo o máximo possível.
## Anotações de Tipo
Em variáveis declaradas usando `var`, `let` e `const`, é possível adicionar opcionalmente um tipo:
```typescript
const x: number = 1;
```
O TypeScript faz um bom trabalho ao inferir tipos, especialmente quando são simples, portanto essas declarações, na maioria dos casos, não são necessárias.
Em funções, é possível adicionar anotações de tipo aos parâmetros:
```typescript
function sum(a: number, b: number) {
return a + b;
}
```
O seguinte é um exemplo usando funções anônimas (as chamadas funções lambda):
```typescript
const sum = (a: number, b: number) => a + b;
```
Essas anotações podem ser evitadas quando um valor padrão para um parâmetro está presente:
```typescript
const sum = (a = 10, b: number) => a + b;
```
Anotações de tipo de retorno podem ser adicionadas às funções:
```typescript
const sum = (a = 10, b: number): number => a + b;
```
Isso é útil especialmente para funções mais complexas, pois escrever explicitamente o tipo de retorno antes de uma implementação pode ajudar a pensar melhor sobre a função.
Geralmente, considere anotar as assinaturas de tipo, mas não as variáveis locais do corpo, e sempre adicione tipos a objetos literais.
## Propriedades Opcionais
Um objeto pode especificar Propriedades Opcionais adicionando um ponto de interrogação `?` ao final do nome da propriedade:
```typescript
type X = {
a: number;
b?: number; // Opcional
};
```
É possível especificar um valor padrão quando uma propriedade é opcional:
```typescript
type X = {
a: number;
b?: number;
};
const x = ({ a, b = 100 }: X) => a + b;
```
## Propriedades Somente Leitura (Readonly)
É possível impedir a escrita em uma propriedade usando o modificador `readonly`, que garante que a propriedade não possa ser reescrita, mas não fornece nenhuma garantia de imutabilidade total:
```typescript
interface Y {
readonly a: number;
}
type X = {
readonly a: number;
};
type J = Readonly<{
a: number;
}>;
type K = {
readonly [index: number]: string;
};
```
## Assinaturas de Índice (Index Signatures)
No TypeScript, podemos usar como assinatura de índice `string`, `number` e `symbol`:
```typescript
type K = {
[name: string | number]: string;
};
const k: K = { x: 'x', 1: 'b' };
console.log(k['x']);
console.log(k[1]);
console.log(k['1']); // Mesmo resultado que k[1]
```
Observe que o JavaScript converte automaticamente um índice com `number` em um índice com `string`, portanto `k[1]` ou `k["1"]` retornam o mesmo valor.
## Estendendo Tipos
É possível estender uma `interface` (copiar membros de outro tipo):
```typescript
interface X {
a: string;
}
interface Y extends X {
b: string;
}
```
Também é possível estender de múltiplos tipos:
```typescript
interface A {
a: string;
}
interface B {
b: string;
}
interface Y extends A, B {
y: string;
}
```
A palavra-chave `extends` funciona apenas em interfaces e classes; para tipos, use uma interseção:
```typescript
type A = {
a: number;
};
type B = {
b: number;
};
type C = A & B;
```
É possível estender um tipo usando uma inferência, mas não o contrário:
```typescript
type A = {
a: string;
};
interface B extends A {
b: string;
}
```
## Tipos Literais
Um Tipo Literal é um conjunto de elemento único a partir de um tipo coletivo; ele define um valor exato que é um primitivo do JavaScript.
Os Tipos Literais no TypeScript são números, strings e booleanos.
Exemplo de literais:
```typescript
const a = 'a'; // Tipo literal de string
const b = 1; // Tipo literal numérico
const c = true; // Tipo literal booleano
```
Tipos Literais de String, Numéricos e Booleanos são usados em uniões, protetores de tipo (type guards) e apelidos de tipo (type aliases).
No exemplo a seguir, você pode ver um apelido de tipo de união. `O` consiste apenas nos valores especificados; nenhuma outra string é válida:
```typescript
type O = 'a' | 'b' | 'c';
```
## Inferência Literal
A Inferência Literal é um recurso do TypeScript que permite que o tipo de uma variável ou parâmetro seja inferido com base em seu valor.
No exemplo a seguir, podemos ver que o TypeScript considera `x` um tipo literal, pois o valor não pode ser alterado posteriormente, enquanto `y` é inferido como string, pois pode ser modificado posteriormente.
```typescript
const x = 'x'; // Tipo literal de 'x', porque este valor não pode ser alterado
let y = 'y'; // Tipo string, pois podemos alterar este valor
```
No exemplo a seguir, podemos ver que `o.x` foi inferido como uma `string` (e não um literal de `a`), pois o TypeScript considera que o valor pode ser alterado posteriormente.
```typescript
type X = 'a' | 'b';
let o = {
x: 'a', // Esta é uma string mais ampla (wider string)
};
const fn = (x: X) => `${x}-foo`;
console.log(fn(o.x)); // Argument of type 'string' is not assignable to parameter of type 'X'
```
Como você pode observar, o código lança um erro ao passar `o.x` para `fn`, pois X é um tipo mais estreito (narrower).
Podemos resolver este problema usando asserção de tipo com `const` ou o tipo `X`:
```typescript
let o = {
x: 'a' as const,
};
```
ou:
```typescript
let o = {
x: 'a' as X,
};
```
## strictNullChecks
`strictNullChecks` é uma opção do compilador TypeScript que impõe a verificação estrita de nulos. Quando esta opção está habilitada, variáveis e parâmetros só podem receber `null` | `undefined` se tiverem sido explicitamente declarados como sendo desse tipo usando o tipo de união `null` | `undefined`. Se uma variável ou parâmetro não for explicitamente declarado como anulável, o TypeScript gerará um erro para evitar possíveis erros de tempo de execução.
## Enums
No TypeScript, um `enum` é um conjunto de valores constantes nomeados.
```typescript
enum Color {
Red = '#ff0000',
Green = '#00ff00',
Blue = '#0000ff',
}
```
Enums podem ser definidos de diferentes maneiras:
### Enums numéricos
No TypeScript, um Enum Numérico é um Enum onde cada constante recebe um valor numérico, começando em 0 por padrão.
```typescript
enum Size {
Small, // o valor começa de 0
Medium,
Large,
}
```
É possível especificar valores personalizados atribuindo-os explicitamente:
```typescript
enum Size {
Small = 10,
Medium,
Large,
}
console.log(Size.Medium); // 11
```
### Enums de string
No TypeScript, um Enum de String é um Enum onde cada constante recebe um valor de string.
```typescript
enum Language {
English = 'EN',
Spanish = 'ES',
}
```
Nota: O TypeScript permite o uso de Enums heterogêneos, onde membros de string e numéricos podem coexistir.
### Enums constantes
Um enum constante (const enum) no TypeScript é um tipo especial de Enum onde todos os valores são conhecidos em tempo de compilação e são inseridos diretamente (inlined) onde quer que o enum seja usado, resultando em um código mais eficiente.
```typescript
const enum Language {
English = 'EN',
Spanish = 'ES',
}
console.log(Language.English);
```
Será compilado para:
```typescript
console.log('EN' /* Language.English */);
```
Notas:
Enums Constantes têm valores fixos (hardcoded), apagando o Enum, o que pode ser mais eficiente em bibliotecas autocontidas, mas geralmente não é desejável. Além disso, enums constantes não podem ter membros computados.
### Mapeamento reverso
No TypeScript, os mapeamentos reversos em Enums referem-se à capacidade de recuperar o nome do membro do Enum a partir de seu valor. Por padrão, os membros do Enum têm mapeamentos diretos (forward mappings) do nome para o valor, mas mapeamentos reversos podem ser criados definindo explicitamente os valores para cada membro. Os mapeamentos reversos são úteis quando você precisa procurar um membro do Enum pelo seu valor ou quando precisa iterar sobre todos os membros do Enum. Note que apenas membros de enums numéricos gerarão mapeamentos reversos, enquanto membros de Enums de String não possuem um mapeamento reverso gerado.
O seguinte enum:
```typescript
enum Grade {
A = 90,
B = 80,
C = 70,
F = 'fail',
}
```
Compila para:
```javascript
'use strict';
var Grade;
(function (Grade) {
Grade[(Grade['A'] = 90)] = 'A';
Grade[(Grade['B'] = 80)] = 'B';
Grade[(Grade['C'] = 70)] = 'C';
Grade['F'] = 'fail';
})(Grade || (Grade = {}));
```
Portanto, mapear valores para chaves funciona para membros de enums numéricos, mas não para membros de enums de string:
```typescript
enum Grade {
A = 90,
B = 80,
C = 70,
F = 'fail',
}
const myGrade = Grade.A;
console.log(Grade[myGrade]); // A
console.log(Grade[90]); // A
const failGrade = Grade.F;
console.log(failGrade); // fail
console.log(Grade[failGrade]); // Element implicitly has an 'any' type because index expression is not of type 'number'.
```
### Enums de ambiente
Um enum de ambiente no TypeScript é um tipo de Enum que é definido em um arquivo de declaração (*.d.ts) sem uma implementação associada. Ele permite definir um conjunto de constantes nomeadas que podem ser usadas de forma segura em relação aos tipos em diferentes arquivos, sem ter que importar os detalhes da implementação em cada arquivo.
### Membros computados e constantes
No TypeScript, um membro computado é um membro de um Enum que possui um valor calculado em tempo de execução, enquanto um membro constante é um membro cujo valor é definido em tempo de compilação e não pode ser alterado durante o tempo de execução. Membros computados são permitidos em Enums regulares, enquanto membros constantes são permitidos tanto em enums regulares quanto em enums constantes (const enums).
```typescript
// Membros constantes
enum Color {
Red = 1,
Green = 5,
Blue = Red + Green,
}
console.log(Color.Blue); // geração 6 em tempo de compilação
```
```typescript
// Membros computados
enum Color {
Red = 1,
Green = Math.pow(2, 2),
Blue = Math.floor(Math.random() * 3) + 1,
}
console.log(Color.Blue); // número aleatório gerado em tempo de execução
```
Enums são representados por uniões compostas pelos tipos de seus membros. Os valores de cada membro podem ser determinados por meio de expressões constantes ou não constantes, com membros que possuem valores constantes recebendo tipos literais. Para ilustrar, considere a declaração do tipo E e seus subtipos E.A, E.B e E.C. Neste caso, E representa a união E.A | E.B | E.C.
```typescript
const identity = (value: number) => value;
enum E {
A = 2 * 5, // Literal numérico
B = 'bar', // Literal de string
C = identity(42), // Computado opaco
}
console.log(E.C); //42
```
## Estreitamento (Narrowing)
O estreitamento (narrowing) no TypeScript é o processo de refinar o tipo de uma variável dentro de um bloco condicional. Isso é útil ao trabalhar com tipos de união, onde uma variável pode ter mais de um tipo.
O TypeScript reconhece várias maneiras de estreitar o tipo:
### typeof type guards
O protetor de tipo (type guard) `typeof` é um protetor de tipo específico no TypeScript que verifica o tipo de uma variável com base em seu tipo JavaScript integrado.
```typescript
const fn = (x: number | string) => {
if (typeof x === 'number') {
return x + 1; // x é number
}
return -1;
};
```
### Estreitamento de veracidade (Truthiness narrowing)
O estreitamento de veracidade (truthiness narrowing) no TypeScript funciona verificando se uma variável é verdadeira (truthy) ou falsa (falsy) para estreitar seu tipo adequadamente.
```typescript
const toUpperCase = (name: string | null) => {
if (name) {
return name.toUpperCase();
} else {
return null;
}
};
```
### Estreitamento de igualdade (Equality narrowing)
O estreitamento de igualdade (equality narrowing) no TypeScript funciona verificando se uma variável é igual a um valor específico ou não, para estreitar seu tipo adequadamente.
É usado em conjunto com instruções `switch` e operadores de igualdade como `===`, `!==`, `==` e `!=` para estreitar os tipos.
```typescript
const checkStatus = (status: 'success' | 'error') => {
switch (status) {
case 'success':
return true;
case 'error':
return null;
}
};
```
### Estreitamento com operador In
O estreitamento com o operador `in` no TypeScript é uma forma de estreitar o tipo de uma variável com base na existência de uma propriedade dentro do tipo da variável.
```typescript
type Dog = {
name: string;
breed: string;
};
type Cat = {
name: string;
likesCream: boolean;
};
const getAnimalType = (pet: Dog | Cat) => {
if ('breed' in pet) {
return 'dog';
} else {
return 'cat';
}
};
```
### Estreitamento com instanceof
O estreitamento com o operador `instanceof` no TypeScript é uma forma de estreitar o tipo de uma variável com base em sua função construtora, verificando se um objeto é uma instância de uma determinada classe ou interface.
```typescript
class Square {
constructor(public width: number) {}
}
class Rectangle {
constructor(
public width: number,
public height: number
) {}
}
function area(shape: Square | Rectangle) {
if (shape instanceof Square) {
return shape.width * shape.width;
} else {
return shape.width * shape.height;
}
}
const square = new Square(5);
const rectangle = new Rectangle(5, 10);
console.log(area(square)); // 25
console.log(area(rectangle)); // 50
```
## Atribuições
O estreitamento do TypeScript usando atribuições é uma forma de estreitar o tipo de uma variável com base no valor atribuído a ela. Quando uma variável recebe um valor, o TypeScript infere seu tipo com base no valor atribuído e estreita o tipo da variável para corresponder ao tipo inferido.
```typescript
let value: string | number;
value = 'hello';
if (typeof value === 'string') {
console.log(value.toUpperCase());
}
value = 42;
if (typeof value === 'number') {
console.log(value.toFixed(2));
}
```
## Análise de Fluxo de Controle
A Análise de Fluxo de Controle (Control Flow Analysis) no TypeScript é uma forma de analisar estaticamente o fluxo do código para inferir os tipos das variáveis, permitindo que o compilador estreite os tipos dessas variáveis conforme necessário, com base nos resultados da análise.
Antes do TypeScript 4.4, a análise de fluxo de código só seria aplicada ao código dentro de uma instrução `if`, mas a partir do TypeScript 4.4, ela também pode ser aplicada a expressões condicionais e acessos a propriedades discriminantes referenciados indiretamente por meio de variáveis `const`.
Por exemplo:
```typescript
const f1 = (x: unknown) => {
const isString = typeof x === 'string';
if (isString) {
x.length;
}
};
const f2 = (
obj: { kind: 'foo'; foo: string } | { kind: 'bar'; bar: number }
) => {
const isFoo = obj.kind === 'foo';
if (isFoo) {
obj.foo;
} else {
obj.bar;
}
};
```
Alguns exemplos onde o estreitamento não ocorre:
```typescript
const f1 = (x: unknown) => {
let isString = typeof x === 'string';
if (isString) {
x.length; // Erro, sem estreitamento porque isString não é const
}
};
const f6 = (
obj: { kind: 'foo'; foo: string } | { kind: 'bar'; bar: number }
) => {
const isFoo = obj.kind === 'foo';
obj = obj;
if (isFoo) {
obj.foo; // Erro, sem estreitamento porque obj é atribuído no corpo da função
}
};
```
Notas: Até cinco níveis de indireção são analisados em expressões condicionais.
## Predicados de Tipo
Predicados de Tipo (Type Predicates) no TypeScript são funções que retornam um valor booleano e são usadas para estreitar o tipo de uma variável para um tipo mais específico.
```typescript
const isString = (value: unknown): value is string => typeof value === 'string';
const foo = (bar: unknown) => {
if (isString(bar)) {
console.log(bar.toUpperCase());
} else {
console.log('não é uma string');
}
};
```
## Uniões Discriminadas
Uniões Discriminadas (Discriminated Unions) no TypeScript são um tipo de união que usa uma propriedade comum, conhecida como discriminante, para estreitar o conjunto de tipos possíveis para a união.
```typescript
type Square = {
kind: 'square'; // Discriminante
size: number;
};
type Circle = {
kind: 'circle'; // Discriminante
radius: number;
};
type Shape = Square | Circle;
const area = (shape: Shape) => {
switch (shape.kind) {
case 'square':
return Math.pow(shape.size, 2);
case 'circle':
return Math.PI * Math.pow(shape.radius, 2);
}
};
const square: Square = { kind: 'square', size: 5 };
const circle: Circle = { kind: 'circle', radius: 2 };
console.log(area(square)); // 25
console.log(area(circle)); // 12.566370614359172
```
## O tipo never
Quando uma variável é estreitada para um tipo que não pode conter nenhum valor, o compilador TypeScript inferirá que a variável deve ser do tipo `never`. Isso ocorre porque o Tipo Never representa um valor que nunca pode ser produzido.
```typescript
const printValue = (val: string | number) => {
if (typeof val === 'string') {
console.log(val.toUpperCase());
} else if (typeof val === 'number') {
console.log(val.toFixed(2));
} else {
// val tem o tipo never aqui porque nunca pode ser nada além de uma string ou um número
const neverVal: never = val;
console.log(`Valor inesperado: ${neverVal}`);
}
};
```
## Verificação de exaustividade
A verificação de exaustividade (exhaustiveness checking) é um recurso no TypeScript que garante que todos os casos possíveis de uma união discriminada sejam tratados em uma instrução `switch` ou `if`.
```typescript
type Direction = 'up' | 'down';
const move = (direction: Direction) => {
switch (direction) {
case 'up':
console.log('Movendo para cima');
break;
case 'down':
console.log('Movendo para baixo');
break;
default:
const exhaustiveCheck: never = direction;
console.log(exhaustiveCheck); // Esta linha nunca será executada
}
};
```
O tipo `never` é usado para garantir que o caso `default` seja exaustivo e que o TypeScript aponte um erro se um novo valor for adicionado ao tipo `Direction` sem ser tratado na instrução switch.
## Tipos de Objeto
No TypeScript, os tipos de objeto descrevem a forma de um objeto. Eles especificam os nomes e tipos das propriedades do objeto, bem como se essas propriedades são obrigatórias ou opcionais.
No TypeScript, você pode definir tipos de objeto de duas maneiras principais:
Interface, que define a forma de um objeto especificando os nomes, tipos e a opcionalidade de suas propriedades.
```typescript
interface User {
name: string;
age: number;
email?: string;
}
```
Apelido de tipo (type alias), semelhante a uma interface, define a forma de um objeto. No entanto, ele também pode criar um novo tipo personalizado baseado em um tipo existente ou em uma combinação de tipos existentes. Isso inclui definir tipos de união, tipos de interseção e outros tipos complexos.
```typescript
type Point = {
x: number;
y: number;
};
```
Também é possível definir um tipo anonimamente:
```typescript
const sum = (x: { a: number; b: number }) => x.a + x.b;
console.log(sum({ a: 5, b: 1 }));
```
## Tipo Tupla (Anônimo)
Um Tipo Tupla (Tuple Type) é um tipo que representa um array com um número fixo de elementos e seus tipos correspondentes. Um tipo tupla impõe um número específico de elementos e seus respectivos tipos em uma ordem fixa. Os tipos tupla são úteis quando você deseja representar uma coleção de valores com tipos específicos, onde a posição de cada elemento no array tem um significado específico.
```typescript
type Point = [number, number];
```
## Tipo Tupla Nomeado (Rotulado)
Os tipos tupla podem incluir rótulos (labels) ou nomes opcionais para cada elemento. Esses rótulos são para legibilidade e assistência de ferramentas, e não afetam as operações que você pode realizar com eles.
```typescript
type T = string;
type Tuple1 = [T, T];
type Tuple2 = [a: T, b: T];
type Tuple3 = [a: T, T]; // Tupla Nomeada mais Tupla Anônima
```
## Tupla de Comprimento Fixo
Uma Tupla de Comprimento Fixo é um tipo específico de tupla que impõe um número fixo de elementos de tipos específicos e proíbe quaisquer modificações no comprimento da tupla uma vez definida.
Tuplas de Comprimento Fixo são úteis quando você precisa representar uma coleção de valores com um número específico de elementos e tipos específicos, e deseja garantir que o comprimento e os tipos da tupla não possam ser alterados inadvertidamente.
```typescript
const x = [10, 'hello'] as const;
x.push(2); // Erro
```
## Tipo União
Um Tipo União (Union Type) é um tipo que representa um valor que pode ser um de vários tipos. Tipos União são denotados usando o símbolo `|` entre cada tipo possível.
```typescript
let x: string | number;
x = 'hello'; // Válido
x = 123; // Válido
```
## Tipos de Interseção
Um Tipo de Interseção (Intersection Type) é um tipo que representa um valor que possui todas as propriedades de dois ou mais tipos. Tipos de Interseção são denotados usando o símbolo `&` entre cada tipo.
```typescript
type X = {
a: string;
};
type Y = {
b: string;
};
type J = X & Y; // Interseção
const j: J = {
a: 'a',
b: 'b',
};
```
## Indexação de Tipo
Indexação de tipo (type indexing) refere-se à capacidade de definir tipos que podem ser indexados por uma chave não conhecida antecipadamente, usando uma assinatura de índice para especificar o tipo para propriedades que não são declaradas explicitamente.
```typescript
type Dictionary = {
[key: string]: T;
};
const myDict: Dictionary = { a: 'a', b: 'b' };
console.log(myDict['a']); // Retorna a
```
## Tipo a partir de Valor
Tipo a partir de Valor (Type from Value) no TypeScript refere-se à inferência automática de um tipo a partir de um valor ou expressão através da inferência de tipos.
```typescript
const x = 'x'; // O TypeScript infere 'x' como um literal de string com 'const' (imutável), mas alarga para 'string' com 'let' (atribuível novamente).
```
## Tipo a partir de Retorno de Função
Tipo a partir de Retorno de Função refere-se à capacidade de inferir automaticamente o tipo de retorno de uma função com base em sua implementação. Isso permite que o TypeScript determine o tipo do valor retornado pela função sem anotações de tipo explícitas.
```typescript
const add = (x: number, y: number) => x + y; // O TypeScript pode inferir que o tipo de retorno da função é um número
```
## Tipo a partir de Módulo
Tipo a partir de Módulo refere-se à capacidade de usar os valores exportados de um módulo para inferir automaticamente seus tipos. Quando um módulo exporta um valor com um tipo específico, o TypeScript pode usar essa informação para inferir automaticamente o tipo desse valor quando ele é importado para outro módulo.
```typescript
// calc.ts
export const add = (x: number, y: number) => x + y;
// index.ts
import { add } from 'calc';
const r = add(1, 2); // r é number
```
## Tipos Mapeados
Tipos Mapeados (Mapped Types) no TypeScript permitem criar novos tipos baseados em um tipo existente, transformando cada propriedade por meio de uma função de mapeamento. Ao mapear tipos existentes, você pode criar novos tipos que representam a mesma informação em um formato diferente. Para criar um tipo mapeado, você acessa as propriedades de um tipo existente usando o operador `keyof` e as altera para produzir um novo tipo.
No exemplo a seguir:
```typescript
type MyMappedType = {
[P in keyof T]: T[P][];
};
type MyType = {
foo: string;
bar: number;
};
type MyNewType = MyMappedType;
const x: MyNewType = {
foo: ['hello', 'world'],
bar: [1, 2, 3],
};
```
definimos `MyMappedType` para mapear sobre as propriedades de `T`, criando um novo tipo com cada propriedade sendo um array de seu tipo original. Usando isso, criamos `MyNewType` para representar a mesma informação que `MyType`, mas com cada propriedade como um array.
## Modificadores de Tipos Mapeados
Os Modificadores de Tipos Mapeados no TypeScript permitem a transformação de propriedades dentro de um tipo existente:
* `readonly` ou `+readonly`: Torna uma propriedade no tipo mapeado como somente leitura.
* `-readonly`: Permite que uma propriedade no tipo mapeado seja mutável.
* `?`: Designa uma propriedade no tipo mapeado como opcional.
Exemplos:
```typescript
type ReadOnly = { readonly [P in keyof T]: T[P] }; // Todas as propriedades marcadas como somente leitura
type Mutable = { -readonly [P in keyof T]: T[P] }; // Todas as propriedades marcadas como mutáveis
type MyPartial = { [P in keyof T]?: T[P] }; // Todas as propriedades marcadas como opcionais
```
## Tipos Condicionais (Conditional Types)
Tipos Condicionais são uma forma de criar um tipo que depende de uma condição, onde o tipo a ser criado é determinado com base no resultado da condição. Eles são definidos usando a palavra-chave `extends` e um operador ternário para escolher condicionalmente entre dois tipos.
```typescript
type IsArray = T extends any[] ? true : false;
const myArray = [1, 2, 3];
const myNumber = 42;
type IsMyArrayAnArray = IsArray; // Tipo true
type IsMyNumberAnArray = IsArray; // Tipo false
```
## Tipos Condicionais Distributivos
Tipos Condicionais Distributivos são um recurso que permite que um tipo seja distribuído sobre uma união de tipos, aplicando uma transformação a cada membro da união individualmente.
Isso pode ser especialmente útil ao trabalhar com tipos mapeados ou tipos de ordem superior.
```typescript
type Nullable = T extends any ? T | null : never;
type NumberOrBool = number | boolean;
type NullableNumberOrBool = Nullable; // number | boolean | null
```
## infer Inferência de Tipo em Tipos Condicionais
A palavra-chave `infer` é usada em tipos condicionais para inferir (extrair) o tipo de um parâmetro genérico de um tipo que depende dele. Isso permite escrever definições de tipo mais flexíveis e reutilizáveis.
```typescript
type ElementType = T extends (infer U)[] ? U : never;
type Numbers = ElementType; // number
type Strings = ElementType; // string
```
## Tipos Condicionais Predefinidos
No TypeScript, os Tipos Condicionais Predefinidos são tipos condicionais integrados fornecidos pela linguagem. Eles são projetados para realizar transformações comuns de tipo com base nas características de um determinado tipo.
`Exclude`: Este tipo remove todos os tipos de Type que são atribuíveis a ExcludedType.
`Extract`: Este tipo extrai todos os tipos de Union que são atribuíveis a Type.
`NonNullable`: Este tipo remove null e undefined de Type.
`ReturnType`: Este tipo extrai o tipo de retorno de uma função Type.
`Parameters`: Este tipo extrai os tipos de parâmetros de uma função Type.
`Required`: Este tipo torna todas as propriedades em Type obrigatórias.
`Partial`: Este tipo torna todas as propriedades em Type opcionais.
`Readonly`: Este tipo torna todas as propriedades em Type somente leitura (readonly).
## Tipos de União de Template (Template Union Types)
Tipos de união de template podem ser usados para mesclar e manipular texto dentro do sistema de tipos, por exemplo:
```typescript
type Status = 'active' | 'inactive';
type Products = 'p1' | 'p2';
type ProductId = `id-${Products}-${Status}`; // "id-p1-active" | "id-p1-inactive" | "id-p2-active" | "id-p2-inactive"
```
## Tipo any
O tipo `any` é um tipo especial (supertipo universal) que pode ser usado para representar qualquer tipo de valor (primitivos, objetos, arrays, funções, erros, símbolos). É frequentemente usado em situações onde o tipo de um valor não é conhecido em tempo de compilação, ou ao trabalhar com valores de APIs externas ou bibliotecas que não possuem tipagens TypeScript.
Ao utilizar o tipo `any`, você está indicando ao compilador TypeScript que os valores devem ser representados sem quaisquer limitações. Para maximizar a segurança de tipo em seu código, considere o seguinte:
* Limite o uso de `any` a casos específicos onde o tipo é verdadeiramente desconhecido.
* Não retorne tipos `any` de uma função, pois isso enfraquece a segurança de tipo no código que a utiliza.
* Em vez de `any`, use `@ts-ignore` se precisar silenciar o compilador.
```typescript
let value: any;
value = true; // Válido
value = 7; // Válido
```
## Tipo unknown
No TypeScript, o tipo `unknown` representa um valor que é de um tipo desconhecido. Ao contrário do tipo `any`, que permite qualquer tipo de valor, o `unknown` exige uma verificação de tipo ou asserção antes de poder ser usado de uma maneira específica, portanto nenhuma operação é permitida em um `unknown` sem primeiro asseverar ou estreitar para um tipo mais específico.
O tipo `unknown` só é atribuível a si mesmo e ao tipo `any`; é uma alternativa segura em termos de tipos ao `any`.
```typescript
let value: unknown;
let value1: unknown = value; // Válido
let value2: any = value; // Válido
let value3: boolean = value; // Inválido
let value4: number = value; // Inválido
```
```typescript
const add = (a: unknown, b: unknown): number | undefined =>
typeof a === 'number' && typeof b === 'number' ? a + b : undefined;
console.log(add(1, 2)); // 3
console.log(add('x', 2)); // undefined
```
## Tipo void
O tipo `void` é usado para indicar que uma função não retorna um valor.
```typescript
const sayHello = (): void => {
console.log('Hello!');
};
```
## Tipo never
O tipo `never` representa valores que nunca ocorrem. É usado para denotar funções ou expressões que nunca retornam ou que lançam um erro.
Por exemplo, um loop infinito:
```typescript
const infiniteLoop = (): never => {
while (true) {
// faz algo
}
};
```
Lançando um erro:
```typescript
const throwError = (message: string): never => {
throw new Error(message);
};
```
O tipo `never` é útil para garantir a segurança de tipos e capturar possíveis erros em seu código. Ele ajuda o TypeScript a analisar e inferir tipos mais precisos quando usado em combinação com outros tipos e instruções de fluxo de controle, por exemplo:
```typescript
type Direction = 'up' | 'down';
const move = (direction: Direction): void => {
switch (direction) {
case 'up':
// move para cima
break;
case 'down':
// move para baixo
break;
default:
const exhaustiveCheck: never = direction;
throw new Error(`Direção não tratada: ${exhaustiveCheck}`);
}
};
```
## Interface e Type
### Sintaxe Comum
No TypeScript, as interfaces definem a estrutura de objetos, especificando os nomes e tipos de propriedades ou métodos que um objeto deve possuir. A sintaxe comum para definir uma interface no TypeScript é a seguinte:
```typescript
interface InterfaceName {
property1: Type1;
// ...
method1(arg1: ArgType1, arg2: ArgType2): ReturnType;
// ...
}
```
Da mesma forma para a definição de tipo (type):
```typescript
type TypeName = {
property1: Type1;
// ...
method1(arg1: ArgType1, arg2: ArgType2): ReturnType;
// ...
};
```
`interface InterfaceName` ou `type TypeName`: Define o nome da interface/tipo.
`property1`: `Type1`: Especifica as propriedades da interface junto com seus tipos correspondentes. Múltiplas propriedades podem ser definidas, cada uma separada por um ponto e vírgula.
`method1(arg1: ArgType1, arg2: ArgType2): ReturnType;`: Especifica os métodos da interface. Métodos são definidos com seus nomes, seguidos por uma lista de parâmetros entre parênteses e o tipo de retorno. Múltiplos métodos podem ser definidas, cada uma separada por um ponto e vírgula.
Exemplo de interface:
```typescript
interface Person {
name: string;
age: number;
greet(): void;
}
```
Exemplo de tipo:
```typescript
type TypeName = {
property1: string;
method1(arg1: string, arg2: string): string;
};
```
No TypeScript, os tipos são usados para definir a forma dos dados e impor a verificação de tipos. Existem várias sintaxes comuns para definir tipos, dependendo do caso de uso específico. Aqui estão alguns exemplos:
### Tipos Básicos
```typescript
let myNumber: number = 123; // tipo number
let myBoolean: boolean = true; // tipo boolean
let myArray: string[] = ['a', 'b']; // array de strings
let myTuple: [string, number] = ['a', 123]; // tupla
```
### Objetos e Interfaces
```typescript
const x: { name: string; age: number } = { name: 'Simon', age: 7 };
```
### Tipos União e Interseção
```typescript
type MyType = string | number; // Tipo União (Union type)
let myUnion: MyType = 'hello'; // Pode ser uma string
myUnion = 123; // Ou um número
type TypeA = { name: string };
type TypeB = { age: number };
type CombinedType = TypeA & TypeB; // Tipo Interseção (Intersection type)
let myCombined: CombinedType = { name: 'John', age: 25 }; // Objeto com as propriedades name e age
```
## Tipos Primitivos Integrados
O TypeScript possui vários tipos primitivos integrados que podem ser usados para definir variáveis, parâmetros de função e tipos de retorno:
* `number`: Representa valores numéricos, incluindo inteiros e números de ponto flutuante.
* `string`: Representa dados textuais.
* `boolean`: Representa valores lógicos, que podem ser true ou false.
* `null`: Representa a ausência de um valor.
* `undefined`: Representa um valor que não foi atribuído ou não foi definido.
* `symbol`: Representa um identificador único. Symbols são normalmente usados como chaves para propriedades de objetos.
* `bigint`: Representa inteiros de precisão arbitrária.
* `any`: Representa um tipo dinâmico ou desconhecido. Variáveis do tipo any podem conter valores de qualquer tipo e ignoram a verificação de tipos.
* `void`: Representa a ausência de qualquer tipo. É comumente usado como o tipo de retorno de funções que não retornam um valor.
* `never`: Representa um tipo para valores que nunca ocorrem. É normalmente usado como o tipo de retorno de funções que lançam um erro ou entram em um loop infinito.
## Objetos JS Integrados Comuns
O TypeScript é um superconjunto do JavaScript e inclui todos os objetos integrados do JavaScript comumente usados. Você pode encontrar uma lista extensa desses objetos no site de documentação da Mozilla Developer Network (MDN):
[https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects)
Aqui está uma lista de alguns objetos integrados do JavaScript comumente usados:
* Function
* Object
* Boolean
* Error
* Number
* BigInt
* Math
* Date
* String
* RegExp
* Array
* Map
* Set
* Promise
* Intl
## Sobrecargas (Overloads)
As sobrecargas de função (function overloads) no TypeScript permitem definir múltiplas assinaturas para um mesmo nome de função, permitindo que as funções sejam chamadas de diversas maneiras. Aqui está um exemplo:
```typescript
// Sobrecargas
function sayHi(name: string): string;
function sayHi(names: string[]): string[];
// Implementação
function sayHi(name: unknown): unknown {
if (typeof name === 'string') {
return `Hi, ${name}!`;
} else if (Array.isArray(name)) {
return name.map(name => `Hi, ${name}!`);
}
throw new Error('Valor inválido');
}
sayHi('xx'); // Válido
sayHi(['aa', 'bb']); // Válido
```
Aqui está outro exemplo de uso de sobrecargas de função dentro de uma `class`:
```typescript
class Greeter {
message: string;
constructor(message: string) {
this.message = message;
}
// sobrecarga
sayHi(name: string): string;
sayHi(names: string[]): ReadonlyArray;
// implementação
sayHi(name: unknown): unknown {
if (typeof name === 'string') {
return `${this.message}, ${name}!`;
} else if (Array.isArray(name)) {
return name.map(name => `${this.message}, ${name}!`);
}
throw new Error('o valor é inválido');
}
}
console.log(new Greeter('Hello').sayHi('Simon'));
```
## Mesclagem e Extensão
Mesclagem (merging) e extensão (extension) referem-se a dois conceitos diferentes relacionados ao trabalho com tipos e interfaces.
A mesclagem permite combinar várias declarações do mesmo nome em uma única definição, por exemplo, quando você define uma interface com o mesmo nome várias vezes:
```typescript
interface X {
a: string;
}
interface X {
b: number;
}
const person: X = {
a: 'a',
b: 7,
};
```
A extensão refere-se à capacidade de estender ou herdar de tipos ou interfaces existentes para criar novos. É um mecanismo para adicionar propriedades ou métodos adicionais a um tipo existente sem modificar sua definição original. Exemplo:
```typescript
interface Animal {
name: string;
eat(): void;
}
interface Bird extends Animal {
sing(): void;
}
const dog: Bird = {
name: 'Bird 1',
eat() {
console.log('Comendo');
},
sing() {
console.log('Cantando');
},
};
```
## Diferenças entre Type e Interface
Mesclagem de declarações (aumento):
As interfaces suportam a mesclagem de declarações, o que significa que você pode definir várias interfaces com o mesmo nome, e o TypeScript as mesclará em uma única interface com as propriedades e métodos combinados. Por outro lado, os tipos (types) não suportam a mesclagem de declarações. Isso pode ser útil quando você deseja adicionar funcionalidades extras ou personalizar tipos existentes sem modificar as definições originais ou corrigir tipos ausentes ou incorretos.
```typescript
interface A {
x: string;
}
interface A {
y: string;
}
const j: A = {
x: 'xx',
y: 'yy',
};
```
Estendendo outros tipos/interfaces:
Tanto tipos quanto interfaces podem estender outros tipos/interfaces, mas a sintaxe é diferente. Com as interfaces, você usa a palavra-chave `extends` para herdar propriedades e métodos de outras interfaces. No entanto, uma interface não pode estender um tipo complexo, como um tipo união.
```typescript
interface A {
x: string;
y: number;
}
interface B extends A {
z: string;
}
const car: B = {
x: 'x',
y: 123,
z: 'z',
};
```
Para tipos, você usa o operador `&` para combinar múltiplos tipos em um único tipo (interseção).
```typescript
interface A {
x: string;
y: number;
}
type B = A & {
j: string;
};
const c: B = {
x: 'x',
y: 123,
j: 'j',
};
```
Tipos União e Interseção:
Os tipos (types) são mais flexíveis quando se trata de definir Tipos União e Interseção. Com a palavra-chave `type`, você pode criar facilmente tipos união usando o operador `|` e tipos interseção usando o operador `&`. Embora as interfaces também possam representar tipos união indiretamente, elas não possuem suporte integrado para tipos interseção.
```typescript
type Department = 'dep-x' | 'dep-y'; // União
type Person = {
name: string;
age: number;
};
type Employee = {
id: number;
department: Department;
};
type EmployeeInfo = Person & Employee; // Interseção
```
Exemplo com interfaces:
```typescript
interface A {
x: 'x';
}
interface B {
y: 'y';
}
type C = A | B; // União de interfaces
```
## Classes
### Sintaxe Comum de Classes
A palavra-chave `class` é usada no TypeScript para definir uma classe. Abaixo, você pode ver um exemplo:
```typescript
class Person {
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public sayHi(): void {
console.log(
`Olá, meu nome é ${this.name} e eu tenho ${this.age} anos.`
);
}
}
```
A palavra-chave `class` é usada para definir uma classe chamada "Person".
A classe possui duas propriedades privadas: name do tipo `string` e age do tipo `number`.
O construtor é definido usando a palavra-chave `constructor`. Ele recebe name e age como parâmetros e os atribui às propriedades correspondentes.
A classe possui um método `public` chamado `sayHi` que registra uma mensagem de saudação.
Para criar uma instância de uma classe no TypeScript, você pode usar a palavra-chave `new` seguida pelo nome da classe, seguida de parênteses `()`. Por exemplo:
```typescript
const myObject = new Person('John Doe', 25);
myObject.sayHi(); // Saída: Olá, meu nome é John Doe e eu tenho 25 anos.
```
### Construtor
Os construtores são métodos especiais dentro de uma classe que são usados para inicializar as propriedades do objeto quando uma instância da classe é criada.
```typescript
class Person {
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(
`Olá, meu nome é ${this.name} e eu tenho ${this.age} anos.`
);
}
}
const john = new Person('Simon', 17);
john.sayHello();
```
É possível sobrecarregar um construtor usando a seguinte sintaxe:
```typescript
type Sex = 'm' | 'f';
class Person {
name: string;
age: number;
sex: Sex;
constructor(name: string, age: number, sex?: Sex);
constructor(name: string, age: number, sex: Sex) {
this.name = name;
this.age = age;
this.sex = sex ?? 'm';
}
}
const p1 = new Person('Simon', 17);
const p2 = new Person('Alice', 22, 'f');
```
No TypeScript, é possível definir múltiplas sobrecargas de construtor, mas você pode ter apenas uma implementação que deve ser compatível com todas as sobrecargas, o que pode ser alcançado usando um parâmetro opcional.
```typescript
class Person {
name: string;
age: number;
constructor();
constructor(name: string);
constructor(name: string, age: number);
constructor(name?: string, age?: number) {
this.name = name ?? 'Desconhecido';
this.age = age ?? 0;
}
displayInfo() {
console.log(`Nome: ${this.name}, Idade: ${this.age}`);
}
}
const person1 = new Person();
person1.displayInfo(); // Nome: Desconhecido, Idade: 0
const person2 = new Person('John');
person2.displayInfo(); // Nome: John, Idade: 0
const person3 = new Person('Jane', 25);
person3.displayInfo(); // Nome: Jane, Idade: 25
```
### Construtores Privados e Protegidos
No TypeScript, os construtores podem ser marcados como privados ou protegidos, o que restringe sua acessibilidade e uso.
Construtores Privados (Private Constructors):
Podem ser chamados apenas dentro da própria classe. Construtores privados são frequentemente usados em cenários onde você deseja impor um padrão singleton ou restringir a criação de instâncias a um método factual dentro da classe.
Construtores Protegidos (Protected Constructors):
Construtores protegidos são úteis quando você deseja criar uma classe base que não deve ser instanciada diretamente, mas pode ser estendida por subclasses.
```typescript
class BaseClass {
protected constructor() {}
}
class DerivedClass extends BaseClass {
private value: number;
constructor(value: number) {
super();
this.value = value;
}
}
// Tentar instanciar a classe base diretamente resultará em erro
// const baseObj = new BaseClass(); // Erro: O construtor da classe 'BaseClass' é protegido.
// Criar uma instância da classe derivada
const derivedObj = new DerivedClass(10);
```
### Modificadores de Acesso
Modificadores de Acesso `private`, `protected` e `public` são usados para controlar a visibilidade e acessibilidade dos membros da classe, como propriedades e métodos, em classes TypeScript. Esses modificadores são essenciais para impor o encapsulamento e estabelecer limites para acessar e modificar o estado interno de uma classe.
O modificador `private` restringe o acesso ao membro da classe apenas dentro da classe que o contém.
O modificador `protected` permite o acesso ao membro da classe dentro da classe que o contém e suas classes derivadas.
O modificador `public` fornece acesso irrestrito ao membro da classe, permitindo que ele seja acessado de qualquer lugar.
### Get e Set
Getters e setters são métodos especiais que permitem definir comportamentos personalizados de acesso e modificação para propriedades de classe. Eles permitem encapsular o estado interno de um objeto e fornecer lógica adicional ao obter ou definir os valores das propriedades.
No TypeScript, os getters e setters são definidos usando as palavras-chave `get` e `set`, respectivamente. Aqui está um exemplo:
```typescript
class MyClass {
private _myProperty: string;
constructor(value: string) {
this._myProperty = value;
}
get myProperty(): string {
return this._myProperty;
}
set myProperty(value: string) {
this._myProperty = value;
}
}
```
### Auto-acessores em Classes
O TypeScript versão 4.9 adiciona suporte para auto-acessores (auto-accessors), um recurso futuro do ECMAScript. Eles se assemelham a propriedades de classe, mas são declarados com a palavra-chave "accessor".
```typescript
class Animal {
accessor name: string;
constructor(name: string) {
this.name = name;
}
}
```
Os auto-acessores são transformados em acessores `get` e `set` privados, operando em uma propriedade inacessível.
```typescript
class Animal {
#__name: string;
get name() {
return this.#__name;
}
set name(value: string) {
this.#__name = name;
}
constructor(name: string) {
this.name = name;
}
}
```
### this
No TypeScript, a palavra-chave `this` refere-se à instância atual de uma classe dentro de seus métodos ou construtores. Ela permite acessar e modificar as propriedades e métodos da classe de dentro de seu próprio escopo.
Fornece uma maneira de acessar e manipular o estado interno de um objeto dentro de seus próprios métodos.
```typescript
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
public introduce(): void {
console.log(`Olá, meu nome é ${this.name}.`);
}
}
const person1 = new Person('Alice');
person1.introduce(); // Olá, meu nome é Alice.
```
### Propriedades de Parâmetro
As propriedades de parâmetro permitem declarar e inicializar propriedades de classe diretamente dentro dos parâmetros do construtor, evitando o código repetitivo (boilerplate). Exemplo:
```typescript
class Person {
constructor(
private name: string,
public age: number
) {
// As palavras-chave "private" e "public" no construtor
// declaram e inicializam automaticamente as propriedades de classe correspondentes.
}
public introduce(): void {
console.log(
`Olá, meu nome é ${this.name} e eu tenho ${this.age} anos.`
);
}
}
const person = new Person('Alice', 25);
person.introduce();
```
### Classes Abstratas
Classes Abstratas são usadas no TypeScript principalmente para herança; elas fornecem uma maneira de definir propriedades e métodos comuns que podem ser herdados por subclasses.
Isso é útil quando você deseja definir um comportamento comum e garantir que as subclasses implementem certos métodos. Elas fornecem uma maneira de criar uma hierarquia de classes onde a classe base abstrata fornece uma interface compartilhada e funcionalidade comum para as subclasses.
```typescript
abstract class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
abstract makeSound(): void;
}
class Cat extends Animal {
makeSound(): void {
console.log(`${this.name} mia (meows).`);
}
}
const cat = new Cat('Whiskers');
cat.makeSound(); // Saída: Whiskers mia (meows).
```
### Com Genéricos
Classes com genéricos permitem definir classes reutilizáveis que podem trabalhar com diferentes tipos.
```typescript
class Container {
private item: T;
constructor(item: T) {
this.item = item;
}
getItem(): T {
return this.item;
}
setItem(item: T): void {
this.item = item;
}
}
const container1 = new Container(42);
console.log(container1.getItem()); // 42
const container2 = new Container('Olá');
container2.setItem('Mundo');
console.log(container2.getItem()); // Mundo
```
### Decoradores (Decorators)
Os decoradores fornecem um mecanismo para adicionar metadados, modificar comportamentos, validar ou estender a funcionalidade do elemento alvo. São funções que são executadas em tempo de execução. Múltiplos decoradores podem ser aplicados a uma declaração.
Os decoradores são recursos experimentais, e os exemplos a seguir são compatíveis apenas com a versão 5 do TypeScript ou superior usando ES6.
Para versões do TypeScript anteriores à 5, eles devem ser habilitados usando a propriedade `experimentalDecorators` em seu `tsconfig.json` ou usando `--experimentalDecorators` na sua linha de comando (mas o exemplo a seguir não funcionará).
Alguns dos casos de uso comuns para decoradores incluem:
* Observar mudanças de propriedade.
* Observar chamadas de métodos.
* Adicionar propriedades ou métodos extras.
* Validação em tempo de execução.
* Serialização e desserialização automática.
* Registro (Logging).
* Autorização e autenticação.
* Proteção contra erros (Error guarding).
Nota: Decoradores para a versão 5 não permitem decorar parâmetros.
Tipos de decoradores:
#### Decoradores de Classe (Class Decorators)
Os Decoradores de Classe são úteis para estender uma classe existente, como adicionar propriedades ou métodos, ou coletar instâncias de uma classe. No exemplo a seguir, adicionamos um método `toString` que converte a classe em uma representação de string.
```typescript
type Constructor = new (...args: any[]) => T;
function toString(
Value: Class,
context: ClassDecoratorContext
) {
return class extends Value {
constructor(...args: any[]) {
super(...args);
console.log(JSON.stringify(this));
console.log(JSON.stringify(context));
}
};
}
@toString
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
return 'Hello, ' + this.name;
}
}
const person = new Person('Simon');
/* Registra:
{"name":"Simon"}
{"kind":"class","name":"Person"}
*/
```
#### Decorador de Propriedade (Property Decorator)
Os decoradores de propriedade são úteis para modificar o comportamento de uma propriedade, como alterar os valores de inicialização. No código a seguir, temos um script que define uma propriedade para estar sempre em letras maiúsculas:
```typescript
function upperCase(
target: undefined,
context: ClassFieldDecoratorContext
) {
return function (this: T, value: string) {
return value.toUpperCase();
};
}
class MyClass {
@upperCase
prop1 = 'hello!';
}
console.log(new MyClass().prop1); // Registra: HELLO!
```
#### Decorador de Método (Method Decorator)
Os decoradores de método permitem alterar ou aprimorar o comportamento dos métodos. Abaixo está um exemplo de um registrador (logger) simples:
```typescript
function log(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<
This,
(this: This, ...args: Args) => Return
>
) {
const methodName = String(context.name);
function replacementMethod(this: This, ...args: Args): Return {
console.log(`LOG: Entrando no método '${methodName}'.`);
const result = target.call(this, ...args);
console.log(`LOG: Saindo do método '${methodName}'.`);
return result;
}
return replacementMethod;
}
class MyClass {
@log
sayHello() {
console.log('Hello!');
}
}
new MyClass().sayHello();
```
Isso registra:
```shell
LOG: Entrando no método 'sayHello'.
Hello!
LOG: Saindo do método 'sayHello'.
```
#### Decoradores de Getter e Setter
Decoradores de getter e setter permitem alterar ou aprimorar o comportamento dos acessores de classe. Eles são úteis, por exemplo, para validar atribuições de propriedades. Aqui está um exemplo simples de um decorador de getter:
```typescript
function range(min: number, max: number) {
return function (
target: (this: This) => Return,
context: ClassGetterDecoratorContext
) {
return function (this: This): Return {
const value = target.call(this);
if (value < min || value > max) {
throw 'Invalid';
}
Object.defineProperty(this, context.name, {
value,
enumerable: true,
});
return value;
};
};
}
class MyClass {
private _value = 0;
constructor(value: number) {
this._value = value;
}
@range(1, 100)
get getValue(): number {
return this._value;
}
}
const obj = new MyClass(10);
console.log(obj.getValue); // Válido: 10
const obj2 = new MyClass(999);
console.log(obj2.getValue); // Lança: Invalid!
```
#### Metadados de Decorador (Decorator Metadata)
Os Metadados de Decorador simplificam o processo para que os decoradores apliquem e utilizem metadados em qualquer classe. Eles podem acessar uma nova propriedade de metadados no objeto de contexto, que pode servir como uma chave para primitivos e objetos.
As informações de metadados podem ser acessadas na classe via `Symbol.metadata`.
Os metadados podem ser usados para vários fins, como depuração, serialização ou injeção de dependência com decoradores.
```typescript
//@ts-ignore
Symbol.metadata ??= Symbol('Symbol.metadata'); // Polify simples
type Context =
| ClassFieldDecoratorContext
| ClassAccessorDecoratorContext
| ClassMethodDecoratorContext; // O contexto contém os metadados da propriedade: DecoratorMetadata
function setMetadata(_target: any, context: Context) {
// Define o objeto de metadados com um valor primitivo
context.metadata[context.name] = true;
}
class MyClass {
@setMetadata
a = 123;
@setMetadata
accessor b = 'b';
@setMetadata
fn() {}
}
const metadata = MyClass[Symbol.metadata]; // Obtém as informações de metadados
console.log(JSON.stringify(metadata)); // {"bar":true,"baz":true,"foo":true}
```
### Herança
Herança refere-se ao mecanismo pelo qual uma classe pode herdar propriedades e métodos de outra classe, conhecida como classe base ou superclasse. A classe derivada, também chamada de classe filha ou subclasse, pode estender e especializar a funcionalidade da classe base adicionando novas propriedades e métodos ou substituindo (overriding) os existentes.
```typescript
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): void {
console.log('O animal faz um som');
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
speak(): void {
console.log('Woof! Woof!');
}
}
// Cria uma instância da classe base
const animal = new Animal('Animal Genérico');
animal.speak(); // O animal faz um som
// Cria uma instância da classe derivada
const dog = new Dog('Max', 'Labrador');
dog.speak(); // Woof! Woof!"
```
O TypeScript não suporta herança múltipla no sentido tradicional e, em vez disso, permite a herança de uma única classe base.
O TypeScript suporta múltiplas interfaces. Uma interface pode definir um contrato para a estrutura de um objeto, e uma classe pode implementar múltiplas interfaces. Isso permite que uma classe herde comportamento e estrutura de múltiplas fontes.
```typescript
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
class FlyingFish implements Flyable, Swimmable {
fly() {
console.log('Voando...');
}
swim() {
console.log('Nadando...');
}
}
const flyingFish = new FlyingFish();
flyingFish.fly();
flyingFish.swim();
```
A palavra-chave `class` no TypeScript, assim como no JavaScript, é frequentemente chamada de açúcar sintático (syntactic sugar). Ela foi introduzida no ECMAScript 2015 (ES6) para oferecer uma sintaxe mais familiar para criar e trabalhar com objetos de maneira baseada em classes. No entanto, é importante notar que o TypeScript, sendo um superconjunto do JavaScript, acaba sendo compilado para JavaScript, que permanece baseado em protótipos em seu núcleo.
### Estáticos (Statics)
O TypeScript possui membros estáticos. Para acessar os membros estáticos de uma classe, você pode usar o nome da classe seguido por um ponto, sem a necessidade de criar um objeto.
```typescript
class OfficeWorker {
static memberCount: number = 0;
constructor(private name: string) {
OfficeWorker.memberCount++;
}
}
const w1 = new OfficeWorker('James');
const w2 = new OfficeWorker('Simon');
const total = OfficeWorker.memberCount;
console.log(total); // 2
```
### Inicialização de Propriedade
Existem várias maneiras de inicializar propriedades de uma classe no TypeScript:
Em linha (Inline):
No exemplo a seguir, esses valores iniciais serão usados quando uma instância da classe for criada.
```typescript
class MyClass {
property1: string = 'valor padrão';
property2: number = 42;
}
```
No construtor:
```typescript
class MyClass {
property1: string;
property2: number;
constructor() {
this.property1 = 'valor padrão';
this.property2 = 42;
}
}
```
Usando parâmetros do construtor:
```typescript
class MyClass {
constructor(
private property1: string = 'valor padrão',
public property2: number = 42
) {
// Não há necessidade de atribuir os valores às propriedades explicitamente.
}
log() {
console.log(this.property2);
}
}
const x = new MyClass();
x.log();
```
### Sobrecarga de Método
A sobrecarga de método permite que uma classe tenha múltiplos métodos com o mesmo nome, mas diferentes tipos de parâmetros ou um número diferente de parâmetros. Isso nos permite chamar um método de diferentes maneiras com base nos argumentos passados.
```typescript
class MyClass {
add(a: number, b: number): number; // Assinatura de sobrecarga 1
add(a: string, b: string): string; // Assinatura de sobrecarga 2
add(a: number | string, b: number | string): number | string {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
if (typeof a === 'string' && typeof b === 'string') {
return a.concat(b);
}
throw new Error('Argumentos inválidos');
}
}
const r = new MyClass();
console.log(r.add(10, 5)); // Registra 15
```
## Genéricos (Generics)
Os genéricos permitem que você crie componentes e funções reutilizáveis que podem trabalhar com múltiplos tipos. Com os genéricos, você pode parametrizar tipos, funções e interfaces, permitindo que operem em diferentes tipos sem especificá-los explicitamente de antemão.
Os genéricos permitem tornar o código mais flexível e reutilizável.
### Tipo Genérico
Para definir um tipo genérico, você usa colchetes angulares (`<>`) para especificar os parâmetros de tipo, por exemplo:
```typescript
function identity(arg: T): T {
return arg;
}
const a = identity('x');
const b = identity(123);
const getLen = (data: ReadonlyArray) => data.length;
const len = getLen([1, 2, 3]);
```
### Classes Genéricas
Os genéricos também podem ser aplicados a classes, permitindo que trabalhem com múltiplos tipos por meio de parâmetros de tipo. Isso é útil para criar definições de classe reutilizáveis que podem operar em diferentes tipos de dados mantendo a segurança de tipo.
```typescript
class Container {
private item: T;
constructor(item: T) {
this.item = item;
}
getItem(): T {
return this.item;
}
}
const numberContainer = new Container(123);
console.log(numberContainer.getItem()); // 123
const stringContainer = new Container('hello');
console.log(stringContainer.getItem()); // hello
```
### Restrições Genéricas (Generic Constraints)
Parâmetros genéricos podem ser restringidos usando a palavra-chave `extends` seguida por um tipo ou interface que o parâmetro de tipo deve satisfazer.
No exemplo a seguir, T deve conter uma propriedade `length` para ser válido:
```typescript
const printLen = (value: T): void => {
console.log(value.length);
};
printLen('Hello'); // 5
printLen([1, 2, 3]); // 3
printLen({ length: 10 }); // 10
printLen(123); // Inválido
```
Um recurso interessante de genéricos introduzido na versão 3.4 RC é a inferência de tipo de função de ordem superior, que introduziu argumentos de tipo genérico propagados:
```typescript
declare function pipe(
ab: (...args: A) => B,
bc: (b: B) => C
): (...args: A) => C;
declare function list(a: T): T[];
declare function box(x: V): { value: V };
const listBox = pipe(list, box); // (a: T) => { value: T[] }
const boxList = pipe(box, list); // (x: V) => { value: V }[]
```
Essa funcionalidade permite uma programação de estilo sem pontos (pointfree) com segurança de tipo mais fácil, o que é comum na programação funcional.
### Estreitamento Contextual Genérico
O estreitamento contextual (contextual narrowing) para genéricos é o mecanismo no TypeScript que permite ao compilador estreitar o tipo de um parâmetro genérico com base no contexto em que é usado. É útil ao trabalhar com tipos genéricos em declarações condicionais:
```typescript
function process(value: T): void {
if (typeof value === 'string') {
// O valor é estreitado para o tipo 'string'
console.log(value.length);
} else if (typeof value === 'number') {
// O valor é estreitado para o tipo 'number'
console.log(value.toFixed(2));
}
}
process('hello'); // 5
process(3.14159); // 3.14
```
## Tipos Estruturais Apagados
No TypeScript, os objetos não precisam corresponder a um tipo exato e específico. Por exemplo, se criarmos um objeto que satisfaça os requisitos de uma interface, podemos utilizar esse objeto em locais onde essa interface é necessária, mesmo que não haja uma conexão explícita entre eles.
Exemplo:
```typescript
type NameProp1 = {
prop1: string;
};
function log(x: NameProp1) {
console.log(x.prop1);
}
const obj = {
prop2: 123,
prop1: 'Origin',
};
log(obj); // Válido
```
## Namespacing
No TypeScript, os namespaces são usados para organizar o código em contêineres lógicos, evitando colisões de nomes e fornecendo uma maneira de agrupar códigos relacionados.
O uso das palavras-chave `export` permite o acesso ao namespace em módulos externos.
```typescript
export namespace MyNamespace {
export interface MyInterface1 {
prop1: boolean;
}
export interface MyInterface2 {
prop2: string;
}
}
const a: MyNamespace.MyInterface1 = {
prop1: true,
};
```
## Símbolos (Symbols)
Símbolos são um tipo de dado primitivo que representa um valor imutável que é garantido ser globalmente único durante todo o tempo de execução do programa.
Símbolos podem ser usados como chaves para propriedades de objetos e fornecem uma maneira de criar propriedades não enumeráveis.
```typescript
const key1: symbol = Symbol('key1');
const key2: symbol = Symbol('key2');
const obj = {
[key1]: 'value 1',
[key2]: 'value 2',
};
console.log(obj[key1]); // value 1
console.log(obj[key2]); // value 2
```
Em WeakMaps e WeakSets, símbolos agora são permitidos como chaves.
## Diretivas Triple-Slash
As diretivas triple-slash são comentários especiais que fornecem instruções ao compilador sobre como processar um arquivo. Essas diretivas começam com três barras consecutivas (`///`) e são normalmente colocadas no topo de um arquivo TypeScript e não têm efeitos no comportamento em tempo de execução.
As diretivas triple-slash são usadas para referenciar dependências externas, especificar o comportamento de carregamento de módulos, habilitar/desabilitar certos recursos do compilador e muito mais. Alguns exemplos:
Referenciando um arquivo de declaração:
```typescript
///
```
Indicar o formato do módulo:
```typescript
///
```
Habilitar opções do compilador, no exemplo a seguir, o modo estrito:
```typescript
///
```
## Manipulação de Tipos
### Criando Tipos a partir de Tipos
É possível criar novos tipos compondo, manipulando ou transformando tipos existentes.
Tipos Interseção (`&`):
Permitem combinar múltiplos tipos em um único tipo:
```typescript
type A = { foo: number };
type B = { bar: string };
type C = A & B; // Interseção de A e B
const obj: C = { foo: 42, bar: 'hello' };
```
Tipos União (`|`):
Permitem definir um tipo que pode ser um de vários tipos:
```typescript
type Result = string | number;
const value1: Result = 'hello';
const value2: Result = 42;
```
Tipos Mapeados:
Permitem transformar as propriedades de um tipo existente para criar um novo tipo:
```typescript
type Mutable = {
readonly [P in keyof T]: T[P];
};
type Person = {
name: string;
age: number;
};
type ImmutablePerson = Mutable; // As propriedades tornam-se somente leitura
```
Tipos Condicionais:
Permitem criar tipos com base em algumas condições:
```typescript
type ExtractParam = T extends (param: infer P) => any ? P : never;
type MyFunction = (name: string) => number;
type ParamType = ExtractParam; // string
```
### Tipos de Acesso Indexado (Indexed Access Types)
No TypeScript, é possível acessar e manipular os tipos de propriedades dentro de outro tipo usando um índice, `Type[Key]`.
```typescript
type Person = {
name: string;
age: number;
};
type AgeType = Person['age']; // number
```
```typescript
type MyTuple = [string, number, boolean];
type MyType = MyTuple[2]; // boolean
```
### Tipos Utilitários (Utility Types)
Vários tipos utilitários integrados podem ser usados para manipular tipos, abaixo uma lista dos mais comuns:
#### Awaited\
Constrói um tipo que descompacta recursivamente tipos Promise.
```typescript
type A = Awaited>; // string
```
#### Partial\