mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-10 04:22:06 +00:00
docs: WIP
This commit is contained in:
249
content/blog/typescript-type-inferencing/index.md
Normal file
249
content/blog/typescript-type-inferencing/index.md
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
---
|
||||||
|
{
|
||||||
|
title: "The Rules of TypeScript Type Inferrencing",
|
||||||
|
description: "",
|
||||||
|
published: '2025-01-14T21:52:59.284Z',
|
||||||
|
authors: ['crutchcorn'],
|
||||||
|
tags: ['typescript', 'webdev'],
|
||||||
|
attached: [],
|
||||||
|
license: 'cc-by-4'
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
Some lorem ipsum text here.
|
||||||
|
|
||||||
|
Let's jump in:
|
||||||
|
|
||||||
|
# Type Inferencing Basics
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function identity<T>(arg: T): T {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const val = identity(1 as const);
|
||||||
|
// ^ 1
|
||||||
|
```
|
||||||
|
|
||||||
|
This also works with classes:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class IdentityWithMeta<T> {
|
||||||
|
value: T;
|
||||||
|
touched: boolean = false;
|
||||||
|
|
||||||
|
constructor(val: T) {
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(val: T) {
|
||||||
|
this.touched = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const classVal = new IdentityWithMeta(1 as const);
|
||||||
|
classVal.value;
|
||||||
|
// ^ 1
|
||||||
|
```
|
||||||
|
|
||||||
|
# Default Type Values
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function identityWithMeta<T = never>(arg: T): {arg: T, isTouched: boolean} {
|
||||||
|
return {arg, isTouched: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = identityWithMeta(1 as const);
|
||||||
|
meta.arg;
|
||||||
|
// ^ 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Works with classes too:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class IdentityWithMetaAndDelete<T = never> {
|
||||||
|
value: T | null;
|
||||||
|
touched: boolean = false;
|
||||||
|
|
||||||
|
constructor(val: T) {
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(val: T) {
|
||||||
|
this.touched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
this.touched = true;
|
||||||
|
this.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const classValWithDelete = new IdentityWithMetaAndDelete(1 as const);
|
||||||
|
classVal.value;
|
||||||
|
// ^ 1
|
||||||
|
```
|
||||||
|
|
||||||
|
# Usage with Interfaces
|
||||||
|
|
||||||
|
Useful for return types:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IdentityWithMetaInterfaceRet<T> {
|
||||||
|
arg: T;
|
||||||
|
isTouched: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function identityWithMetaReturnInterface<T = never>(arg: T): IdentityWithMetaInterfaceRet<T> {
|
||||||
|
return {arg, isTouched: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
const metaInterface = identityWithMetaReturnInterface(1 as const);
|
||||||
|
metaInterface.arg;
|
||||||
|
// ^ 1
|
||||||
|
```
|
||||||
|
|
||||||
|
And property types as well:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function identityMetaProps<T = never>(arg: {value: T, isTouched: boolean}): T {
|
||||||
|
return arg.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const propReturn = identityMetaProps({value: 1, isTouched: false} as const);
|
||||||
|
propReturn;
|
||||||
|
// ^ 1
|
||||||
|
```
|
||||||
|
|
||||||
|
As well as prop interfaces:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IdentityMetaPropsInterfaceProp<T> {
|
||||||
|
value: T;
|
||||||
|
isTouched: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function identityMetaPropsInterface<T = never>(arg: IdentityMetaPropsInterfaceProp<T>): T {
|
||||||
|
return arg.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const propInterfaceReturn = identityMetaPropsInterface({value: 1, isTouched: false} as const);
|
||||||
|
propInterfaceReturn;
|
||||||
|
// ^ 1
|
||||||
|
```
|
||||||
|
|
||||||
|
# Type Inferencing Rules
|
||||||
|
|
||||||
|
## Object Order Matters
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Five<T = unknown, O = unknown> {
|
||||||
|
seven: T,
|
||||||
|
eight: O
|
||||||
|
nine: O
|
||||||
|
}
|
||||||
|
|
||||||
|
class FiveTest<T, O = unknown> {
|
||||||
|
constructor(public opts: Five<T, O>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
returnOpts() {
|
||||||
|
return this.opts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const otherObj = new FiveTest({
|
||||||
|
seven: "test",
|
||||||
|
eight: "other",
|
||||||
|
nine: 1
|
||||||
|
// ^ Complains about not being "other", since `eight` comes first
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
const {seven, eight, nine } = otherObj.returnOpts();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Don't mix generics and explicit values
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const foo = <T extends string, Defaulted extends string = "default">(
|
||||||
|
t: T,
|
||||||
|
defaulted: Defaulted
|
||||||
|
) => ({
|
||||||
|
t,
|
||||||
|
defaulted
|
||||||
|
})
|
||||||
|
|
||||||
|
const result1 = foo("string", "default") //=>
|
||||||
|
|
||||||
|
// This is okay- TS will infer it over the default
|
||||||
|
const result2 = foo("string", "something else") //=>
|
||||||
|
|
||||||
|
// If you provide any args to the generic directly, any with defaults will be forced to that value
|
||||||
|
|
||||||
|
const result3 = foo<"foo">("foo", "something else")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Which also leads to:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Test<One, Two = unknown> {
|
||||||
|
name: One,
|
||||||
|
opts: Two,
|
||||||
|
derived: Two extends number ? string : number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Other<One> {
|
||||||
|
constructor(meta: Test<One>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
returnSelf() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = new Other({
|
||||||
|
name: "Test",
|
||||||
|
opts: 123,
|
||||||
|
derived: 123
|
||||||
|
// ^ Should throw an error but doesn't, because `Two` is `unknown`, not `number`
|
||||||
|
})
|
||||||
|
|
||||||
|
const val = a.returnSelf();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## WTF
|
||||||
|
|
||||||
|
No really, why is this happening - I have no idea. It doesn't care about property order _now_?!
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface FieldOptions<
|
||||||
|
ValidatorType ,
|
||||||
|
> {
|
||||||
|
validator?: ValidatorType
|
||||||
|
onChange?: ValidatorType extends object
|
||||||
|
? ValidatorType
|
||||||
|
: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FieldApi<ValidatorType> {
|
||||||
|
constructor(
|
||||||
|
public options: FieldOptions<ValidatorType>,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hello = new FieldApi({
|
||||||
|
// Shouldn't onChange be the one erroring and basing it off of validator?
|
||||||
|
validator: {abc: 123},
|
||||||
|
// ^ Type '{ readonly abc: 123; }' is not assignable to type '() => void'.
|
||||||
|
onChange: () => {}
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
hello.options
|
||||||
|
```
|
||||||
|
|
||||||
Reference in New Issue
Block a user