Vue.js學習筆記-基礎部分+完整實現(xiàn)代碼

下載安裝搭建環(huán)境

  • 可以選npm安裝,或者簡單下載一個開發(fā)版的vue.js文件
  • 瀏覽器打開加載有vue的文檔時,控制臺提示可以安裝vue控制臺插件,于是去chrome商店安裝,F(xiàn)irefox的插件是通過模擬chrome插件來輔助安裝的。但是第一次點擊Vue Devtools提示“vue.js is not detected”,咦,原來我忘記勾選“允許訪問文件地址”,勾選后就可以正常運行了。

第一個demo

Hello Vue!
關鍵詞:模板語法、聲明式

這里有個小bug,JSFiddle Hello World例子中,并沒有為new Vue賦值變量,于是教程中接下來說的,“修改app.message”就沒法實現(xiàn),認真看教程中代碼可以看到它為其賦值命名為app,下一個例子命名為app-2,命名后就可以在控制臺采用“名稱.屬性”的形式訪問修改內容了。

第二個demo

新指令:v-bind
用法

  • html——v-bind:title="message"
  • js——data:{message:'內容'}

新指令:v-if
用法:根據(jù)值的真假決定是否渲染DOM內容
原理:Vue可以綁定DOM結構到數(shù)據(jù)


新指令:v-for
用法

  • html——<li v-for="todo in todos">{{todo.text}}</li>
  • js——data:{todos:[{text:"內容1"},{text:"內容2"},{text:"內容3"}]}

原理:v-for是一個包裝過的for in循環(huán),對應的data內需要有一個數(shù)組


新指令:v-on
用法

  • html——綁定事件----v-on:click="事件名字"
  • js——methods:{事件名字:function(){處理內容}}

新指令:v-model
用法:

  • html——<input v-model="message">
  • js——data:{message:"hello vue"}

原理:v-model可實現(xiàn)表單輸入和應用狀態(tài)的雙向綁定,修改表單內容,message對應值也會修改。

組件化應用構建

  • 注冊組件
Vue.component('todo-item',{
      template:'<li>這是一個待辦項</li>'
})
  • 使用
<ol>
    <todo-item></todo-item>
</ol>

但是這樣實現(xiàn),會為每個元素綁定相同文本,Vue可以將數(shù)據(jù)從父作用域傳到子組件。

  • 注冊組件
Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})
  • 創(chuàng)建元素并v-bind綁定數(shù)據(jù)到一個對象
<div id="app-7">
  <ol>
    <todo-item v-for="item in groceryList" v-bind:todo="item"></todo-item>
  </ol>
</div>
  • 創(chuàng)建對象
var app7 = new Vue({
  el: '#app-7',
  data: {
    groceryList: [
      { text: '蔬菜' },
      { text: '奶酪' },
      { text: '隨便其他什么人吃的東西' }
    ]
  }
})
  • props是作為一個接口存在的。
  • Vue組件非常類似于自定義元素,但是有一些純自定義元素不具備的重要功能,包括跨組件數(shù)據(jù)流,自定義事件通信和構建工具集成。

Vue實例

  • Vue 構造器
  • Vue.extend({})可以擴展Vue構造器
  • 每個Vue實例都會代理其data對象里面的所有屬性,即實例.屬性===data.屬性
  • 實例引用內部屬性和方法需要加前綴$
vm.$el===document.getElementById('exzample');
  • 實例聲明周期與鉤子
    created是實例被創(chuàng)建后調用,還有mounted、updated、destroyed,鉤子的this指向調用它的實例

模板語法

  • 文本:雙大括號的文本插值(默認是響應式的,對應屬性的值變化,此處內容會更新)
<span>Message:{{ msg}}</span>

也可以通過v-once執(zhí)行一次性插值,不再更新內容,但是這會影響這個節(jié)點的所有數(shù)據(jù)綁定

<span v-once>This will never change:{{message}}</span>
  • 純HTML:用v-html可以輸出真正的html
<div id="app-8" v-html='rawHtml'>   </div>
var app8=new Vue({
        el:'#app-8',
        data:{
            rawHtml:'<li>純html</li>'
        }
    })
//輸出:● 純html

但是要注意只對可信內容使用html插值,不然容易導致xss攻擊

  • 屬性:html屬性不能用雙大括號語法,需要用v-bind。
<div id="app-2">
    <span v-bind:title="message">懸停幾秒看信息</span>
</div>
  • 所有的數(shù)據(jù)綁定都可以用js表達式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>

然而,語句和流控制是不行滴,三元表達式是可以的

{{var a = 1 }}
{{if(ok){return message}}}
  • 指令:帶有v-前綴的特殊屬性,它的值預期是單一js表達式,指定的職責是,當表達式的值改變時相應的將某些行為應用到DOM上。
  • 部分指令接收一個參數(shù),在指令后以冒號指明,比如v-bind:href="url"
    還比如v-on:click="方法名"綁定一個事件
  • 修飾符,以半角句號指明的特殊后綴,指出一個指令應該以特殊方式綁定,.prevent 修飾符告訴 v-on 指令對于觸發(fā)的事件調用 event.preventDefault():
<form v-on:submit.prevent="onSubmit"></form>
  • 過濾器:大括號語法和v-bind表達式,用于文本轉換
<div id="app-9">{{message|format}}</div>
var app9=new Vue({
        el:'#app-9',
        data:{
            message:'hello,Filters!'
        },
        filters:{
            format:function(value){
                value=value.toLowerCase()
            return value.charAt(0).toUpperCase()+value.slice(1)
            }
        }
    })

過濾器可以串聯(lián),可以接受參數(shù)

  • 縮寫:v-bind縮寫為空,后接冒號
<a v-bind:href="url"></a>
<a :href="url"></a>

v-on縮寫為@,并省略了后面的冒號

<a v-on:click="doSomething"></a>
<a @click="doSomething></a>

計算屬性

  • 聲明一個計算屬性,其后的函數(shù),將會用作調用屬性時的getter
    computed:{內容}——computed是關鍵字
  • computed計算屬性 vs methods函數(shù)調用:
    計算屬性基于依賴進行緩存,只要message不變,多次訪問都是返回之前的結果,而函數(shù)調用是只要發(fā)生重新渲染就會執(zhí)行該函數(shù)——例如,在計算緩存里返回Date.now(),不再更新,因為Date.now()不是響應式依賴。
  • computed vs watch
    watch用來觀測,但是有些情況用computed更方便。
  • computed不止有getter,還有setter,需要顯式設置:
var app11=new Vue({
        el:'#app-11',
        data:{
            firstName:'Foo',
            lastName:'Bar'
        },
        computed:{
            fullName:{
                get:function(){
                    return this.firstName+' '+this.lastName
                },
                set:function(newValue){
                    var names = newValue.split(' ')
                    this.firstName=names[0]
                    this.lastName=names[names.length-1]
                }   
            }
            
        }
    })

class和style綁定

對class的增強綁定:

  • 傳入一個對象:
<div id="app-01" v-bind:class="{active:isActive}"></div>
var app01=new Vue({
        el:'#app-01',
        data:{
            isActive:true
        }
    })
//div class="active"
  • v-bind:class可與普通class屬性共存,并可以傳入更多屬性來動態(tài)切換:
<div class="static"
     v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>
  • 還可以綁定為一個對象:
<div id="app-02" v-bind:class='classObject'></div>
var app02=new Vue({
        el:'#app-02',
        data:{
            classObject:{
            active:true,
            'text-danger':false
            }
        }
        
    })
  • 還可以結合計算屬性,加入函數(shù)
computed: {
  classObject: function () {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal',
    }
  }
}

總結:只要冒號后的數(shù)據(jù)形式為‘名稱:布爾值’就可以

  • 可以傳入數(shù)組
    數(shù)組語法涉及邏輯判斷很少,可以理解為對象語法的組合版。
<div v-bind:class="[activeClass, errorClass]">
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
<div v-bind:class="[{ active: isActive }, errorClass]">
  • 可以用在組件上
<my-component v-bind:class="{ active: isActive }"></my-component>

和內置元素的使用差不多

對style樣式增強綁定:
由于默認格式里就包括一對引號,要注意單雙引號的區(qū)分,不然會出錯

<div id='app-03' v-bind:style='{color:activeColor,fontSize:fontSize + "px"}'>
<p>1111</p>
</div>
var app03=new Vue({
        el:'#app-03',
        data:{
            activeColor:'red',
            fontSize:30
        }
    })

條件渲染

  • v-if/v-else
<div id="app-04">
    <p v-if="ok">Yes</p>
    <p v-else>No</p>
</div>
var app04=new Vue({
        el:'#app-04',
        data:{
            ok:false
        }
    })
  • template可以作為一個包裝元素,包裹多個需要同一個v-if的元素
<div id="app-04">
    <template v-if="ok">
    <h1>Title</h1>
    <p>Para</p>
    </template>
    <p v-else>No</p>
</div>
  • 還有一個v-else-if作為鏈式使用判斷
  • template包裝起來,用v-if和v-else判斷的組件,默認是復用的,不想復用,給各自添加一個不同名稱的key就可以了。
    復用的如下:
<div id="app-05">
<template v-if="loginType==='username'">
    <label for="">Username</label>
    <input placeholder="Enter your username">
</template> 
<template v-else>
    <label for="">Email</label>
    <input placeholder="Enter your email address">
</template>
<button v-on:click="toggle">Toggle login type</button>
</div>
var app05=new Vue({
        el:'#app-05',
        data:{
            loginType:'username'
        },
        methods:{
            toggle:function(){
                this.loginType==='username'?this.loginType='email':this.loginType='username'
            }
        }

    })

不復用的如下:

<div id="app-05">
<template v-if="loginType==='username'">
    <label>Username</label>
    <input placeholder="Enter your username" key="username">
</template> 
<template v-else>
    <label>Email</label>
    <input placeholder="Enter your email address" key="email">
</template>
<button v-on:click="toggle">Toggle login type</button>
</div>
  • v-show始終都會渲染,簡單的切換display屬性,不支持template語法,也不支持v-else
<h1 v-show='ok'>222</h1>

v-show vs v-if
v-show適合頻繁切換,v-if適合條件不太改變的情況

列表渲染

  • 基本用法:v-for = "item in items",items是源數(shù)組,item是數(shù)組元素迭代的別名
<ul id="app-06">
    <li v-for="item in items">{{item.message}}</li>
</ul>
var app06=new Vue({
        el:'#app-06',
        data:{
            items:[
            {message:'Foo'},
            {message:'Bar'}
            ]
        }
    })
  • v-for支持索引參數(shù),但是是從0開始滴(改為{{index+1}}就是從1開始了),并且擁有對父作用域的完全訪問權限,可以引用其他屬性,比如例子中的parentMessage
<ul id="app-06">
    <li v-for="(item,index) in items">{{parentMessage}}-{{index}}-{{item.message}}</li>
</ul>
  • 還可以用of替代in作為分隔符,更接近js迭代器
  • 支持template組合語句單元
<ul id="app-07">
    <template v-for="(item,index) in items">
        <span>組合列表{{index+1}}</span>
        <li>{{item.message}}</li>       
    </template>
</ul>
  • 對象迭代
    v-for = "value in object"——object是一個擁有多個屬性的對象,不再是數(shù)組。
    不同于數(shù)組迭代,對象迭代是三個參數(shù),分別為迭代內容,迭代鍵值和參數(shù):(item,key ,index)順序固定
<ul id="app-08">
    <li v-for="(item,key,index) in object">
        {{item}}-{{key}}-{{index}}
    </li>
</ul>
  • v-for也可做簡單的數(shù)字循環(huán):
<ul id='app-09'>
  <li v-for="n in 10">{{n}}</li>
</ul>

但是同樣需要建立Vue對象,

var app09=new Vue({
        el:'#app-09'
    })
  • ‘就地復用’的選擇:與v-if相同,添加唯一key屬性值可以不復用,v-for中最好綁定一個唯一id
<ul id="app-10" >
<li v-for="(item,index) in items"  :key='item.id=index+3'>{{item.message}}-{{index}}-{{item.id}}</li>
</ul>

這里我綁了元素的index在id上

  • 包含一組觀察數(shù)組的變異方法:
  • push()——數(shù)組末端添加新項(返回新長度)
  • pop()——刪除數(shù)組最后一項(返回元素值)
  • shift()——刪除數(shù)組第一項(返回元素值)
  • unshift()——數(shù)組頭部添加新項(返回新長度)
  • splice()——添加或刪除項目
  • sort()
  • reverse()
  • 包含幾個非變異數(shù)組方法(變異是指改變原數(shù)組):
  • filter()
  • concat()
  • slice()
  • 數(shù)組操作方法的局限:
  • 不能通過索引值直接設置一個項:
vm.items[indexOfItem] = newValue
  • 但是可以用set方法設置:
Vue.set(example1.items,indexOfItem,newValue)
  • 或者用萬能的splice:
example1.items.splice(indexOfItem,1,newValue)

注意這里的第二個參數(shù)為"1",表示原地替換原元素

  • 不能直接修改數(shù)組長度:
vm.items.length = newLength

但是依舊可以用萬能的splice:

example1.items.splice(newLength)
第二個參數(shù)為刪除個數(shù),不填第二個參數(shù)時表示刪除到末尾

此處翻閱了犀牛書和高程,高程中沒提到splice()省略第二個參數(shù)的情況,犀牛書提到了,省略第二個參數(shù),從起始點到結尾的所有元素都將被刪除。查了ecma-262的文檔,原文是:

5  If the number of actual arguments is 0, then
     a. Let insertCount be 0.
     b. Let actualDeleteCount be 0.
6  Else if the number of actual arguments is 1, then 
     a. Let insertCount be 0. 
     b. Let actualDeleteCount be len ? actualStart.
  • 顯示過濾、排序結果
  • 使用計算屬性computed
<ul id="app-11">
    <li v-for="n in evenNumbers">{{n}}</li>
</ul>
var app11=new Vue({
        el:'#app-11',
        data:{
            numbers:[1,2,3,4,5]
        },
        computed:{
            evenNumbers:function(){
                return this.numbers.filter(function(number){
                    return number%2 === 0
                })
            }
        }
    })
  • 使用方法,methods——區(qū)別很小,引用時需要傳入參數(shù)
<ul id="app-11">
    <li v-for="n in evenNumbers(numbers)">{{n}}</li>
</ul>
var app11=new Vue({
        el:'#app-11',
        data:{
            numbers:[1,2,3,4,5]
        },
        methods:{
            evenNumbers:function(){
                return this.numbers.filter(function(number){
                    return number%2 === 0
                })
            }
        }
    })

事件處理器

  • 最簡單的把處理代碼寫在v-on:click里:
<div id="app-12">
    <button v-on:click="counter+=1">增加1</button>
    <p>這個按鈕被點擊了{{counter}}次</p>
</div>
var app12=new Vue({
        el:"#app-12",
        data:{
            counter:0
        }
    })
  • 命名一個方法,把方法名寫在v-on:click后
  • 可以傳參數(shù)
