iT邦幫忙

2021 iThome 鐵人賽

DAY 12
0

元件的概念大概有了部分的了解,同一個元件可以重複利用,但這句話好像有個問題,不同頁面使用相同架構的元件內容肯定需要做替換,這時候就是slot要出場啦!

元件編譯作用域

編譯作用域的意思就是「變數的作用範圍」。

<h1>{{msg}}</h1>
<my-component>{{msg}}</my-component>
const app = Vue.createApp({
    data() {
        return {
            msg: 'Parent'
        }
    }
});
app.component('my-component', {
    template: `<h2>Hello!</h2>`,
    data() {
        return {
            msg: 'Child'
        }
    },
})

當內外層都有data時,畫面會出現

Untitled

my-component內的msg不是出現Child而是template的內容,這表示元件在編譯的時候會直接忽略內容物,直接放入template中的模板。

slot插槽

對子元件鑽洞,讓外層元件的資料可以放入。

app.component('my-component', {
    template: `
    <h2>Hello!
        <slot></slot>   
    </h2>`,
    data() {
        return {
            msg: 'Child'
        }
    },
})

將slot放入template中,可以得出這樣的結果

Untitled

為什麼slot內容物不是Child???

slot特性是保留一個空間可以從外部傳入內容,而子元件本身對slot並沒有控制權。 ——重新認識 Vue.js

若想要給予子元件一個「預設內容」,可以直接在slot中放上預設內容。

<my-component></my-component>
app.component('my-component', {
    template: `
    <h2>Hello!
        <slot>預設內容</slot>   
    </h2>`,
    data() {
        return {
            msg: 'Child'
        }
    },
})

具名插槽

上面的範例是當只有一個內容物需要做變更的情況,當template內有很多需要換內容的時候就需要幫他們命名,讓資料換到對應的位置。

  • v-slot:名稱 : 會到指定的位置(相同slot名稱)顯示
  • 沒有給名稱 : 在模板上就會被放到沒有slot名稱的位置

Untitled

<light-box>
    <template v-slot:header>
        <h1>這是header</h1>
    </template>
    <div>
        沒有給名字的slot
    </div>
</light-box>
const app = Vue.createApp({
    data() {
    }
});
app.component('light-box', {
    template: `
    <div class="lightbox">
    <div class="modal-mask" :style="modalStyle">
        <div class="modal-container" @click.self="toggleModal">
            <div class="modal-body">
                <header>
                <slot name="header">Default header</slot>
                </header>
                <hr>
                <main>
                    <slot>Default body</slot>
                </main>
                <hr>
                <footer>
                    <slot name="footer">Default footer</slot>
                </footer>
            </div>
        </div>
    </div>
    <button @click="isShow = true">Click</button>
</div>`,
    data: () => ({
        isShow: false
    }),
    computed: {
        modalStyle() {
            return {
                'display': this.isShow ? '' : 'none'
            }
        }
    },
    methods: {
        toggleModal() {
            console.log('click')
            this.isShow = !this.isShow
        }
    }
})

動態切換具名插槽

方法其實和昨天的is差不多,在這邊的寫法改為v-slot:[],可以自己選擇位置放入更新內容。

Untitled

<label v-for="opt in options">
    <input type="radio" :value="opt" v-model="dynamic_slot_name">{{opt}}
</label>
<light-box>
    <template v-slot:[dynamic_slot_name]>
        <h1>更新後內容</h1>
    </template>
</light-box>
const app = Vue.createApp({
    data() {
        return {
            options: ['header', 'footer', 'default'],
            dynamic_slot_name: 'header'
        }
    }
});

Scoped Slots

slot資料都是由父層提供,若希望子元素的data也可以顯示就可以用到以下方式

  1. :hello="helloString[lang]" 綁定子元素資料
  2. 插槽v-slot:default=""{hello}接收並吐值給下面的{{hello}}

Untitled

<p>
    請選擇
    <select v-model="lang">
        <option v-for="n in langOptions" :value="n.val">
            {{n.name}}
        </option>
    </select>
</p>
<light-box :lang='lang'>
<!-- 寫法一 -->
    <template v-slot:default="props">
        {{langOptions.find(d=>d.val===lang)['name']}}
        {{props.hello}}
    </template>
<!-- 寫法二(解構) -->
	  <template v-slot:default="{hello}">
        {{langOptions.find(d=>d.val===lang)['name']}}
        {{hello}}
    </template>
</light-box>
const app = Vue.createApp({
    data: () => ({
        langOptions: [{
            name: '繁體中文',
            val: 'tw'
        }, {
            name: 'Deutsch',
            val: 'de'
        }, {
            name: 'English',
            val: 'en'
        }],
        lang: 'tw'
    })
});
app.component('light-box', {
    template: `
    <div class="lightbox">
    <div class="modal-mask" :style="modalStyle">
        <div class="modal-container" @click.self="toggleModal">
            <div class="modal-body">
                <slot name="default" :hello="helloString[lang]"></slot>
            </div>
        </div>
    </div>
    <button @click="isShow = true">Click</button>
</div>`,
    props: {
        lang: {
            type: String,
            default: 'tw'
        }
    },
    data: () => ({
        helloString: {
            'tw': '哈囉',
            'de': 'Hallo',
            'en': 'Hello'
        },
        isShow: false
    }),
    computed: {
        modalStyle() {
            return {
                'display': this.isShow ? '' : 'none'
            }
        }
    },
    methods: {
        toggleModal() {
            console.log('click')
            this.isShow = !this.isShow
        }
    }
})

這部分的內容有點複雜,所以再次改部分名稱看看到底是怎麼傳資料的。

  1. :hello="helloString[lang]"中的:hello傳出值後只是個屬性的名稱,所以換成abc也可以
  2. 當要呼叫的時候{{prop.abc}}就可以看到值出現,或是用解構方式v-slot:default="{abc}"

Untitled

teleport

控制元件選染的位置,像是上面的範例,因為元件的父層不是body,所以灰底的遮罩蓋不到整的頁面,teleport to="body"就可以把她掛載到body

<div class="lightbox">
  <teleport to="body">
      <div class="modal-mask" :style="modalStyle">
          <div class="modal-container" @click.self="toggleModal">
              <div class="modal-body">
                  <slot name="default" :abc="helloString[lang]"></slot>
              </div>
          </div>
      </div>
  </teleport>
  <button @click="isShow = true">Click</button>
</div>

Untitled

參考資料

複用元件的好幫手:Vue Slots(v-slot、Scoped Slots)
https://medium.com/unalai/複用元件的好幫手-vue-slot-v-slot-scoped-slots-5364a0048ab7


上一篇
Day11-動態元件
下一篇
Day13-元件漸變語動畫
系列文
我的Vue學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言