23
loading...
This website collects cookies to deliver better user experience
interface Dict {
[key: string]: any
}
const dict: Dict = {
name: 'Dictionary',
someRecord: {}
}
interface Item {
id: number,
name: string,
description: string,
amount: number,
}
const item: Item = {
id: 1,
name: "testItem",
description: "Lorem Ipsum",
amount: 1
};
function update(toUpdate: any, key: string, value: any) {
toUpdate[key] = value;
}
update(item, 'description', 'Dolor Sit Amet')
console.log(item.description) // Dolor Sit Amet
update(item, 'otherProp', 'someValue') //Setting a new prop
update(item, 'description', {}) //Changing type of an existing prop
function updateGeneric<T>(toUpdate: T, key: keyof T, value: any) {
toUpdate[key] = value;
}
keyof
. If you are not familiar with keyof
, it contains all the keys of the generic item.update(item, 'otherProp', 'someValue') //Fails as the prop 'otherProp' doesnt exists on type Item
update(item, 'description', {}) //This will still work
function updateGenericTwo<T,U extends keyof T>(toUpdate: T, key: U, value: T[U]) {
toUpdate[key] = value;
}
value
with T[U]
. This specifies that the type of value must be exactly the type of the item with the key U.update(item, 'otherProp', 'someValue') //Fails as the prop 'otherProp' doesnt exists on type Item
update(item, 'description', {}) //Fails as {} is not a string
const itemUpdate = {
description: 'Dolor Sit Amet',
amount: 5
};
type Update<T> = {
[U in keyof T]: T[U]
}
const itemUpdate: Update<Item> = { //Fails
"description": 'Dolor Sit Amet'
};
type Update<T> = {
[U in keyof T]?: T[U] //Notice the ? in the declaration
}
const itemUpdate: Update<Item> = { //Works now
"description": 'Dolor Sit Amet'
};
function apply<T>(toUpdate: T, update: Update<T>) {
Object.assign(toUpdate, update)
}
Update<T>
is pretty nifty, and does actually exist as a generic type in typescript itself: Partial<T>
. However, I wanted to take the route of rediscovery for you to get an understanding of how to declare such types yourself.Object.assign
to be able to update values of any object without breaking the original interface. There's multiple candidates for the same treatment, and a good example of that is the deepcopy with JSON. For those who don't know a very common and easy way of deepcloning an object is using the following function.function deepClone<T>(input: T): T {
return JSON.parse(JSON.stringify(input))
}
type NotADate<T> = Exclude<T, Date>
type ContainsNoDates<T> = NotADate<T> & {
[K in keyof T]: ContainsNoDates<T[K]>
}
function deepClone<T>(input: ContainsNoDates<T>): T {
return JSON.parse(JSON.stringify(input))
}
const date = deepClone(new Date()) // Compile error
const someObject = {
id: 1,
name: "Lorem Ipsum"
}
const clone = deepClone(someObject)
const dateObject ={
id: 1,
name: "Lorem Ipsum",
events: [
{eventName: "some event", time: new Date()}
]
}
const dateClone = deepClone(dateObject) //Compile error
const chain = <T extends any[]>(...funcs: T): (any): any => {
throw "Not implemented"
}
type SingleReturnType<T> = T extends (arg: any) => infer R ? R : never
type SingleParameterType<T> = T extends (arg: infer A) => any ? A : never
infer
keyword to return the type of the parameter and returned value respectively. They also enforce that the function takes only a single argument, which is important in chaining.chain
functiontype Chain<T extends any[], S = T> =
S extends [infer Head, infer Next, ...infer Tail]
? SingleReturnType<Head> extends SingleParameterType<Next>
? Chain<T, [Next, ...Tail]>
: never
: T
never
.type ChainParam<T extends any[]> =
T extends [infer Head , ...infer _]
? SingleParameterType<Head>
: never
type ChainReturn<T extends any[]> =
T extends [...infer __, infer Last]
? SingleReturnType<Last>
: never
SingleReturnType
and SingleParameterType
that I defined earlier. However, these takes a list of functions instead.chain()
const chain = <T extends any[]>(...funcs: Chain<T>): ((arg: ChainParam<T>) => ChainReturn<T>) => {
return (input) => {
let value = input;
for(const func of funcs) {
value = func(value)
}
return value
}
}
// Dummy functions
let strToNumber = (str: string) => 3
let numberToBool = (num: number) => true
let boolToString = (bool: boolean) => "foo"
// The chains
chain(strToNumber, numberToBool) // (arg: string) => boolean
chain(strToNumber, numberToBool)("Some Input") // boolean
chain(strToNumber, boolToString) // Compile error
chain(numberToBool)(3) // boolean
chain(boolToString)(3) // Compile Error