typescript - Πώς να περάσετε μια περιορισμένη συμβολοσειρά σε έναν τύπο χρησιμοποιώντας ως παραμέτρους μια παράμετρο που χρησιμοποιεί τον ίδιο περιορισμό
Μπορώ να φτιάξω αριθμό σαν αυτό:
type Digits = '0'| '1'| '2'| '3'| '4'| '5'| '6'| '7'| '8'| '9'
type NonZero = Exclude <Digits, '0'>
type Negative = '-'
type PositiveNumbers =
| NonZero
| `${NonZero}${Digits}`
| `${NonZero}${Digits}${Digits}`
| `${NonZero}${Digits}${Digits}${Digits}`
| `${NonZero}${Digits}${Digits}${Digits}${Digits}`
| `${NonZero}${Digits}${Digits}${Digits}${Digits}${Digits}` // Error: Expression produces a union type that is too complex to represent.(2590)
type Numbers =
| PositiveNumbers
| `${Negative}${PositiveNumbers}`
Αλλά μετά περιορίζομαι σε αριθμούς από -99999 έως 99999, και εκτός αυτού η χρήση του τύπου Numbers σε άλλους τύπους κάνει τον μεταγλωττιστή να δυσκολεύεται. Έτσι χρησιμοποιώ έναν τύπο για να αναπαριστώ αριθμούς ως περιορισμό ως εξής:
type ConstrainNumber <N extends string> =
N extends '0'
? unknown
: N extends `${infer Char}${infer Rest}`
? Char extends '0'
? never
: Char extends Negative
? ConstrainNumber <Rest>
: ConstrainNumberRec <N, Exclude <Digits, '0'>>
: never
type ConstrainNumberRec <S extends string, D = Digits> =
S extends `${infer Char}${infer Rest}`
? Char extends D ? ConstrainNumberRec <Rest> : never
: unknown
Όταν το χρησιμοποιείτε σε έναν τύπο λειτουργεί όπως αναμένεται:
type UseNumbers <N extends string & _N, _N = ConstrainNumber <N>> = N
type testValid = UseNumbers <'2875467'>
type testInvalid = UseNumbers <'02875467'> // Expected error: Type 'string' does not satisfy the constraint 'never'.(2344)
Αλλά όταν χρησιμοποιώ έναν τύπο που χρησιμοποιεί UseNumbers, σπάει
type UseUseNumbers <N extends string & _N, _N = ConstrainNumber <N>> =
UseNumbers <N> // Unwanted Error: type 'N' does not satisfy the constraint 'string & ConstrainNumber<N>'
Πώς μπορώ να περάσω μια συμβολοσειρά περιορισμού σε έναν τύπο χρησιμοποιώντας ως παραμέτρους μια παράμετρο που χρησιμοποιεί τον ίδιο περιορισμό;
Και επιπλέον, δεν είμαι σίγουρος να καταλάβω γιατί σπάει; Στο UseNumbers <N> το N είναι ήδη περιορισμός ή όχι;
παιδική χαρά
Απάντηση #1
Το κύριο ζήτημα είναι ότι οι προεπιλεγμένες τιμές των γενικών παραμέτρων δεν είναι γενικοί περιορισμοί:
type Foo<T extends number> = T;
type BadFoo = Foo<string>; // error, not satisfying constraint
type Bar<T = number> = T;
type DefaultBar = Bar; // number
type StillGoodBar = Bar<string>; // string
Στο παραπάνω, η παραμετροποίηση τύπου T στο Foo περιορίζεται σε number, ενώ στο Bar δεν υπάρχει περιορισμός αλλά απλώς προεπιλέγεται σε number. Έτσι, δεν μπορείτε να γράψετε Foo<string>. Αν απλώς γράψετε Bar, θα είναι Bar<number>, αλλά τίποτα δεν εμποδίζει κάποιον να γράψει Bar<string>. Δεν είναι σφάλμα και ο μεταγλωττιστής δεν μπορεί να υποθέσει μέσα στο Bar ότι το T θα είναι υποτύπος του number.
Στην ορισμό αυτό,
type UseUseNumbers<N extends string & _N, _N = ConstrainNumber<N>> = ...
Η παράμετρος τύπου _N δεν έχει περιορισμό. Μπορεί να είναι οτιδήποτε, παρόλο που προεπιλέγεται σε ConstrainNumber<N>. Μόνο το N περιορίζεται σε string & _N.
Η κύρια χρήση σας εδώ είναι ότι κανείς δεν καθορίζει το _N, και ότι το UseUseNumbers<N> είναι ισοδύναμο με το UseUseNumbers<N, ConstrainNumber<N>>, το οποίο μεταγλωττίζεται αν και μόνο αν N extends string & ConstrainNumber<N>. Αυτός είναι ένας έξυπνος τρόπος για να αποφύγετε το σφάλμα περιορισμού κυκλικότητας που θα συνέβαινε αν γράφατε type Oopsie<N extends string & ConstrainNumber<N>>, αλλά αυτό το κόλπο λειτουργεί μόνο αφήνοντας ανοικτό το ενδεχόμενο το _N να μην εξαρτάται από το N.
Και έτσι, ο μεταγλωττιστής δεν μπορεί να γνωρίζει ότι το _N είναι κάτι συγκεκριμένο. Κάποιος ανεύθυνος προγραμματιστής μπορεί να γράψει UseUseNumbers<string, unknown> ή UseUseNumbers<"abc","abc" | "def"> ή κάποια άλλη τρελή μη τυποποιημένη χρήση του UseUseNumbers η οποία τυχαίνει να μεταγλωττιστεί. Αυτό πιθανώς να είναι αναπόφευκτο (αν και το αν είναι αποτρέψιμο ή όχι είναι εκτός θέματος για την ερώτηση), αλλά επίσης απίθανο να συμβεί πραγματικά, ειδικά αν τεκμηριώσετε ότι το _N είναι για «εσωτερική χρήση μόνο» (αλλά και πάλι, εκτός θέματος). Το πόσο θέλετε να λάβετε υπόψη αυτή την πιθανότητα εξαρτάται από εσάς.
Τέλος πάντων, ο μεταγλωττιστής είναι εκνευρισμένος με
type UseUseNumbers<N extends string & _N, _N = ConstrainNumber<N>> =
UseNumbers<N> // <-- error
Γιατί; επειδή UseNumbers<N> αξιολογείται ως UseNumbers<N, ConstrainNumber<N>>, το οποίο (παρόμοια με το UseUseNumbers) συντάσσεται μόνο αν N extends string & ConstrainNumber<N>. Αλλά ο μεταγλωττιστής δεν ξέρει τι ισχύει για το τρέχον N, το οποίο είναι γνωστό μόνο ότι επεκτείνεται σε string & _N, και το _N δεν έχει περιορισμούς. Επομένως, δεν συντάσσεται.
Ο τρόπος να διορθωθεί αυτό είναι να πάρουμε απλώς ρητά την υπόθεση ότι κανείς δεν πρόκειται να πειράξει το _N στο UseUseNumbers, και να το περάσουμε κάτω στο UseNumbers:
type UseUseNumbers<N extends string & _N, _N = ConstrainNumber<N>> =
UseNumbers<N, _N> // <-- this fixes it
Δεδομένου ότι το _N στο UseNumbers δεν έχει περιορισμούς, μπορείτε να γράψετε UseNumbers<N, _N>, το οποίο θα μεταγλωττιστεί μόνο αν και μόνο αν N extends string & _N. Αλλά γνωρίζουμε ότι αυτό είναι αληθές από τον ορισμό του N στο UseUseNumbers. Όσο οι άνθρωποι γράφουν UseUseNumbers<N> και δεν περνούν ένα δεύτερο τύπο παραμέτρου, τότε αυτό θα αξιολογηθεί σε UseNumbers<N> όπως επιθυμείται.
Σύνδεσμος playground για κώδικα
