Vue筆記系列
一、Vue.js入門
三、Vue.js進階
如下會隨用隨記一些API,可能會不按期更新。html
註冊或獲取全局組件。註冊還會自動使用給定的id設置組件的名稱。vue
// 註冊組件,傳入一個擴展過的構造器 Vue.component('my-component', Vue.extend({ /* ... */ })) // 註冊組件,傳入一個選項對象(自動調用 Vue.extend) Vue.component('my-component', { /* ... */ }) // 獲取註冊的組件(始終返回構造器) var MyComponent = Vue.component('my-component')
使用基礎 Vue 構造器,建立一個「子類」。參數是一個包含組件選項的對象。注意:data 選項是特例,在 Vue.extend() 中它必須是函數。git
<!-- HTML --> <div id="mount-point"></div>
// 建立構造器 var Profile = Vue.extend({ template: '<p v-text="name"></p>', data: function () { return { name: '第一個構造器!' } } }) // 建立 Profile 實例,並掛載到一個元素上(會替換#mount-pointer)。掛載的組件會把被掛載的元素替換掉。 new Profile().$mount('#mount-pointer');
結果以下:github
<p>第一個構造器!</p>
若是掛載元素不想被替換掉,能夠用如下方法:segmentfault
var component = new Profile().$mount() document.getElementById('mount-pointer').appendChild(component.$el)
Vue 不能檢測到對象屬性的添加或刪除。因爲 Vue 會在初始化實例時對屬性執行 getter/setter 轉化過程,因此屬性必須在 data 對象上存在才能讓 Vue 轉換它,這樣才能讓它是響應的。Vue 不容許在已經建立的實例上動態添加新的根級響應式屬性(因此,set方法的object參數也不能是 Vue 實例,或者 Vue 實例的根數據對象)。可使用 Vue.set(object, key, value) 方法將響應屬性添加到嵌套的對象
以前說過的v-for指令,當你利用索引直接設置一個項時,例如上文的example1.words[0] = {text: 'A'},若是想讓視圖更新,其中一種方法就是用set。api
Vue.set(example1.items, 0, {text: 'A'})
在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。數組
<div id="example">{{message}}</div> var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改數據 vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true })
爲毛第一次DOM裏面的內容沒有變,拿不到改變的內容,通過nextTick方法後才才能拿到改變的內容。
這是由於,當你設置 vm.someData = 'new value' ,該組件不會當即從新渲染。當刷新隊列時,組件會在事件循環隊列清空時的下一個「tick」更新。瀏覽器
全局配置——Vue.config
是一個對象,包含 Vue 的全局配置。有如下屬性:服務器
Vue.config.silent = true;
取消 Vue 全部的日誌與警告,false時開啓。Vue.config.devtools = true;
配置是否容許 vue-devtools 檢查代碼。開發版本默認爲 true,生產版本默認爲 false。vue-devtools指的是一個瀏覽器插件,在谷歌應用裏面有。
安裝以後,是在google 開發者工具的這裏找到。
app
一、全局註冊
註冊一個全局組件,可使用 Vue.component(tagName, options)
。
注意:對於自定義標籤名,Vue.js 不強制要求遵循 W3C規則 (小寫,而且包含一個短槓),可是建議這樣寫。
組件在註冊以後,即可以在父實例的模塊中以自定義元素的形式使用。謹記要確保在初始化根實例以前註冊了組件。而且, el 和 data 選項必須是函數。
<!-- HTML --> <div id="example"> <my-component></my-component> </div>
// 註冊,這就是所謂的語法糖,由於下面的方法有點麻煩。 Vue.component('my-component', { template: '<div>個人第一個組件!</div>' }) // 建立父實例 new Vue({ el: '#example' })
渲染爲:
<div id="example"> <div>個人第一個組件!</div> </div>
二、構造器用做組件
可使用 Vue.extend({...})
建立一個組件構造器,extend 方法建立基礎Vue構造器的子類,參數是一個對象,包含組件選項,這裏要注意的特例是 el 和 data 選項,在 Vue.extend() 中,它們必須是函數。註冊組件的component方法也同樣。這是由於,若是使用一個數據對象(是一個引用),那麼全部的組件實例都共享這一個對象,這樣就會牽一髮而動全身。
有了這個構造器,咱們既能夠用全局註冊的方式用 Vue.component(tag, constructor)
註冊,也能夠利用該構造器構建一個實例,而後用 Vue.$mount() 將該組件實例添加到DOM樹上。
<!-- HTML --> <div id="example"> <my-component></my-component> </div>
// 建立構造器 var Profile = Vue.extend({ template: '<p v-text="name"></p>', data: function () { return { name: '第一個構造器組件!' } } }) // 註冊 Vue.component('my-component',Profile) // 建立父實例 new Vue({ el: '#example' })
渲染爲:
<div id="example"> <p>第一個構造器組件!</p> </div>
三、局部註冊
經過使用組件實例選項註冊,可使組件僅在另外一個實例/組件的做用域中可用。即在註冊的對象參數中添加 components 成員,components成員的標籤就只在該組件內使用,不在全局DOM樹中使用局部註冊的組件。
//實例做用域 var Child = { template: '<div>一個局部組件!</div>' } new Vue({ // ... components: { // <my-component> 將只在父模板可用 'my-component': Child } }) //組件做用域 <div id="comp-ex"> <contact></contact> </div> <script> var Person = Vue.extend({ // constructor template: "<div><span>name:</span> {{name}} <span>Age: </span> {{age}}</div>", data: function() { return { name: ' li ming', age: 22 } } }); var Contact = Vue.extend({ template: "<div><span>Tel: </span> {{tel}}, <span>E-mail: </span> {{email}}<person></person></div>", data: function() { return { tel: '152-0101-1010', email: 'admin#163.com' } }, components: { 'person': Person // 局部註冊 person 標籤 } }) Vue.component('contact', Contact) var you = new Vue({ // init component, render template el: '#comp-ex' }) </script>
子組件只能在父組件的template中使用。注意下面兩種子組件的使用方式是錯誤的:
<div id="app"> <parent-component> <child-component></child-component> </parent-component> </div>
由於當子組件註冊到父組件時,Vue.js會編譯好父組件的模板,模板的內容已經決定了父組件將要渲染的HTML。
<parent-component>…</parent-component>至關於運行時,它的一些子標籤只會被看成普通的HTML來執行,<child-component></child-component>不是標準的HTML標籤,會被瀏覽器直接忽視掉。
<div id="app"> <parent-component> </parent-component> <child-component> </child-component> </div>
運行這段代碼,瀏覽器會提示如下錯誤:
四、is特性
一些 HTML 元素,如<ul>
<ol>
<table>
<select>
,限制什麼元素能夠放在它裏面。自定義元素不在白名單上,將被放在元素的外面,於是渲染不正確。這時應當使用 is 特性,指示它是一個自定義元素。
<table> <my-row>...</my-row> </table> <!-- 自定義組件 <my-row> 被認爲是無效的內容,所以在渲染的時候會致使錯誤。須要使用特殊的 is 屬性 --> <table> <tr is="my-row"></tr> </table>
良好的流程: Vue.js 中,父子組件的關係能夠總結爲 props down, events up 。父組件經過 props 向下傳遞數據給子組件,子組件經過 events 給父組件發送消息。
一、Prop顯式聲明
組件實例的做用域是孤立的。這意味着不能而且不該該在子組件的模板內直接引用父組件的數據。可使用 props 把數據傳給子組件。prop 是父組件用來傳遞數據的一個自定義屬性。子組件須要顯式地用 props選項聲明 「prop」:
讀到這裏,咱們掌握的組件的構造選項對象的屬性包括了:
- template,要渲染的內容
- data,數據,必須是一個函數,函數返回一個對象
- props,從父組件傳遞數據到子組件。
<div id="comp-ex"> <!-- 注意在HTML中屬性要寫kebab-case(短橫線隔開) --> <child my-message='qiqihaobenben'></child> <child my-message='qiqihaobenben'></child> </div> <script> Vue.component('child', { // 就像 data 同樣,prop 能夠用在模板內 template: '<span>{{message}}</span>', //聲明 props // HTML特性不區分大小寫,名字形式爲 camelCase 的prop用做特性時,寫在HTML中要用短橫線隔開,不然不起做用,如上。 props: ['myMessage'], // 一樣也能夠在 vm 實例中像 「this.message」 這樣使用 data: function (){ return { message: this.myMessage } } }) var me = new Vue({ el: '#comp-ex' }) <script>
輸出結果爲:
<div id="comp-ex"> <span>qiqihaobenben qiqihaobenben</span> </div>
HTML特性不區分大小寫,可是名字形式爲 camelCase 的prop用做特性時,須要轉爲 kebab-case(短橫線隔開)。在html中的屬性使用短橫線隔開,而在js的template中的標識使用的駝峯命名,能夠參考上面的例子。
上面的例子使用節點屬性方式向子組件傳遞數據,若是父組件的數據變化,子組件並不會隨之變化,這就是其動態性,若是要綁定動態的值,應該使用 v-bind 指令,這樣每當父組件的數據變化時,也會傳遞給子組件
<div> <input v-model="parentMsg"> <br> <child v-bind:my-message="parentMsg"></child> </div>
動態和非動態這兩種傳遞方式在傳遞數字時是不一樣的,以下:
<!-- 傳遞了一個字符串"1" --> <comp some-prop="1"></comp> <!-- 傳遞實際的數字1 --> <comp v-bind:some-prop="1"></comp> 雖然html渲染的時候並不太區分字符串和數字,可是注意有這種區別。
prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,可是不會反過來。這是爲了防止子組件無心修改了父組件的狀態——這會讓應用的數據流難以理解。
注意:在 JavaScript 中對象和數組是引用類型,指向同一個內存空間,若是 prop 是一個對象或數組,在子組件內部改變它會影響父組件的狀態。
原本不想說的,但是看文檔時怎麼用怎麼不對,後來想通了,因此就拿出來講一下。
看官方文檔能夠知道,type 能夠是下面原生構造器:
其中的除了String,其餘的類型都須要動態綁定才能驗證正確,不然獲得的就是一水的String類型。
//js屬性驗證 props: { myAge: { type: [Number,Boolean,Object,Array,Function] } } <!--如下都會報錯,Expected XXX, got String.--> <my-component my-age="12"></my-component> <my-component my-age="true"></my-component> <my-component my-age="{}"></my-component> <my-component my-age="[]"></my-component> <my-component my-age="consoleOne"></my-component> <!--正確的作法是用v-bind來綁定屬性--> <my-component v-bind:my-age="12"></my-component> ... ...
另外,default和required這兩個驗證規則,須要組件的屬性徹底不存在時纔會生效
//設置默認值 props: { myName: { default: 2 } } //設置必傳項 props: { myName: { required: true } } <!--如下,不論是必傳項或者默認值都不會有效果--> <my-component my-name="" ></my-component> <!--只有這個屬性在組件上真的沒有,纔會觸發驗證效果--> <my-component ></my-component>
以上,當 prop 驗證失敗了,若是使用的是開發版本會拋出一條警告。
二、自定義事件
儘管能夠訪問父鏈上任意的實例,不過子組件應當避免直接依賴父組件的數據。另外子組件中修改父組件的狀態是很是糟糕的,由於:
父組件是使用 props 傳遞數據給子組件,若是子組件要把數據傳遞回去,那就是自定義事件!
每一個 Vue 實例都實現了事件接口(Events interface),即:
父組件——使用 $on(eventName) 監聽事件
子組件——使用 $emit(eventName) 觸發事件
下面的列子跟官網的幾乎同樣,可是區別在於,是經過傳參來改變父組件的狀態的賦值。
<div id="counter-event-example"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter> <button-counter v-on:increment="incrementTotal"></button-counter> </div>
var allTotal = {number: 0} //這個是統計兩個按鈕的點擊的次數,這樣就能直接賦給父組件 Vue.component('button-counter', { template: '<button v-on:click="increment">{{ counter }}</button>', data: function () { return { counter: 0, allCounter: allTotal } }, methods: { increment: function () { this.counter += 1; this.allCounter.number += 1; // 準備給父組件的自定義事件方法傳參 this.$emit('increment',this.allCounter.number); } }, }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal: function (value) { this.total = value; } } })
在某個組件的根元素上監聽一個原生事件。可使用 .native 修飾 v-on 。這就是說全部的原生事件若是在組件的根元素上不加.native,vue會自動認爲它是自定義事件
使用 v-model 來進行數據雙向綁定。牢記:這個指令僅僅是一個語法糖。
<input v-model="something"> <!--上下兩種方式是等價的--> <input v-bind:value="something" v-on:input="something = $event.target.value">
因此在組件中使用時,它至關於下面的簡寫:
<custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input> <!--改寫成語法糖以下--> <custom-input v-model="something"></custom-input>
尤爲注意,這個地方有點繞:若是是自定義的表單組件,而且父組件在加載這個表單組件時使用了v-model指令,那麼做爲子組件,接收到的prop應該是value而不是something。
<div id="parent"> <user-profile ref:profile></user-profile> </div> <script> var parent = new Vue({el: '#parent'}); var child = parent.$refs.profile; // 能夠獲得子組件 </script>
父組件模板的內容在父組件做用域內編譯;子組件模板的內容在子組件做用域內編譯。說白了,就是一眼看上去,在誰裏面就是誰的。
<div id="app"> <my-component v-show="display"></my-component> </div> <template id="myComponent"> <table> <tr> <th colspan="3">{{msg}}</td> </tr> </table> </template> <script> var my = Vue.extend({ template: '#myComponent', data : function (){ return { msg : '這是子組件', display: false } } }) var vm = new Vue({ el: '#app', data: { display: true }, components: { 'myComponent': my } }) </script>
在my-component標籤上使用指令v-show="display",這個display數據是來源於Vue實例vm ,仍是my-component組件呢?
答案是Vue實例
下面的代碼在定義my-component組件的模板時,指定了一個<slot></slot>元素。
<div id="app"> <my-component> <h1>這是父組件的內容!</h1> </my-component> <my-component> </my-component> </div> <template id="myComponent"> <div class="content"> <h2>這是一個子組件!</h2> <slot>若是沒有分發內容,則顯示slot中的內容</slot> <p>Hello,Vue.js</p> </div> </template> <script> Vue.component('my-component', { template: '#myComponent' }) new Vue({ el: '#app' }) </script>
第一個<my-component>標籤有一段分發內容 <h1>這是父組件的內容!</h1>
,渲染組件時顯示了這段內容。
第二個<my-component>標籤則沒有,渲染組件時則顯示了slot標籤中的內容。
<slot> 元素能夠用一個特殊的屬性 name 來配置如何分發內容。多個 slot 能夠有不一樣的名字。具名 slot 將匹配內容片斷中有對應 slot 特性的元素。
<div id="app"> <my-component> <div slot="header"> 這是一個頭部 </div> <p>neirong</p> <p>neirong</p> <div slot="footer"> 這是一個底部 </div> </my-component> </div> <template id="myComponent"> <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </template> <script> Vue.component('my-component', { template: '#myComponent' }) new Vue({ el: '#app' }) </script>
能夠看出仍然能夠有一個匿名 slot ,它是默認 slot ,做爲找不到匹配的內容片斷的備用插槽。若是沒有默認的 slot ,這些找不到匹配的內容片斷將被拋棄。
做用域插槽是一種特殊類型的插槽,用做使用一個(可以傳遞數據到)可重用模板替換已渲染元素。
數據傳遞,可重用,天然而然的想到循環
<div id="app"> <my-awesome-list :items="items"> <template slot="item" scope="props"> <li>{{ props.text }}</li> </template> </my-awesome-list> </div> <script> Vue.component('my-awesome-list',{ template: `<ul> <slot name="item" v-for="item in items" :text="item.text"> </slot> </ul>`, props: ['items'] }) var demo = new Vue({ el: '#app', data: { items: [ {text: 'aaaaa'}, {text: 'bbbbb'}, {text: 'ccccc'}, {text: 'ddddd'}, {text: 'eeeee'} ] } }) </script>
當註冊組件(或者 props)時,可使用 kebab-case ,camelCase ,或 TitleCase。可是,在 HTML 模版中,請使用 kebab-case 形式:
// 在組件定義中 components: { // 使用 kebab-case 形式註冊 'kebab-cased-component': { /* ... */ }, // register using camelCase 'camelCasedComponent': { /* ... */ }, // register using TitleCase 'TitleCasedComponent': { /* ... */ } } <!-- 在HTML模版中始終使用 kebab-case --> <kebab-cased-component></kebab-cased-component> <camel-cased-component></camel-cased-component> <title-cased-component></title-cased-component>
注意:當使用字符串模式時,能夠不受 HTML 的 case-insensitive 限制。
在實例初始化以後,數據觀測(data observer) 和 event/watcher 事件配置以前被調用。
實例已經建立完成以後被調用。在這一步,實例已完成如下的配置:數據觀測(data observer),屬性和方法的運算, watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前不可見。
在掛載開始以前被調用:相關的 render 函數首次被調用。該鉤子在服務器端渲染期間不被調用。
el 被新建立的 vm.$el 替換,並掛載到實例上去以後調用該鉤子。若是 root 實例掛載了一個文檔內元素,當 mounted 被調用時 vm.$el 也在文檔內。該鉤子在服務器端渲染期間不被調用。
數據更新時調用,發生在虛擬 DOM 從新渲染和打補丁以前。
你能夠在這個鉤子中進一步地更改狀態,這不會觸發附加的重渲染過程。
該鉤子在服務器端渲染期間不被調用。
因爲數據更改致使的虛擬 DOM 從新渲染和打補丁,在這以後會調用該鉤子。
當這個鉤子被調用時,組件 DOM 已經更新,因此你如今能夠執行依賴於 DOM 的操做。然而在大多數狀況下,你應該避免在此期間更改狀態,由於這可能會致使更新無限循環。
該鉤子在服務器端渲染期間不被調用。
keep-alive 組件激活時調用。該鉤子在服務器端渲染期間不被調用。
keep-alive 組件停用時調用。該鉤子在服務器端渲染期間不被調用。
實例銷燬以前調用。在這一步,實例仍然徹底可用。該鉤子在服務器端渲染期間不被調用。
Vue 實例銷燬後調用。調用後,Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬。該鉤子在服務器端渲染期間不被調用。
手動地掛載一個未掛載的實例,返回值是實例自身。於是能夠鏈式調用其它實例方法。
若是沒有提供 elementOrSelector 參數,模板將被渲染爲文檔以外的的元素,而且你必須使用原生DOM API把它插入文檔中。
var MyComponent = Vue.extend({ template: '<div>Hello!</div>' }) // 建立並掛載到 #app (會替換 #app) new MyComponent().$mount('#app') // 同上 new MyComponent({ el: '#app' }) // 或者,在文檔以外渲染而且隨後掛載,這種方式不會替換#app var component = new MyComponent().$mount() document.getElementById('app').appendChild(component.$el)
徹底銷燬一個實例。清理它與其它實例的鏈接,解綁它的所有指令及事件監聽器。觸發 beforeDestroy 和 destroyed 的鉤子。
注意:在大多數場景中你不該該調用這個方法。最好使用 v-if 和 v-for 指令以數據驅動的方式控制子組件的生命週期。
將回調延遲到下次 DOM 更新循環以後執行。在修改數據以後當即使用它,而後等待 DOM 更新。它跟全局方法 Vue.nextTick 同樣,不一樣的是回調的 this 自動綁定到調用它的實例上。
應用上,在組件內使用 vm.$nextTick() 實例方法特別方便,由於它不須要全局 Vue ,而且回調函數中的 this 將自動綁定到當前的 Vue 實例上:
Vue.component('example', { template: '<span>{{ message }}</span>', data: function () { return { message: 'not updated' } }, methods: { updateMessage: function () { this.message = 'updated' console.log(this.$el.textContent) // => '沒有更新' this.$nextTick(function () { console.log(this.$el.textContent) // => '更新完成' }) } } })