其他章節請看:
vue 的基礎應用(下)
上篇聚焦於基礎知識的介紹;本篇聚焦於基礎知識的應用。
遞迴元件
元件是可以在它們自己的模板中呼叫自身的。不過它們只能通過 name 選項來做這件事。我們實現一個自定義樹的元件。請看示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src='vue.js'></script>
</head>
<body>
<div id='app'>
<custom-tree :list='treeData'></custom-tree>
</div>
<script>
// 遞迴元件 - 自定義樹
Vue.component('custom-tree', {
// 給元件命名
name: 'custom-tree', // {1}
props: ['list'],
template: `
<ul>
<li v-for='item in list'>
{{item.name}}
<!-- v-if 指定退出的條件,防止無線遞迴 -->
<custom-tree
:list='item.children'
v-if='item.children'
></custom-tree>
</li>
</ul>
`
})
const app = new Vue({
el: '#app',
// 資料也需要符合遞迴的條件
data: {
treeData: [ // {2}
{
name: 'a',
children: [
{
name: 'b'
},
{
name: 'c'
}
]
},
{
name: 'd',
children: [
{
name: 'e',
children: [
{
name: 'f'
},
{
name: 'g'
}
]
}
]
}
]
}
})
</script>
</body>
</html>
// 頁面輸出:
a
b
c
d
e
f
g
有3點需要注意:
- 給元件設定 name (行{1})
- 使用一個條件來結束無限遞迴。這裡使用了
v-if
- 資料得滿足遞迴(行{2})
Tip: 後續不在提供完整的程式碼,省略 head、body 等。
動態元件
vue 提供了 <component>
來動態的掛載元件。請看示例:
<div id='app'>
<!-- vue 提供了 <component> 來動態的掛載元件 -->
<component v-bind:is="currentComponent"></component>
<button @click='switchHandle'>切換元件</button>
</div>
<script>
var comC = {
template: `<p>我是元件 C</p>`
};
var app = new Vue({
el: '#app',
data: {
currentComponent: 'comB'
},
// 區域性註冊。components 選項中定義你想要使用的元件
components: {
comA: {
template: `<p>我是元件 A</p>`
},
comB: {
template: `<p>我是元件 B</p>`
},
comC: comC
},
methods: {
switchHandle: function(){
let map = {
'comA': 'comB',
'comB': 'comC',
'comC': 'comA'
};
// 動態切換元件
this.currentComponent = map[this.currentComponent]
}
}
})
</script>
// 頁面輸出:
我是元件 A
切換元件
// 點選按鈕(‘切換元件’),依次顯示'我是元件 B'、'我是元件 C'...
內建的元件 component
根據屬性 is 的值來決定哪個元件被渲染。
nextTick
Vue.nextTick( [callback, context] )
用法:
在下次 DOM 更新迴圈結束之後執行延遲迴調。在修改資料之後立即使用這個方法,獲取更新後的 DOM。—— 不明白的化,請看示例:
<div id="example">{{message}}</div>
<script>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改資料 // {20}
console.log(vm.$el.textContent === 'new message') // false {21}
Vue.nextTick(function () {
console.log(vm.$el.textContent === 'new message') // true {22}
})
</script>
更改資料後(行{20},dom 元素中的內容其實沒有得到更新,輸出 false
(行{21});在 Vue.nextTick() 方法中才被更新,輸出 true
(行{22})。
這裡涉及到 vue 中一個概念:非同步更新。
假如在更改狀態(行{20})後,dom 元素立馬得到更新(行{21}),也就是輸入出 true
,那麼使用者使用 for 迴圈改變某個狀態 100 次,dom 元素就得更新 100 次,是否覺得浪費!所以 vue 的策略是:使用非同步更新,也就是不會馬上更新 dom。
手動掛載
vm.$mount( [elementOrSelector] )
用法:
如果 Vue 例項在例項化時沒有收到 el 選項,則它處於“未掛載”狀態。我們可以使用 vm.$mount() 方法手動。
我們建立一個元件,三秒後再掛載它。請看示例:
<div id="app"></div>
<script>
// Vue.extend() 使用基礎 Vue 構造器,建立一個“子類”
var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})
// 建立並掛載到 #app (會替換 #app)
setTimeout(function(){
// 3 秒後頁面上才會看見 Hello!
new MyComponent().$mount('#app')
}, 3000)
</script>
3 秒後,頁面上才會看見 Hello!
。
數字輸入框元件
需求:數字輸入框只能輸入數字,有兩個按鈕,分別是減1和加1。此外還可以設定初始值、最大值、最小值,數字改變時通知父元件。
請看完整程式碼:
<div id='app'>
父元件 value = {{value}}
<!-- v-model 實現雙向繫結 -->
<custom-number
v-model='value'
:max-value='maxValue'
:min-value='minValue'
:step='10'
></custom-number>
</div>
<script>
Vue.component('custom-number', {
props:{
// 必須是一個數字
value: {
type: Number,
default: 0
},
maxValue: {
type: Number,
// 正無窮大
default: Number.POSITIVE_INFINITY
},
minValue: {
type: Number,
// 負無窮大
default: Number.NEGATIVE_INFINITY
},
// 預設加減 1
step: {
type: Number,
default: 1
}
},
data: function(){
return {
inputValue: this.value
}
},
created: function(){
// 處理:初始值不在最大值和最小值的範圍內
this.update(this.value)
},
computed: {
// 減(-)不可點
minIsDisabled: function(){
return this.inputValue <= this.minValue
},
maxIsDisabled: function(){
return this.inputValue >= this.maxValue
}
},
watch: {
// 監聽 inputValue,通知父元件
inputValue: function(val, oldVal){
this.$emit('input', val)
},
// 父元件改變值,子元件的值也跟著改變
value: function(val){
this.update(val);
}
},
template: `
<div>
<button :disabled="minIsDisabled" @click='minus'> - </button>
<input :value='inputValue' @change='changeHandle' >
<button :disabled="maxIsDisabled" @click='add'> + </button>
<p>
子元件 inputValue = {{inputValue}}
</p>
</div>
`,
methods: {
// 如果輸入值不是數字,則不會更改值
changeHandle: function(e){
var val = e.target.value;
this.update(val, e.target)
},
// obj 是否是數字。摘自 jquery
isNumeric: function(obj) {
return !isNaN( parseFloat(obj) ) && isFinite( obj );
},
minus: function(){
this.update(this.inputValue - this.step);
},
add: function(){
this.update(this.inputValue + this.step);
},
// 是數字才更新
update: function(val, target={}){
if(!this.isNumeric(val)){
// 將 input 值置為上次的值
target.value = this.inputValue;
return;
}
val = Number(val);
// 大於最大值,則重置為最大值
if (val > this.maxValue){
val = this.maxValue
}else if(val < this.minValue){
val = this.minValue
}
this.inputValue = val;
}
}
});
var app = new Vue({
el: '#app',
data: {
value: 10,
maxValue: 100,
minValue: 1
}
})
</script>
// 頁面輸出:
父元件 value = 10
- 10 +
子元件 inputValue = 10
// 點選 +(每次加減10)
父元件 value = 20
- 20 +
子元件 inputValue = 20
// 繼續點選2次
// 減號(-)變為不可點
父元件 value = 1
- 1 +
子元件 inputValue = 1
Tabs 標籤頁
需求:實現一個常用的元件 —— tabs 標籤頁。
注:不明白需求的可以看element-ui-tabs
思路:
- 定義元件 el-tabs
- 定義 el-tabs 的子元件 tab-pane
- 父子元件通訊使用 vm.$parent 和 vm.$children
請看完整程式碼:
<style>
ul{margin:0;padding: 0;border-bottom: 1px solid;margin-bottom: 10px;}
li{display:inline-block;margin-right:10px;cursor:pointer;}
.active{color:#409eff;}
</style>
<div id='app'>
<el-tabs v-model="activeKey">
<el-tab-pane label="使用者管理">
使用者管理內容
<p>我是 A</p>
</el-tab-pane>
<el-tab-pane label="配置管理" name="second">配置管理內容</el-tab-pane>
<el-tab-pane label="角色管理">
角色管理內容
<p>我是 C</p>
</el-tab-pane>
<el-tab-pane label="定時任務補償" name="fourth">定時任務補償內容</el-tab-pane>
</el-tabs>
</div>
<script>
// 父元件
Vue.component('el-tabs', {
props:{
value:{
type: [String, Number]
}
},
data: function(){
return {
currentTab: this.value,
// 存放 tab
tabLists: []
}
},
watch: {
currentTab: function(){
this.updateStatus();
},
// 處理:父元件更改 value
value: function(val, oldVal){
this.currentTab = val
}
},
template: `
<div>
<ul>
<li
v-for='(item, index) in tabLists'
:class='{active: item.name === currentTab}'
@click='handleClick(index)'
>{{item.label}}</li>
</ul>
<slot></slot>
</div>
`,
methods: {
// 取得 tab-pane
getTabPanes: function(){
return this.$children.filter(item => {
return item.$options.name === 'tab-pane'
})
},
// 更新 tabLists
updateTabLists: function(){
let tabLists = [];
this.getTabPanes().forEach((item, index) => {
if(!item.id){
item.id = index
}
tabLists.push({
label: item.label,
name: item.id
})
// 預設展示第一個
if(index === 0){
if(!this.currentTab){
this.currentTab = item.id;
}
}
})
this.tabLists = tabLists;
this.updateStatus()
},
handleClick: function(index){
this.currentTab = this.tabLists[index].name;
console.log(`name = ${this.currentTab}`)
this.updateStatus()
},
// 讓子元件顯示或隱藏
updateStatus: function(){
this.getTabPanes().forEach(item => {
item.updateShow(this.currentTab === item.id)
})
}
}
});
// 子元件
Vue.component('el-tab-pane', {
name: 'tab-pane',
props: {
// 標籤標題
label:{
type: String,
default: ''
},
// pane 的名字,不必填
name: [String, Number]
},
data: function(){
return {
// 顯示與否,由父元件決定
show: false,
// 不允許通過父元件更改 props 中的屬性 name
// 用 id 來替代 name 屬性
id: this.name
}
},
created: function(){
this.$parent.updateTabLists();
},
template: `
<div v-if='show'>
<slot></slot>
</div>
`,
methods: {
updateShow: function(v){
this.show = v;
}
}
});
const app = new Vue({
el: '#app',
data: {
// 當前選中的 tab
activeKey: 2
}
})
</script>
// 頁面輸出:
// 第一行是 4 個 tab,現在是`角色管理`被選中
使用者管理 配置管理 角色管理 定時任務補償
角色管理內容
我是 C
其他章節請看: