Skip to content
This repository was archived by the owner on Nov 28, 2020. It is now read-only.

Commit 9544ecc

Browse files
committed
Vuex Module that are strongly typed with TypeScript
1 parent 343cc45 commit 9544ecc

File tree

13 files changed

+269
-179
lines changed

13 files changed

+269
-179
lines changed

lib/models/feed.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export interface Feed {
2+
comments_count: number
3+
domain: string
4+
id: number
5+
points: number
6+
time: number | Date | undefined
7+
time_ago: string
8+
title: string
9+
type: string
10+
url: string
11+
user: string
12+
}

lib/models/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from "./feed"
2+
export * from "./item"
3+
export * from "./store"
4+
export * from "./user"

lib/models/item.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export interface Items {
2+
[key: number]: Item
3+
}
4+
5+
export interface Item {
6+
comments: any[]
7+
comments_count: number
8+
content: string
9+
domain: string
10+
id: number
11+
points: number
12+
time: number | Date | undefined
13+
time_ago: string
14+
title: string
15+
type: string
16+
url: string
17+
user: string
18+
}

lib/models/store.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { User } from "./user"
2+
import { Feed } from "./feed"
3+
import { Items } from "./item"
4+
5+
export interface Dictionary<T> {
6+
[key: string]: T
7+
}
8+
9+
// tslint:disable-next-line: no-empty-interface
10+
export interface StoreStateRoot {}
11+
12+
export interface StoreStateUser {
13+
items: Dictionary<User>
14+
}
15+
16+
export interface StoreStateFeed {
17+
feeds: Dictionary<Feed>
18+
items: Items
19+
selected: number | null
20+
}

lib/models/user.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// export interface Users {
2+
// [key: string]: User
3+
// }
4+
export interface User {
5+
created: string
6+
created_time: number
7+
id: string
8+
karma: number
9+
}

nuxt.config.ts

-4
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ if (enableWorkbox) {
2121
}
2222