<div id="app-13">
    <button v-on:click="say('hi')">1</button>
    <button v-on:click="say('what')">2</button>
</div>
var app13=new Vue({
        el:'#app-13',
        data:{
            message:'new event!'
        },
        methods:{
            say:function(m){
                alert(m)
            }
        }
    })
  • 可以訪問原生DOM事件,用特殊變量$event傳入
<div id="app-14">
    <button v-on:click="mod('event is modified',$event)">submit</button>
</div>
var app14=new Vue({
        el:'#app-14',
        methods:{
            mod:function(message,e){
                if(e)e.preventDefault()
                alert(message)
            }
        }
    })

代碼中點擊button會觸發(fā)一個click事件,把event作為參數(shù)傳入,就可以對這個事件進行操作

  • 事件修飾符
    從方法中拆分出了事件處理過程給指令,表達更直觀。
  • .stop——阻止冒泡
  • .prevent——?
  • .capture——捕獲事件
  • .self——該元素本身(而不是子元素)時觸發(fā)回調
  • .once——點擊事件只會觸發(fā)一次
  • 按鍵修飾符:用于監(jiān)聽鍵盤事件
<input type="text" id="app-1" v-on:keyup.13="submit()">
var app1=new Vue({
   el:'#app-1',
   methods:{
       submit:function(){
           alert('success')
       }
   }
})

keyup.13是"enter"
還有別名:

<input type="text" v-on:keyup.13="submit">
<input type="text" v-on:keyup.enter="submit">
<input type="text" @keyup.enter="submit">
  • .enter
  • .tab
  • .delete
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
  • .ctrl
  • .alt
  • .shift
  • .meta
  • 還可以自定義別名:
Vue.config.keyCodes.f2=113

表單控件綁定

v-model本質上是語法糖,監(jiān)聽輸入事件以更新數(shù)據(jù)

  • 基礎用法:
<div id="app-2">
    <input type="text" v-model="message" placeholder="'edit me">
    <p>Message is:{{message}}</p>   
</div>
var app2=new Vue({
    el:'#app-2',
    data:{
        message:''
    }   
})

多行文本同理

  • 單選框很方便
<div id="app-3">
    <input type="checkbox" id="chechbox" v-model="checked">
    <label for="checkbox">{{checked}}</label>
</div>
var app3=new Vue({
    el:'#app-3',
    data:{
        checked:false
    }
})

這里,checked預設值如果設為空,則開始時label沒有內容,如果設為true,則選框會默認選上,所以最好只設為false

  • 復選框可共用v-model
<div id="app-4">
    <input type="checkbox" value="Jack" v-model="checkedNames" id="jack">
    <label for="jack">jack</label>
    <input type="checkbox" value="John" v-model="checkedNames" id="john">
    <label for="john">john</label>
    <input type="checkbox" value="Mike" v-model="checkedNames" id="mike" >
    <label for="mike">mike</label>
    <br>
    <span>checked names: {{ checkedNames }}</span>
</div>
var app4=new Vue({
    el:'#app-4',
    data:{
        checkedNames:[]
    }
})
  • 單選按鈕
  • 單選列表
<div id="app-6">
    <select name="" id="" v-model="selected">
        <option>A</option>
        <option>B</option>
        <option>C</option>
    </select>
    <span>selected:{{selected}}</span>
</div>
var app06=new Vue({
    el:'#app-6',
    data:{
        selected:""
    }
})

此處遇到一小問題,建立option時,emmet的補全加了屬性value='',如果不改動的話,默認option的值是為空的,選中無效,需要刪除這個屬性或者顯式填值

  • 多選列表,在select處添加屬性,multiple="multiple"就可以了。多選時按住ctrl或command鍵才可以多選
  • 動態(tài)選項值
<div id="app-6">
    <select v-model="selected">
        <option v-for="option in options" v-bind:value="option.value">{{option.text}}</option>
    </select>
    <span>selected:{{selected}}</span>
</div>
var app06=new Vue({
    el:'#app-6',
    data:{
        selected:'A',
        options:[
        {text:'One',value:'A'},
        {text:'Two',value:'B'},
        {text:'Tree',value:'C'}
        ]
    }
})

此例子將選項內容與選項值區(qū)分成兩組數(shù)據(jù),而selected顯示的是value的內容,同上一個小問題相同,option不顯式設置值時,option標簽內容即為值,顯式設置后,value值會覆蓋標簽內容被發(fā)送。

  • 動態(tài)value值
    v-model綁定的value通常是靜態(tài)字符串,通過v-bind可以動態(tài)綁定變量到value上,語法是v-bind:true-value/v-bind:false-value
<div id="app-7">
    <input id="bindvalue" type="checkbox" v-model="toggle" v-bind:true-value="a" v-bind:false-value='b'>
    <label for="bindvalue">{{toggle}}</label>
</div>
var app07=new Vue({
    el:'#app-7',
    data:{
        toggle:'false',
            a:'true',
            b:'false'       
    }
})

例子中綁定了true-value到a變量,false-value到b變量
單選框同理:

<input type="radio" v-model='pick' v-bind:value="a">
  • 修飾符
  • .lazy——默認情況下v-model載input事件中同步輸入框的值與數(shù)據(jù),添加一個.lazy修飾符后,轉變?yōu)樵赾hange事件中同步,問題是,change事件是神馬?存疑
<input v-model.lazy="msg">
  • .number自動將輸入值轉為Number類型
<input v-model.number='age' type='number'>

這個例子玄機很多:
輸入字母e/E情況特殊,會被理解為指數(shù)標志,不同設定效果不同:
1. 只設置type為number:字母輸入無效;e后為有效數(shù)字時會正常顯示,比如:

輸入:123abc456,
框內顯示為:123456,值顯示為123456
、、、
輸入:123e12,
框內顯示為:123e12  值顯示為:123e12

但是超過一定數(shù)值會顯示為空:

(很大很大一個數(shù)字)顯示為:
123e1234 值顯示為:

然而負指數(shù)會正確顯示:

123e-1234 值顯示為 123e-1234
   2. 只設置v-model為number時:
輸入:123abc456,
框內顯示為123abc456,失去焦點時更新為123,
值顯示為123
、、、
輸入:abc123,
框內顯示為abc123,失去焦點沒變化,
值顯示為abc123,說明判定失效啦
、、、
123e12 值顯示為:123000000000000
——同時輸入框內會被更新成123000000000000

指數(shù)e超過一定數(shù)值會顯示為Infinity

123e1234值顯示為:Infinity
——同時輸入框內會被更新成Infinity

在一個范圍內的負指數(shù)會被格式化后顯示,
超過一定值的負指數(shù)會被顯示為0:

123e-123  值顯示為:1.23e-121
123e-1234 值顯示為:0
——同時輸入框內會被更新
   3. 設置type為number并且v-model為number時:
輸入123abc456,
框內顯示為:123456,值顯示為123456
、、、
輸入abc123,
框內顯示為:123,值顯示為123
、、、
123e12 值顯示為:123000000000000
——同時輸入框內會被更新成123000000000000
指數(shù)大于20位會轉化成指數(shù)形式

超過一定數(shù)值顯示為空:

123e1234 值顯示為:

在一個范圍內的負指數(shù)會被格式化后顯示,
超過一定值的負指數(shù)會被顯示為0:

123e-123  值顯示為:1.23e-121
123e-1234 值顯示為:0
——同時輸入框內會被更新

總結:控制數(shù)字輸入的主要依賴type="number",v-model.number是將輸入值轉化為Number類型,但是表現(xiàn)不太穩(wěn)定。

  • .trim自動過濾用戶輸入的首尾空格:
空空abc空空def空空  ——>  abc空空def

組件

  • 基礎語法:
<div id="app-01">
    <my-component></my-component>
</div>
Vue.component('my-component',{
    template:'<div>A custom component!</div>'
})
var app01=new Vue({
    el:'#app-01'
})

要確保,先注冊組件,再初始化實例,調換前后順序會出錯

  • 局部注冊:把組件用components注冊在實例中,注意components為復數(shù)形式,好處是限制了作用域
<div id="app-01">
    <my-component></my-component>
</div>
var app01=new Vue({
    el:'#app-01',
    components:{
        'my-component':{
            template:'<div>A custom component!</div>'
        }
    }
})

注意此處相當于先實例再在實例中注冊組件,因此可以判斷,注冊組件行為不能落后于實例化,但是可以包含在實例的同時進行。

  • DOM模板解析——當把模板加載到一些已存在的元素上時,可能會受到HTML的一些限制,因為像一些ul/ol/table/select限制了能被它包裹的元素,而一些如option的元素只能出現(xiàn)在某些元素內部,因此,有時自定義組件會失效而出錯:
<table>
  <my-row>...</my-row>
</table>

需要使用特殊的is屬性:

<table id="app-01">
    <tr is="my-component"></tr>
</table>

這相當于把組件偽裝成一個tr ,查看瀏覽器文檔結構解析為:

<table id="app-01">
        <tbody>
        <div>A custom component</div>——(組件內容)
        </tbody>
</table>

tbody問題
此處出現(xiàn)的tbody是html解析自動補全的,tbody功能為,劃分表格結構,默認情況下是一個tbody,也可手動建立多個并行的tbody,這影響表格分別渲染及切分(很長的表格換頁的時候斷在哪里就是切分)

  • 限制不適用情況沒看懂(存疑):
    • <script type="text/x-template">----這個解決了,后面有解釋
    • JavaScript內聯(lián)模版字符串
    • .vue 組件
  • 組件關聯(lián)構造器的data必須是函數(shù),否則會報錯,深層次的原因是,每個模板都會引用data,如果data是定值,則每個模板引用的都是同一個,如果data是函數(shù),則返回不同,每個模板才會區(qū)別開
<div id="app-02">
    <simple-counter></simple-counter>
    <simple-counter></simple-counter>
    <simple-counter></simple-counter>
