Vue基礎:元件--slot、非同步元件、遞迴元件及其他
slot分發內容
為了讓元件可以組合,我們需要一種方式來混合父元件的內容與子元件自己的模板。這個過程被稱為內容分發。Vue中使用特殊的 <slot>
元素作為原始內容的插槽。
問題(編譯作用域)
message
應該繫結到父元件的資料,還是繫結到子元件的資料?
<child-component>
{{ message }}
</child-component>
答案是父元件。父元件模板的內容在父元件作用域內編譯;子元件模板的內容在子元件作用域內編譯。
單個slot
除非子元件模板包含至少一個 <slot>
插口,否則父元件的內容將會被丟棄
<slot>
標籤中的任何內容都被視為備用內容。備用內容在子元件的作用域內編譯,並且只有在宿主元素為空,且沒有要插入的內容時才顯示備用內容。
具名slot
<slot>
元素可以用一個特殊的屬性 name
來配置如何分發內容。多個 slot 可以有不同的名字。具名 slot 將匹配內容片段中有對應 slot
特性的元素。仍然可以有一個匿名 slot,它是預設 slot,作為找不到匹配的內容片段的備用插槽。如果沒有預設的 slot,這些找不到匹配的內容片段將被拋棄。
<div id="app">
<my-component>
<template>
<p>父元件分發內容</p>
</template>
</my-component>
<my-component2>
<b>內容a</b>
<template slot="title">
父元件分發的title
</template>
<b>內容b</b>
</my-component2 >
</div>
<script>
const app = new Vue({
el: '#app',
components: {
'my-component': {
template: `<div><slot><p>備選內容</p></slot></div>`
},
'my-component2': {
template: `<div><slot name="title">備選title</slot><slot><b>備選內容</b></slot></div>`
}
}
});
</script>
渲染結果:
<div>
<p>父元件分發內容</p>
</div>
<div>
父元件分發的title
<b>內容a</b>
<b>內容b</b>
</div>
作用域插槽
在子元件插槽中可以通過slot插槽標籤的屬性將資料傳遞到父元件要分發的內容當中,父元件要通過<template scope=""></template>
模板來接收子元件插槽傳遞上來的資料。
<my-component>
<template scope="props">
<p>父元件分發內容</p>
<p>{{props.message}}</p>
</template>
</my-component>
<script>
const app = new Vue({
el: '#app',
components: {
'my-component': {
template: `<div><slot message="哈哈"></slot></div>`
}
}
});
</script>
渲染結果:
<div>
<p>父元件分發內容</p>
<p>哈哈</p>
</div>
具代表性的列表組:
<my-list :list="['a.com', 'b.com']">
<template slot="item" scope="props">
<li class="link"><a>{{props.link}}</a></li>
</template>
</my-list>
<script>
// ...
'my-list': {
props: ['list'],
template: `<ul><slot name="item" v-for="item in list" :link="item"></slot></ul>`
}
</script>
渲染結果:
<ul>
<li class="link"><a>a.com</a></li>
<li class="link"><a>b.com</a></li>
</ul>
動態元件
動態地繫結到它的 is
特性,讓多個元件可以使用同一個掛載點,並動態切換:
<component v-bind:is="currentView">
<!-- 元件在 vm.currentview 變化時改變! -->
</component>
var vm = new Vue({
el: '#example',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
keep-alive可以把切換出去的元件保留在記憶體中,保留它的狀態,避免重新渲染。
<keep-alive>
<component :is="currentView">
<!-- 非活動元件將被快取! -->
</component>
</keep-alive>
雜項
編寫可複用元件
可複用元件應當定義一個清晰的公開介面,同時也不要對其使用的外層資料作出任何假設。
- Prop 允許外部環境傳遞資料給元件;
- 事件允許從元件內觸發外部環境的副作用;
- 插槽允許外部環境將額外的內容組合在元件中。
<my-component
:foo="baz"
:bar="qux"
@event-a="doThis"
@event-b="doThat"
>
<img slot="icon" src="...">
<p slot="main-text">Hello!</p>
</my-component>
子元件引用
儘管有 prop 和事件,但是有時仍然需要在 JavaScript 中直接訪問子元件。為此可以使用 ref
為子元件指定一個引用 ID。
<div id="parent">
<user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({ el: '#parent' })
// 訪問子元件例項
var child = parent.$refs.profile
注意:$refs
只在元件渲染完成後才填充,並且它是非響應式的。它僅僅是一個直接操作子元件的應急方案——應當避免在模板或計算屬性中使用 $refs
。
ref
被用來給元素或子元件註冊引用資訊。引用資訊將會註冊在父元件的 $refs
物件上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子元件上,引用就指向元件例項,可以通過$el,獲取DOM元素。
非同步元件
Vue.js 允許將元件定義為一個工廠函式,非同步地解析元件的定義。
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 將元件定義傳入 resolve 回撥函式
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
Vue.component('async-webpack-example', function (resolve) {
// 這個特殊的 require 語法告訴 webpack
// 自動將編譯後的程式碼分割成不同的塊,
// 這些塊將通過 Ajax 請求自動下載。
require(['./my-async-component'], resolve)
})
webpack 2 + ES2015 的語法時可以這樣:
Vue.component(
'async-webpack-example',
() => import('./my-async-component')
)
元件命名約定
當註冊元件 (或者 prop) 時,可以使用 kebab-case (短橫線分隔命名)、camelCase (駝峰式命名) 或 PascalCase (單詞首字母大寫命名);在 HTML 模板中,請使用 kebab-case。
components: {
myComponent: { /* ... */ }
}
<my-component/>
遞迴元件
一定要確保遞迴呼叫有終止條件,可以通過v-if進行控制。
元件間的迴圈引用
內聯模板
如果子元件有 inline-template
特性,元件將把它的內容當作它的模板,而不是把它當作分發內容。這讓模板編寫起來更靈活。
<my-component inline-template>
<div>
<p>這些將作為元件自身的模板。</p>
<p>而非父元件透傳進來的內容。</p>
</div>
</my-component>
但是 inline-template
讓模板的作用域難以理解。使用 template
選項在元件內定義模板或者在 .vue
檔案中使用 template
元素才是最佳實踐。
X-Template
另一種定義模板的方式是在 JavaScript 標籤裡使用 text/x-template
型別,並且指定一個 id。例如:
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
template: '#hello-world-template'
})
注意:在有很多大模板的演示應用或者特別小的應用中可能有用,其它場合應該避免使用,因為這將模板和元件的其它定義分離了。
對低開銷的靜態元件使用v-once
儘管在 Vue 中渲染 HTML 很快,不過當元件中包含大量靜態內容時,可以考慮使用 v-once
將渲染結果快取起來,就像這樣:
Vue.component('terms-of-service', {
template: '\
<div v-once>\
<h1>Terms of Service</h1>\
...很多靜態內容...\
</div>\
'
})