该系列是为了让看过Vue官方文件或学过Vue但是却不知道怎麽下手去重构现在有的网站而去规画的系列文章,在这边整理了许多我自己使用Vue重构很多网站的经验分享给读者们。
使用过Vue的朋友都一定都有听过 Slots 这个功能,但是有通过不一定有使用过,就让我来稍微介绍一下我常在专案里面使用Slots 的时机与案例。
我们先来看一下以下的例子
我们在制作网站的时候很常会有像是这样的 title,我们会看到除了内容不一样以外,旁边的 icon 跟 style 都是一样的,所以理所当然地会把这个 title给拆出组件来重复使用,这边我来示范一个我最常看到的做法。
首先我会新增一个 TitleBar.vue
的组件
<script>
export default {
props: {
content: {
type: String,
default: "",
},
},
setup(props) {
return {
props,
};
},
};
</script>
<template>
<h1>
<img class="icon" src="../assets/icon.png" alt="" />
{{ props.content }}
</h1>
</template>
然後这个组件会透过 props
来传递 title 的文字内容,然後我只要在上层去传入 props
,title 的内容就会不一样,像下面这样子。
<TitleBar :content="'最新消息'" />
<TitleBar :content="'关於我们'" />
<TitleBar :content="'热门商品'" />
<TitleBar :content="'你也感兴趣的'" />
codesandbox 范例: https://codesandbox.io/s/vue-slot-title-1-7roh7
这样的做法虽然可行,但是却不直觉,我们可以搭配 Slots 把这个组件当作是一个 html tag
一样使用,变成是下面这样
<TitleBar>最新消息</TitleBar>
<TitleBar>关於我们</TitleBar>
<TitleBar>热门商品</TitleBar>
<TitleBar>你也感兴趣的</TitleBar>
要怎麽做才能这样? Slots到底是什麽?
我们来看一下官网的这张图
这张图其实就已经说明完了 Slots 的整个概念「渲染作用域」。
我们可以在我们的 component 里面去定义一个 <slot></slot>
的作用域,只要上层有放入内容,就会把这个内容给 Render 到你 <slot></slot>
的区域内,我们来看一下改过後的范例,我们删除掉 TitleBar.vue
里面所有的 props,然後在原本的内容位置塞入 <slot></slot>
。
TitleBar.vue
<template>
<h1>
<img class="icon" src="../assets/logo.png" alt="" />
<slot></slot>
</h1>
</template>
App.vue
<TitleBar>最新消息</TitleBar>
<TitleBar>关於我们</TitleBar>
<TitleBar>热门商品</TitleBar>
<TitleBar>你也感兴趣的</TitleBar>
我使用的时候就可以像是 html tag
一样使用,这样在看code的时候会比较明确,也减少不必要的 props,你也可以对 slot 插入预设的内容,当今天如果你没有在上层插入你的内文,就会直接Render你的预设的内容。
<template>
<h1>
<img class="icon" src="../assets/logo.png" alt="" />
<slot>这是预设的内容喔</slot>
</h1>
</template>
<!-- 不带内容进去 -->
<TitleBar></TitleBar>
codesandbox 范例:https://codesandbox.io/s/vue-slot-title-2-jywyl
关於 Slots 官方还有很多的使用方式,例如下面我列出来的几个,但是这些给你们自己慢慢看就好了,我不想把文件内容整个复制贴上来一次。
Slots 官方文件 : https://v3.vuejs.org/guide/component-slots.html
之前也有针对 slots 开一场直播,有兴趣的朋友可以看一下,虽然是Vue2的
以刚刚我们举的例子,我的 TitleBar.vue
是用h1
来放我的文字内容,然後旁边有一个 icon,这是现在的设计,但是如果我今天有一个 ul
、li
的结构,然後这个 li
也要跟我现在这个 TitleBar.vue
的设计一样,会改变内文,但是 icon 不变,或是甚至是可以选择这个 icon 图片能不能客制,那这样的需求我能不能直接拿 TitleBar.vue
来用呢?
<ul>
<TitleBar>最新消息</TitleBar>
<TitleBar>关於我们</TitleBar>
<TitleBar>热门商品</TitleBar>
<TitleBar>你也感兴趣的</TitleBar>
<TitleBar></TitleBar>
</ul>
你这样使用画面的呈现当然可以,但是你的 HTML 规范就完全的不行了。
天啊! 简直一团糟 !
於是我灵光一闪,如果我可以跟 vue-router
中的 router-link
一样可以自己决定我这个组件的 html tag
的话,也就是支援动态改变html tag
,那该有多好~ 想用在那个地方就用在哪个地方,把这个组件能客制化的地方最大化 !!!
那我们现在来实际来改一下,首先如果要动态的改变我们的 html tag
的话,不能用原本的 template
的方式来写 html,我们需要透过 Render Functions
的方式来 Render 我们的DOM元素,我先砍掉原本的 template
,然後加上了 props
,然後要用一个我们平常比较少用到的函式 render()
来渲染我们的 DOM。
<script>
import { h } from "vue";
import icon from "../assets/icon.png";
export default {
props: {
tag: {
type: String,
default: "h1",
},
},
render() {
return h(this.tag, {}. [
h("img", {
class: "icon",
src: icon,
}),
this.$slots.default(),
]);
},
};
</script>
这个时候你只要在使用 <TitleBar>
的时候带入一个名叫 tag
的 props,它就会去替换它里面的 html tag
,进而达到动态改变的效果。
<ul>
<TitleBar :tag="'li'">最新消息</TitleBar>
<TitleBar :tag="'li'">关於我们</TitleBar>
<TitleBar :tag="'li'">热门商品</TitleBar>
<TitleBar :tag="'li'">你也感兴趣的</TitleBar>
</ul>
你可能突然头会很痛,看到又是 h
又是 render
,想说 WTF...
h()
???h()
函式是一个用於创造 VNode
( virtual node 虚拟节点 ) 的方法,但由於太频繁的使用且基於语法简洁的考量,它被称为 h()
。
h()
可以带入三个参数:
tag name
- { String
} (必填) : html 的标签名称。attribute
- { Object
} (非必填) : html 身上的属性,像是 src、class、alt 等等。VNodes
- { String
| Array
| Object
} (非必填) : 所以要放入这个 DOM 内部的 vNode 及 Slots 的内容。我们要可以在 render
函式内取得 slots 的内容,可以用下面的方式来取得。
this.$slots.default()
所以刚刚范例的那段 code 我们再回来看一次
render() {
return h(this.tag, {}. [
h("img", {
class: "icon",
src: icon,
}),
this.$slots.default(),
]);
},
h
函式放入了我从 props 传入的 tag 名称 this.tag
,如果没有传入,那就是预设的 h1
tag。tag
并没有要放入其他 attribute
,所以给一个空物件。img
以及 slots
的内容,所以我这边用 Array 来带入。Array[0]
用 h()
来创建一个 img
的 virtual DOM,这次因为要塞入 src
跟 class
这两个 attribute,所以我第二个参数的 Object 里面就有带入这两个 attribute 要塞入的内容。Array[1]
塞入接下来的 slots 的内容。现在这样看起来没啥问题,但是如果你上层不带入任何内容的话就会报错,所以我们现在要来处理预设内容的部分
<!-- 现在不带内容进去会出错 -->
<TitleBar :tag="'li'"></TitleBar>
我们可以先在 render
函式内去做判断
render() {
const slotsContext = Object.keys(this.$slots).length === 0 ? "这是预设内容" : this.$slots.default();
return h(this.tag, {}, [
h("img", {
class: "icon",
src: icon,
}),
slotsContext,
]);
},
如果今天没有带入内容的话 this.$slots
就会是一个空的 Object,所以我们去检查它是不是空的,如果是空的就给它一个预设的内容。
以下就完成了可以动态去改变 html tag
的 component ,阿如果要可以改变 icon 的话自己在写一个 props
去带入 src
的部分即可,我就不特别示范了。
<script>
import { h } from "vue";
import icon from "../assets/logo.png";
export default {
props: {
tag: {
type: String,
default: "h1",
},
},
render() {
const slotsContext =
Object.keys(this.$slots).length === 0
? "这是预设内容"
: this.$slots.default();
return h(this.tag, {}, [
h("img", {
class: "icon",
src: icon,
}),
slotsContext,
]);
},
};
</script>
关於Vue DOM的文件 : https://v3.cn.vuejs.org/api/options-dom.html#template
Render Functions 的文件 : https://v3.vuejs.org/guide/render-function.html#render-functions
codesandbox 范例 : https://codesandbox.io/s/vue-slot-title-3-e3fl1
Slots 在很多地方其实都非常的好用,除了帮我减少不必要的 props 以外,还可以帮我在需多重复性功能太多的 component 上面做一个整合,今天示范的不管是Slots 或是 Render Functions 的东西都只有其中一部分,还不是全部,它其实还有很多东西我没有讲到,不过我们先把这些基本的使用方式熟悉的之後,再去慢慢往後延伸了解也不迟,好啦~那我们明天见。
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\
>>: [Day29]What is the Probability?
大家好,今天要来做Model的转换,使用到json_serializable、build_runne...
(ISC)² 道德准则仅适用於 (ISC)² 会员。垃圾邮件发送者的身份不明或匿名,这些垃圾邮件发送...
前二篇解释了 GUI Design 阶段的重点,也提到此时花费设计师的工时相当可观。功欲善其事,必先...
写在前面 test for placeholder test for placeholder tes...
梯度下降法经常被使用为优化学习的一种方式,寻找局部最佳解(至於为何是局部,之後会提到),想像有个...