Willkommen zu Teil 2 dieser Miniserie über die Erstellung eines RSS-Viewers mit Vue. Im letzten Beitrag habe ich erläutert, wie ich meine Demo mit Vue.js und Vuetify im Frontend und Webtask im Backend erstellt habe. Als ich diese erste Version erstellte, wusste ich, dass es genau das war – eine "erste" Version. Ich habe mir Zeit genommen, um einige Updates zu erarbeiten, und obwohl ich es nicht wagen werde, dies als "perfekte" Version zu bezeichnen, glaube ich doch, dass ich einige Verbesserungen vorgenommen habe und diese gerne mit Ihnen teilen möchte.
Artikelserie
- Einrichtung und erste Iteration
- Verfeinerungen und finale Version (Dieser Beitrag)
Bevor ich beginne, hier sind Links zur fertigen Demo und zum Quellcode.
Fühlen Sie sich frei, zu forken, PRs einzureichen und Bugs nach Herzenslust zu melden!
Der Plan
Als ich die erste Version in Teil 1 vorstellte, skizzierte ich einige Ideen zur Verbesserung des RSS-Readers, darunter:
- Umstieg auf Vuex.
- Beginn der Umstellung auf Komponenten im Layout. (Nun, ich benutzte bereits Vuetify-Komponenten, aber ich meinte benutzerdefinierte Komponenten für meine Anwendung.)
- Verwendung von IndexedDB zum Speichern von Feed-Elementen für schnelleren Zugriff und Offline-Unterstützung.
Das war der Plan, und wie bei den meisten Plänen konnte ich bei diesem Update nicht unbedingt alles umsetzen (und ich werde am Ende erklären, warum). Aber hoffentlich sehen Sie die Verbesserungen als eine allgemeine "Bewegung in die richtige Richtung" für die Anwendung. Damit das aus dem Weg ist, fangen wir an!
Vuex implementieren
Ich beginne mit der Erörterung der größten Änderung an der Anwendung, der Hinzufügung von Vuex. Wie ich im vorherigen Beitrag sagte, beschreibt sich Vuex auf seiner Seite "Was ist Vuex" selbst als "State Management Pattern + Library". Ohne ihre Dokumentation zu beleidigen, hatte ich Schwierigkeiten, wirklich zu verstehen, was das aus praktischer Sicht bedeutet.
Nachdem ich es nun in einigen kleinen Projekten verwendet habe, beginne ich zu schätzen, was es bietet. Für mich liegt der Kernvorteil darin, eine zentrale Schnittstelle für Ihre Daten bereitzustellen. Wenn ich eine einfache Vue-App habe, die mit einem Array von Werten arbeitet, habe ich möglicherweise mehrere verschiedene Methoden, die es ändern. Was passiert, wenn ich anfange, bestimmte Regeln anzuwenden, bevor sich die Daten ändern? Als einfaches Beispiel stellen Sie sich ein Array von RSS-Feeds vor. Bevor ich einen neuen hinzufüge, möchte ich sicherstellen, dass er nicht bereits in der Liste vorhanden ist. Wenn ich eine Methode habe, die zur Feed-Liste hinzufügt, ist das kein Problem, aber wenn ich mehr habe, kann es umständlich werden, diese Logik über die verschiedenen Methoden hinweg synchron zu halten. Ich könnte einfach ein Dienstprogramm dafür erstellen, aber was passiert, wenn auch andere Komponenten im Spiel sind?
Obwohl es sich **absolut nicht** um einen Eins-zu-eins-Vergleich handelt, erinnert mich Vuex meiner Meinung nach daran, wie Provider oder Services in Angular funktionieren. Wenn ich jemals Daten verarbeiten möchte, stelle ich sicher, dass ich einen zentralen Provider verwende, der den gesamten Zugriff auf diese Daten handhabt. So sehe ich Vuex.
Die große Änderung in dieser Anwendung war also, alle datenbezogenen Elemente in einen Store zu migrieren. Ich begann damit, die Bibliothek zu meinem HTML hinzuzufügen
<script src="https://unpkg.com/vuex"></script>
Woot! Halbzeit! (Ok, vielleicht nicht.)
Dann erstellte ich eine Instanz meines Stores in meiner JavaScript-Datei
const feedStore = new Vuex.Store({
// lots of stuff here
});
und integrierte sie in meine Vue-App
let app = new Vue({
el: '#app',
store:feedStore,
// lots of stuff here too...
});
Jetzt kommt der interessante Teil. Jedes Mal, wenn meine Vue-Anwendung Daten benötigt, die hauptsächlich aus der Liste der Feeds und den Elementen dieser Feeds bestehen, wird sie diese vom Store abfragen. So ist zum Beispiel mein feeds-Wert jetzt berechnet
feeds() {
return feedStore.state.feeds;
},
Dies ist jetzt im state-Teil meines Stores definiert
state: {
allItems: [],
feeds: [],
selectedFeed: null
},
Beachten Sie, dass feeds standardmäßig ein leeres Array ist. Zuvor hatte ich das created-Ereignis meiner Vue-App verwendet, um die Daten aus localStorage zu lesen. Jetzt bitte ich den Store, das zu tun
created() {
feedStore.dispatch('restoreFeeds');
},
Zurück im Store ist die Logik so gut wie gleich
restoreFeeds(context) {
let feedsRaw = window.localStorage.getItem('feeds');
if(feedsRaw) {
try {
let feeds = JSON.parse(feedsRaw);
context.state.feeds = feeds;
context.state.feeds.forEach(f => {
context.dispatch('loadFeed', f);
});
} catch(e) {
console.error('Error restoring feed json'+e);
// bad json or other issue, nuke it
window.localStorage.removeItem('feeds');
}
}
},
Ich sage "so gut wie gleich", außer dass ich jetzt ein bisschen Fehlerprüfung am aus localStorage gelesenen Wert durchführe. Aber hier ist der entscheidende Punkt. Ich habe bereits gesagt, dass ich bei der Umstellung auf IndexedDB versagt habe, aber theoretisch könnte ich eine dritte Version dieser Anwendung mit einem aktualisierten Store erstellen und meine Vue-App würde keinen Unterschied bemerken. Und da wurde ich wirklich aufgeregt. Je mehr ich arbeitete, desto "dümmer" wurde meine Vue-App und desto weniger war sie an eine bestimmte Speicherimplementierung gebunden. Sehen wir uns die vollständige Vue-App jetzt an
let app = new Vue({
el: '#app',
store:feedStore,
data() {
return {
drawer:true,
addFeedDialog:false,
addURL:'',
urlError:false,
urlRules:[],
selectedFeed:null
}
},
computed: {
showIntro() {
return this.feeds.length == 0;
},
feeds() {
return feedStore.state.feeds;
},
items() {
return feedStore.getters.items;
}
},
created() {
feedStore.dispatch('restoreFeeds');
},
methods:{
addFeed() {
this.addFeedDialog = true;
},
allFeeds() {
feedStore.dispatch('filterFeed', null);
},
addFeedAction() {
this.urlError = false;
this.urlRules = [];
feedStore.dispatch('addFeed', {url:this.addURL})
.then(res => {
this.addURL = '';
this.addFeedDialog = false;
})
.catch(e =>{
console.log('err to add', e);
this.urlError = true;
this.urlRules = ["URL already exists."];
});
},
deleteFeed(feed) {
feedStore.dispatch('deleteFeed', feed);
},
filterFeed(feed) {
feedStore.dispatch('filterFeed', feed);
}
}
})
Was Sie bemerken werden, ist, dass praktisch die gesamte eigentliche Logik jetzt verschwunden ist und ich hier im Grunde nur noch UI-Sachen mache. Öffne hier ein Modal, füge dort einen Fehler hinzu und so weiter.
Sie können den vollständigen Store hier einsehen, obwohl ich mich entschuldige, dass ich alles in einer Datei zusammengefasst habe.
Eine Komponente hinzufügen
Eine weitere erwähnte Änderung war der Beginn der "Komponentarisierung" der View-Schicht. Ich habe schließlich nur eine Komponente erstellt: feed-item. Dies hat die Gesamtzahl der Zeilen im HTML etwas reduziert
<v-flex xs12 v-for="item in items" :key="item.link">
<feed-item :title="item.title" :content="item.content" :link="item.link" :feedtitle="item.feedTitle" :color="item.feedColor" :posted="item.pubDate"></feed-item>
</v-flex>
Es ist keineswegs eine *riesige* Änderung, aber es hat mir die Arbeit bei der Anzeige der Feeds etwas erleichtert. Da ich noch keinen schicken Builder verwende, habe ich meine Komponente direkt in JavaScript wie folgt definiert
Vue.component('feed-item', {
props:[
'color','title','content','link','feedtitle', 'posted'
],
template: `
<v-card :color="color">
<v-card-title primary-title>
<div class="headline">{{title}} ({{posted | dtFormat}})</div>
</v-card-title>
<v-card-text>
{{content | maxText }}
</v-card-text>
<v-card-actions>
<v-btn flat target="_new" :href="link">Read on {{feedtitle}}</v-btn>
</v-card-actions>
</v-card>
`
});
Ich mache hier nichts Besonderes – es gibt keine dynamische Logik, keine Events oder ähnliches –, aber das könnte ich später dort hinzufügen, wo es Sinn macht. Ich habe es endlich geschafft, Datum und Uhrzeit der Veröffentlichung hinzuzufügen. Wenn Sie neugierig sind, wie ich den Formatter dafür erstellt habe, lesen Sie meinen Artikel "Erstellen eines i18n-Filters mit Vue.js & Native Web Specs."
Die Macht des Löschens!
Oh, und ich habe endlich eine Möglichkeit hinzugefügt, Feeds zu löschen

