div滚动到指定位置 vue_【Vue 进阶】从 slot 到无渲染组件
什么是插槽
插槽(slot)通俗的理解就是“占坑”,在組件模板中占有位置,當使用該組件的時候,可以指定各個坑的內容。也就是我們常說的內容分發
值得一提的是,插槽這個概念并不是 Vue 提出的,而是 web Components 規范草案中就提出的,具體入門可以看 使用 templates and slots[1] ,Vue 只是借鑒了這個思想罷了
在 Vue 2.6.0 中,我們為具名插槽和作用域插槽引入了一個新的統一的語法 (即 v-slot 指令)。它取代了 slot 和 slot-scope,這兩個目前已被廢棄但未被移除且仍在文檔中的 attribute
本文的例子基于 Vue 2.6.X,所以用的都是 v-slot 的語法。
本文 DEMO 已全部放到 Github[2] 和 沙箱[3] 中,供大家學習,如有問題,歡迎評論提出。
默認插槽
我們新建父組件 Parent 和子組件 Child,結構如下:
父組件:
<h3>默認插槽h3>
<Child>
????<div?class="parent-text">Hi,?I?am?from?parent.div>
Child>
子組件:
<div?class="child">????<slot>slot>
????<div>Hello,?I?am?from?Child.div>
div>
父組件調用 Child 組件的時候,會在 Child 標簽中將內容傳入到子組件中的 標簽中,如下所示
也就是最后的渲染結果如下:
<div?class="child">????<div?class="parent-text">Hi,?I?am?from?parent.div>
????<div>Hello,?I?am?from?Child.div>
div>
后備內容
我們可以在子組件中的 中加入一些內容,像下面一樣
<div?class="child">??<slot>當父組件不傳值的時候,我就展示,我只是一個后備軍slot>
??<div>Hello,?I?am?from?Child.div>
div>
當父組件調用的時候, 子組件標簽內沒有相關的內容時候, 標簽內的內容就會生效,否則就不會渲染,可以理解就是個“備胎”
如父組件調用上面子組件:
????????<h3>后備內容h3>
????<Child1>Child1>
結果如下:
具名插槽
當然,插槽可以不止一個,這個主要是為了能夠靈活的控制插槽的位置以及組件的抽象。我們可以通過在子組件的 slot 標簽中設置 name 屬性,然后在父組件中通過 v-slot:(或者使用簡寫 #) + 子組件 name 屬性值的方式指定要插入的位置。如果是默認插槽的話,v-slot:default 即可
如下父組件:
????????<h3>具名插槽h3>
????<Child2>
??????<template?v-slot:footer><div>我是底部div>template>
??????<template?#header><div>我是頭部div>template>
??????<template?v-slot:default>
????????<div>我是內容div>
??????template>
????Child2>
子組件
??<div?class="child">????<slot?name="header">slot>
????<slot>slot>
????<div>Hello,?I?am?from?Child.div>
????<slot?name="footer">slot>
??div>
需要留意的是,最后渲染的順序是以子組件的順序為主,也就是上面的例子,渲染出來如下:
作用域插槽
有時候,我們想在一個插槽中使用子組件的數據和事件,類似如下(注意:user 是定義在 Child3 組件中的數據):
????<Child3>??????<template>
????????<div>我的名字:{{user.name}}div>
????????<div>我的年齡:{{user.age}}div>
????????<button?@click="callMe">Clicl?Mebutton>
??????template>
????Child3>
會直接報錯:
原因在于父組件取不到子組件的數據,這里記住一個原則:父級模板里的所有內容都是在父級作用域中編譯的;子模板里的所有內容都是在子作用域中編譯的。
那我們怎樣才能獲取到子組件的數據或者事件呢?我們可以直接在子組件中通過 v-bind 的方式將數據或者事件傳遞給父組件中,如下所示
??<div?class="child">????<div>Hello,?I?am?from?Child.div>
????
????<slot?:user="user"?:callMe="callMe">slot>
??div>
然后在父組件中的插槽內,通過類似 v-slot:default="slotProps" 接受子組件傳遞過來的數據
????<Child3>??????
??????<template?v-slot:default="slotProps">
????????<div>我的名字:{{slotProps.user.name}}div>
????????<div>我的年齡:{{slotProps.user.age}}div>
????????<button?@click="slotProps.callMe">Clicl?Mebutton>
??????template>
????Child3>
以上 slotProps 可以自定義,而且可以使用解構賦值的語法
<template?v-slot:other="{?user,?callMe}">
??<div>我的名字:{{user.name}}div>
??<div>我的年齡:{{user.age}}div>
??<button?@click="callMe">Clicl?Mebutton>
template>
實例:解耦業務邏輯和視圖
我們經常會遇到一個場景,就是兩個組件的業務邏輯是可以復用的,但是視圖卻不一樣,比如我們經常會有類似切換開關的需求,功能包括:
- 關閉開關
- 打開開關
- 切換開關
- 開關關閉或者打開的時候不一樣的內容
我們可以很快的寫出它的一個 JS 業務邏輯代碼:
export?default?{??data()?{
????return?{
??????currentState:?this.state
????}
??},
??props:?{
????state:?{
??????type:?Boolean,
??????default:?false
????}
??},
??methods:?{
????openState()?{
??????this.currentState?=?true;
????},
????closeState()?{
??????this.currentState?=?false;
????},
????toggle()?{
??????this.currentState?=?!this.currentState;
????}
??}
}
但是可能現在我的樣式一是這樣的
然而另外一個地方的樣式是這樣的(只是舉個例子,現實可能更加的復雜,甚至有可能一些按鈕直接就隱藏掉了)
這個時候,插槽就派上了用場。上面提到作用域插槽可以將數據和事件從子組件傳遞給父組件,這就相當于對外暴露了接口。而且可以將 HTML 中的 DOM 以及 CSS 交給父組件(調用方)去維護,子組件通過 標簽插入的位置即可,主要邏輯如下:
子組件:
<template>??<div?class="toggle-container">
????<slot?:currentState="currentState"?:setOn="openState"?:setOff="closeState"?:toggle="toggle">slot>
??div>
template>
父組件:
<Toggle1?:state="state"?class="toggle-container-two">??<template?v-slot:default="{currentState,?setOn,?setOff,?toggle?}">
????<button?@click="toggle">切換button>
????<button?@click="setOff">關閉button>
????<button?@click="setOn">打開button>
????<div?v-if="currentState">我是打開的內容div>
????<div?v-else>我是關閉的內容div>
??template>
Toggle1>
我們現在采用的是單文件的方式書寫的,實際上子組件還是會有相關的 HTML 結構,如何做到子組件完全不需要渲染自己的 HTML 呢?那得了解下無渲染組件的實現
進階:無渲染組件的實現
無渲染組件(renderless components)是指一個不需要渲染任何自己的 HTML 的組件。相反,它只管理狀態和行為。它會暴露一個單獨的作用域,讓父組件或消費者完全控制應該渲染的內容。Vue 中,提供了單文件組件的寫法。像上面的示例一樣,我們始終還是在子組件中進行了一些渲染的操作,那如何做到真正的不渲染組件呢?
比如上面的 toggle 例子,我們已經做到了子組件暴露一個單獨的作用域,讓父組件或消費者完全控制應該渲染的內容。現在我們需要將單文件中的 template 結構(slot 標簽外層的 div)完全交給父組件,但單文件組件中 slot 標簽是不能作為 template 的根元素的
這個時候,我們需要了解一下 Vue 渲染函數(render function)
歸根結底,Vue 及其所有的組件都只是 JavaScript。單文件組件最后會被構建工具,如 webpack,將 CSS 抽取形成一個文件,其他的內容會被轉換成 ?JavaScript,類似如下:
export?default?{??template:?<div?class="mood">...div>,
??data:?()?=>?({?todayIsSunny:?true?})
}
當然,這個不是它的最終形態,模板編譯器會提取 template 屬性內容并將其內容編譯為 JavaScript,然后通過 render 函數添加到組件對象中。最終形態應該是如下:
render(h)?{??return?h(
????'div',
????{?class:?'mood'?},
????this.todayIsSunny???'Makes?me?happy'?:?'Eh!?Doesn't?bother?me'
??)
}
具體的渲染函數可參見官網[4],雖然寫 render 函數的成本會高一些,但是它的性能會比單文件組件好很多。
以上的例子,只有插槽的時候,我們只需要在 render 函數中,使用 this.$scopedSlots.default 代替掉 標簽即可
代碼如下:
export?const?toggle?=?{??data()?{
????return?{
??????currentState:?this.state
????}
??},
??render()?{
????return?this.$scopedSlots.default({
??????currentState:?this.currentState,
??????setOn:?this.openState,
??????setOff:?this.closeState,
??????toggle:?this.toggle,
????})
??},
??props:?{
????state:?{
??????type:?Boolean,
??????default:?false
????}
??},
??methods:?{
????openState()?{
??????this.currentState?=?true;
????},
????closeState()?{
??????this.currentState?=?false;
????},
????toggle()?{
??????this.currentState?=?!this.currentState;
????}
??}
}
?
以上就可以做到子組件完全不渲染自己的 HTML了
總結
本文介紹了一些 Vue 插槽的基本知識,包括
- 默認插槽
- 后備內容
- 具名插槽
- 作用域插槽
然后介紹了一下,如何通過插槽實現業務邏輯和視圖的解耦,再結合渲染函數實現真正的無渲染函數
本文 DEMO 已全部放到 Github[5] 和 沙箱[6] 中,供大家學習,如有問題,可以評論提出。
這么用心了,求個贊,哈哈
希望對大家有所幫助~
往期優秀文章推薦
- 【Vue進階】——如何實現組件屬性透傳?[7]
- 前端應該知道的 HTTP 知識【金九銀十必備】[8]
- 最強大的 CSS 布局 —— Grid 布局[9]
- 如何用 Typescript 寫一個完整的 Vue 應用程序[10]
- 前端應該知道的web調試工具——whistle[11]
參考:
Vue 插槽(slot)使用(通俗易懂)[12]
vue 2.6 中 slot 的新用法[13]
(譯)函數式組件在Vue.js中的運用[14]
Building “Renderless” Vue Components[15]
參考資料
[1]使用 templates and slots: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_templates_and_slots
[2]Github: https://github.com/GpingFeng/vue-slot
[3]沙箱: https://codesandbox.io/s/hopeful-nash-id826?file=/src/main.js
[4]官網: https://cn.vuejs.org/v2/guide/render-function.html
[5]Github: https://github.com/GpingFeng/vue-slot
[6]沙箱: https://codesandbox.io/s/hopeful-nash-id826?file=/src/main.js
[7]【Vue進階】——如何實現組件屬性透傳?: https://juejin.im/post/6865451649817640968
[8]前端應該知道的 HTTP 知識【金九銀十必備】: https://juejin.im/post/6864119706500988935
[9]最強大的 CSS 布局 —— Grid 布局: https://juejin.im/post/6854573220306255880
[10]如何用 Typescript 寫一個完整的 Vue 應用程序: https://juejin.im/post/6860703641037340686
[11]前端應該知道的web調試工具——whistle: https://juejin.im/post/6861882596927504392
[12]Vue 插槽(slot)使用(通俗易懂): https://juejin.im/post/6844903920037281805
[13]vue 2.6 中 slot 的新用法: https://juejin.im/post/6844903885476200461
[14](譯)函數式組件在Vue.js中的運用: https://juejin.im/post/6844903752164442120
[15]Building “Renderless” Vue Components: https://css-tricks.com/building-renderless-vue-components/
總結
以上是生活随笔為你收集整理的div滚动到指定位置 vue_【Vue 进阶】从 slot 到无渲染组件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis存10万条数据_redis详细
- 下一篇: 小米手环4怎么使用_小米手环4/5 NF