在做 PawPal 的醫院功能時,我一直覺得,只有地圖和醫院基本資料還不太夠。
既然我們想做的是一站式的寵物照護網站,那我會希望使用者找醫院的時候,不用看完位置和基本資料之後,又跑到其他網站查評價。
所以我後來有一個想法:
要做的話,就把資訊盡量做完整一點。
除了醫院位置和基本資料之外,也加入評論和星等,讓使用者可以直接在 PawPal 裡看其他人的評價,也能留下自己的使用經驗。
這個功能後來也是由我負責實作。
一開始看起來其實很單純:
五顆星
+
一個留言框
+
一顆送出按鈕
但真的開始做之後,我才發現,畫面上看起來這麼簡單的一個功能,背後其實連著一整條資料流程。
我印象很深,第一次把五星評分的互動做出來時,我點到第 4 顆星,前面的星星沒有像我預期的一起亮。
正常來說,選擇 4 星應該要看到:
★ ★ ★ ★ ☆
這個看起來很小的問題,反而讓我開始比較清楚地理解:
五星評分不是五個彼此獨立的按鈕。
使用者真正選擇的,其實是一個:
1 ~ 5
之間的評分數值。
畫面上的五顆星,只是把這個數字用比較直覺的方式呈現出來。
例如:
rating = 4
代表的就是:
第 1 顆 → 亮
第 2 顆 → 亮
第 3 顆 → 亮
第 4 顆 → 亮
第 5 顆 → 不亮
所以真正的判斷不是:
我剛才點的是哪一顆?
而比較像:
這一顆的位置,有沒有包含在目前選擇的星數裡?
在 PawPal 的實作裡,會先記住使用者目前選擇的評分。
例如:
selectedRating = 4
畫面再依照目前的 rating,決定每一顆星是不是要呈現選取狀態。
下面是簡化概念:
const activeRating = hoverRating || selectedRating
const isActive = activeRating >= star
以上為簡化概念,用來說明五星評分的主要判斷方式,不是 PawPal Repository 的完整原始程式碼。
這裡還多了一個 hoverRating。
當使用者把滑鼠移到第 5 顆星時,可以先看到五顆星亮起來;如果沒有真的點下去,滑鼠移開後又會回到原本選擇的星數。
以前看到這種評分元件時,我只會看到:
「五顆可以點的星星。」
自己做過一次之後,我才比較明確地理解:
真正被程式保存的是 rating,星星只是把 rating 顯示出來。
五星互動做好之後,接下來才是這個功能真正比較完整的部分。
使用者最後送出去的資料主要是:
rating
+
comment
例如:
4 星
+
「醫生很有耐心」
這裡只是簡單示意一筆評論可能包含的內容。
按下送出之後,事情並不是把這段文字加到畫面上就結束了。
整個流程其實比較像:
使用者選擇星等
↓
輸入評論
↓
送出 rating + comment
↓
POST 評論 API
↓
後端建立評論
↓
重新計算平均星等
↓
重新計算評論數
↓
API 回傳最新統計
↓
前端更新醫院資料
↓
重新取得評論列表
↓
畫面顯示最新結果
在開始實作以前,我本來就知道:
多了一則評論之後,星等和評論數量也應該一起更新。
但知道這條流程應該怎麼運作,和真的自己把它接起來,感覺還是不太一樣。
做到這裡時,我才比較有:
「原來一個評論功能真的會牽到這麼多地方。」
的感覺。
先用一個簡化概念來看。
假設現在一間醫院有三則評論:
5 星
4 星
3 星
平均星等就是:
(5 + 4 + 3) ÷ 3
= 4
所以:
平均星等:4
評論數量:3
以上數字為簡化概念,只是用來說明平均星等的計算方式,不代表 PawPal 的真實評論資料。
PawPal 並不是讓前端把全部評論抓回來,再自己把 rating 加總後計算平均。
這件事情是交給後端與資料庫處理。
主要會用到:
AVG(rating)
COUNT(*)
可以先簡單理解成:
AVG(rating)
→ 算出這間醫院目前所有評分的平均
COUNT(*)
→ 算出目前有幾則評論
實際上平均星等還會處理到小數一位。
所以當新的評論成功建立後,後端除了建立那一筆評論,也會重新取得最新的:
average_rating
review_count
再一起回傳給前端。
對我來說,這裡比較有意思的是:
前端不用自己負責重新計算平均值,而是接收後端整理好的最新統計結果,再把它更新到畫面上。
如果事情只做到資料庫新增成功,其實還不夠。
假設使用者原本看到:
4.2 星
10 則評論
現在又新增了一則評論。
資料庫可能已經變成最新狀態了,但如果畫面還顯示:
4.2 星
10 則評論
使用者看到的還是舊資料。
所以評論新增成功之後,前端還需要把最新的結果同步回畫面。
在 PawPal 裡,POST 評論成功後會取得最新的 summary,再用這份資料更新目前醫院的:
rating
reviewCount
而這裡還有一個我實作時需要注意的地方。
醫院不只會出現在列表,也會出現在地圖相關的資料裡。
所以不能只更新其中一邊。
實際 Store 裡會同步處理:
hospitals
mapHospitals
這樣同一間醫院的評分資訊,才不會一邊已經更新,另一邊還停在舊資料。
平均星等和評論數更新之後,還有一個很直覺的問題:
我剛剛送出去的評論,要不要馬上看得到?
答案當然是要。
所以 PawPal 在評論送出成功後,還會把評論畫面切回列表,重新取得一次最新的評論資料。
流程可以整理成:
POST 評論成功
↓
取得最新 summary
↓
更新平均星等與評論數
↓
切回評論列表
↓
重新 GET 最新評論
↓
更新 reviewList
↓
顯示最新評論
也就是使用者送出一則評論之後,看到的不只是:
「送出成功。」
而是真的能看到:
自己的評論出現在列表
平均星等更新
評論數量更新
這樣整個操作才算真正接起來。
做到這裡之後,我才比較有感地看到一件事情。
使用者表面上只是做了一個動作:
送出評論
但背後實際發生的是:
rating + comment
↓
API
↓
新增評論資料
↓
重新計算平均星等
↓
重新計算評論數
↓
回傳最新資料
↓
更新 Store
↓
重新取得評論列表
↓
更新畫面
以前剛開始學前端的時候,我很容易把功能想成:
「畫面做出來,而且按鈕可以按。」
但做完這次評論功能後,我開始比較會把功能想成一整條流程:
使用者做了什麼?
↓
前端送出什麼?
↓
後端收到什麼?
↓
資料庫怎麼改變?
↓
後端回傳什麼?
↓
前端最後有哪些地方需要更新?
這也是這次評論功能讓我最有感的地方。
不是哪一段程式碼特別厲害。
而是我第一次比較完整地看到:
一個看起來很普通的功能,是怎麼從前端一路跑到後端、資料庫,再回到使用者看到的畫面。
回頭看一開始為什麼想加入評論和星等,其實理由還是很單純。
我希望使用者來 PawPal 找醫院時,可以直接完成:
找醫院
↓
看醫院基本資料
↓
看星等
↓
閱讀評論
↓
留下自己的評價
而不是查完一家醫院之後,還要再另外開其他網站找評價。
當評論功能真的完成之後,我確實覺得醫院頁面更接近我一開始想要的樣子。
在同一個網站裡,就能把需要的資訊盡量一次看完。
對使用者來說更方便。
而對我來說,也第一次真的把一個看似簡單的功能,從畫面一路接成一條完整的前後端資料流程。
這次做醫院評論與星等,我最有感的有幾件事情:
1~5 的 rating,五顆星只是把這個數值視覺化。
AVG()、COUNT() 幫忙完成評分統計,再把結果交給前端。如果是以前的我,看到五星評論,看到的可能只是:
五顆星
+
一個留言框
但做完之後,我看到的已經變成:
一條從畫面走到資料庫,再回到畫面的完整流程。
這次我做的是醫院評論。
看起來只是五星和留言,但真正做下去之後,才發現背後牽著前端、後端、資料庫和畫面同步。
而下一個功能,畫面上看起來可能更簡單。
只是一顆「收藏」按鈕。
但當它真的需要記住「哪一個使用者收藏了哪一間醫院」時,就不可能只改一個按鈕的樣式。
下一篇:
Day 18|一顆收藏按鈕,背後其實不只有前端