2323
const runtimeSwitches: { [key: string]: string | boolean } = {
24-
NODE_ENV,
25-
NUXT_ENV_LOG_LEVEL,
26-
NUXT_ENV_ENABLE_WORKBOX,
27-
NUXT_ENV_API_URL,
2824
apiBaseUrl,
2925
enableWorkbox,
3026
isDev,

pages/_feed/_page.vue

+16-10
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,16 @@ import { Component, Watch, Vue } from "vue-property-decorator"
2020
import { Transition } from "@nuxt/vue-app"
2121
import { Route } from "vue-router"
2222
23-
const Item = () => import(/* webpackChunkName: "components--item" */ '~/components/item.vue')
24-
const ItemListNav = () => import(/* webpackChunkName: "components--item-list-nav" */ '~/components/item-list-nav.vue')
25-
const LazyWrapper = () => import(/* webpackChunkName: "components--lazy-wrapper" */ '~/components/lazy-wrapper.vue')
23+
const Item = () =>
24+
import(/* webpackChunkName: "components--item" */ "~/components/item.vue")
25+
const ItemListNav = () =>
26+
import(
27+
/* webpackChunkName: "components--item-list-nav" */ "~/components/item-list-nav.vue"
28+
)
29+
const LazyWrapper = () =>
30+
import(
31+
/* webpackChunkName: "components--lazy-wrapper" */ "~/components/lazy-wrapper.vue"
32+
)
2633
2734
import { feeds, validFeeds } from "~/common/api"
2835
@@ -41,7 +48,7 @@ import { feeds, validFeeds } from "~/common/api"
4148
return validFeeds.includes(feed)
4249
},
4350
fetch({ store, params: { feed, page = 1 } }) {
44-
return store.dispatch("FETCH_FEED", { feed, page })
51+
return store.dispatch("feed/FETCH_FEED", { feed, page })
4552
},
4653
head(this: Page) {
4754
return {
@@ -57,10 +64,9 @@ export default class Page extends Vue {
5764
| Transition
5865
| ((to: Route, from: Route) => string) = "slide-right"
5966
60-
6167
get feed() {
6268
const params = this.$route.params
63-
const { feed = 'news' } = params
69+
const { feed = "news" } = params
6470
return feed
6571
}
6672
@@ -76,14 +82,14 @@ export default class Page extends Vue {
7682
}
7783
7884
get pageData() {
79-
const feed = this.feed || 'news'
85+
const feed = this.feed || "news"
8086
const page = this.page
81-
const data = this.$store.state.feeds[feed][page]
87+
const data = this.$store.state.feed.feeds[feed][page]
8288
return data
8389
}
8490
8591
get displayedItems() {
86-
const items = this.$store.state.items
92+
const items = this.$store.state.feed.items
8793
return this.pageData.map(id => items[id])
8894
}
8995
@@ -104,7 +110,7 @@ export default class Page extends Vue {
104110
105111
// Prefetch next page
106112
this.$store
107-
.dispatch("FETCH_FEED", {
113+
.dispatch("feed/FETCH_FEED", {
108114
feed: this.feed,
109115
page: this.page + 1,
110116
prefetch: true

pages/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { validFeeds } from "~/common/api"
33

44
@Component({
55
fetch({ redirect }) {
6+
// #DefaultRootRedirect
7+
// If you wondered how it knows how to redirect from / to /news
8+
// ... this is why
69
redirect("/" + validFeeds[0])
710
}
811
})

pages/item/_id.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const LazyWrapper = () => import(/* webpackChunkName: "components--lazy-wrapper"
4545
}
4646
},
4747
fetch({ store, params: { id } }) {
48-
return store.dispatch("FETCH_ITEM", { id })
48+
return store.dispatch("feed/FETCH_ITEM", { id })
4949
}
5050
})
5151
export default class ItemView extends Vue {
@@ -54,7 +54,7 @@ export default class ItemView extends Vue {
5454
}
5555
5656
get item() {
57-
return this.$store.state.items[this.id]
57+
return this.$store.state.feed.items[this.id]
5858
}
5959
}
6060
</script>

pages/user/_id.vue

+6-4
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
<script lang="ts">
3131
import { Component, Vue } from "vue-property-decorator"
3232
33-
const LazyWrapper = () => import(/* webpackChunkName: "components--lazy-wrapper" */ '~/components/lazy-wrapper.vue')
34-
33+
const LazyWrapper = () =>
34+
import(
35+
/* webpackChunkName: "components--lazy-wrapper" */ "~/components/lazy-wrapper.vue"
36+
)
3537
3638
@Component({
3739
components: {
@@ -46,12 +48,12 @@ const LazyWrapper = () => import(/* webpackChunkName: "components--lazy-wrapper"
4648
params: { id }
4749
}
4850
}) {
49-
return store.dispatch("FETCH_USER", { id })
51+
return store.dispatch("user/FETCH_USER", { id })
5052
}
5153
})
5254
export default class UserView extends Vue {
5355
get user() {
54-
return this.$store.state.users[this.$route.params.id]
56+
return this.$store.state.user.items[this.$route.params.id]
5557
}
5658
}
5759
</script>

store/feed.ts

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { ActionTree, MutationTree, GetterTree } from "vuex"
2+
import Vue from "vue"
3+
4+
import { StoreStateRoot, StoreStateFeed, Item } from "~/lib/models"
5+
6+
import { apiBasePath } from "~/store"
7+
import { lazy } from "~/common/utils"
8+
9+
import { validFeeds } from "~/common/api"
10+
11+
export const namespaced = true
12+
13+
export const name = "feed"
14+
15+
export { StoreStateFeed as State }
16+
17+
export const types = {
18+
SELECT: "SELECT",
19+
FEED: "SET_FEED",
20+
ITEM: "SET_ITEM",
21+
ITEMS: "SET_ITEMS"
22+
}
23+
24+
export const state = (): StoreStateFeed => {
25+
const out: StoreStateFeed = {
26+
feeds: {
27+
// [page: number] : [ [id: number] ]
28+
},
29+
items: {
30+
// [id: number]: Item
31+
},
32+
selected: null
33+
}
34+
35+
validFeeds.forEach((feed: string) => {
36+
// @ts-ignore
37+
out.feeds[feed] = {}
38+
})
39+
40+
return out
41+
}
42+
43+
export const actions: ActionTree<StoreStateFeed, StoreStateRoot> = {
44+
FETCH_FEED(actionContext, { feed, page, prefetch = false }) {
45+
// Don't priorotize already fetched feeds
46+
if (
47+
actionContext.state.feeds[feed][page] &&
48+
actionContext.state.feeds[feed][page].length
49+
) {
50+
prefetch = true
51+
}
52+
if (!prefetch) {
53+
if ((this as any).feedCancelSource) {
54+
;(this as any).feedCancelSource.cancel(
55+
"prioritize feed: " + feed + " page: " + page
56+
)
57+
}
58+
}
59+
60+
return lazy(
61+
items => {
62+
const ids = items.map(item => item.id)
63+
actionContext.commit(types.FEED, { feed, ids, page })
64+
actionContext.commit(types.ITEMS, { items })
65+
},
66+
() =>
67+
(this as any).$axios.$get(`${apiBasePath}/${feed}/${page}.json`, {
68+
cancelToken:
69+
(this as any).feedCancelSource &&
70+
(this as any).feedCancelSource.token
71+
}),
72+
(actionContext.state.feeds[feed][page] || []).map(
73+
id => actionContext.state.items[id]
74+
)
75+
)
76+
},
77+
78+
FETCH_ITEM(actionContext, { id }) {
79+
return lazy(
80+
item => {
81+
actionContext.commit(types.SELECT, item.id)
82+
actionContext.commit(types.ITEM, { item })
83+
},
84+
() => (this as any).$axios.$get(`${apiBasePath}/item/${id}.json`),
85+
Object.assign(
86+
{ id, loading: true, comments: [] },
87+
actionContext.state.items[id]
88+
)
89+
)
90+
}
91+
}
92+
93+
export const getters: GetterTree<StoreStateFeed, StoreStateRoot> = {}
94+
95+
export const mutations: MutationTree<StoreStateFeed> = {
96+
[types.FEED]: (mutationStateContext, { feed, ids, page }) => {
97+
Vue.set(mutationStateContext.feeds[feed], page, ids)
98+
},
99+
[types.ITEM]: (mutationStateContext, { item }: { item: Item }) => {
100+
if (item) {
101+
Vue.set(mutationStateContext.items as Item[], item.id, item)
102+
}
103+
},
104+
[types.ITEMS]: (mutationStateContext, { items }: { items: Item[] }) => {
105+
items.forEach(item => {
106+
if (item) {
107+
Vue.set(mutationStateContext.items as Item[], item.id, item)
108+
}
109+
})
110+
},
111+
[types.SELECT](mutationStateContext, id: number | null = null) {
112+
mutationStateContext.selected = id
113+
}
114+
}

0 commit comments

Comments
 (0)