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'