iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

0
自我挑戰組

跟 VueJS 認識的30天系列 第 14

[DAY14]跟 Vue.js 認識的30天 - Vue 模組插槽(`slot`)

最近在 youtube 找到一個學習 webpack 很好用的教學影片,所以正努力的學習 webpack 中,一個不小心就太沉迷在新技術的練習了,導致有點忽略 Vue.js 的筆記,再次跟自己喊話,一定要做完 Vue.js 的系列(之後還有 Vuex 跟 Vuetify 呢!)。

另外說明一下,我筆記的順序是我自己認為這樣的順序比較好理解,所以會跟文件中的說明順序有差異。

新增插槽並設定插槽內容

在設定模組時,會透過 template 來決定模組在頁面上要顯示的內容,而我們可以在 template 中加入 <slot> 標籤來設定模組標籤內容的位置,並且在網頁顯示的時候,模組標籤的內容會取代該 <slot> 標籤。

模組標籤裡的內容可以是純文本、 HTML 標籤或其他模組等等。

<basic-slot>
  Next
  <i class="fas fa-angle-right"></i>
</basic-slot>
<script>
Vue.component("basic-slot", {
  // <slot></slot>指定外層模組標籤內容放置的位置
  // 可放入純文本、html標籤、其他模組
  template: `<div>
    <div>
      <slot></slot>
    </div>
  </div>`
});
</script>

https://ithelp.ithome.com.tw/upload/images/20201121/20127553dEyhheGxI5.png

插槽預設值

直接在 <slot> 標籤內寫定預設內容,如果模組標籤內沒有任何內容,那麼該 component 的畫面顯示就是該預設內容。

<!-- 模組標籤沒有任何內容 -->
<basic-slot></basic-slot>
<script>
Vue.component("basic-slot", {
  // 在 <slot> 標籤中設定模組畫面內容的預設值
  template: `<div>
    <div>
      <slot>
        Preview
      </slot>
    </div>
  </div>`
});
</script>

具名插槽

如果要將不同內容的插槽資料分類,就可以透過 <slot name="slotName">v-slot:slotName 的來命名插槽及決定不同內容所放置的位置。

如果 <slot 標籤未命名的話,預設的名字是 default ,所以在模組標籤可以透過 v-slot:default 或是不寫 v-slot:default 來找到那個未命名的 <slot 標籤。

須注意 v-slot 只能添加在 <template> 上。除非是只有默認插槽的狀況才可寫在模組標籤上

<name-slot>
  <!-- 為了最後顯示時不要有太多標籤,所以使用template來避免出現太多元素-->
  <!-- 對應模組內名為 footer 的插槽位置-->
  <template v-slot:footer>
    <h4>Here is footer</h4>
  </template>
  <!-- 對應模組內名為 header 的插槽位置-->
  <template v-slot:header>
    <h4>Here is header</h4>
  </template>
  <!-- 對應模組內名為 container 的插槽位置-->
  <template v-slot:container>
    <h4>Here is container</h4>
  </template>
  <!-- 對應模組內未命名或名為 default 的插槽位置-->
  <template>
    <h4>Here is default</h4>
  </template>
  <!-- <template>
  <h4>Here is default</h4>
  </template> 跟下面意思相同-->
  <template v-slot:default>
    <h4>Here is default</h4>
  </template>
</name-slot>
<script>
Vue.component("name-slot", {
  // 在 <slot> 標籤中利用 attribute 來命名插槽,並決定不同名字的插槽內容在畫面顯示後的位置
  template: `<div>
    <div>
      <slot name="header"></slot>
      <slot name="container"></slot>
      <slot name="footer"></slot>
      <slot></slot>
    </div>
  </div>`
});
</script>

只有下面這種情況才可以將 v-slot:default 寫在模組標籤上,另外要注意也不能將 default 改成其他名字喔!

<name-slot v-slot:default>
  <h4>Here is default</h4>
</name-slot>
<script>
Vue.component("default-slot", {
  template: `<div>
    <slot></slot>
  </div>`
});
</script>

模組標籤(外層)及插槽標籤(內層)內容的作用域

在模組標籤中的內容只能取得該層的資料,而在模組內的 <slot> 標籤也只能取得模組內的資料,簡單來說就是在哪設定要取資料就是只能取得那一層的資料。

模組標籤內容的作用域(外層)

  <scoped-slot name="Michelle">
    外層的 data 資料:{{ user.name }},
    模組內的 props 資料: {{ name }}
    模組內的 data 資料: {{ user2.name }}
  </scoped-slot>
<script>
Vue.component("scoped-slot", {
  template: `<div>
    <div>
      <slot></slot>
    </div>
  </div>`,
  data() {
    return {
      user2: {
        name: "Linda",
        age: 9
      }
    };
  }
});
const vm = new Vue({
  el: "#vm",
  data: {
    user: {
      name: "Celeste",
      age: 18
    }
  }
});
</script>

以上面為例,會發現只要是想取得模組內的 data 或是 props 都會是失敗的(會出錯),但是取得外層(模組標籤那一層)資料是成功的。

插槽標籤的作用域(內層)

  <scoped-slot name="Michelle"></scoped-slot>
<script>
Vue.component("scoped-slot", {
  props:[ 'name' ],
  template: `<div>
    <div>
      <slot>
        模組內的 props 資料: {{ name }},
        模組內的 data 資料: {{ user2.name }},
        外層的 data 資料:{{ user.name }}
      </slot>
    </div>
  </div>`,
  data() {
    return {
      user2: {
        name: "Linda",
        age: 9
      }
    };
  }
});
const vm = new Vue({
  el: "#vm",
  data: {
    user: {
      name: "Celeste",
      age: 18
    }
  }
});
</script>

