Vue 組件:$ref & 父子組件的數據傳遞 & 組件參數校驗與非 props 特性 & 給組件綁定原生事件 & 非父子組件間的傳值 & 在 Vue 中使用插槽 & 作用域插槽 & 動態組件

使用組件的細節點

<table>
	<tbody>
	  <row></row>
	  <row></row>
	  <row></row>
	</tbody>
</table>
<script>
Vue.component('row', {
  template: '<tr><td>This is a row</td></tr>'
})
var vm = new Vue({
  el: '#app'
})
</script>

我們發現 tr 不在 tbody 裏面,原因是 HTML5 規定 table 下面必須是 tbody,tbody 下面必須是 tr。
所以我們需要在 tbody 下面寫 tr,但是我們可以通過 is 屬性來指明模版。

<table>
	<tbody>
	  <tr is="row"></tr>
	  <tr is="row"></tr>
	  <tr is="row"></tr>
	</tbody>
</table>
<script>
Vue.component('row', {
  template: '<tr><td>This is a row</td></tr>'
})
var vm = new Vue({
  el: '#app'
})
</script>

<div id="app">
  <table>
    <tbody>
      <tr is="row"></tr>
      <tr is="row"></tr>
      <tr is="row"></tr>
    </tbody>
  </table>
</div>
<script>
  Vue.component('row', {
    data() {
      return {
        content: 'This is a row'
      }
    },
    template: '<tr><td>{{content}}</td></tr>'
  })
  var vm = new Vue({
    el: '#app'
  })
</script>

在子組件中定義 data 時,data 必須是一個函數,不能是一個對象。


ref 引用

我希望 div 被點擊的時候,把 div 的內容打印出來。

<div id="app">
  <div ref="hello" @click="handleClick">hello world</div>
</div>
<script>
  var vm = new Vue({
    el: '#app',
    methods: {
      handleClick() {
        console.log(this.$refs.hello.innerHTML); 
      }
    }
  })
</script>

$refs 指所有引用, .hello 就是 ref=“hello” 這個引用。

$ref 在組件中的使用:

「 計數器 」
<div id="app">
  <counter></counter>
</div>
<script>
  Vue.component('counter', {
    data() {
      return {
        number: 0
      }
    },
    template: '<div @click="handleClick">{{number}}</div>',
    methods: {
      handleClick() {
        this.number++;
      }
    }
  })
  var vm = new Vue({
    el: '#app'
  })
</script>

「 counter 求和 」
<body>
  <div id="app">
    <counter ref="one" @change="handleChange"></counter>
    <counter ref="two" @change="handleChange"></counter>
    <div>total: {{total}}</div>
  </div>
  <script>
    Vue.component('counter', {
      data() {
        return {
          number: 0
        }
      },
      template: '<div @click="handleClick">{{number}}</div>',
      methods: {
        handleClick() {
          this.number++;
          this.$emit('change');
        }
      }
    })
    var vm = new Vue({
      el: '#app',
      data: {
        total: 0
      },
      methods: {
        handleChange() {
          this.total = this.$refs.one.number + this.$refs.two.number;
        }
      }
    })
  </script>
</body>

父子組件的數據傳遞

父組件通過屬性的形式向子組件傳遞數據。
子組件通過 props:[] 接收數據。

<div id="app">
  <counter :count="0"></counter>  <!-- 通過:count屬性傳值 -->
  <counter :count="1"></counter>

</div>
<script>
  var counter = {
    props: ['count'],
    template: '<div>{{count}}</div>'
  }
  var vm = new Vue({
    el: '#app',
    components: {
      counter: counter
    }
  })
</script>

Vue 單向數據流

父組件可以向子組件通過屬性的形式傳遞參數,傳遞的數據可以隨便進行修改。
但是子組件只能使用父組件傳遞過來的數據,不能修改父組件傳遞過來的數據。

一個父組件可以傳值給多個子組件。一旦子組件接收的是引用形式的數據時,在子組件中改變了數據內容,會影響其它的子組件。

子組件使用父組件傳遞過來的數據時,不能修改。這個時候可以複製一份:(在 data 裏定義一個 number,把父組件的值賦值給 number)

 <div id="app">
  <counter :count="0"></counter>
  <counter :count="1"></counter>
</div>
<script>
  var counter = {
    props: ['count'],
    data() {
      return {
        number: this.count
      }
    },
    template: '<div @click="handleClick">{{number}}</div>',
    methods: {
      handleClick() {
        this.number++;
      }
    }
  }
  var vm = new Vue({
    el: '#app',
    components: {
      counter: counter
    }
  })
</script>

子組件通過事件的形式向父組件傳值。子組件被點擊的時候,可以通過 this.$emit('change') 觸發一個 change 事件。

<div id="app">
  <counter :count="0" @change="handleIncrease"></counter>
  <!-- @監聽事件,名字可以隨便寫(比如可以寫成 @inc),和下面 this.$emit 裏的對應即可。 -->
  <counter :count="1" @change="handleIncrease"></counter>
  <div>total: {{total}}</div>
