實戰(zhàn)|拼豆制圖37:最近生成記錄如何做到去重、置頂與限長)
用戶每生成或?qū)С鲆粡堊灾茍D頁面都會把它放進“最近生成”。這個列表看似只是數(shù)組頭插但實際要同時滿足四個規(guī)則只記錄用戶生成內(nèi)容、當前項排在最前、同一 ID 不重復、最多保留 10 條。若分散成多次push、filter和slice收藏頁、作品頁和導出流程很容易得到不同順序。拼豆制圖把規(guī)則集中在savePatternRecord()中。本篇逐行驗證現(xiàn)有循環(huán)的邊界說明為什么它最多得到 10 條而不是 11 條并進一步處理異步持久化、重復對象版本和內(nèi)置圖收藏等容易被忽略的數(shù)據(jù)語義。一、先把業(yè)務不變量寫成一句話最近記錄的期望可以表達為next [current] old 中所有 id ! current.id 的項 next 最多取前 10 條 只有 user-generated 開頭的圖案進入最近記錄 當前圖案無論是否自制都可以加入收藏這里“最近記錄”和“收藏”是兩個集合不能因為一次操作同時更新就混成同一規(guī)則。內(nèi)置圖可以收藏但不應出現(xiàn)在“我的生成”用戶生成圖可以既在最近列表又在收藏。二、現(xiàn)有實現(xiàn)如何在一次循環(huán)中完成三件事核心代碼privatesavePatternRecord(pattern:Pattern):void{if(pattern.id.indexOf(user-generated)0){constnextRecords:Pattern[][pattern];for(leti0;ithis.generatedRecords.length;i){if(this.generatedRecords[i].id!pattern.idnextRecords.length10){nextRecords.push(this.generatedRecords[i]);}}this.generatedRecordsnextRecords;}if(!this.isFavorite(pattern.id)){this.favoriteIdsthis.favoriteIds.concat([pattern.id]);}}循環(huán)從[pattern]開始因此當前項天然置頂遇到相同 ID 就跳過因此不會重復只有nextRecords.length 10才追加因此最終長度不超過 10。最后一次從 9 追加到 10后續(xù)項全部跳過。三、為什么條件順序不會產(chǎn)生第 11 條以舊列表已有 10 條且當前項不在其中為例初始 next 長度1 追加舊記錄 1..9長度依次到 10 舊記錄第 10 條next.length 10 為 false不追加 最終10若當前項原本位于舊列表第 4 位初始放入新版 current 舊 1、2、3 依次追加 舊第 4 項同 ID被跳過 繼續(xù)追加后續(xù)項直到總長 10因此它不僅去掉重復還用傳入的新對象替換舊對象。若同一 ID 的標題、統(tǒng)計或格子內(nèi)容發(fā)生更新置頂項會攜帶最新數(shù)據(jù)。四、ID 前綴是來源協(xié)議不應到處復制當前來源判斷為pattern.id.indexOf(user-generated)0同一前綴還用于收藏列表中決定是否加入當前生成圖。字符串協(xié)議簡單但若未來 ID 改成 UUID這些判斷會同時失效。更穩(wěn)妥的模型是顯式來源字段typePatternOriginbuilt-in|user;interfacePattern{id:string;origin:PatternOrigin;// ...}保存邏輯變?yōu)閜attern.origin user。業(yè)務 ID 只負責身份來源字段負責分類二者不再互相推斷。若暫時不能改模型也應抽出isUserPattern(pattern)避免多個頁面各寫一遍前綴判斷。五、收藏去重與最近記錄去重要共享同一身份收藏只保存 IDprivateisFavorite(id:string):boolean{returnthis.favoriteIds.indexOf(id)0;}if(!this.isFavorite(pattern.id)){this.favoriteIdsthis.favoriteIds.concat([pattern.id]);}最近記錄保存完整Pattern但兩者都用pattern.id判斷身份。這樣作品內(nèi)容更新不會制造第二個收藏項收藏頁仍可通過 ID 找到最新版記錄。風險在于 ID 由Date.now()生成。如果極端情況下同一毫秒生成兩個不同作品它們會被視為同一項第二個覆蓋第一個。頁面當前生成流程串行概率很低但數(shù)據(jù)層最好使用更強的唯一 ID 策略例如時間戳加進程內(nèi)遞增序號。privatestaticsequence:number0;functionnextUserId():string{returnuser-generated-${Date.now()}-${IdFactory.sequence};}六、把數(shù)組規(guī)則抽成純函數(shù)更容易回歸頁面方法同時修改兩份狀態(tài)不方便單獨驗證??梢蕴崛unctionupsertRecent(current:Pattern,oldRecords:Pattern[],limit:number):Pattern[]{if(limit0)return[];constnext:Pattern[][current];for(leti0;ioldRecords.lengthnext.lengthlimit;i){if(oldRecords[i].id!current.id){next.push(oldRecords[i]);}}returnnext;}頁面只負責來源判斷和狀態(tài)提交。純函數(shù)參數(shù)顯式包含limit可以驗證 0、1、10 等邊界也不會把“10”散落到界面代碼。這里不建議先filter再slice再展開雖然寫法短但會創(chuàng)建多個中間數(shù)組。當前最多 10 條差異不大顯式單循環(huán)更容易看出何時停止。七、列表限長不等于存儲限長當前generatedRecords是頁面State應用重啟后會恢復為空。未來接入持久化時要決定存什么摘要id、標題、尺寸、色數(shù)、創(chuàng)建時間、文件路徑 完整圖4900 個 chartCells 1225 個 previewCells 色卡把 10 張完整圖都塞進一個 Preferences JSON 會快速膨脹也會讓每次置頂都重寫大量數(shù)據(jù)。更合理的是 Preferences 保存最近 ID 或摘要列表完整圖紙按 ID 寫入獨立 JSON 文件刪除第 11 條摘要時再根據(jù)收藏和引用關系決定是否刪除文件。限長規(guī)則屬于“最近視圖”不一定意味著用戶作品只能存在 10 張。若產(chǎn)品要長期作品庫最近列表與作品倉庫必須分離。八、異步持久化要防止舊寫覆蓋新寫數(shù)組在內(nèi)存中同步更新時順序確定。接入異步存儲后連續(xù)保存 A、B 可能出現(xiàn)B 先寫完A 后寫完最終磁盤回到舊狀態(tài)。可在服務層串行寫入privatewriteChain:PromisevoidPromise.resolve();enqueueSave(snapshot:PatternSummary[]):Promisevoid{this.writeChainthis.writeChain.then(async(){awaitthis.repository.saveRecent(snapshot);});returnthis.writeChain;}或者每次寫入帶updatedAt/版本號倉庫拒絕舊版本覆蓋新版本。頁面不要一邊先更新數(shù)組一邊由多個點擊處理器各自讀取舊快照再保存。導出和生成都調(diào)用savePatternRecord()集中入口已經(jīng)為后續(xù)串行化創(chuàng)造了條件。九、收藏列表合并還要防跨來源重復favoritePatterns()先遍歷用戶記錄再檢查當前生成圖最后遍歷內(nèi)置圖。當前生成圖只有在結(jié)果中不存在同 ID 時才追加if(this.generatedPattern.id.indexOf(user-generated)0this.isFavorite(this.generatedPattern.id)!this.hasPatternInList(result,this.generatedPattern.id)){result.push(this.generatedPattern);}這一步說明“去重”不僅發(fā)生在保存時也發(fā)生在不同數(shù)據(jù)源合并時。若未來從磁盤加載記錄應繼續(xù)保證用戶記錄、當前編輯對象和內(nèi)置倉庫共享同一 ID 命名空間或使用{origin,id}復合身份。不要用對象引用去重。持久化反序列化后同一業(yè)務圖案會變成新對象只有穩(wěn)定 ID 才能跨生命周期識別。十、用小數(shù)組覆蓋所有邊界建議固定樣本// 空舊列表 A - [A]// [A舊版] A新版 - [A新版]// [B,C] A - [A,B,C]// 10 條無 A A - A 前 9 條// 10 條且 A 在末尾 A新版 - A新版 前 9 個非 A// limit1 - [A]// limit0 - []頁面驗收再驗證生成同一對象后多次導出不會重復生成 11 個不同對象后只顯示最近 10 個收藏的內(nèi)置圖不會混入最近生成刪除收藏不會改變最近順序。若出現(xiàn) 11 條檢查限長條件是否放在 push 之后若當前項重復核對 ID 是否有空格或類型差異若新內(nèi)容沒有替換舊內(nèi)容確認初始數(shù)組放入的是傳入對象而不是舊列表對象。十一、結(jié)語最近列表并不是簡單的unshift()。一條可靠的更新需要同時守住來源、身份、順序和容量。拼豆制圖用“新對象先入、循環(huán)跳過同 ID、長度小于 10 才追加”的單次遍歷實現(xiàn)了最小閉環(huán)收藏則復用相同 ID 保持跨集合一致。下一步演進的重點不是把循環(huán)改得更短而是把來源從 ID 前綴升級為字段、把數(shù)組規(guī)則提成純函數(shù)、把最近摘要與完整作品分開持久化并讓異步寫入按順序提交。這樣“最近 10 條”才不會意外變成“作品最多只能有 10 條”。