該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。
在前面的章節,我們講了許多 vue3 directive 的使用方式還有實際應用,接下來我們要把我們前面所講的 Skeleton 還有我們的  directive 給結合在一起,做一個組合的實戰應用。
You should use Skeleton : https://ithelp.ithome.com.tw/articles/10260925
這就是我們要處理,透過 directive + skeleton 完成以下的實際案例。

首先我先把資料給拿到手,我透過 axios來拿資料
<script>
import { ref, onMounted } from "vue";
import axios from "axios";
export default {
  setup() {
    const postCard = ref([]);
    const isLoad = ref(true);
    onMounted(() => {
      axios
        .get("https://60bd9841ace4d50017aab3ec.mockapi.io/api/post_card")
        .then((res) => {
          postCard.value = res.data;
          isLoad.value = false;
        });
    });
    return {
      postCard,
      isLoad,
    };
  },
};
</script>
這邊會看到我宣告了兩筆資料,postCard是我拿來存放 API 回來的資料,isLoad是讓我判斷 API 的資料回來沒有,接下來我們來看一下我們的 html 的部分
<template>
  <div class="card" v-for="card in postCard" :key="card.id">
    <header>
      <img v-src="card.avatar" class="avatar load" />
      <div>
        <h1 :class="{ load: isLoad }">{{ isLoad ? "" : card.name }}</h1>
        <p :class="{ load: isLoad }" v-timeformat="card.post_date"></p>
      </div>
    </header>
    <p :class="['content', { load: isLoad }]">
      {{ isLoad ? "" : card.content }}
    </p>
    <img class="post_photo load" v-src="card.photo" alt="" />
  </div>
</template>
首先你會看到圖片的地方我使用了 v-src的這個模板語法,這就是我們在上一個章節所提到的用 directive包起來的方法。
app.directive("src", (el, binding) => {
  if (binding.value) {
    const img = new Image();
    img.src = binding.value;
    img.onload = () => {
      el.src = binding.value;
    };
  }
});
然後所有跟文字有關的地方都加上了一個 load 的class,這個 load 的class 就是為了讓我們可以在資料回來以前,讓我們的文字區塊出現灰色色塊所準備的,然後再裡面的內文我用了 isLoad來判斷要不要讓文字顯示出來。
h1.load, p.load {
  display: block;
  width: 300px;
  height: 14px;
  background-color: #ededed;
  color: rgba(#fff, 0);
}
@keyframes loading {
  to {
    background-position-x: -20%;
  }
}
.load {
    background: linear-gradient(
        100deg,
        rgba(256, 256, 256, 0) 30%,
        rgba(256, 256, 256, 0.5) 50%,
        rgba(256, 256, 256, 0) 30%
    )
        #ededed;
    background-size: 200% 100%;
    background-position-x: 180%;
    animation: 2s loading ease-in-out infinite;
}
透過這樣的方式我就可以很簡單的處理圖片的 load以及 Skeleton所需要顯示的那些區塊
import dayjs from "dayjs";
// 其他省略...
app.directive("timeformat", {
  mounted(el, binding) {
    const time = dayjs(binding.value).format("YYYY年MM月DD日");
    el.innerText = time;
  },
});
最後時間的部分處理就用了 v-timeformat這個方法就大功告成了。

以下就是完整的程式碼的部分
// index.js
import { createApp } from "vue";
import App from "./App.vue";
import dayjs from "dayjs";
const app = createApp(App);
app.directive("src", (el, binding) => {
  if (binding.value) {
    const img = new Image();
    img.src = binding.value;
    img.onload = () => {
      el.src = binding.value;
    };
  }
});
app.directive("timeformat", {
  mounted(el, binding) {
    const time = dayjs(binding.value).format("YYYY年MM月DD日");
    el.innerText = time;
  },
});
app.mount("#app");
// App.vue
<script>
import { ref, onMounted } from "vue";
import axios from "axios";
export default {
  setup() {
    const postCard = ref([]);
    const isLoad = ref(true);
    onMounted(() => {
      axios
        .get("https://60bd9841ace4d50017aab3ec.mockapi.io/api/post_card")
        .then((res) => {
          postCard.value = res.data;
          isLoad.value = false;
        });
    });
    return {
      postCard,
      isLoad,
    };
  },
};
</script>
<template>
  <div class="card" v-for="card in postCard" :key="card.id">
    <header>
      <img v-src="card.avatar" class="avatar load" />
      <div>
        <h1 :class="{ load: isLoad }">{{ isLoad ? "" : card.name }}</h1>
        <p :class="{ load: isLoad }" v-timeformat="card.post_date"></p>
      </div>
    </header>
    <p :class="['content', { load: isLoad }]">
      {{ isLoad ? "" : card.content }}
    </p>
    <img class="post_photo load" v-src="card.photo" alt="" />
  </div>
</template>
<style lang="scss">
.card {
  width: 400px;
  height: auto;
  background-color: #fff;
  box-shadow: 0 0 5px rgba(#000, 0.3);
  margin: 0 auto 30px auto;
  header {
    display: flex;
    padding: 10px 10px 0px 10px;
    margin: 0 0 5px 0;
    color: #666;
    h1 {
      font-size: 15px;
    }
    p {
      font-size: 13px;
    }
  }
  p.content {
    font-size: 14px;
    padding: 0px 10px 10px 10px;
    color: #666;
    &.load {
      display: block;
      width: 200px;
      height: 14px;
      background-color: #ededed;
      color: rgba(#fff, 0);
      margin: 10px 10px 10px 10px;
    }
  }
}
img.avatar {
  width: 40px;
  height: 40px;
  border-radius: 50px;
  margin-right: 10px;
  background-color: #ededed;
}
img.post_photo {
  width: 100%;
  height: 300px;
  background-color: #ededed;
}
h1.load {
  display: block;
  width: 300px;
  height: 14px;
  background-color: #ededed;
  margin-bottom: 10px;
  color: rgba(#fff, 0);
}
p.load {
  display: block;
  width: 100px;
  height: 14px;
  background-color: #ededed;
  color: rgba(#fff, 0);
}
@keyframes loading {
  to {
    background-position-x: -20%;
  }
}
.load {
  background: linear-gradient(
      100deg,
      rgba(256, 256, 256, 0) 30%,
      rgba(256, 256, 256, 0.5) 50%,
      rgba(256, 256, 256, 0) 30%
    )
    #ededed;
  background-size: 200% 100%;
  background-position-x: 180%;
  animation: 2s loading ease-in-out infinite;
}
</style>
我們今天特別把之前所講過的東西給組合在一起 (Skeleton 跟  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