</div>
<script>
  var counter = {
    props: ['count'],
    data() {
      return {
        number: this.count
      }
    },
    template: '<div @click="handleClick">{{number}}</div>',
    methods: {
      handleClick() {
        this.number++;
        this.$emit('change', 1);
      }
    }
  }
  var vm = new Vue({
    el: '#app',
    data: {
      total: 1
    },
    components: {
      counter: counter
    },
    methods: {
      handleIncrease(step) {
        this.total += step;
      }
    }
  })
</script>

this.$emit('change', 1); 的第二個參數實際上和上面那句 this.number++; 相對應,代表增長的步長。比如,上面寫成 this.number=this.number+2; 則可以寫成 this.$emit('change', 2); 用以告訴父組件增加了多少。


組件參數校驗與非 props 特性

組件參數校驗

我們都知道父組件通過屬性(content="hello world")向子組件傳遞參數,那組件的參數校驗是什麼?
父組件向子組件傳遞了一些內容,子組件有權對這些內容做一個約束。這些約束就可以叫做參數的校驗。

比如:需求是傳遞過來的 content 必須是一個字符串。

<div id="app">
 <child :content="{}"></child> <!-- :content 有:時,後面跟的是js變量,沒有:時,後面的值是字符串-->
</div>
<script>
  Vue.component('child', {
    props: {
      content: String
    },
    template: '<div>{{content}}</div>'
  })
  var vm = new Vue({
    el: '#app',
  })
</script>

如果 content 不是一個字符串(現在是{},一個對象),控制檯就會報錯。
在這裏插入圖片描述
參數校驗可以是一個數組(有多個條件):

content: [Number, String]

也可以是一個對象:

<child content="hello"></child>
content: {
  type: String,               // 類型
  required: true,             // 是否必須存在
  default: 'default value'    // 默認值
  validator(value) {          // 自定義校驗器
    return (value.length > 5)
  }
}

非 props 特性

props 特性指:當父組件使用子組件時,通過屬性向子組件傳值的時候,恰好子組件中聲明瞭對父組件傳遞過來的屬性的接收(props: )。父子組件有一個對應關係。
父組件傳,子組件接。

非 props 特性,子組件中沒有聲明。沒有接收父組件傳的值。沒辦法使用。
父組件傳,子組件不接。
聲明瞭一個非 props 特性的時候,其屬性會展示在子組件最外層的 dom 標籤的 html 屬性裏面。

給組件綁定原生事件

<div id="app">
  <child @click="handleClick"></child>   <!-- 這裏綁定的事件是監聽的自定義事件(目前無效)-->
</div>
<script>
  Vue.component('child', {
    template: '<div @click="handleChildClick">Child</div>',  // 這裏綁定的事件是監聽的原生事件
    methods: {
      handleChildClick() {
        console.log("handleChildClick");
      }
    }
  })
  var vm = new Vue({
    el: '#app',
    methods: {
      handleClick() {
      	this.$emit('click')
        console.log("handleClick");  // (目前無效)
      }
    }
  })
</script>

監聽的自定義事件如何被觸發?子組件想觸發父組件的事件的監聽,需要調用 this.$emit() 方法。

<div id="app">
  <child @click="handleClick"></child>
</div>
<script>
  Vue.component('child', {
    template: '<div @click="handleChildClick">Child</div>',
    methods: {
      handleChildClick() {
        this.$emit('click');
        // console.log("handleChildClick");
      }
    }
  })
  var vm = new Vue({
    el: '#app',
    methods: {
      handleClick() {
        console.log("handleClick");
      }
    }
  })
</script>

還有什麼更簡單的方法嗎?

<child @click.native="handleClick"></child>

可以在事件綁定的後面加上 .native 修飾符,監聽原生事件。

非父子組件間的傳值

利用總線機制解決非父子組件間的傳值問題。
( Bus / 總線 / 發佈訂閱模式 / 觀察者模式 )

比如:我想要點擊上面的 div 時,把其內容改成 daze;點擊下面的 div 時,把其內容改成 bule。

<div id="app">
  <child content="bule"></child>
  <child content="daze"></child>
</div>
<script>
  Vue.prototype.bus = new Vue()   //bus總線
  Vue.component('child', {
    data() {
      return {
        selfContent: this.content
      }
    },
    props: {
      content: String
    },
    template: '<div @click="handleClick">{{selfContent}}</div>',
    methods: {
      handleClick() {
        this.bus.$emit('change', this.selfContent);
      }
    },
    mounted() {
      var this_ = this;
      this.bus.$on('change', function (msg) {
        this_.selfContent = msg;
      })
    }
  })
  var vm = new Vue({
    el: '#app'
  })
</script>

在 Vue 中使用插槽

<div id="app">
  <child content='<p>bule_daze</p>'></child>
</div>
<script>
  Vue.component('child', {
    props: ['content'],
    template: `<div>
                  <p>hello</p>
                  <div v-html="this.content">{{content}}</div>
                </div>`
  })
  var vm = new Vue({
    el: '#app'
  })
</script>

如果我不想要 p 標籤外面顯示 div 標籤,應該如何去做?

