- Notifications
You must be signed in to change notification settings - Fork 13k
Description
Thanks to @s-ve ,I've resolved my questions. But there is still something we can discuss.
Let's focus on Example 3 and 7.
// 3. enum _C { A, B, C } const c = fn(_C.A) // can not express if I want to infer this constant to have type _C.A (Now `_C`) // 7. function fn<T extends Symbol>(x: T): T {return x} const Symb = Symbol() const g = fn(Symb) // expected to have type `unique symbol` refer to Symb but `symbol`
How to write type if you want a literal generics
// DO const ActionCreator = <T extends string, P = any>(type: T) => (payload: P) => ({ type, payload }); const TodoAddOne = ActionCreator("todo.add"); // DONT const ActionCreator = <T, P = any>(type: T) => (payload: P) => ({ type, payload }); const TodoAddOne = ActionCreator<"todo.add">("todo.add");
Content below is useless now, I've learnt the correct way to write types I want(See above).
Search Terms: type string literal
In some scenarios, we need to get type inference by a sure string, but not a string
type.
(Most famous one is ActionCreator in Redux, but not the only one.)
Now, typescript can infer the type of (function <T>(x: T): T {return x})('hello')
is the string, but if we want to get a more precise infer, it seems no way to do this.
I'm sorry for my ignorance, typescript actually can do this.
I'll show how in my following examples.
This is not a formal language feature proposal. But a demo one is enough to explain what I mean.
This how ActionCreator
works now:
// Before (Don't) const ActionCreator = <T, P = any>(type: T) => (payload: P) => ({type, payload}) const TodoAddOne = ActionCreator<'todo.add'>('todo.add') // After(Do) const ActionCreator = <T extends string, P = any>(type: T) => (payload: P) => ({ type, payload }); const TodoAddOne = ActionCreator("todo.add");
Now TodoAddOne has type (payload: any) => { type: "todo.add"; payload: any; }
function ActionCreator<Payload, literal Action>(type: Action){ return (payload: Payload) => ({ type, payload }) } const TodoAddOne = ActionCreator<{add: number}>('todo.add')
With no duplication of todo.add
, we get the same type as above.
Though this is a small reduction, it goes useful when actions get greater.
How do I think this should work? (NO, Skip this section)
- Since who wrote the code choose to use a literal type, if Typescript cannot infer the precise type of it(not
the string I want
, juststring
type), Typescript should emit an Error.
const fn = <literal T>(a: T) => a // 1. Direct infer (ts can do this) const a = fn('hey') // has type 'hey' // 2. Direct infer (number) (ts can do this) const b = fn(2) // has type 2 // 3. Direct infer (enum) (No, ts cannot do this) enum _C { A, B, C } const c = fn(_C.A) // has type _C.A // 4. Infer from constant (Rejected proposal) const _d_name = 'nyaa' const d = fn(_d_name) // has type 'nyaa' // 5. Infer from **simple** string operation (Rejected proposal) const _e_name = 'prefix~' const e = fn(_e_name + 'hello') // has type 'prefix~hello' // 6. Infer from another file (if possible) (Rejected proposal) import { OH_MY_LITTLE_PONY } from './constant' // export const OH_MY_LITTLE_PONY = 'MyLittlePony' in 'constant.ts' const f = fn(OH_MY_LITTLE_PONY) // has type 'MyLittlePony' // 7. Infer from Symbol() (No, ts cannot do this, it has type `symbol` now) const Symb = Symbol() const g = fn(Symb) // has type `unique Symbol` // 8. Report error if compiler cannot infer the type (Rejected proposal) const _h: any = 'hello' const h = fn(_h) // ~~~~~~ // `any` is incompatiable with type `literal T`. // You must provide a presice type for `literal T` // 10. Maybe a way to bypass the error? (Useless now since 8 is rejected.) const q: any = 0 fn(q!) // has type any // 11. Not in generics (// Well, this case works when using 'const' but not 'let', that's reasonable.) const n: literal string = 'okay' // has type 'okay'