该系列是为了让看过Vue官方文件或学过Vue但是却不知道怎麽下手去重构现在有的网站而去规画的系列文章,在这边整理了许多我自己使用Vue重构很多网站的经验分享给读者们。
我们很常会再 vue 的 template 中使用 v-if
或 v-show
等语法来处理我们的 ui ,但其实 vue 有提供我们一个 API 叫做directive
让我们可以自己定义模板语法,我们就会使用 directive
来把一些对 ui 的操作给包起来,方便我们使用。
我们可以看一下官方所提供的这个例子
<div id="simplest" class="demo">
<input v-focus />
</div>
const app = Vue.createApp({});
app.directive('focus', {
mounted(el) {
el.focus()
}
})
app.mount('#simplest')
在这边我对 directive
去定义一个叫做 focus
的语法,然後塞入我们的 vue 里面,这样一来我再 template 中就可以用 v-[name]
的方式再 html 使用,然後达到我要的效果,所以这边我用 v-focus
然後绑定再 input
这个 DOM 身上。
再来我们来看 directive
的第二个参数,这里面塞的就是我们的生命周期函式。
以下是我们 directive
的生命周期表,在这边可以看到跟我们的 component 的生命周期很像,但是我们是以挂到DOM上面来作为执行的顺序。
官方文件: https://v3.cn.vuejs.org/api/application-api.html#directive
// Vue3 版本
app.directive('my-directive', {
// 在绑定DOM的 attribute 或事件监听被使用之前调用
created() {},
// 在绑定DOM的父组件挂载之前调用
beforeMount() {},
// 绑定DOM的父组件被挂载时调用
mounted() {},
// 在包含组件的 VNode 更新之前调用
beforeUpdate() {},
// 在包含组件的 VNode 及其子组件的 VNode 更新之後调用
updated() {},
// 在绑定DOM的父组件移除之前调用
beforeUnmount() {},
// 移除绑定DOM的父组件时调用
unmounted() {}
})
vue2 directive
的生命周期函式已经被重命命名,所以再升级 vue3 的时候要特别注意一下。
// Vue2 版本
Vue.directive('highlight', {
// 绑定到DOM後调用。只调用一次。
bind() {},
// DOM插入父组件後调用。
inserted () {},
// 当DOM更新,但子组件尚未更新时调用。
update () {},
// 组件和子组件被更新,就会调用。
componentUpdated () {},
// 指令被移除就会调用,也只调用一次。
unbind () {},
})
官方文件 : https://v3.vuejs.org/guide/migration/custom-directives.html#overview
我们的 mounted
会把它挂载的 DOM 实体给 return
回来,这里回传是一个 input 的表单DOM,所以我们就可以像一般的 javascript 操作一样,我执行这个.focus()
的函式,让我一进来这个页面的时候,将我们滑鼠目标放到这个表单身上。
app.directive('focus', {
mounted(el) {
el.focus()
}
})
官方的 codepen 范例 : https://codepen.io/team/Vue/pen/JjdxaJW?editors=1010
directive
有了概念之後,现在我们来看一些更加实际的使用方式首先这是一个像是FB贴文的卡片,然後当我拿到资料之後,我就会把内容给一个摆上去,但是你注意看一下时间的地方,下面是API 回传的格式。
{
"createdAt": "2021-09-14T04:28:05.885Z",
"name": "Phyllis Abernathy V",
"avatar": "https://cdn.fakercloud.com/avatars/amanruzaini_128.jpg",
"post_date": 1631608299879,
"photo": "http://placeimg.com/640/480",
"content": "transmit cross-platform capacitor",
"id": "1"
},
一切都看起来蛮正常的,但是在时间的地方它是回传一个 Timestamp ( milliseconds )
给你,这其实蛮常见的,不是所有的後端都会帮你转换时间的部分,所以这时候我们放上去卡片的地方就要写一个 function 去做转换时间的动作,在这边我选择用 day.js
来帮助我转换时间,体积小功能又强大,没用过的可以考虑用用看。
Day.js 官网 : https://day.js.org/en/
我们可以用以下的方式来转换时间格式,所以接下来包装一下。
dayjs(1631608299879).format('YYYY/MM/DD')
<script>
import { ref, onMounted } from "vue";
import axios from "axios";
import dayjs from "dayjs";
export default {
setup() {
const postCard = ref([]);
const timestamp = (time) =>{
return dayjs(time).format('YYYY/MM/DD')
}
onMounted(() => {
axios.get("https://60bd9841ace4d50017aab3ec.mockapi.io/api/post_card").then((res) => {
postCard.value = res.data;
});
});
return {
postCard,
timestamp
};
},
};
</script>
<template>
<div class="card" v-for="card in postCard" :key="card.id">
<header>
<img class="avatar" :src="card.avatar" />
<div>
<h1>{{ card.name }}</h1>
<p>
{{ timestamp(card.post_date) }}
</p>
</div>
</header>
<p class="content">{{ card.content }}</p>
<img class="post_photo" :src="card.photo" alt="" />
</div>
</template>
我写了一个 function 会回传 format 之後的时间,然後放到 html 之中timestamp(card.post_date)
给 render 出来,这样可以把我们的时间做转换。
如果很多地方都要做这样的时间转换,然後我又要一直写 dayjs(time).format('YYYY/MM/DD')
,看起来就不是很理想,所以这边我要使用 directive
来包装这个 dayjs
的 format
函式。
首先我的需求是要像下面这样,我只要使用 v-timeformat
就可以把 timestamp 给转换成我要的格式给我。
<p v-timeformat="card.post_date"></p>
所以接下来我们来注册一个 timeformat 的模板语法。
import { createApp } from "vue";
import dayjs from "dayjs";
import App from "./App.vue";
const app = createApp(App);
// 先注册一个 timeformat 的语法
app.directive("timeformat", {
mounted(el, binding) {
const time = dayjs(binding.value).format("YYYY年MM月DD日");
el.innerText = time;
}
});
app.mount("#app");
因为再 template 这边我有塞内容 (card.post_date
) 进去
<template>
<div class="card" v-for="card in postCard" :key="card.id">
<header>
<img class="avatar" :src="card.avatar" />
<div>
<h1>{{ card.name }}</h1>
<p v-timeformat="card.post_date"></p>
</div>
</header>
<p class="content">{{ card.content }}</p>
<img class="post_photo" :src="card.photo" alt="" />
</div>
</template>
所以 mounted
除了回传绑定的 DOM 以外,第二个参数会回传你传入的 value
,所以我们可以 binding.value
的方式取得它的值,当我拿到 value
之後我就执行 dayjs(binding.value).format("YYYY年MM月DD日")
,把我的时间给转换完後,再透过 innerText
给塞入到我们的 DOM 之中,就大功告成了。
codesandbox 完整范例 : https://codesandbox.io/s/zv46o?file=/src/main.js
directive
的另外一种使用方式我们很常会制作有很多图片的页面,但是图片的载入是非同步的,所以通常我们会写一个 load 的 function 来判断图片有没有载入完成,在我们前面的范例也有示范相关的做法,不过那种做法通常都是图片前面会盖一个 laoding 的页面,等到图片载入完成之後再拿掉 laoding page,这种作法虽然很常见,不过今天我们来介绍一下如何使用 directive
来做到像是图片的 lazyload 效果。
首先我希望我的 img 身上可以挂一个 v-src
的语法
<img v-src="https://cdn.fakercloud.com/avatars/popey_128.jpg" />
然後它可以被在背後去载入,载入完成後把图片给放到 img 标签身上给 show 出来。
app.directive("src", (el, binding) => {
});
我们先注册一个名叫 src
的 directive
,然後第二个参数给它一个 callback 函式,callback 函式???,你可能会问说怎麽不是一个物件,directive
的第二个参数总共可以带两种不同类型的参数,一个是物件
,一个是函式
,物件就像我们上面一样,可以有多个生命周期,如果是一个函式的话,这个函式等同於 mounted
的阶段执行,然後一样可以回传绑定的 DOM 还有 value。
app.directive("src", (el, binding) => {
el.style.opacity = 0;
if (binding.value) {
const img = new Image();
img.src = binding.value;
img.onload = () => {
el.src = binding.value;
el.style.opacity = 1;
};
}
});
首先一开始的时候,我先把图片的透明度设成 0
,你可能会问说,怎麽不是 display: none
而是用 opacity = 0
呢 ? 原因是因为我的图片如果有设css的高度的话,设定透明度高度还会在,所以我的版型还不会跑版,再来判断我的 binding.value
是否有将图片的路径给传入进来,如果有传入图片路径我就去对它执行 onload
看图片有没有载入完成,如果载入完成我就把图片的透明度给变成 1
,也就是opacity = 1
,这时候可以做一下透明度 0 ~ 1
的补间动态,所以加一下 css3 的 transition
就大功告成。
<style>
img {
transition: opacity 0.3s;
}
</style>
这个时候我就会加一个 onerror
来处理有问题的图片,我推荐像是下面这种作法。
import errorImg from "./assets/error.png";
app.directive("src", (el, binding) => {
el.style.opacity = 0;
if (binding.value) {
const img = new Image();
img.src = binding.value;
img.onload = () => {
el.src = binding.value;
el.style.opacity = 1;
};
img.onerror = () => {
el.src = errorImg;
el.style.opacity = 1;
};
}
});
透过 onerror
我们可以确保当图片出现问题的时候,不会让画面直接出现问题,而是我们可以载入预设的图片,让画面至少维持在一个状态,不会看起来很突兀,所以我先载入了一个 error.png
,当图片出现问题的时候,我就把这个图片给替换上去,透明度改回 1
,这样一来画面上面就可以看到我们预先准备好的图片了。
codesandbox 完整范例 : https://codesandbox.io/s/oulpd?file=/src/main.js
所以在开发上面不是所有的共用逻辑都需要使用 composition api
,我们可以依照自己的需求来决定使用那些功能来帮助我们开发,像是把第三方套件结合自己定义的模板语法来产生最後结果的做法,或是自己做一个 lazyload
的效果也是一种不错的选择,当然能用 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
<<: [Day 03] 一声探气,索性来资料分析 (探索性资料分析)
>>: Kotlin Android 第13天,从 0 到 ML - Activity 和 Activity 生命周期
前言 如果只有画面像的话,那也太弱了吧! 赶紧来实作新增闹钟的功能,做完拿去炫耀给边身边的人看! 实...
好的,中秋节连假第二天,大家是吃烤肉吃的不要不要的阿? 那我们今天主要要做的就是关於登入页面。今天...
Q: 是不是来点icon比较知道这是干嘛的? A: 不复杂的可以用css画,复杂的可以考虑出图或是...
今天这篇文章会介绍CSS文字大小、文字粗细、字体和字型,这些都是有关文字样式的相关属性: 文字大小 ...
尚气与十环传奇在线观看 漫威影业荣誉出品史诗冒险《尚气与十环传奇》,结合前所未见的震撼性动作、令人惊...