template 佔位符在這裏是不好用的。

而通過 content 傳值,想直接使用 content 裏面的 p 標籤是有問題的,必須在外面包裹一個 div (<div v-html="this.content">);且當傳遞的內容很多時,代碼不利於閱讀。

當子組件有一部分內容是根據父組件傳遞過來的 DOM 進行顯示時,我們可以利用插槽 slot

<div id="app">
  <child>
    <p>bule_daze</p>
  </child>
</div>
<script>
Vue.component('child', {
  props: ['content'],
  template: `<div>
                <p>hello</p>
                <slot></slot>
              </div>`,

})
var vm = new Vue({
  el: '#app'
})
</script>

默認值

slot 插槽還可以定義默認值:<slot>默認內容</slot>(當父組件使用子組件時不傳遞插槽的內容時,纔會顯示出默認內容。<child></child>

具名插槽

如果只寫成 <slot></slot>,代表插槽中的所有內容。如果我希望 slot 的內容分別對應,可以利用具名插槽,即給插槽取個名字。

  • 父組件中 slot="header"
  • 子組件中 <slot name="header"></slot>
<div id="app">
  <my-content>
    <div class="header" slot="header">header</div>
    <div class="footer" slot="footer">footer</div>
  </my-content>
</div>
<script>
  Vue.component('my-content', {
    template: `<div>
                  <slot name="header"></slot>  
                  <div class="content">content</div>
                  <slot name="footer"></slot>
                </div>`
  })
  var vm = new Vue({
    el: '#app'
  })
</script>

具名插槽也可設置默認值。

作用域插槽

<div id="app">
 <child></child>
</div>
<script>
  Vue.component('child', {
    data() {
      return {
        list: [1, 2, 3, 4]
      }
    },
    template: `<div>
                  <ul>
                    <li v-for="item of list">
                      {{item}}
                    </li>
                  </ul>
    		   </div>`
  })
  var vm = new Vue({
    el: '#app'
  })
</script>

現在有一個需求,child 組件有可能在很多的地方被調用。我們希望在不同的地方調用 child 組件的時候,列表怎樣循環列表的樣式不是由 child 組件控制的,而是外部告訴我們的。(即 template 裏面需要把 li 去掉)

需要用到作用域插槽:

<div id="app">
  <child>
    <template slot-scope="props">
      <li>{{props.item}}-hello</li>
    </template>
  </child>
</div>
<script>
  Vue.component('child', {
    data() {
      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: '#app'
  })
</script>

父組件調用子組件的時候,給子組件傳了一個插槽,這個插槽叫做作用域插槽。作用域插槽必須是一個以 <template></template> 開頭和結尾的內容;且這個插槽要聲明其要接收的數據都放在哪(slot-scope="props" 都放在 props 裏面);還需要告訴子組件一個模版的信息,接到 props 應該如何展示(<li>{{props.item}}-hello</li>)。

使用情境

當子組件做循環時,或者某一部分的 DOM 結構應該由外部傳遞進來的時候,使用作用域插槽。

使用作用域插槽時,子組件可以向父組件的插槽裏面傳數據。父組件如果想要接收這個數據,必須在外層使用 template,同時通過 slot-scope 對應的屬性的名字來接收傳遞過來的所有的數據。

動態組件與 v-once 指令

<div id="app">
  <child-one></child-one>
  <child-two></child-two>
  <button>change</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: '#app'
  })
</script>

現在想實現當點擊 change 按鈕的時候,一會 child-one 進行顯示,一會 child-two 進行顯示:

<div id="app">
  <child-one v-if="type === 'child-one'"></child-one>
  <child-two v-if="type === 'child-two'"></child-two>
  <button @click="handleBtnClick">change</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: '#app',
    data: {
      type: 'child-one'
    },
    methods: {
      handleBtnClick() {
        this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
      }
    }
  })
</script>

動態組件

可以利用動態組件的方式來實現上述需求。

<div id="app">
  <component :is="type"></component>
  <button @click="handleBtnClick">change</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: '#app',
    data: {
      type: 'child-one'
    },
    methods: {
      handleBtnClick() {
        this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
      }
    }
  })
</script>

<component></component> 是 Vue 裏面自帶的一個標籤,指的就是動態組件。動態組件的意思就是根據屬性 is 裏面數據的變化自動加載不同的組件。

v-once

上面的動態組件在每次點擊按鈕切換的時候,Vue 底層會幫我們進行判斷,第一個組件不用了,就會銷燬第一個組件,創建第二個組件;當第二個組件不用了的時候,就會銷燬第二個組件,創建第一個組件。

這種操作會耗費一定的性能。如果組件的內容每一次都一樣,我們可以使用 v-once 指令:

Vue.component('child-one', {
  template: '<div v-once>child-one</div>'
})
Vue.component('child-two', {
  template: '<div v-once>child-two</div>'
})

第一次被渲染的時候,會保存至內存中;當再次使用的時候不需要重新創建,而是直接從內存中拿。
v-once 可以提高靜態內容的展示效率

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章