一.組件使用的細節點
1.瀏覽器解析問題
<div id="root">
<table>
<tbody>
<row></row>
<row></row>
<row></row>
</tbody>
</table>
</div>
<script>
Vue.component("row",{
template:"<tr><td>this is a row</td></tr>"
})
var vm = new Vue({
el:"#root"
})
</script>
- 三個tr沒有在tbody裏面,而tbody要求必須放有tr
- 可以用is屬性,讓這個tr等價於row
- 這樣解析就不會有問題
<div id="root">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component("row",{
template:"<tr><td>this is a row</td></tr>"
})
var vm = new Vue({
el:"#root"
})
</script>
- ul-li、select-option也是同樣的解決方法
2.組件中的data
- 在子組件中運用data,頁面報錯
<div id="root">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component("row",{
data:{
content:"this is a row"
},
template:"<tr><td>{{content}}</td></tr>"
})
var vm = new Vue({
el:"#root"
})
</script>
- data應該是function 而不是對象
- 在最外層的vue實例中定義data用對象沒問題
- 但是在非根組件中,比如子組件定義時只能用function,而不能用對象格式
<div id="root">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component("row",{
data:function(){
return {
content:"this is a row"
}
},
template:"<tr><td>{{content}}</td></tr>"
})
var vm = new Vue({
el:"#root"
})
</script>
- 子組件定義data,data只能是函數,而不能是對象
- 一個子組件不想根組件只調用一次,它可能在不同的地方,比如row子組件調用三次
- 每個子組件的數據不希望和其他的子組件產生衝突(每個子組件應該有自己的數據,比如上面的row是三套數據,而不是共享一個數據)
- 通過一個函數返回一個對象的目的,讓子組件擁有獨立的數據存儲,不會出現多個子組件相互影響的情況
3.vue中的ref引用
- vue不建議我們在代碼中操作dom
- 但是複雜的動畫效果,光靠vue數據綁定是不行的,所以有些必要情況下必須要操作dom
- vue通過ref引用形式操作dom
<div id="root">
<div ref="hello" @click="handleClick">hello world</div>
</div>
<script>
Vue.component("row",{
data:function(){
return {
content:"this is a row"
}
},
template:"<tr><td>{{content}}</td></tr>"
})
var vm = new Vue({
el:"#root",
methods:{
handleClick:function () {
alert(this.$refs.hello.innerHTML)
}
}
})
</script>
- vue所有的引用下的名爲hello的引用,對應的就是這個div的dom,指向的就是這個div的dom節點
- 如果一個組件上加了ref,this.$refs.xxx就是對應這個組件的引用
- 實現一個點擊第一、二行都自增1,第三行顯示第一行+第二行的和,這裏就要用到ref引用
<div id="root">
<counter ref="one" @change="handleChange"></counter>
<counter ref="two" @change="handleChange"></counter>
<div>{{total}}</div>
</div>
<script>
Vue.component("counter",{
template:"<div @click='handleClick'>{{number}}</div>",
data:function(){
return{
number:0
}
},
methods:{
handleClick:function () {
this.number++;
this.$emit("change")
}
}
})
var vm = new Vue({
el:"#root",
data:{
total:0
},
methods:{
handleChange:function () {
this.total = this.$refs.one.number + this.$refs.two.number
}
}
})
</script>
二.父子組件的間的數據傳遞
1.父組件向子組件數據傳遞
- 此時count中的0和1都只是字符串
<div id="root">
<counter count="0"></counter>
<counter count="1"></counter>
</div>
<script>
var counter = {
template: "<div>0</div>"
}
var vm = new Vue({
el:"#root",
components:{
counter:counter
}
})
</script>
- 加了冒號就是js表達式,也就是數字類型
<div id="root">
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
<script>
var counter = {
template: "<div>0</div>"
}
var vm = new Vue({
el:"#root",
components:{
counter:counter
}
})
</script>
- 子組件要接收這個數據才能使用,所以用props屬性來接收
<div id="root">
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
<script>
var counter = {
props:['count'],
template: "<div>{{count}}</div>"
}
var vm = new Vue({
el:"#root",
components:{
counter:counter
}
})
</script>
- 子組件累加的功能
<div id="root">
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
<script>
var counter = {
props:['count'],
template: "<div @click='handleAdd'>{{count}}</div>",
methods:{
handleAdd:function () {
this.count++
}
}
}
var vm = new Vue({
el:"#root",
components:{
counter:counter
}
})
</script>
雖然可以實現,但是還是報了警告
- 不能直接修改父組件傳遞過來的數據,vue有個單向數據流的概念,一旦接收的count不是基礎類型數據,如果是對象形式,引用類型時,子組件改變了傳遞過來的內容,有可能接收的引用的數據還在被其他子組件所使用,這樣子組件不僅影響了自己,還影響了其他子組件。所以不能改變父組件傳遞過來的數據
- 我們可以拷貝一份來實現這個累加功能
<div id="root">
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
<script>
var counter = {
props:['count'],
data:function(){
return{
number:this.count
}
},
template: "<div @click='handleAdd'>{{number}}</div>",
methods:{
handleAdd:function () {
this.number++
}
}
}
var vm = new Vue({
el:"#root",
components:{
counter:counter
}
})
</script>
2.子組件向父組件數據傳遞
- 簡單說就是讓子組件向外觸發事件
- 父組件監聽這個事件
<div id="root">
<counter :count="2" @change="handleIncrease"></counter>
<counter :count="3" @change="handleIncrease"></counter>
<div>{{total}}</div>
</div>
<script>
var counter = {
props:['count'],
data:function(){
return{
number:this.count
}
},
template: "<div @click='handleAdd'>{{number}}</div>",
methods:{
handleAdd:function () {
this.number = this.number + 2
this.$emit("change",2)
}
}
}
var vm = new Vue({
el:"#root",
components:{
counter:counter
},
data:{
total:5
},
methods:{
handleIncrease:function (msg) {
this.total = this.total + msg
}
}
})
</script>
三.組件參數校驗與非props特性
1.組件參數校驗
- 平常情況下都這麼寫的
<div id="root">
<counter count="123"></counter>
<counter count="321"></counter>
</div>
<script>
Vue.component("counter",{
props:["count"],
template:"<div>{{count}}</div>"
})
var vm = new Vue({
el:"#root"
})
</script>
- 如果你要加參數校驗,就要把props從數組改成對象
- 要求count爲字符串(加了冒號123就變成數字了,所以報錯)
<div id="root">
<counter count="hello"></counter>
<counter :count="123"></counter>
</div>
<script>
Vue.component("counter",{
props:{
count:String
},
template:"<div>{{count}}</div>"
})
var vm = new Vue({
el:"#root"
})
</script>
- 可以設置成多種類型
<div id="root">
<counter count="hello"></counter>
<counter :count="123"></counter>
</div>
<script>
Vue.component("counter",{
props:{
count:[String,Number]
},
template:"<div>{{count}}</div>"
})
var vm = new Vue({
el:"#root"
})
</script>
- 此時傳對象就會報錯
<div id="root">
<counter :count="{a:1}"></counter>
</div>
- 數據後邊也可以跟着對象(類型、是否必要、默認值)
<div id="root">
<counter></counter>
</div>
<script>
Vue.component("counter",{
props:{
count:{
type:String,
required:false,
default:'哈哈'
}
},
template:"<div>{{count}}</div>"
})
var vm = new Vue({
el:"#root"
})
</script>
- 以及要求長度之類的validator對象(校驗器)
<div id="root">
<counter count="hello world"></counter>
</div>
<script>
Vue.component("counter",{
props:{
count:{
type:String,
validator:function (value) {
return (value.length > 5)
}
}
},
template:"<div>{{count}}</div>"
})
var vm = new Vue({
el:"#root"
})
</script>
2.props特性
- 父組件使用子組件時,通過屬性子組件傳值時,子組件申明瞭對父組件傳遞過來的數據的接收
- 比如父組件傳遞content 給子組件,子組件恰好又申明瞭content
- 屬性傳遞不會在dom節點上顯示的,子組件也能把傳遞數據通過插值表達式顯示出來
<div id="root">
<child content="hello world"></child>
</div>
<script>
Vue.component("child",{
props:{
content:{
type:String
}
},
template:"<div>{{content}}</div>"
})
var vm = new Vue({
el:"#root"
})
</script>
3.非props特性
- 父組件傳遞數據,子組件並沒有申明接收父組件傳遞的內容
<div id="root">
<child content="hello world"></child>
</div>
<script>
Vue.component("child",{
template:"<div>hello</div>"
})
var vm = new Vue({
el:"#root"
})
</script>
- 雖然會報錯,但是網頁會顯示dom屬性
- 實際情況下非props特性很少用到
四.給組件綁定原生事件
- 定義一個全局的子組件,按邏輯實例中methods寫這個方法,但是會發現沒有用
<div id="root">
<child @click="handleClick"></child>
</div>
<script>
Vue.component("child",{
template:"<div>Child</div>",
methods:{
handleClick:function () {
alert('1')
}
}
})
var vm = new Vue({
el:"#root"
})
</script>
- 當給組件綁定事件的時候,這個事件綁定的是自定義的事件
- 真正鼠標點擊觸發的事件並不是我綁定的這個click事件
- 在組件中給元素綁定事件,這個纔是原生事件,而Child綁定的click是自定義事件
- 父組件這裏自定義事件監聽用this.$emit
- 點擊時,子組件會監聽到div元素被點擊了,然後執行這個自定義的事件,向外觸發一個自定義click事件,父組件用子組件監聽了這個事件,所以對應代碼會執行 ,但是這樣寫代碼很麻煩
<div id="root">
<child @btn="handleClick"></child>
</div>
<script>
Vue.component("child",{
template:"<div @click='handleChildClick'>Child</div>",
methods:{
handleChildClick:function () {
alert('handleChildClick')
this.$emit("btn")
}
}
})
var vm = new Vue({
el:"#root",
methods:{
handleClick:function(){
alert('handleClick')
}
}
})
</script>
- 如果非要child監聽原生的事件,可以加上.native修飾符就可以
<div id="root">
<child @click.native="handleChildClick"></child>
</div>
<script>
Vue.component("child",{
template:"<div>Child</div>",
})
var vm = new Vue({
el:"#root",
methods:{
handleChildClick:function () {
alert('handleChildClick')
}
}
})
</script>
五.非父子組件間的傳值
1.非父子組件間的傳值介紹
- 假如我們拆分成3個組件
- 第一層和第二層傳遞數據就是父子組件傳值
- 父組件通過props向子組件傳值
- 子組件通過向外觸發事件向父組件傳值
- 那麼萬一是第一層和第三層傳遞數據呢?
- 不能通過屬性直接傳遞數據
- 把第一層數據傳給第二層然後在傳給第三層…反過來也一樣,但是這樣就複雜了
- 萬一是左邊第3層像右邊第3層傳值呢?難道還一層一層傳遞?
- Vue是輕量級視圖框架,不是把事情變複雜的
- 如果遇到複雜的數據傳遞,光靠vue框架是解決不了問題的,要引入一些設計模式之類的
- 一般有兩種解決方式(解決複雜的非父子組件傳值)
- 藉助vue官方的數據層框架vuex(使用起來有難度)—後邊結合項目來說
- 使用bus格式來解決(vue稱爲總線機制/發佈訂閱模式/觀察者模式)
2.總線機制
- 我們通過組件間傳值例子來講解
- 在vue實例掛載一個bus屬性,bus屬性又指向vue的實例
- this.bus指的就是實例上掛載的bus,每個實例上都會有bus這個屬性,而bus又是一個vue實例,所以它下面就會有$emit方法
<div id="root">
<child content="hello"></child>
<child content="world"></child>
</div>
<script>
Vue.prototype.bus = new Vue()
Vue.component("child",{
props:{
content:{
type:String
}
}
template:"<div @click='handleClick'>{{content}}</div>",
methods:{
handleClick:function () {
this.bus.$emit('change',this.content)
}
}
})
var vm = new Vue({
el:"#root"
})
</script>
- 接下來就讓其他組件監聽這個事件,可以藉助生命週期鉤子mounted(組件被掛載後執行的函數),$on監聽bus觸發的事件
<div id="root">
<child content="hello"></child>
<child content="world"></child>
</div>
<script>
Vue.prototype.bus = new Vue()
Vue.component("child",{
props:{
content:{
type:String
}
}
template:"<div @click='handleClick'>{{content}}</div>",
methods:{
handleClick:function () {
this.bus.$emit('change',this.content)
}
},
mounted:function(){
var this_ = this
this.bus.$on('change',function (msg) {
this_.content = msg //注意作用域
})
}
})
var vm = new Vue({
el:"#root"
})
</script>
- 這樣兩個組件傳值通過總線模式就搞定了,但是沒有問題了嗎?
- 雖然實現,但是看瀏覽器還是會報警告(因爲content是父組件傳來的數據,在vue中要時刻有單項數據流的概念)
- 可以用子組件拷貝父組件傳來的數據的方法
<div id="root">
<child content="hello"></child>
<child content="world"></child>
</div>
<script>
Vue.prototype.bus = new Vue()
Vue.component("child",{
props:{
content:{
type:String
}
},
data:function(){
return {
selfContent : this.content
}
},
template:"<div @click='handleClick'>{{selfContent}}</div>",
methods:{
handleClick:function () {
this.bus.$emit('change',this.selfContent)
}
},
mounted:function(){
var this_ = this
this.bus.$on('change',function (msg) {
this_.selfContent = msg //注意作用域
})
}
})
var vm = new Vue({
el:"#root"
})
</script>
六.在vue中使用插槽
1.插槽的使用場景
- 子組件除了展示p標籤外,還要展示一段內容,而這段內容不是子組件決定的,是父組件傳遞過來的
- 以前的想法怎麼傳?可以通過屬性形式向子組件傳值
- 但是你會發現做了轉義
<div id="root">
<child content='<p>world</p>'></child>
</div>
<script>
Vue.component('child',{
props:["content"],
template:'<div><p>hello</p> {{content}}</div>'
})
var vm = new Vue({
el:"#root"
})
</script>
- 所以只能用模板佔位符
<div id="root">
<child content='<p>world</p>'></child>
</div>
<script>
Vue.component('child',{
props:["content"],
template:'<div><p>hello</p> <div v-html="this.content"></div></div>'
})
var vm = new Vue({
el:"#root"
})
</script>
- 這種形式傳遞少可以,但傳遞一旦多了,顯示沒問題,但是代碼很難閱讀,插槽就是解決這個問題出來的
2.插槽的使用
- 顯示的就是父組件向子組件插入進來的這個p標籤
<div id="root">
<child>
<h1>world</h1>
</child>
</div>
<script>
Vue.component('child',{
template:'<div> <p>hello</p> <slot></slot> </div>'
})
var vm = new Vue({
el:"#root"
})
</script>
- 也方便向子組件傳遞dom元素,同時子組件使用插槽內容也很簡單
- 也可以在slot標籤內寫默認內容
3.插槽的命名
- 如果要實現header、content、footer用如下代碼是不行的
- 子組件全部都是插槽的內容,所以就會出現如下下圖的樣子,相當於調用了兩次
- 此時要給插槽命名
<div id="root">
<body-content>
<div class="header">header</div>
<div class="footer">footer</div>
</body-content>
</div>
<script>
Vue.component('body-content',{
template:'<div> <slot></slot> <div class="content">content</div> <slot></slot></div>'
})
var vm = new Vue({
el:"#root"
})
</script>
<div id="root">
<body-content>
<div class="header" slot="header">header</div>
<div class="footer" slot="footer">footer</div>
</body-content>
</div>
<script>
Vue.component('body-content',{
template:'<div> <slot name="header"></slot> <div class="content">content</div> <slot name="footer"></slot></div>'
})
var vm = new Vue({
el:"#root"
})
</script>
- 當然也可以設置默認值(當沒有同名的插槽)
<div id="root">
<body-content>
<div class="footer" slot="footer">footer</div>
</body-content>
</div>
<script>
Vue.component('body-content',{
template:'<div> <slot name="header"><h1>default slot</h1></slot> <div class="content">content</div> <slot name="footer"></slot></div>'
})
var vm = new Vue({
el:"#root"
})
</script>
七.作用域插槽
1.作用域插槽的使用
- 循環遍歷列表
<div id="root">
<child></child>
</div>
<script>
Vue.component('child',{
data:function(){
return {
list:[1,2,3,4]
}
},
template:'<div><ul> <li v-for="item of list">{{item}}</li> </ul></div>'
})
var vm = new Vue({
el:"#root"
})
</script>
- 這個child組件會在很多地方調用,列表樣式應該不是由child控制的,而是由外部決定的
<div id="root">
<child>
<template slot-scope="props">
<h1> {{props.item}} -- hello </h1>
</template>
</child>
</div>
<script>
Vue.component('child',{
data:function(){
return {
list:[1,2,3,4]
}
},
template:'<div><ul> <slot v-for="item of list" :item=item></slot> </ul></div>'
})
var vm = new Vue({
el:"#root"
})
</script>
- 當子組件用slot時,會往slot傳遞一個item數據,item數據放在slot-scope的屬性之中
- 這就不是由子組件決定的了,而是父組件調用子組件的時候給子組件去傳遞對應的模板,這就是作用域槽的使用
- 父組件調用子組件時,給子組件傳了一個插槽,這個插槽是作用域插槽,作用域插槽必須是template的,申明從子組件接收的數據slot-scope放在那裏,告訴子組件模板信息
- 當子組件做循環或者某一個部分dom結構由外部傳遞進來的時候用作用域插槽,子組件可以像父組件插槽傳遞數據
,父組件插槽向接收這個數據,必須外層申明一個template的,接收傳遞數據的放在那裏,傳遞一個item過來 ,接收到這個item
八.動態組件與 v-once 指令
1.v-once指令
- 實現一個動態互相展示的效果
- 不過這種方式都是創建一次 銷燬一次(重新),很消耗性能
<div id="root">
<child-one v-if="type ==='child-one' "></child-one>
<child-two v-if="type ==='child-two' "></child-two>
<button @click="handleClick">交換</button>
</div>
<script>
Vue.component('child-one',{
template:'<div>child-one</div>'
})
Vue.component('child-two',{
template:'<div>child-two</div>'
})
var vm = new Vue({
el:"#root",
data:{
type:'child-one'
},
methods:{
handleClick:function () {
this.type = (this.type === 'child-one'? 'child-two':'child-one')
}
}
})
</script>
- 可以用v-once指令優化
- 第一次展示後都會放到內存裏面,切換不會重新創建組件,直接去內存中拿的,性能更高
<div id="root">
<child-one v-if="type ==='child-one' "></child-one>
<child-two v-if="type ==='child-two' "></child-two>
<button @click="handleClick">交換</button>
</div>
<script>
Vue.component('child-one',{
template:'<div v-once>child-one</div>'
})
Vue.component('child-two',{
template:'<div v-once>child-two</div>'
})
var vm = new Vue({
el:"#root",
data:{
type:'child-one'
},
methods:{
handleClick:function () {
this.type = (this.type === 'child-one'? 'child-two':'child-one')
}
}
})
</script>
2.動態組件
- 也可以用動態組件的形式來展示這個效果
- component是vue自帶的,會根據type屬性自帶的變化加載不同的組件
<div id="root">
<component :is="type"></component>
<button @click="handleClick">交換</button>
</div>
<script>
Vue.component('child-one',{
template:'<div>child-one</div>'
})
Vue.component('child-two',{
template:'<div>child-two</div>'
})
var vm = new Vue({
el:"#root",
data:{
type:'child-one'
},
methods:{
handleClick:function () {
this.type = (this.type === 'child-one'? 'child-two':'child-one')
}
}
})
</script>