# Type Manipulation
🍞
TIP
we actually have a wide variety of type operators available to use. It’s also possible to express types in terms of values that we already have
# Generics
- Types which take parameters
function loggingIdentity<Type>(arg: Type[]): Type[] {
console.log(arg.length);
return arg;
}
let myIdentity: <Type>(arg: Type) => Type = loggingIdentity;
// class
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
# Type Operator
Keyof
- hhh, just get keys
type Point = { x_YIKI: number; y: number };
type P = keyof Point; // x_YIKI | y
const test : P = "x_YIKI"; // must the same
- If the type has a string or number
index signature
, keyof will return thosetypes
instead:
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish;
let t : A = 12;
- ohters
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number
Typeof
- you can use it in a
type context
to refer to the type of avariable
orproperty
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;
let test : P = { x: 11, y: 13};
# Indexed[] Access Types
- Using Type['a'] syntax to access a subset of a type
- Using an
indexed access type
to look up a specificproperty
onanother
type
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];
# Conditional Types
- Types which act like if statements in the type system
- help describe the
relation
between the types ofinputs
andoutputs
SomeType extends OtherType ? TrueType : FalseType;
interface Animal {}
interface Dog extends Animal {}
type Example1 = Dog extends Animal ? number : string;
type Example2 = RegExp extends Animal ? number : string;
- When conditional types act on a generic type, they become distributive when given a union type
type ToArray<Type> = Type extends any ? Type[] : never;
- Distributive
- When
conditional
types act on ageneric
type, they become distributive when given aunion
type
- When
type ToArray<Type> = Type extends any ? Type[] : never;
type p = ToArray<string | number>; // StrArrOrNumArr = string[] | number[]
const test1 : p = [1,3,'test'] // error
const test2 : p = [1,3] // ok
const test3 : p = ['1','3','test'] // ok
- To
avoid
that behavior, surround each side of the extends keyword with[square brackets]
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
type p2 = ToArrayNonDist<string | number>; // (string | number)[]
const test1 : StrArrOrNumArr = [1,3,'test'] // ok
# infer
- always with
extends
and in the conditionnal type'strue
branch
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
- Conditional types provide us with a way to infer from types we compare against in the
true
branch using theinfer
keyword
type GetReturnType<Type> = Type extends
(...args: never[]) => infer p ? // match `() => any` ? return type p : type nerver
p : never;
type Num = GetReturnType<() => number>;
type Str = GetReturnType<(x: string) => string>;
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
let test : Bools = [true,false];
type nevers = GetReturnType<string>;
let test2 : nevers = (() => { // test2 : never
throw new Error('Throw my hands in the air like I just dont care');
})();
- Don't understand
Type extends (...args: never[]) => infer p
? view 1st chapter
type ParamType<T> = T extends (...args: infer P) => any ? P : T;
interface User {
name: string;
age: number;
}
type Func = (user: User) => void;
type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string
# Mapped Types
- Mapped types build on the syntax for
index signatures
, which are used todeclare
the types ofproperties
which havenot
beendeclared
ahead of time
type p = {
[key: number]: boolean;
};
const t: p = {
1: true,
};
- demo, take
all
theproperties
from the typeType
andchange
their values to be a boolean
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
type FeatureFlags = {
darkMode: () => void;
newUserProfile: () => void;
};
type FeatureOptions = OptionsFlags<FeatureFlags>;
/* turn as
type FeatureOptions = {
darkMode: boolean;
newUserProfile: boolean;
}
*/
Modifiers
readonly
with prefixing-
or+
?
type c<Type> = {
/*
type test = {
id: string;
name: string;
}
*/
-readonly [Property in keyof Type]: Type[Property];
// if
readonly [Property in keyof Type]: Type[Property];
/*
type test = {
readonly id: string;
readonly name: string;
}
*/
// ?
[Property in keyof Type]-?: Type[Property]; // reset match properties to NOT optional
};
type a = {
readonly id: string;
readonly name: string;
};
type test = c<a>;
Key Remapping
- can re-map
keys
in mapped types, means the final keys's name havechange
as
Mapped Types
type map<Type> = {
[Properties in keyof Type as NewKeyType]: Type[Properties]
}
- with
condition
type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};
type DBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};
type test = ExtractPII<DBFields>;
type test = {
id: false;
name: true; // input pii, so return true
}
# Template Literal Types
- Mapped types which change properties via template literal strings
- with
as
inMapped
Types to changeproperties
(keys's name)
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
/*type LazyPerson = {
getName: () => string; // key has change
getAge: () => number;
getLocation: () => string;
}
*/
Intrinsic
, To help with string manipulationUppercase<StringType>
Lowercase<StringType>
Capitalize<StringType>
Converts thefirst
character in the string to anuppercase
equivalent.Uncapitalize<StringType>
Converts thefirst
character in the string to alowercase
equivalent.