Dies löst lediglich eine Methode im Vue-Objekt aus, die wiederum einen Aufruf an den Store auslöst, der für das Entfernen des Feeds und der Elemente aus der Benutzeroberfläche und deren anschließende Persistenz zuständig ist. Eine Kleinigkeit, aber wow, ich wünschte, ich hätte das in der ersten Version beim Testen gehabt. Und hier ist ein letzter Schnappschuss von allem

Nächste Schritte... und was geschah mit IndexedDB?
Wie ich eingangs sagte, ist diese Version immer noch nicht *perfekt*, aber ich fühle mich auf jeden Fall besser damit. Ich ermutige Sie dringend, Tipps, Vorschläge und Fehlerberichte in den Kommentaren unten oder im GitHub-Repository zu teilen.
Was geschah also mit der IndexedDB-Unterstützung? Das Problem, auf das ich stieß, war, wie man die Datenbank richtig initialisiert. Vuex-Stores haben kein Konzept eines created-Prozesses. Ich hätte etwas wie folgt machen können
// dummy code for getting feeds
dispatch('getDB')
.then(() =>
// do stuff
);
Wobei die getDB-Aktion ein Promise zurückgibt und das einmalige Öffnen von IndexedDB und das Speichern des Wertes im State übernimmt. Das werde ich vielleicht später versuchen, und wieder, was ich an Vuex liebe, ist, dass ich weiß, dass ich das sicher tun kann, ohne den Rest der Anwendung zu beeinträchtigen.
Artikelserie
- Einrichtung und erste Iteration
- Verfeinerungen und finale Version (Dieser Beitrag)