如果是在 <slot> 中設定預設值,會發現在 <slot> 內只能取得該模組內的資料(如 data 或是 props ),如果想要取得外層的 data 資料會出錯。

模組標籤內容(外層)使用插槽標籤動態綁定值(內層)

如上面所說,一般情況下外層無法取得內層的資料,但是可以透過在 <slot name="slotName" v-bind:slotPropName="slotPropValue"> 標籤上設定 Attribute 來動態綁定值以將內層的值往外傳到外層去,而外層透過 v-slot:slotName="customName" 或是 ES6語法 v-slot:slotName="{ slotPropName }" 來決定適用哪一個作用域。

使用 v-slot:slotName="customName"

設定 customName 來接收從 <slot> 接收的動態綁定值,像是將所有動態綁定的值放入 customName 這個物件中。

<!-- 用 slotProps 來接收從 slot 傳進來的值 -->
<v-bind-slot v-slot:default="customName">
  姓{{ slotProps.user.firstName }},
  年齡{{ slotProps.user.age }}
  喜歡水果{{ slotProps.fruit[0] }}
</v-bind-slot>
<script>
Vue.component("v-bind-slot", {
  template: `<div>
    <div>
      <slot :user="user" :fruit="fruit">
      {{ user.lastName }}
      </slot>
    </div>
  </div>`,
  data() {
    return {
      user: {
        lastName: "Linda",
        firstName: "KUO",
        age: 9
      },
      fruit: ["apple", "banana", "orange"]
    };
  }
});
</script>

使用 v-slot:slotName="{ slotPropName }"

透過 ES6 解構語法來將動態綁定值一一傳入。

<!-- 用解構語法來接收從 slot 傳進來的值 -->
<v-bind-slot v-slot:default="{ user, fruit }">
  姓{{ slotProps.user.firstName }},
  年齡{{ slotProps.user.age }}
  喜歡水果{{ slotProps.fruit[0] }}
</v-bind-slot>
<script>
Vue.component("v-bind-slot", {
  template: `<div>
    <div>
      <slot :user="user" :fruit="fruit">
      {{ user.lastName }}
      </slot>
    </div>
  </div>`,
  data() {
    return {
      user: {
        lastName: "Linda",
        firstName: "KUO",
        age: 9
      },
      fruit: ["apple", "banana", "orange"]
    };
  }
});
</script>

作用域

只有在自己那個具名插槽中有綁定的 Attribute 才可以在外層相同名字的 v-slot 作用域中使用。

<v-bind-slot>
  <template v-slot:default="{ user, fruit }">
    姓{{ user.firstName }},
    年齡{{ user.age }}
    喜歡水果{{ fruit[1] }}
    <br/>
  </template>
  <template v-slot:cellphone="{ cellphone, fruit }">
  <!-- user未在名為 cellphone 的插槽中綁定,所以不可以在這個作用域使用 -->
    姓{{ user.firstName }},
    手機{{ cellphone[2] }}
    喜歡水果{{ fruit[1] }}
  </template>
</v-bind-slot>
<script>
Vue.component("v-bind-slot", {
  template: `<div>
    <div>
      <slot name="default" :user="user" :fruit="fruit"></slot>
      <slot name="cellphone" :cellphone="cellphone" :fruit="fruit"></slot>
    </div>
  </div>`,
  data() {
    return {
      user: {
        lastName: "Linda",
        firstName: "KUO",
        age: 9
      },
      fruit: ["apple", "banana", "orange"],
      cellphone: ["iphone", "samsung", "oppo"]
    };
  }
});
</script>

動態插槽名

可以透過 v-slot:[動態插槽名] 來動態可以模組標籤內容的位置。
另須注意動態插槽名需全為小寫,因為 Vue 會自動將該動態插槽名轉為小寫,而導致在外層找不到該筆資料( dynamicslotname!==dynamicSlotName )。

<dynamic-slot>
  <!--動態插槽名要全為小寫-->
  <!--error-->
  <template v-slot:[dynamicSlotName]>
    I am {{dynamicSlotName}}
  </template>
  <template v-slot:[lowercaseslotname]>
    I am {{lowercaseslotname}}
  </template>
</dynamic-slot>
<script>
Vue.component("dynamic-slot", {
  template: `<div>
      <slot name="header">Here is header</slot>
      <br/>
      <slot name="container">Here is container</slot>
      <br/>
      <slot name="footer">Here is footer</slot>
      <slot></slot>
  </div>`
});
const vm = new Vue({
  el: "#vm",
  data: {
    user: {
      name: "Celeste"
    },
    dynamicSlotName: "header",
    lowercaseslotname: "footer"
  }
});
</script>

v-slot 縮寫

可以將以 # 來縮寫 v-slot ,像是 v-slot:header 可以縮寫成 #headerv-slot:footer={ user } 可以縮寫成 #footer={ user } ,另外要注意使用縮寫字符時一定要有參數,例如 #footer={ user }footer 即為參數,不可以只有 #={ user } ,這樣會出錯,如果是預設值的狀況可以寫成 #default={ user }

Demo:DAY14 | 跟 Vue.js 認識的30天 - Vue 模組插槽(slot)

參考資料:

Vue.js - 插槽


上一篇
[DAY13] 跟 Vue.js 認識的30天 - Vue 模組自定義事件(Custom Events)
下一篇
[DAY15]跟 Vue.js 認識的30天 - Vue 動態模組(Dynamic Components)
系列文
跟 VueJS 認識的30天15

尚未有邦友留言

立即登入留言