</div>
如果是:
var data={counter:0}
var app02=new Vue({
    el:'#app-02',
    components:{
        'simple-counter':{
            template:'<button v-on:click="counter +=1">{{counter}}</button>',
            data:function(){
                    return data
                }
        }
        
    }
則修改一個模板,其他模板數(shù)字也被修改,因為data的返回值是data自身,而所有模板都會引用data,都有修改權。
改一下:
var app02=new Vue({
    el:'#app-02',
    components:{
        'simple-counter':{
            template:'<button v-on:click="counter +=1">{{counter}}</button>',
            data:function(){
                    return {counter:0}
                }
        }
        
    }
每個模板可以有獨立的值了,因為data作為一個函數(shù)返回的是不同的counter,不會被共用

模板內部的data與外部是沖突的

構成組件
<div id="app-03">
    <child message="hello"></child>
    <div>{{value}}</div>
</div>
var app03=new Vue({
    el:'#app-03',
    components:{
        'child':{
            template:'<span>{{message}}</span>',
            props:['message']
        }
    },
    data:{
        value:'yyy'
    }
})

此例子中,props請求的message通過組件元素傳入,即特性傳入,props與外部的data不沖突

  • 特性命名問題:
  • 矛盾點一:html的特性不區(qū)分大小寫
  • 矛盾點二:Vue中除了模板命名,其他命名不允許出現(xiàn)小橫杠 ‘-’
    于是當兩個單詞串聯(lián)成一個名字時,怎么區(qū)分呢?
    vue貌似做了一個命名自動轉換,在js文件內,命名為駝峰式,camerCase,進入html文件,自動轉換成短橫線隔開式,kebab-case,對應的是同一個變量
props:['myMessage']——js內
<child-11 my-message="hello"></child-11>——html內
  • 也可以用v-bind動態(tài)綁定prop到一個model
<div id="app-03">
    <input type="text" v-model='parentMsg'>
    <child-11 v-bind:my-message="parentMsg"></child-11>
</div>
var app03=new Vue({
    el:'#app-03',
    components:{
        'child-11':{
            template:'<span>{{myMessage}}</span>',
            props:['myMessage']
        }
    },
    data:{
        parentMsg:''
    }
})
  • 字面量語法vs動態(tài)語法:
<comp some-prop='1'></comp>
由于這是一個字面prop,傳遞了一個字符串'1',它的值是字符串‘1’而不是number
<comp v-bind:some-prop='1'></comp>
這樣會把值當做一個javascript表達式計算,才是number
(存疑)
- prop是單向綁定:父組件屬性變化時,將傳到給子組件,但不會反過來。同時,每次父組件更新時,子組件的所有prop都會更新為最新值,這意味著,不應該在子組件內部改變prop。但是還要用怎么辦呢?
  - 定義一個局部變量,用prop初始化
  - 定義一個計算屬性,處理prop值
然而,js中,對象和數(shù)組是引用類型,指向同一個內存空間,如果prop是對象或者數(shù)組,在子組件內部改變也會影響父組件的狀態(tài)。
- Prop驗證

<div id="app-04">
<child prop-c="111"></child>

<child prop-c="333" prop-d="222"></child>
</div>
var app04=new Vue({
el:'#app-04',
components:{
'child':{
template:'<span>{{propC}}-{{propD}}-{{propE}}</span>',
props:{
propC:{
type:String,
required:true
},
propD:[String,Number],
propE:{
type:[String,Number],
default:100
}
}
}
}
})

有幾種驗證:
 - 指定類型驗證:

單個的,propA:Number;
多個的,propB:[String,Number]

 - 是否必須存在的驗證:

propC:{
type:String,
required:true
}

 - 帶默認值的驗證:

propD:{
type:Number,
default:100
}

默認值可以是函數(shù):

propE:{
type:Object,
default:function(){
return { message:'hello'}

 - 還可以自定義驗證函數(shù):

propF:{
validator:function(value){
return value>0
}
這個驗證失敗時是控制臺報錯


- type的類型是以下原生構造器:
 - String
 - Number
 - Boolean
 - Function
 - Object
 - Array

#####自定義事件

<div id="app-05">
<p>{{total}}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
var app05=new Vue({
el:'#app-05',
components:{
'button-counter':{
template:'<button v-on:click="increment">{{counter}}</button>',
data:function(){
return{
counter:0
}
},
methods:{
increment:function(){
this.counter+=1
this.$emit('increment')
}
}
}
},
data:{
total:0
},
methods:{
incrementTotal:function(){
this.total+=1
}
}
})

這個例子中,創(chuàng)建了父元素和內部的子組件,子組件包含一個點擊事件,v-on:click=‘increment’,點擊后改變counter數(shù)值,同時為了上傳事件,在函數(shù)increment內部自定義了一個事件,并在點擊時觸發(fā)此事件,$emit,也叫increment,并在子元素上綁定了這個事件,v-on:increment,然后對此在父元素上綁定了一個函數(shù),incrementTotal,從而達到計算總數(shù)的效果。
我覺得這個名字區(qū)分一下比較好,子組件的方法不必和觸發(fā)事件同一個名字,所以改一下:

<div id="app-05">
<p>{{total}}</p>
<button-counter v-on:user-defined="incrementTotal"></button-counter>
<button-counter v-on:user-defined="incrementTotal"></button-counter>
</div>
var app05=new Vue({
el:'#app-05',
components:{
'button-counter':{
template:'<button v-on:click="increment">{{counter}}</button>',
data:function(){
return{
counter:0
}
},
methods:{
increment:function(){
this.counter+=1
this.$emit('user-defined')
}
}
}
},
data:{
total:0
},
methods:{
incrementTotal:function(){
this.total+=1
}
}
})

- 給組件綁定原生事件
用.native修飾v-on

<new-component v-on:click.native="doSomeThing"></new-component>

- v-on與$on的不同:
v-on可以被父組件用來監(jiān)聽子組件的事件
(存疑):$on的用法
- v-model的使用:
 - ref與$.refs——注冊引用信息和使用:
   - 在元素上注冊:<p ref="a">hello</p>
使用注冊:vm.$refs.a就是p這個DOM節(jié)點
   - 在組件上注冊:<input ref="b">
使用注冊:this.$refs.b就是指向了組件實例,this.$refs.b.value指向的是組件input.value
  - mounted ——生命周期鉤子,當el被新創(chuàng)建的vm.$el替換,并掛載到實例上之后調用該鉤子
 - NumberObject.toFixed(num)——把Number四舍五入為指定小數(shù)位數(shù)的數(shù)字

<script src="https://cdn.rawgit.com/chrisvfritz/5f0a639590d6e648933416f90ba7ae4e/raw/98739fb8ac6779cb2da11aaa9ab6032e52f3be00/currency-validator.js"></script>
//載入一個驗證插件
<div id="app-07">
<currency-input label="Price" v-model="price"></currency-input>
<currency-input label="Shipping" v-model="shipping"></currency-input>
<currency-input label="Handling" v-model="handling"></currency-input>
<currency-input label="Discount" v-model="discount"></currency-input>
//此處有一個合計
<p>Total: ${{total}}</p>
</div>
//組件部分
Vue.component('currency-input',{
template:'<div>
//如果有l(wèi)abel,則顯示,否則不顯示
<label v-if="label">{{label}}</label>
$<input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)"
v-on:focus="selectAll" v-on:blur="formatValue"></div>',
props:{
//對參數(shù)進行驗證
value:{
type:Number,
default:0
},
label:{
type:String,
default:''
}
},
//生命周期鉤子,當組件被掛載到實例時會觸發(fā)
mounted:function(){
this.formatValue()
},
methods:{
updateValue:function(value){
var result=currencyValidator.parse(value,this.value)
if(result.warning){
this.$refs.input.value=result.value
}
this.$emit('input',result.value)
},
formatValue:function(){
this.$refs.input.value =currencyValidator.format(this.value)
},
//做setTimeout是由于safari此處有bug,不考慮兼容的話不必如此
selectAll:function(event){
setTimeout(function(){
event.target.select()
},0)
}
}
})

- 非父子組件間的通信(簡單場景)——創(chuàng)建一個空的Vue實例作為連接:

var bus = new Vue()
//在A組件內
bus.$emit('id-selected', 1)
//在B組件內
bus.$on('id-selected', function (id) {
// ...
})

- slot 內容分發(fā)
 - 單個slot:子組件只有一個沒有名字的slot時,父組件的整個內容片斷會插入到slot所在的DOM位置
 - 具名slot:

子模板
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
父模板
<app-layout>
<h1 slot="header">這里可能是一個頁面標題</h1>
<p>主要內容的一個段落。</p>
<p>另一個主要段落。</p>
<p slot="footer">這里有一些聯(lián)系信息</p>
</app-layout>
渲染結果
<div class="container">
<header>
<h1>這里可能是一個頁面標題</h1>
</header>
<main>
<p>主要內容的一個段落。</p>
<p>另一個主要段落。</p>
</main>
<footer>
<p>這里有一些聯(lián)系信息</p>
</footer>
</div>

 - 作用域插槽:子組件中把數(shù)據(jù)傳遞到插槽slot,父元素用一個template scope="props" 獲取數(shù)據(jù),然后就可以使用了

Vue.component('child',{
template:'<div><slot text="hello from child"></slot></div>'
})
var app03=new Vue({
el:'#app-03',
components:{
'parent-component':{
template:'<div>
<child>
<template scope="props">
<span>hello from parent</span>
<span>{{props.text}}</span>
</template>
</child></div>'
}
}
})
//顯示為:
hello from parent hello from child

 - 更復雜的列表組件的例子:

//需要把mylist傳遞進parent-list
<parent-list id="app-04" v-bind:mylist="mylist"></parent-list>
Vue.component('child-list',{
props:['mylist'],
template:'<ul><slot name="item" v-for="item in mylist" v-bind:text="item.text"></slot></ul>'
})
var app04=new Vue({
el:'#app-04',
data:{
mylist:[
{text:'蔬菜'},
{text:'水果'},
{text:'肉'}
]
},
components:{
'parent-list':{
props:['mylist'],
//此處也需要把mylist傳遞進child-list
template:'<child-list v-bind:mylist="mylist">
<template slot="item" scope="props">
<li>{{props.text}}</li>
</template>
</child-list>'
}
}
})
顯示為:

  • 蔬菜
  • 水果
- 渲染一個元組件為動態(tài)組件,根據(jù)is的值來決定渲染哪個組件

<component id="app-06" v-bind:is="currentView"></component>
var app06=new Vue({
el:'#app-06',
data:{
currentView:'posts'
},
components:{
'home':{
template:'<span>home</span>'
},
'posts':{
template:'<span>posts</span>'
},
'archive':{
template:'<span>archive</span>'
}
}
})
//輸入為posts

- keep-alive保留狀態(tài)或避免重新渲染

<keep-alive><component id="app-06" v-bind:is="currentView"></component></keep-alive>

- 可復用組件,三個接口:
 - props
 - events
 - slots

<my-component
:foo="baz"
:bar="qux"
@event-a="doThis"
@event-b="doThat"

<img slot="icon" scr="...">
<p slot="main-text">Hello!</p>
</my-component>

- 子組件索引:使用ref指定一個索引id

<div id="parent">
<user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({el:'#parent'})
var child = parent.$refs.profile

當ref與v-for一起使用時,ref是一個數(shù)組或對象,包含相應的子組件,是非響應式的,應避免在模板或計算屬性中使用
- 異步組件,接受一個工廠函數(shù),可以動態(tài)解析組件的定義:
  • 可以搭配webpack的代碼分割功能
  • 可以使用webpack2 +es2015返回一個promise resolve函數(shù)
  • 組件命名約定,三種都可以:kebab-case,camelCase,TitleCase
    但是,html模板中,只能使用kebab-case
    字符串模板中,三種都可以
  • 遞歸組件,組件可以遞歸的調用自己
    (這里的雷是,不能在實例內部創(chuàng)建組件,因為遞歸時會查找不到子組件,需要定義全局組件)
<div id="app-07">
  <recursion-component :count="0"></recursion-component>
</div>
Vue.component('recursion-component',{
  props:['count'],
  name:'count',
  template:'<div><span>{{count}}</span><recursion-component :count="count+1" v-if="count<10"></recursion-component></div>'
})
var app07=new Vue({
  el:'#app-07'
})
//輸出是:0 1 2 3 4 5 6 7 8 9 10
  • 組件間的循環(huán)引用——注冊為全局組件時沒有問題,但是使用webpack或者browerify用requiring/importing組件的話,會報錯,需要用生命周期鉤子中注冊它的優(yōu)先級:
beforeCreate:function(){
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue')
}

此處(存疑,以后用了webpack再說)
文件目錄樹的例子:

<div id="app-08">
  <tree-folder :folder="folder"></tree-folder>
</div>
Vue.component('tree-folder',{
  template:'<p><span>{{folder.name}}</span><tree-folder-contents :children="folder.children"/></p>',
  props:['folder']
})
Vue.component('tree-folder-contents',{
  template:'<ul><li v-for="child in children">\
  <tree-folder v-if="child.children" :folder="child"/>\
  <span v-else>{{child.name}}</span>\
  </li></ul>',
  props:['children']
})
var app08=new Vue({
  el:'#app-08',
  data:{
    folder:{
      name:'總目錄',
      children:[
        {name:"二層目錄1"},
        {name:"二層目錄2",
          children:[
          {name:"三層目錄1"},
          {name:"三層目錄1"}
          ]
        },
        {name:'二層目錄3'}
      ]
    }
  }
})
//輸出:
總目錄
  - 二層目錄1
  - 二層目錄2
     - 三層目錄1
     - 三層目錄2
  - 二層目錄3
  • 內聯(lián)模板:一個組件有inline-template屬性時,組件會把它的內容當做它的模板,而不是當做分發(fā)內容。應對于很簡單的模板,因為有作用域問題
<parent-com><span>啦啦啦</span></parent-com>
\\js部分
Vue.component('parent-com',{
  template:'<div><child-com><span>this is part of parents</span></child-com></div>'
})
\\默認情況下,“啦啦啦”作為分發(fā)內容,不會顯示,而是顯示this is part of parents.
\\如果改為:
<parent-com inline-template><span>啦啦啦</span></parent-com>
\\則顯示為“啦啦啦”,原設定的template被覆蓋了
  • 另一種定義模板的方式:x-template
<div id="app-10">
  <hello-world></hello-world>
</div>
\\先單獨一個js,類型為type="text/x-template",并且需要有id
<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
\\然后在另一個js里創(chuàng)建組件,只不過模板可以直接引用這個id:
Vue.component('hello-world', {
  template: '#hello-world-template'
})
\\別忘了創(chuàng)建實例(掩面)
var app10=new Vue({
  el:'#app-10'
})
  • 對低開銷的靜態(tài)組件使用v-once只渲染一次,下次時直接應用緩存
Vue.component('terms-of-service', {
  template: '\
    <div v-once>\
      <h1>Terms of Service</h1>\
      ... a lot of static content ...\
    </div>\
  '
})

基礎部分結束

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容