-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
45 changed files
with
449 additions
and
11 deletions.
There are no files selected for viewing
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
Examples/en/TypeScript/Advanced Meta-Types/Conditional Types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Conditionals Types provide a way to do simple logic in the | ||
// TypeScript type system. This is definitely an advanced | ||
// feature, and it's quite feasible that you won't need to | ||
// use this in your normal day to day code. | ||
|
||
// A conditional type looks like: | ||
// | ||
// A extends B ? C : D | ||
// | ||
// Where the conditional is whether a type extends an | ||
// expression, and if so what type should be returned. A | ||
// type could also be deferred for | ||
|
||
// Let's go through some simple examples | ||
|
||
type Cat = { meows: true } | ||
type Dog = { barks: true } | ||
type Cheetah = { meow: true, fast: true } | ||
type Wolf = { barks: true, howls: true } | ||
|
||
// We can create a conditional type which lets use extract | ||
// types which only conform to something which barks. | ||
|
||
type ExtractDogish<A> = A extends { barks: true } ? A : never | ||
|
||
// Then we can create types which ExtractDogish wraps: | ||
|
||
// A cat doesn't bark, so it will return never | ||
type NeverCat = ExtractDogish<Cat> | ||
// A wolf will bark, so it returns the wolf | ||
type Wolfish = ExtractDogish<Wolf> | ||
|
||
// This becomes useful when you want to work with a | ||
// union of many types and reduce the number of potential | ||
// options in a union, | ||
|
||
type Animals = Cat | Dog | Cheetah | Wolf | ||
|
||
// When you apply ExtractDogish to a union type, it is the | ||
// same as running the conditional against each member of | ||
// the type | ||
|
||
type Dogish = ExtractDogish<Animals> | ||
|
||
// = ExtractDogish<Cat> | ExtractDogish<Dog> | | ||
// ExtractDogish<Ceetah> | ExtractDogish<Wolf> | ||
|
||
// = never | Dog | never | Wolf | ||
|
||
// = Dog | Wolf (see example:unknown-and-never) | ||
|
||
// This is call a distributive conditional type because the | ||
// type distributes over each member of the union. | ||
|
||
|
||
// Deferred Conditional Types | ||
|
||
// Conditional types can be used to tighten your APIs which | ||
// can return different types depending on the inputs. | ||
|
||
// For example this function which could return either a | ||
// string or number depending on the boolean passed in. | ||
|
||
declare function getUserID<T extends boolean>(user: {}, oldSystem: T): T extends true ? string : number | ||
|
||
// Then depending on how much the type-system knows about | ||
// the boolean, you will get different return types: | ||
|
||
let stringReturnValue = getUserID({}, /* oldSystem */ true) | ||
let numberReturnValue = getUserID({}, /* oldSystem */ false) | ||
let stringOrID = getUserID({}, /* oldSystem */ Math.random() < 0.5) | ||
|
||
// In this case above TypeScript can know the return value | ||
// instantly. However, you can use conditional types in functions | ||
// where the type isn't known yet. This is called a deferred | ||
// conditional type. | ||
|
||
// Same as our Dogish above, but as a function instead | ||
declare function isCatish<T>(x: T): T extends { meows: true } ? T : undefined; | ||
|
||
// In this Generic function there are no constraints on the | ||
// generic parameter U, and so it could be any type. | ||
|
||
// This means using Catish would need to wait to know what | ||
// the actual type of U is before it can be applied. | ||
|
||
function findOtherAnimalsOfType<U>(animal: U) { | ||
let maybeCat = isCatish(animal) | ||
|
||
// You can use assignment to short-circuit the process | ||
// if you' have a good idea about the types during | ||
// implementation | ||
let b: Cat | Cheetah | undefined = maybeCat; | ||
return maybeCat | ||
} | ||
|
||
const a = findOtherAnimalsOfType<Animals>({ meow: true, fast: true }) | ||
const b = findOtherAnimalsOfType<Cat>({ meows: true}) | ||
|
||
// TODO: This example needs work! | ||
|
||
|
||
// typeof process("foo") |
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Without a background in type theory, you're unlikely | ||
// to be familiar with the idea of a type system being "sound" | ||
|
||
// Soundness is the idea that the compiler can make guarantees | ||
// about what types are available at runtime, and not just | ||
// during compilation. | ||
|
||
// Building a type system for a language is a trade-off with | ||
// three qualities: Simplicity, Usability and Soundness. | ||
|
||
// By aiming to support all JavaScript code, TypeScript | ||
// varies its | ||
|
||
// code. While runtime safety is an important requirement, | ||
// usability is another important one. | ||
|
||
// One of these usability trade-offs is in having a completely | ||
// sound type-system vs having a type system that maps to real | ||
// world usage of JavaScript | ||
|
||
|
||
|
||
// https://github.com/Microsoft/TypeScript/issues/9825 |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Mapped types are a way to creates new types based | ||
// on another type. Effectively a transformational type. | ||
|
||
// Common cases for using a mapped type is dealing with | ||
// partial subsets of an existing type. For example | ||
// an API may return an Artist: | ||
|
||
interface Artist { | ||
id: number | ||
name: string; | ||
bio: string; | ||
} | ||
|
||
// However, if you were to send an update to the API which | ||
// only changes a subset of the Artist then you would | ||
// typically have to create an additional type: | ||
|
||
interface ArtistForEdit { | ||
id: number | ||
name?: string | ||
bio?: string | ||
} | ||
|
||
// It's very likely that this would get out of sync with | ||
// the Artist above. Mapped types let you create a change | ||
// in an existing type. | ||
|
||
type MyPartialType<Type> = { | ||
// For every existing property inside the type of Type | ||
// convert it to be a ?: version | ||
[Property in keyof Type]?: Type[Property]; | ||
} | ||
|
||
// Now we can use the mapped type instead to create | ||
// our edit interface: | ||
type MappedArtistForEdit = MyPartialType<Artist> | ||
|
||
// This is close to perfect, but it does allow id to be null | ||
// which should never happen. So, let's make one quick | ||
// improvement by using an intersection type (see: | ||
// example:union-and-intersection-types ) | ||
|
||
type MyPartialTypeForEdit<Type> = { | ||
[Property in keyof Type]?: Type[Property]; | ||
} & { id: number } | ||
|
||
// This takes the partial result of the mapped type, and | ||
// merges it with an object which has id: number set. | ||
// Effectively forcing id to be in the type. | ||
|
||
type CorrectMappedArtistForEdit = MyPartialTypeForEdit<Artist> | ||
|
||
// This is a pretty simple example of how mapped type | ||
// work, but covers most of the basics. If you'd like to | ||
// dive in with more depth, check out the hand-book | ||
// | ||
// https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Any is the TypeScript escape clause. You can use any to | ||
// either declare a section of your code to be dynamic and | ||
// JavaScript like, or to work around limitations in the | ||
// type system. | ||
|
||
// A good case for any is JSON parsing: | ||
|
||
const myObject = JSON.parse("{}") | ||
|
||
// Any declares to TypeScript to trust your code as being | ||
// safe because you know more about it. Even if that is | ||
// not strictly true. For example, this code would crash: | ||
|
||
myObject.x.y.z | ||
|
||
// Using an any gives you the ability to write code closer to | ||
// original JavaScript with the trade off of type safety. | ||
|
||
// Any is considered a top type in type theory, which means | ||
// that all other objects (except never) can be classed as | ||
// an any - this makes it a good case for functions which | ||
// have a very open allowance for input. | ||
|
||
declare function debug(value: any) | ||
|
||
debug("a string") | ||
debug(23) | ||
debug({ color: "blue" }) | ||
|
||
// Unknown is a sibling type to any, if any is about saying | ||
// "I know what's best", then unknown is a way to say "I'm | ||
// not sure what is best, so you need to tell TS the type" | ||
// example:unknown-and-never |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// TypeScript has some fun special cases for literals in | ||
// source code. | ||
|
||
// In part, a lot of the support is covered in type widening | ||
// and narrowing ( example:type-widening-narrowing ) and it's | ||
// worth covering that first. | ||
|
||
// A literal is a more concrete subtype of a collective type. | ||
// What this means is that "Hello World" is a string, but a | ||
// string is not "Hello World" inside the type system | ||
|
||
const helloWorld = "Hello World" | ||
let hiWorld = "Hi World" // this is a string because it is let | ||
|
||
// This function takes all strings | ||
declare function allowsAnyString(arg: string) | ||
allowsAnyString(helloWorld) | ||
allowsAnyString(hiWorld) | ||
|
||
// This function only accepts the string literal "Hello World" | ||
declare function allowsOnlyHello(arg: "Hello World") | ||
allowsOnlyHello(helloWorld) | ||
allowsOnlyHello(hiWorld) | ||
|
||
// This lets you declare APIs which use unions to say it | ||
// only accepts a particular literal: | ||
|
||
declare function allowsFirstFiveNumbers(arg: 1 | 2 | 3 | 4 | 5) | ||
allowsFirstFiveNumbers(1) | ||
allowsFirstFiveNumbers(10) | ||
|
||
let potentiallyAnyNumber = 3 | ||
allowsFirstFiveNumbers(potentiallyAnyNumber) | ||
|
||
// At first glance, this rule isn't applied to complex objects | ||
|
||
const myUser = { | ||
name: "Sabrina" | ||
} | ||
|
||
// See how it transforms name: "Sabrina" to name: string? | ||
// even though it is defined as a constant. This is because | ||
// the name can still change any time: | ||
|
||
myUser.name = "Cynthia" | ||
|
||
// Because myUser's name property can change, TypeScript | ||
// cannot use the literal version in the type system. There | ||
// is a feature which will allow you to do this however. | ||
|
||
const myUnchangingUser = { | ||
name: "Fatma" | ||
} as const | ||
|
||
// When "as const" is applied to the object, then it becomes | ||
// a object literal which doesn't change instead of a | ||
// mutable object which can. | ||
|
||
myUnchangingUser.name = "Raîssa" | ||
|
||
// "as const" is a great tool for fixtured data, and places | ||
// where you treat code as literals inline. As const also | ||
// works with arrays | ||
|
||
const exampleUsers = [ | ||
{ name: "Brian" }, | ||
{ name: "Fahrooq" } | ||
] as const | ||
|
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.