本篇大綱:
- 簡介
- 前置作業
- Vuex 設定
- CRUD 功能
簡介
這是一個 Nuxt 搭配 Vuex 的 CRUD 栗子。
Source code
Live Demo
前置作業
新增 Nuxt 專案
1
| npx create-nuxt-app crud-app
|
在輸入指令後會提供幾個項目的選擇,包括服務器端框架、UI 框架、測試框架、Nuxt 模式等。這裡的 UI 框架選擇的是 Vuetify.js.
準備資料
這裡使用了 JSONPlaceholder 提供的 API. 裡面提供的例子使用 fetch
, 所以在這個例子裡,就可以直接 抄起來 使用. 要注意的是:這個 API 在發送新增 / 更新 / 刪除的 request 時並不會真的更改原本的資料。
設定 UI 部分
這裡需要的 UI 只有 form, card, button, 直接套用 vuetify 裡的模板。
Vuex 設定
Vuex 是類似 Redux 的 state 集中管理器。這裡的 state 比較單純,只有我們設定的 posts
一項,用來儲存每筆的資料。
這裡會用到 Vuex 的功能包含了: state, mutation, action. 因為 Nuxt 已經幫忙處理 Vuex 後面的東西,所以只要按照 文件 上的方式使用就可以了。
Vuex 的檔案會放在 store
底下。
State
這裡創造了一個 posts
為空 array, 用來儲存資料。
1 2 3 4 5 6
|
export const state = () => ({ posts: [] })
|
Action
要執行的動作會在 action 裡處理. Action 並不會直接改變 state, 而是會提交 mutation,由mutation 來改變 state.
Action 會通過 store.dispatch
被觸發,會在 nuxt 的 methods 中 dispatch。
1 2 3 4 5 6 7 8 9
|
methods: { async createPost () { await this.$store.dispatch('createPost', { title: this.title, body: this.body }) this.title = '' this.body = '' }, }
|
dispatch 的第二個參數是我們想要傳到 action 的參數。如果要傳多個參數就要將他們包成一個 object 往下傳,因為 action 只能接受 1 個參數。
action 在接收參數時候,要注意參數位置。第一個參數:內建的參數,如 commit / state;第二個參數:自己要往下傳的參數。
1 2 3 4 5 6 7 8 9 10 11
|
export const actions = { async createPost ({ commit , title, body }) {
· · · } }
|
mutation
Mutation 是最為底層的操作,比如說「把某個變數改成 true」、「把這筆資料塞到 array 裡」的操作。因此在命名上也要以改 function 做了什麼為標準,才能做到最大程度的重複使用和避免混淆。
CRUD 功能
CRUD 的功能包括增加 (Create), 讀取 (Read), 更新 (Update) 和 刪除 (Delete). JSONplaceholder 提供了這四個方法但因為發送的請求不會改變原本的資料,所以其實都是在操作自己的 array.
Read 讀取
在執行其他 function 前,會先將資料抓取下來,所以這個步驟會放在 Nuxt mouted()
裡。
1 2 3 4 5 6
|
async mounted () { await this.$store.dispatch('initPost') },
|
Action 中的 init post 會發送請求,data 會是 100 筆的資料。
- 第 4 行 :
commit
是 action 自帶的參數
- 第 7 行 : 每筆資料都需要
show
這個變數,用來控制是一般狀態 / 可編輯狀態
1 2 3 4 5 6 7 8 9 10 11 12
|
export const actions = { async initPost ({ commit }) { const res = await fetch('https://jsonplaceholder.typicode.com/posts') const data = await res.json() data.forEach((item) => { item.show = true })
commit('setPosts', data) }, ... }
|
1 2 3 4 5 6 7 8 9 10
|
export const mutations = { setPosts (state, data) { state.posts = data }, ... }
|
Create 增加
提供用戶輸入 title 和 body 兩個部分。需要把用戶輸入的 title 和 body 都往下傳。
1 2 3 4 5 6 7 8 9 10
|
methods: { async createPost () { await this.$store.dispatch('createPost', { title: this.title, body: this.body }) this.title = '' this.body = '' }, ... }
|
每則 comment 的 id 使用亂數產生。這裡發送的 request 並不會直接更新到原本的資料,只會把這筆資料加到 posts
裡。 與前面的相同,這裡需要為新增的資料加上 show
變數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
export const actions = { ... async createPost ({ commit }, { title, body }) { const res = await fetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', body: JSON.stringify({ title, body, userId: 1 }), headers: { 'Content-type': 'application/json; charset=UTF-8' } })
const post = await res.json() post.show = true post.id = Math.floor(Math.random() * 10000) commit('pushPost', post) }, ... }
|
這裡的動作就是將新增加的這筆資料加到 posts 裡。這裡的 post 存在 state 裡,所以要使用 state.posts
才能讀取。
1 2 3 4 5 6 7 8 9 10 11
|
export const mutations = { ... pushPost (state, post) { state.posts.unshift(post) }, ... }
|
Update 更新
更新資料一樣使用 JSONplaceholder 提供的 API. 更新資料的邏輯是設定兩個區塊( 一般顯示/ 顯示 form 欄位 ),使用 show
當做變數來操控:當 show
為 true 時,就顯示正常的狀況、反之則顯示 form 讓用戶更新資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
methods: { ... async updatePost (post, editedTitle, editedBody) { await this.$store.dispatch('updatePost', { post, editedTitle: this.editedTitle, editedBody: this.editedBody }) },
editBtn (post) { this.$store.dispatch('editBtn', post) this.editedTitle = post.title this.editedBody = post.body }, ... }
|
因為這裡不會改變原本的資料,因此這裡的 URL 後面的 id 可以不用改。再者,因為在創造新資料的時候,創造新的 id 是使用亂數產生,這些亂數並不存在在原本的資料中,因此用新 id 去發送請求會出現 error.
在 form 出現的時候,submit
和 cancel
button 會一同出現,edit
和 delete
就會隱藏起來。(會在 template 裡處理).
edit
button 和 cancel
button 都是使用 for loop 的方式去找導被點擊的卡片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
export const actions = { ... async updatePost ({ commit }, { post, editedTitle, editedBody }) { const res = await fetch('https://jsonplaceholder.typicode.com/posts/1', { method: 'PUT', body: JSON.stringify({ id: 1, title: post.title, body: post.body, userId: 1 }), headers: { 'Content-type': 'application/json; charset=UTF-8' } }) await res.json() commit('setPost', { id: post.id, title: editedTitle, body: editedBody }) commit('setShow', { id: post.id, show: true }) },
editBtn ({ commit, state }, post) { state.posts.forEach((item) => { if (item.id !== post.id) { commit('setShow', { id: item.id, show: true }) } })
commit('setShow', { id: post.id, show: !post.show }) },
cancelBtn ({ commit, state }, post) { const postIndex = state.posts.findIndex(item => item.id === post.id)
state.posts.forEach((item) => { if (item.id === postIndex) { commit('setShow', { id: item.id, show: true }) } })
commit('setShow', { id: post.id, show: true }) }, ... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
export const mutations = { ...
setPost (state, { id, title, body }) { const postIndex = state.posts.findIndex(item => item.id === id)
this._vm.$set(state.posts, postIndex, { title, body, id, show: true }) },
setShow (state, { id, show }) { const postIndex = state.posts.findIndex(item => item.id === id) state.posts[postIndex].show = show } ... }
|
Delete 刪除
如前面所說的,發出去的 request 並不會真的改變原本的資料。因此這裡刪除的處理方式就是將被點到 comment 從 posts array 中移除。
1 2 3 4 5 6 7
| methods: { ... deletePost (post) { this.$store.dispatch('deletePost', post) } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
|
export const actions = { ...
deletePost ({ commit }, post) { fetch(`https://jsonplaceholder.typicode.com/posts/${post.id}`, { method: 'DELETE' })
commit('removePost', post.id) } }
|
1 2 3 4 5 6 7 8 9 10 11
|
export const mutations = { ... removePost (state, id) { const removeItem = state.posts.findIndex(item => item.id === id) state.posts.splice(removeItem, 1) }, ... } }
|
其他小筆記
全局引入 CSS
這個例子其實沒有用到很多的樣式,但還是有一個 CSS 檔案需要引入。這裡選擇了最方便的方式:在 nuxt.config.js
中全局引入。
因為還沒有深入研究全局引入和其他方式引入有什麼優劣,因此這裡就是用我認為最方便的方法。
methods 裡的 function
methods 是一個 dictionary, 內容看起來不是常見的 key: value pair, 他其實是簡寫。原本長得是這個樣子,但如果後面接的是匿名函式就可以簡寫成 createPost() {...}
.
1 2 3 4 5 6 7
|
methods: { createPost: () => { ... } }
|
部署到 github page
這裡參考了這篇文章:28. Nuxt 靜態頁部署 ,就沒有在另外寫筆記啦。
小結
這次機緣巧合下需要接觸 Vue 和 Nuxt 所做出來的小栗子。當中有遇到一些參數傳遞的問題,以及一些元件沒有及時更新的問題,後者的問題在使用 Vuex 之後就解決了。其他遇到的坑或是需要留意的部分都記錄下來了。
心得就是不會就要問,卡太久就要問,問了就會記下來這樣。因為這是第一次接觸 Nuxt, 以前也沒有寫過 Vue, 弄起來還是有些吃力,背後的原理還是概念會再慢慢補起來,這裡就當做一個簡單記錄。