J'ai essayé d'écrire un VUE simple en utilisant du typographie, mais la première étape a échoué. J'ai trouvé beaucoup de réponses et je n'ai pas trouvé de solution qui résoudrait mon problème.
Je veux déclarer dynamiquement certaines propriétés de classes, qu'elles obtiennent via des constructeurs, mais je ne sais pas comment rédigez de telles déclarations.
Environnement
typScript 3.4.5
interface IOptions {
data: () => Record<string, any>
}
class Vue {
private $options: IOptions = {
data: () => ({})
}
constructor(options: IOptions) {
this.$options = options
const proxy = this.initProxy()
return proxy
}
initProxy() {
const data = this.$options.data ? this.$options.data() : {}
return new Proxy(this, {
set(_, key: string, value) {
data[key] = value
return true
},
get(_, key: string) {
return data[key]
}
})
}
}
const vm = new Vue({
data() {
return {
a: 1
}
}
})
vm.a = 2
// ^ Property 'a' does not exist on type 'Vue'.
console.log(vm.a) // => 2
// ^ Property 'a' does not exist on type 'Vue'.
Ceci est un aperçu en ligne de l'adresse https://stackblitz.com/edit/typescript-kh4zmn
Ouvrez-le et vous pouvez voir que la console affiche la sortie attendue mais l'éditeur donne une erreur de typographie avec La propriété 'a' n'existe pas sur le type 'Vue'. .
Je m'attends à ce que vm ait le type correct pour que je puisse accéder aux propriétés déclarées dans le constructeur sans erreur.
3 Réponses :
Typescript ne connaît pas le proxy et les noms de propriétés qu'il pourrait accepter. Par exemple, considérez un setter tel que:
set(_, key: string, value: any) {
if (!key.startsWith('foo')) {
return false;
}
data[key] = value;
return true;
}
Typescript devrait exécuter le code pour déterminer quels noms de propriétés sont légaux ici.
Une solution rapide à votre problème serait be d'ajouter une propriété comme [key: string]: unknown; à la classe Vue qui indiquera au typecript d'accepter quoi que ce soit tant que la clé est une chaîne , indépendamment de son type. Cela rendra votre exemple compilé.
Vous devriez probablement envisager de déclarer correctement les propriétés que la classe Vue utilisera, si vous le pouvez, pour profiter de la vérification de type statique de Typescript.
[key: string]: unknown; fonctionnera mais rendra fondamentalement toute la classe de type très dangereux (c'est-à-dire. vm.blaBla conviendrait)
C'est vrai, mais c'est précisément ce que demande la question: les propriétés de la classe sont entièrement inconnues jusqu'à ce que le constructeur soit appelé. S'ils ne le sont pas, ils devraient probablement être déclarés pour la sécurité de type comme indiqué ci-dessus. Que vous fassiez cela via un mixin comme dans la réponse de Titian, via l'héritage de la classe Vue de base, ou directement sur la classe dépendrait du contexte.
Votre classe Vue renvoie un objet Proxy.
Votre proxy a une fonction get and set, ce qui signifie que vous pouvez définir et obtenir l'objet encapsulé (dans votre cas Record
Donc, pour utiliser correctement votre objet Vue pour ajouter la propriété "a" puis récupérer sa valeur, vous utiliseriez:
vm["a"] = 2 console.log(vm["a"])
Merci de votre réponse rapide. Cela semble résoudre mon problème, mais cela me fait perdre la vérification de type statique, de sorte que je ne signale pas d'erreur lors de l'utilisation d'une variable qui n'existe pas.
@ TitianCernicova-Dragomir Ah oui. Belle réponse que vous avez donnée.
La première partie du problème est d'obtenir le bon type de retour de initProxy . Puisque vous ajoutez toutes les propriétés renvoyées par data au proxy, le type de retour doit les contenir. Pour y parvenir, nous aurons besoin d'un paramètre de type ( T ) pour la classe Vue . Ce paramètre de type capturera le type réel du type de retour data . Avec ce paramètre de type en main, nous pouvons faire savoir au typographie que initProxy renvoie en fait T & Vue , c'est-à-dire qu'il renvoie un objet qui est à la fois T et la classe d'origine
class _Vue<T = {}> {
private $options: IOptions<T> = {
data: () => ({})
} as IOptions<T>
private constructor(options: IOptions<T>) {
this.$options = options
const proxy = this.initProxy()
return proxy
}
initProxy(): Vue<T> {
const data = this.$options.data ? this.$options.data() : {}
return new Proxy(this as unknown as Vue<T>, {
set(_, key: string, value) {
data[key] = value
return true
},
get(_, key: string) {
return data[key]
}
})
}
}
type Vue<T> = _Vue<T> & T
const Vue: new<T>(data: IOptions<T>) => Vue<T> = _Vue as any
const vm = new Vue({
data() {
return {
a: 1
}
}
})
vm.a = 2;
La deuxième partie du problème est que même si le typographie vous permettra de renvoyer un objet depuis le constructeur, cela ne changera en aucun cas le type de retour de l'appel du constructeur (vous ne pouvez pas non plus annoter le type de retour du constructeur). C'est pourquoi bien que vm.initProxy (). A fonctionne, vm.a ne fonctionne toujours pas.
Pour contourner cette limitation, nous avons deux options:
Utilisez un constructeur privé et une méthode statique correctement typée:
class Vue<T = {}> {
private $options: IOptions<T> = {
data: () => ({})
} as IOptions<T>
private constructor(options: IOptions<T>) {
this.$options = options
const proxy = this.initProxy()
return proxy
}
static create<T>(data: IOptions<T>):Vue<T> & T {
return new Vue<T>(data) as unknown as Vue<T> & T
}
initProxy(): T & Vue<T> {
const data = this.$options.data ? this.$options.data() : {}
return new Proxy(this as unknown as T & Vue<T>, {
set(_, key: string, value) {
data[key] = value
return true
},
get(_, key: string) {
return data[key]
}
})
}
}
const vm = Vue.create({
data() {
return {
a: 1
}
}
})
vm.a = 2;
Utilisez une signature distincte pour la classe
interface IOptions<T> {
data: () => T
}
class Vue<T = {}> {
private $options: IOptions<T> = {
data: () => ({})
} as IOptions<T>
constructor(options: IOptions<T>) {
this.$options = options
const proxy = this.initProxy()
return proxy
}
initProxy(): T & Vue<T> {
const data = this.$options.data ? this.$options.data() : {}
return new Proxy(this as unknown as T & Vue<T>, {
set(_, key: string, value) {
data[key] = value
return true
},
get(_, key: string) {
return data[key]
}
})
}
}
const vm = new Vue({
data() {
return {
a: 1
}
}
})
vm.initProxy().a // ok now
tellement incroyable! ! J'ai besoin d'une telle réponse, ce qui est très utile pour moi d'apprendre la dactylographie, merci beaucoup pour votre réponse détaillée.