-
Notifications
You must be signed in to change notification settings - Fork 3
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
1 parent
8ab6a4e
commit 52093ef
Showing
5 changed files
with
149 additions
and
3 deletions.
There are no files selected for viewing
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
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
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
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,88 @@ | ||
Title: "Simplifying Complex Type Display in TypeScript and VS Code" | ||
|
||
Introduction: | ||
When working with TypeScript and VS Code, you may encounter situations where complex types, especially those involving generics and conditional types, are displayed in a verbose and difficult-to-read manner when hovering over variables or types. This can make it challenging to quickly understand the type information and can clutter the IDE's interface. In this article, we'll explore a workaround that can help simplify the display of complex types in VS Code using TypeScript. | ||
|
||
The Problem: | ||
Consider the following example where we have a `State<T>` type and a `createState` function: | ||
|
||
```typescript | ||
export type State<TStateValue extends StateValue> = { | ||
// ... | ||
get: < | ||
TSelector extends (state: TStateValue) => unknown = ( | ||
state: TStateValue | ||
) => TStateValue, | ||
>( | ||
selector?: TSelector | ||
) => TSelector extends (state: TStateValue) => infer TReturnType | ||
? TReturnType | ||
: TStateValue; | ||
// ... | ||
}; | ||
|
||
function createState<T extends StateValue>(initialValue: T): State<T> { | ||
// ... | ||
} | ||
``` | ||
|
||
When using the `createState` function to create a store with a specific type, like `createState(0)`, VS Code expands the type and displays a verbose and complex type definition: | ||
|
||
```typescript | ||
const numberStore = createState(0); | ||
/* | ||
numberStore: { | ||
get: <TSelector extends (state: number) => unknown = (state: number) => number>(selector?: TSelector | undefined) => TSelector extends (state: number) => infer TReturnType ? TReturnType : number; | ||
set: (newValueOrFn: number | ((prev: number) => number)) => void; | ||
use: <TSelector extends (state: number) => unknown = (state: number) => number>(selector?: TSelector | undefined, equalityFn?: EqualityChecker<...> | undefined) => TSelector extends (state: number) => infer TReturnType ? TReturnType : number; | ||
onChange: (callback: (value: number, prevValue: number) => void, options?: OnChangeOptions<...> | undefined) => UnsubscribeFn; | ||
} | ||
*/ | ||
``` | ||
|
||
The Solution: | ||
To simplify the display of complex types in VS Code, we can leverage TypeScript's intersection types and a clever type definition. Here's how it works: | ||
|
||
1. Define a `Simplify` utility type: | ||
|
||
```typescript | ||
export type Simplify<T> = T extends any[] | Date | ||
? T | ||
: { | ||
[K in keyof T]: T[K]; | ||
} & {}; | ||
``` | ||
|
||
This type expands objects to show all the key/value types. Given an empty object, it will resolve to an empty object. | ||
|
||
2. Intersect your complex type with `Simplify<{}>`: | ||
|
||
```typescript | ||
export type State<TStateValue extends StateValue> = { | ||
// ... | ||
} & Simplify<{}>; | ||
``` | ||
|
||
By intersecting the `State<T>` type with `Simplify<{}>`, we trigger a mechanism in VS Code that simplifies the displayed type when hovering over variables or types, without altering the actual type. | ||
|
||
3. Use the modified type in the `createState` function: | ||
|
||
```typescript | ||
function createState<T extends StateValue>(initialValue: T): State<T> { | ||
// ... | ||
} | ||
|
||
const numberStore = createState(0); | ||
// Hovering over `numberStore` will display `State<number>` instead of the expanded type | ||
``` | ||
|
||
When using the `createState` function with the modified `State<T>` type, VS Code will display the simplified type name `State<number>` instead of the verbose type definition when hovering over `numberStore`. | ||
|
||
Conclusion: | ||
The workaround presented in this article can help simplify the display of complex types in VS Code when working with TypeScript. By intersecting your complex types with `Simplify<{}>`, you can trigger a mechanism in VS Code that simplifies the displayed type, making it more readable and easier to understand. | ||
|
||
However, it's important to note that this approach is a workaround and may not be suitable for all situations. It's still crucial to have well-defined and properly structured types in your codebase. The simplified type display is primarily aimed at improving readability and reducing clutter in the IDE. | ||
|
||
Remember, this technique only affects the display of types in VS Code and doesn't change the actual type definitions. It's a tool to enhance the developer experience when working with complex types. | ||
|
||
I hope this article helps you navigate and simplify the display of complex types in your TypeScript projects using VS Code. Happy coding! |
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,54 @@ | ||
import { describe, expectTypeOf, it } from 'vitest'; | ||
import { Simplify, State, state, StateValue, store, StoreApi } from '../../src'; | ||
|
||
describe('correct store types', () => { | ||
it('store should have StoreApi<TStateValue> type', () => { | ||
const myStore = store(0); | ||
expectTypeOf(myStore).toEqualTypeOf<StoreApi<number>>(); | ||
//EXPECTED = const myStore: StoreApi<number> | ||
//RECIEVED = const myStore: StoreApi<number> | ||
// correctly inferred the type | ||
}); | ||
it('state should have State<TStateValue> type', () => { | ||
const myState = state(0); | ||
expectTypeOf(myState).toEqualTypeOf<State<number>>(); | ||
|
||
// EXPECTED = const myState: State<number> | ||
// RECIEVED = const myState: State<number> | ||
// correctly inferred the type | ||
}); | ||
}); | ||
|
||
// notes on how I fixed it: | ||
|
||
// to fix it, I added & Simplify<{}> to the state type. | ||
// adding just & {} didn't work | ||
// maybe it's because there are two generics being resolved so the IDE just simplifies the type to the name of the type, instead of showing the entire type on hover. | ||
// by making advantage of this mechanism, I think we can just add any type that doesn't change the type, like Simplify<{}> to make the type easier to read. | ||
|
||
// for some reason, when passing the generic directly to the staet function, like this: | ||
type StoreApi2Number = State<number>; | ||
// the type is expanded to something like this: | ||
/** | ||
const myState: { | ||
get: <TSelector extends (state: number) => unknown = (state: number) => number>(selector?: TSelector | undefined) => TSelector extends (state: number) => infer TReturnType ? TReturnType : number; | ||
set: (newValueOrFn: number | ((prev: number) => number)) => void; | ||
use: <TSelector extends (state: number) => unknown = (state: number) => number>(selector?: TSelector | undefined, equalityFn?: EqualityChecker<...> | undefined) => TSelector extends (state: number) => infer TReturnType ? TReturnType : number; | ||
onChange: (callback: (value: number, prevValue: number) => void, options?: OnChangeOptions<...> | undefined) => UnsubscribeFn; | ||
*/ | ||
|
||
// however, if you cast the type as a return value of a function, like this: | ||
function getStore<T extends StateValue>() { | ||
return null as unknown as State<T> | ||
} | ||
|
||
// and then use the function to get the type, like this: | ||
const myStore = getStore<number>(); | ||
|
||
// then you get the correct type: | ||
/** | ||
const myStore: State<number> | ||
*/ | ||
|
||
// this is a workaround to get the correct type. It's not ideal, but it gets the job done. | ||
|