該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。
你是否有在使用網頁的時候填了很多資料,但是一個不小心重整網頁就導致辛苦填寫的資料都消失不見了,今天我們就要來針對輸入表單的使用者體驗來進行優化的部分。
我們希望可以達到像是下面的效果。

你會發現在我輸入了內容之後重新整理網頁,表單裡面的內容都還有留下,所以今天不管我怎麼輸入內容,他都會暫存在你的網頁之中,今天我們就要來看看再 Vue 裡面是如何實作的。
暫存的部分你可以選用 cookie 或是 localStorage,在這邊我選用 localStorage 來暫存。
純 JavaSrcipt 這邊我就提供一個思路,不全部寫出來了
// 1. 抓取 input 表單實體
const el = document.getElementById("#name");
// 3. localStorage 寫入暫存資料
const handSave = (e) => {
  localStorage.setItem('form-name', e.target.value);
}
// 2. 監聽 input 跟 change 事件,不監聽 keyUp 是因為手機鍵盤無法觸發
el.addEventListener("input", handSave);
el.addEventListener("change", handSave);
總之就是去監控表單是否有被填入內容,如果觸發了 input 或是 change,就把輸入的內容給寫入 localStorage。
首先針對表單一個個去綁定事件會有點太重工了,而且要處理也不是這麼方便,所以這時候我就有一個想法,我們不是透過 v-model 可以去同步表單的資料嗎?那如果再有一個跟 v-model 相似的語法,但是具有暫存的功能的話,那不是方便很多,所以今天就來自己寫一個有暫存功能的 v-model。
我希望這樣,有一個 v-model-save 的語法,它的功能跟 v-model 一模一樣,然後還增加了儲存 localStorage的功能,我們來看一下怎麼做。
const name = ref("");
<input
   class="name"
   type="text"
   placeholder="輸入使用者名稱"
   v-model-save="name"
/>
首先要做這樣一個 v-model-save 語法就需要我們之前所教的 vue 的 directive 這個功能,首先在 directive裡面我們要去監聽  input 跟 change事件。
app.directive('model-save', {
  mounted(el, binding) {
    el.handSave = (e) => {}
    el.addEventListener("input", el.handSave);
    el.addEventListener("change", el.handSave);
  },
  unmounted(el) {
    el.removeEventListener("input", el.handSave);
    el.removeEventListener("change", el.handSave);
  },
})
在這邊你會看到我在 mounted 傳進來的 el塞入一個 handSave的 function,而不是我們一般去定義新的function 那樣,為什麼? 因為我需要在 unmounted 去 removeEventListener ,但是我又不想把這個 function 丟到塞到 vue 或是 window 裡面,所以這邊我選擇塞到  el裡面。
接下來我們要來定義一下儲存的部分,這邊有要定義三個儲存的 function
// 1. 寫入 Input DOM 的 value
const setInputValue = (value) => el.value = value; 
// 2. 同步 Vue 中宣告的的變數(資料)
const setSyncDate = (value) => binding.instance[el.className] = value;
// 3. 塞入 localStorage 暫存資料
const setLocalStorage = (value) => localStorage.setItem(`form-${el.className}`, value);
這邊有個重點,就是你的 class name !
在這邊我們是透過你的  class name 還有你宣告變數的名稱,讓它們保持一致來做比對,讓它可以更簡單的去同步表單上面以及變數上面的資料。
你會看到我使用了 binding.instance,這個 instance 裡面就是你透過 directive 傳入的 proxy 物件,所以只去修改裡面的 proxy 物件參數,自然上層傳入的資料就會跟者被修改了。
app.directive('model-save', {
  mounted(el, binding) {
    
    const setInputValue = (value) => el.value = value; 
    const setSyncDate = (value) => binding.instance[el.className] = value;
    const setLocalStorage = (value) => localStorage.setItem(`form-${el.className}`, value);
    
    // 檢查目前是 localStorage 有沒有暫存的資料
    if(!localStorage[`form-${el.className}`]){
      setLocalStorage(binding.value);
    }
    
    setInputValue(localStorage[`form-${el.className}`]);
    setSyncDate(el.value);
    
    el.handSave = (e) => {
      setLocalStorage(e.target.value);
      setSyncDate(e.target.value);
    }
    
    el.addEventListener("input", el.handSave);
    el.addEventListener("change", el.handSave);
  },
  unmounted(el) {
    el.removeEventListener("input", el.handSave);
    el.removeEventListener("change", el.handSave);
  },
})
在這邊我會特別在 localStorage 儲存的 key前面加上一個自己定義的字串,去做區分,畢竟整個網站還會有其他地方用到localStorage,避免  key的衝突,所以我前面就會加一個前綴字做區分。
接下來你就可以把你要的資料都定義好
setup(){
  const name = ref("");
  const password = ref("");
  const submitLogin = () => {
    console.log({name, password})
    for (var key in localStorage) {
      if (/^form-(.+)/.test(key)) {
        localStorage.removeItem(key)
      }
    }
    alert("登入成功");
  }
  
  return {
    name,
    password,
    submitLogin
  }
}
這邊你會看到我在 submitLogin 的 function 中去清掉了跟表單有關的 localStorage,因為畢竟表單送出了,自然暫存也就不需要了,那接下來我們就可以在 input 上面使用 v-model-save。
<div class="center">
  <div class="input-box">
    <p>NAME</p>
    <input
      class="name"
      type="text"
      placeholder="輸入使用者名稱"
      v-model-save="name"
    />
  </div>
  <div class="input-box mb-20">
    <p>PASSWORD</p>
    <input
      class="password"
      type="password"
      placeholder="輸入密碼"
      v-model-save="password"
    />
  </div>
  <button class="btn" @click="submitLogin">登入</button>
</div>
codepen 範例:https://codepen.io/MikeCheng1208/pen/vYZQeVq
在 Safari 上 cookie 跟 localStorage 的使用最多就是 7 天,所以如果你的暫存功能是會需要一個很長時間的話,可能會需要改變其他做法,相關內容可以參考一下文件。
Intelligent Tracking Prevention 2.1 : https://webkit.org/blog/8613/intelligent-tracking-prevention-2-1/
Apple adds a 7-Day Cap on All Script-Writable Storage : https://support.didomi.io/apple-adds-a-7-day-cap-on-all-script-writable-storage
今天介紹了一個很實際可以防呆且增加使用者體驗的方式,透過 directive 能玩的花樣其實還蠻多的,就看我們自己在實作上面要依照何種情境來做選擇,如果你有其他 directive的使用方式也可以在下面留言分享給大家知道。

Ps. 購買的時候請登入或註冊該平台的會員,然後再使用下面連結進入網站點擊「立即購課」,這樣才可以讓我獲得更多的課程分潤,還可以幫助我完成更多豐富的內容給各位。
我有開設了一堂專門針對Vue3從零開始教學的課程,如果你覺得不錯的話,可以購買我課程來學習
https://hiskio.com/bundles/9WwPNYRpz?s=tc
那如果對於JS基礎不熟的朋友,我也有開設JS的入門課程,可以參考這個課程
https://hiskio.com/bundles/b9Rovqy7z?s=tc
Mike 的 Youtube 頻道
Mike的medium
MIke 的官方 line 帳號,好友搜尋 @mike_cheng