全部的vuejs組件都是被擴展的vue實例;
var MyComponent = Vue.extend({ //擴展選項對象 }) var myComponentInstance = new MyComponent();
每一個Vue實例都會代理這個實例的data屬性對象裏的全部屬性:
var data = { a: 1 } var vm = new Vue({ data: data }) vm.a === data.a // -> true // 設置屬性也會影響到原始數據 vm.a = 2 data.a // -> 2 // ... 反之亦然 data.a = 3 vm.a // -> 3
全部Vue實例自己暴露的屬性和方法,都以$爲頭來區別,對應Vue.set global API
例如:vm.$data,vm.$elvm.$watch,這個有利於和data屬性對象的數據來區分;javascript
全部directive都以v-xxx形式存在:
<p v-if="greeting">Hello!</p> //根據greeting表達式的值真假來插入或者刪除p元素; <a v-bind:href="url"></a> //href是argument,表示將a元素的href屬性和url表達式綁定起來 其對應的簡寫形式爲: <a :href="url"></a> //這個是一種縮寫的形式 <a v-on:click="doSomething"> //click是參數,表示on click時,執行doSomething這個表達式(函數) <!-- 完整語法 --> <a v-on:click="doSomething"></a> <!-- 縮寫 --> <a @click="doSomething"></a> <a v-bind:href.literal="/a/b/c"></a> //href爲argument,literal爲修飾符,表示後面"/a/b/c"爲字面值而不是表達式!! <button :disabled="someDynamicCondition">Button</button> // 綁定到一個布爾值,若是真則disabled屬性就加在button上
directive object context暴露出來供update/bind函數使用的屬性:
el: the element the directive is bound to. vm: the context ViewModel that owns this directive. expression: the expression of the binding, excluding arguments and filters. arg: the argument, if present. name: the name of the directive, without the prefix. modifiers: an object containing modifiers, if any. descriptor: an object that contains the parsing result of the entire directive. params: an object containing param attributes.
好比下面的directive例子中:css
<div id="demo" v-demo:arghello.modifiera.modifierb="expmsg" :parax="xdata"></div> // 注意須要watch directive的parameter才能實現xdata變化就能觸發directive內部的變化 Vue.directive('example', { params: ['parax'], paramWatchers: { parax: function (val, oldVal) { console.log('parax changed!') } } })
綁定css class和style:html
<div v-bind:style="styleObject"></div> <li v-bind:class="{'j_current': currenttab=='login'}"> 這是class綁定的演示</li> data: { styleObject: { color: 'red', fontSize: '13px' } } <div v-bind:class="classObject"></div> data: { classObject: { 'class-a': true, 'class-b': false } }
v-if directive:條件渲染:
注意v-if會直接在DOM中插入刪除對應的元素前端
<h1 v-if="ok">Yes</h1> <h1 v-else>No</h1> <template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template>
v-show條件顯示vue
不會刪除元素,只會使用display:none css的方式java
<h1 v-show="ok">Hello!</h1>
v-for列表數據渲染(注意在v-for子塊內能夠訪問父組件做用域內的屬性,還有一個$index)
<ul id="example-2"> <li v-for="(index, item) of items"> {{index}} {{ parentMessage }} - {{ $index }} - {{ item.message }} </li> </ul> var example2 = new Vue({ el: '#example-2', data: { parentMessage: 'Parent', items: [ { message: 'Foo' }, { message: 'Bar' } ] } }) //結果: 0 Parent - 0 -Foo 1 Parent - 1 -Foo //v-for也能夠應用在template標籤上,這樣作的好處是:不用額外的無心義的tag,template tag不會被渲染出來 <ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider"></li> </template> </ul>
vuejs能夠被應用在數組的push,pop,shift,unshift,splice,sort,reverse方法改變數組的場景,可是若是你使用下面的語法1. vm.items[0]={}; 2.vm.items.length=0改變數組vuejs則沒法感知這個變化,vuejs推薦的解決方案是:1.使用$set方法: example1.items.$set(0,{});node
2.使用空數組來替換items便可 example1.items = [];react
vuejs也提供了直接刪除一個數組一個元素的簡單方法 this.items.$remove(item)jquery
v-for應用在對象上面(而不是數組)webpack
<ul id="repeat-object" class="demo"> <li v-for="value in object"> {{ $key }} : {{ value }} </li> </ul> new Vue({ el: '#repeat-object', data: { object: { FirstName: 'John', LastName: 'Doe', Age: 30 } } }) //輸出一下結果 <ul id="repeat-object" class="demo"> <li> FirstName : John </li><li> LastName : Doe </li><li> Age : 30 </li> </ul>
v-on內聯語句訪問event參數:若是是一個函數做爲v-on綁定的表達式的話,該函數自動帶有(event參數),這個和普通的js事件處理函數是同樣的。
<button v-on:click="say('hello!', $event)">Submit</button> // ... methods: { say: function (msg, event) { // 如今咱們能夠訪問原生事件對象 event.preventDefault() } }
事件修飾符:
v-on:click.stop/v-on:submit.prevent/v-on:click.stop/v-on:click.capture/v-on:click.self="dothat"
v-model表單控件綁定:
http://vuejs.org.cn/guide/forms.html
過渡動畫
http://vuejs.org.cn/guide/transitions.html
組件:
須要確保在初始化root instance以前註冊了組件!
<div id="example"> <my-component></my-component> </div> // 定義 var MyComponent = Vue.extend({ template: '<div>A custom component!</div>' }) // 註冊 Vue.component('my-component', MyComponent) // 建立根實例 new Vue({ el: '#example' }) //結果: <div id="example"> <div>A custom component!</div> </div>
注意:組件的模板會替換自定義元素my-component標籤,也就是說my-component只是做爲一個掛載點而已,固然,這能夠經過replace選項來改變這個缺省行爲
組件的局部註冊:
有時咱們可能但願一個組件只能在其餘組件內使用,那麼能夠用實例選項components來註冊:
var Child = Vue.extend({ template: '<div>A custom component!</div>' }) var Parent = Vue.extend({ template: '...', components: { // <my-component> 只能用在父組件模板內 'my-component': Child } })
組件註冊語法糖
上面全局和局方方式註冊組件老是使用Vue.extend來定義組件,隨後傳入Vue.component()或者components選項,有時顯得很囉嗦,vuejs提供了一種簡化方式來聲明定義組件(參數傳入仍是分兩種狀況:全局和局部)
// 在一個步驟中擴展與註冊 Vue.component('my-component', { template: '<div>A custom component!</div>' }) // 局部註冊也能夠這麼作 var Parent = Vue.extend({ components: { 'my-component': { template: '<div>A custom component!</div>' } } })
組件選項數據隔離問題(data和el選項)
傳入Vue構造器的多數選項(new Vue({el,data,components,prop...}))均可以用在Vue.extend()中,可是data和el是兩個特例,不能直接簡單地把一個對象做爲data選項傳給Vue.extend(),緣由以下:
var data = { a: 1 } var MyComponent = Vue.extend({ data: data })
若是直接傳入data對象給data選項,那麼全部的MyComponent組件的實例都將共享同一個data對象!!所以咱們正確的作法是利用javascript的閉包的概念,使用一個函數來返回對應的數據:
var MyComponent = Vue.extend({ data: function () { return { a: 1 } } })
watch
watch: { // whenever question changes, this function will run question: function (newQuestion) { this.answer = 'Waiting for you to stop typing...' this.getAnswer() }, deepwachedArrayOrObject: { handler: function(nv,ov){ // watch handler function body }, deep: true // 指示深度偵測 }
須要注意的是在vuejs2.0中初次mount的組件並不會調用這個watch,而只有數據變化後纔會調用,不像1.0中初次mount時也會調用
注意: 對於object對象或者array對象最好使用deep: true的參數,不然可能不會對對象key值變動作出反應
模板解析問題:
vue的模板是DOM模板,使用的是瀏覽器原生的解析器,DOM模板必須是有效的HTML片斷。咱們必須注意有一些HTML元素對於什麼元素可以放在他裏面是有限制的,好比:
a不能包含其餘的交互元素(好比按鈕,連接)
ul/ol只能包含li
select只能包含option和optgroup,
table只能包含thead,tbody,tfoot,tr,caption,col,colgroup,
tr只能包含th,td
咱們若是違反這些規則,好比把<ul> <my-component>這種方式來組織的話,瀏覽器會把my-component提到元素的外面,致使渲染不正確。
這時,若是咱們又必須使用這樣的結構,總麼辦?
咱們使用is屬性吧!!!
<table> <tr is="my-component"></tr> </table>
組件component實例做用域
組件實例的scope做用域是孤立的,這意味着不能而且也不該該在子組件的模板內直接引用父組件的數據,可是咱們可使用props屬性來吧數據傳給子組件:
prop是組件數據的一個字段,指望從父組件傳下來。子組件須要顯式地用props選項聲明props:
Vue.component('child', { // 聲明 props props: ['msg'], // prop 能夠用在模板內 // 能夠用 `this.msg` 設置 template: '<span>{{ msg }}</span>' }) //傳入一個普通的字符串給child組件的msg屬性 <child msg="hello!"></child>
camelCase和kebab-case的轉換
因爲HTML屬性是不區分大小寫的,所以咱們組件的prop使用camelCase來定義時,須要轉爲短橫線隔開(這個和angular相似:
Vue.component('child', { // camelCase in JavaScript props: ['myMessage'], template: '<span>{{ myMessage }}</span>' }) <!-- kebab-case in HTML --> <child my-message="hello!"></child> 心法:camelCase in JavaScript : kebab-case in HTML Vue.component('child', { // camelCase in JavaScript props: ['myMessage'], template: '<span>{{ myMessage }}</span>' }) <!-- kebab-case in HTML --> <child my-message="hello!"></child>
綁定自定義組件的屬性
相似於綁定一個普通的屬性到一個表達式,咱們也可使用v-bind來綁定自定義組件的props到父組件的數據,這樣每當父組件的數據發生變化時,也會傳導這個數據給子組件:
<div> <input v-model="parentMsg"> <br> <child v-bind:my-message="parentMsg"></child> </div> //簡化版本: <child :my-message="parentMsg"></child>
傳遞值給prop
初學者每每犯錯的是直接傳遞數值給prop,可是其實是傳遞了一個字符串,咱們必須使用:前綴,這樣告訴vue,咱們後面的是一個表達式,而不是字面量:
<!-- 傳遞了一個字符串 "1" --> <comp some-prop="1"></comp> <!-- 傳遞實際的數字 --> <comp :some-prop="1"></comp>
prop從父親到兒子雙向綁定(在vuejs2.0中反向回傳是嚴正反對的!)
默認狀況下prop是單向綁定:當父組件屬性變化時,傳遞給子組件,可是反過來不會。你可使用.sync或者.once綁定修飾符來顯式地強制雙向綁定或者單次綁定:
<!-- 默認爲單向綁定 --> <child :msg="parentMsg"></child> <!-- 雙向綁定 --> <child :msg.sync="parentMsg"></child> <!-- 單次綁定 :注意單次綁定在數據傳入後就不會同步後面的任何變化了,適合傳入初始化數據的場景--> <child :msg.once="parentMsg"></child>
須要注意的是:若是prop自己是一個對象或者數組的話,因爲javascript對象是引用方式,不管是什麼綁定方式都會是雙向綁定!!
父子組件通訊:
子組件可硬用this.$parent訪問父組件,this.$root訪問祖根實例,每一個父組件都有一個數組this.$children來包含全部子元素。
可是在vuejs2.0中,任何試圖在組件內修改經過props傳入的父組件數據都被認爲是anti-pattern的,報如下錯誤:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders
咱們必須從新思考如何架構咱們的應用:
1. 不要在組件內試圖修改props傳進來的父組件數據,數據只應該由該數據的源頭組件來負責app內的CRUD;
2. 數據永久性功能(就是和後臺數據庫交互保存)最好是由須要處理數據的子組件來和數據庫交互,可是經過$emit一個事件通知該數據的源頭組件來更新web app的數據;(在子組件tag引用處以@resource-deleted來引用父組件的事件函數) 這種模式帶來的問題是數據來源和數據消費者有事件交互的強烈耦合。
還有一種小方案是將數據永久性功能以及web app內部的數據一致兩個功能合爲一處,即:子組件須要修改源數據時,$emit消息給父親,由owner來同時完成數據永久性保存和內部app數據一致
3.對於大型web app可使用vuex來實現解耦:父子之間經過vuex store state tree來保持聯繫,任什麼時候候須要獲取數據則getter,若是須要修改數據則setter,數據修改後的reactive則由vuejs處理,這種方式最大限度地實現瞭解耦
4. 可是有時也須要一些平衡:難道每一片小數據都得經過$emit事件給數據源頭來作修改嗎?因爲若是傳入的數據是Object類型或者array類型的,則自己因爲是reference傳參的,也就是說子組件prop傳入供子組件讀/寫的object/array和「源頭object/array」其實是一個對象,所以只要咱們不直接簡單粗暴以
this.objFromParent = newObjectCreatedByChild //這樣會致使vuejs報錯 Avoid mutating a prop directly ...
可是若是咱們這樣操做,則是能夠的,而且數據徹底是同步的!
this.objFromParent.propertyChangedByChild = newObjectCreatedByChild.propertyChangedByChild //這樣數據就很輕鬆保持了一致性,而不用$emit消息到數據源來維護數據了!!!
5.第4點和vuex的機制比較相似。你能夠在須要共享給兒子組件的數據(起源於hosted by本組件)存放在一個localStore對象的屬性中,將localStore對象做爲屬性值傳給兒子組件,好比:pstore="localStore",在兒子組件中,則能夠直接操做pstore.parentData = somedatahandledbyson ,曲線救國,繞過了vuejs2.0不容許對屬性賦值操做的限制。
6. 若是是須要共享給後代子孫的數據,則能夠引入一種thisrootstore機制,全部這類數據做爲thisrootstore的屬性對象存在,後代以this.rootstore.xxx來引用它。
5和6的好處是剔除大部分不須要$emit事件來sync數據的冗餘代碼,更加易於實現組件功能的抽象和重用
7. 也能夠考慮利用slot機制的特性: slot自己所在html js scope屬於父組件,這樣就能夠如下面的形式來解決這個數據同步問題:
<!-- within parent template removeThisSon() resonsible for remove data which hosted by parent --> <son v-for="son in sons"> <div @click="removeThisSon(son)"> some operation affected parent data</div> </son>
心法:對整個屬性對象替換(賦值),新增,或者刪除操做必須由數據源來操做,可是對傳入的屬性對象的某些property修正,則能夠在子組件內部直接操做,數據就同步反映到父組件中
心法:數據優先,你首先抽象驅動你的app的state,全部代碼就圍繞着這些個state的變遷,而讓vuejs自己來執行構建和更新DOM的工做。
The whole idea is "data first". you define what the state of your application should look like, and let Vue build and update the DOM accordingly
自定義事件
Vue實例的事件系統獨立於DOM事件系統,作法有不一樣:
使用$on()監聽事件;
使用$emit()在這個組件上面觸發事件;
使用$dispatch()來派發事件,事件沿着父鏈冒泡;
$broadcast()廣播事件,從父親向下到全部後代;
子組件索引v-ref
<div id="parent"> <user-profile v-ref:profile></user-profile> </div> var parent = new Vue({ el: '#parent' }) // 訪問子組件 var child = parent.$refs.profile
transclusion: slot分發內容
學過angular,你可能知道有一個很是晦澀難懂的概念:transclusion,在vuejs中也有相似的說法slot
咱們先搞清楚編譯做用域吧,看看下面的代碼:
<child> {{ msg }} </child>
這個代碼中的msg究竟是綁定到父組件的數據,仍是綁定到子組件的數據呢??正確答案是父組件。關於組件的做用域有如下心法,緊緊記住:
父組件模板的內容在父組件的做用域內編譯;子組件模板的內容在子組件做用域內編譯。
一個常見的錯誤是試圖在父組件的模板內將一個directive綁定到子組件的屬性和方法:
<!-- 無效:緣由是這個模板是父組件的,而父組件模板不知道子組件的數據狀態! --> <child v-show="someChildProperty"></child>
注意:分發內容是在父組件的做用域內編譯
v-for和組件共用:
v-for能夠像普通元素上同樣在compent tag上面使用:
<my-component v-for="item in items" :item="item" :index="$index"> </my-component>
上例中每一個my-component實例將會傳入item數據/index索引以便使用
async components:
有時候,咱們的組件在render以前,可能須要一些數據準備的工做:好比從後端ajax過來數據,而且feed到組件中去,這時就須要使用async組件的概念了。
http://jsbin.com/maqagocise/edit?html,js,output
上面的代碼例子能夠參考:
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { resolve({ template: '<div>I am async!</div>' }); }, 1000); }); new Vue({ el: '#body' }); <async-example></async-example>
動態插入和刪除一個vue組件
你雖然能夠經過如下代碼實現動態插入和刪除vue組件的需求,可是這種方式的問題是:在vue devtool中並未看到數據鏈的關係,咱們仍是建議使用v-if來實現這種應用場景。
methods:{ attachNewPermission: function(){ var vm = this; var html = '<async-example roleid='+this.roleid+' ></async-example> '; var vmtemp = Vue.extend({ template: html, replace: false, el: function(){ return vm.$el.getElementsByClassName('asyncloadednode')[0]; } }); new vmtemp(); } }
經過prop向子組件傳入父組件的函數做爲callback,而且訪問子組件的數據
http://012.vuejs.org/guide/components.html#Passing_Callbacks_as_Props
prop傳入父組件數據例子
1 <!-- url爲update-text-inplace組件的屬性,其值爲/admin/roles/xx,其中role.id爲在父組件的template可見的數據 --> 2 <update-text-inplace :url='"/admin/roles/"+role.id' fieldname="name"> 3 <div class="role-name">@{{ role.name }}</div> 4 </update-text-inplace>
VUEJS 2.0新feature及變化
https://github.com/vuejs/vue/issues/2873
vue component定義及引用命名約定
// PascalCase import TextBox from './components/text-box'; import DropdownMenu from './components/dropdown-menu'; export default { components: { // use in templates as <text-box> and <dropdown-menu> TextBox, DropdownMenu } } // in a component definition components: { // register using camelCase myComponent: { /*... */ } } <!-- use dash case in templates --> <my-component></my-component>
何爲fragment instance
http://vuejs.org/guide/components.html#Fragment-Instance
ES6 moudle export/import
// flash-message.js function alertMessage(message) { alert(message); } function logMessage(message) { console.log(message); } export {alertMessage, logMessage}; //app.js import {alertMessage, logMessage} from './flash-message'; alertMessage("Hello"); logMessage("Hello"); //flash-message.js export default function(message){ alert(message); } //app.js import flashMessage from './flast-message'; flashMessage("Hello");
http://www.cnblogs.com/Answer1215/p/5131548.html
注意root Vue instance不能屢次初始化,不然可能會出現組件找不到的狀況
javascript模塊化開發模式:
每一個文件都組織爲一個模塊;
文件的開頭經過import(es6)/require(cmd,amd)方式聲明須要從外部導入的依賴;
每一個文件須要輸出的好比component defination object, function,object等經過export定義;
第三方組件經過npm install --save-dev或者bower install --save下載安裝,經過require('jquery')(這種方式是經過npm安裝的,能夠不用傳入路徑)或者require('path/to/jquery/jquery')(這種是非npm安裝模式從本地文件require)來引入
全部第三方組件(若是自己不支持CMD,AMD,ES6模塊化加載的話)或者本身寫的過程式js文件須要作簡單的改造,改形成ES6/CMD/AMD模塊格式,以支持模塊化開發模式
關於vuejs大小寫,camcase等
在使用vueify時,須要import一個組件的配置對象,這時建議所有使用首字母大寫的命名方式,下例:
import MyComponent from './my-component' export default { components: { MyComponent // es2015 shorhand } } //而後在template中使用-代替非首單詞大寫字母: <my-component></my-component>
以js模塊化方式寫一個export一個singleton object:
var Spinner = require('spin.js'); // export singleton:這是因爲require會cache一個object module.exports = exports = new Spinner; 在須要引用該singleton object的地方: var spin = require('./supportlibs/spinner'); var spin2 = require('./supportlibs/spinner'); spin===spin2 //返回true!
hot reloading
咱們在使用browsersync這個工具作前端開發時,能夠只對頁面的css進行注入,這個概念在vueify的組件開發中也是能夠的
參考: http://vuex.vuejs.org/zh-cn/hot-reload.html
在browserify工具鏈下有如下plugin實現相似的功能: https://github.com/AgentME/browserify-hmr/
如何在Html inspection pannel中清楚查看一段html屬於哪一個組件?
在組件式開發模式下,咱們的頁面就是一堆component組件按照邏輯關係堆砌出來的,不少時候咱們發現找到對應的html片斷屬於哪一個組件的模版定義的不是一件容易的事情,如何處理這種棘手的問題使得開發更容易和有趣?
我總結下來一個簡單有效的方法是:在組件的root node下增長一行html註釋: <!-- yourcomponent-name -->, 這樣在html inspection界面就一眼看出是什麼組件了,對應你想修改的話,打開那個組件.vue文件修改便可。
vuejs組件vueify開發模式下的依賴問題
咱們使用vueify開發模式來開發vuejs組件,將全部的代碼:html+javascript+css都放在component.vue文件中,這對於前端開發能夠說是一種革命,大大地便利了組件的迭代式開發,大大增長了代碼的可重用性,可是同時也帶來一些「問題」,其中一個問題就是:當咱們更改一個component.vue後,必須從新編譯全部引用過這個vue文件的bundle.js文件才能自動使用最新的component邏輯,這在以往純粹<script>tag引入js的開發模式是不可想象的。之因此必須從新編譯那是由於每個bundle.js文件都是獨立編譯相應模版及components數組中定義的依賴組件而最終造成一個包含全部js/css的bundle的。
Vuejs directive對應this.vm指向誰?
customized directive的bind函數中若是返回this.vm到底指向的是誰呢?這有時候仍是容易混淆不清的。通常性原則:若是該directive attached到一個component的template內部,則該值指向VueComponent,若是該directive attached dom node並不屬於任何component,則這個值就直接指向root Vue instance.
Js開發中引用第三方庫的幾種方式
在js組件開發中,咱們常常須要引用一些成熟的js組件,例如jquery datepicker, jquery select2, vue-mdl等庫,引用他們的方式有如下幾種:
首先咱們經過npm install vue-strap來安裝vue-strap庫;
1. CommonJS:
var alert = require('vue-strap/src/alert'); // or var alert = require('vue-strap').alert; new Vue({ components: { 'alert': alert } })
2.ES6
import alert from 'vue-strap/src/alert' // or import { alert } from 'vue-strap' new Vue({ components: { alert } })
3.AMD(這種方式會異步加載js,所以每每經過bower安裝,後序會加載到瀏覽器)
$ bower install vue-strap define(['vue-strap'], function(VueStrap) { var alert = VueStrap.alert; ... });
4. Browser globals
以vue-strap爲例,它會被以window.VueStrap全局對象來暴露相應的接口:
<script src="path/to/vue.js"></script> <script src="path/to/vue-strap.js"></script> <script> var vm = new Vue({ components: { alert: VueStrap.alert }, el: "#app", data: { showRight: false, showTop: false } }) </script>
強烈建議參考 segmentfault.com/a/119000000… 該文章對各類模塊化的寫法解釋的很清楚
動態load html而且編譯
若是你有一個組件,它會動態加載一部分html代碼,而這個代碼中包含一些vuejs代碼,你須要使用$compile功能:
_self.vm.$compile(_self.vm.$el); //這個是不行的,由於dom已是編譯過的 //下面的代碼是獲取須要recompile的html node,而且$compile,這樣就造成了活動的代碼了! _.each($('[recompile]'), function(el){ _self.vm.$compile(el); });
以上方法未通過驗證,也沒有徹底搞清楚,可是替代方案我是親手測試過的: 1. 經過async component;2.經過v-if async組件的方式動態向後端獲取數據和模版,在resolve方法中將對應數據和模版及數據來綁定;經過v-if的方式能夠將html partial做爲一個變量方式綁定在模版中,當該partial數據ready時,v-if天然會啓動編譯過程
Dynamic components with prop data傳入
若是你須要在同一個mount point顯示不一樣的component,這時Dynamic components就很合適了。
<!-- html --> <div id="app"> by dynamic Component: <component v-for="item in items" :is="item.component" //這裏根據item.component來決定渲染哪類組件 :opts="item.options"> //同時經過opts傳入要渲染組件的props </component> </div> Vue.component('node', { template: "<div>node: {{ opts }}</div>", props: ['opts'] }); Vue.component('node2', { template: "<div>node2: {{ opts }}</div>", props: ['opts'] }); new Vue({ el: '#app', data() { return { items: [{ component: "node", //node節點類型 options: 'node節點數據' }, { component: "node2", //node2節點類型 options: 'node2節點數據' }] }; } methods: { addItem() { this.items.push(this.newItem); this.newItem = { component: "", options: "" } } } });
https://jsfiddle.net/matiascx/qn29r3vt/
https://jsbin.com/nikimigaju/edit?html,output
webpack構建環境相關內容
什麼是webpack:
webpack是和browserify/gulp/grunt等類似的構建工具(Webpack is a module bundler. It takes a bunch of files, treating each as a module, figuring out the dependencies between them, and bundle them into static assets that are ready for deployment.),webpack比較完美地解決了前端模塊化開發的工具鏈支持。特別是webpack的loader插件機制使得能夠任意加載第三方開發的插件來擴展其支持的功能。接下來要說的vue-loader就是其中的一個典型案例;
什麼是loader:
Webpack 因爲自己只能處理 JavaScript 模塊(commonJS,AMD,ES6),若是要處理其餘類型的文件,就須要使用 loader 進行轉換。
Loader 能夠理解爲是模塊和資源的轉換器,它自己是一個函數,接受源文件做爲參數,返回轉換的結果。這樣,咱們就能夠經過 require
來加載任何類型的模塊或文件,好比 CoffeeScript、 JSX、 LESS 或圖片。
先來看看 loader 有哪些特性?
- Loader 能夠經過管道方式鏈式調用,每一個 loader 能夠把資源轉換成任意格式並傳遞給下一個 loader ,可是最後一個 loader 必須返回 JavaScript。
- Loader 能夠同步或異步執行。
- Loader 運行在 node.js 環境中,因此能夠作任何可能的事情。
- Loader 能夠接受參數,以此來傳遞配置項給 loader。
- Loader 能夠經過文件擴展名(或正則表達式)綁定給不一樣類型的文件。
- Loader 能夠經過
npm
發佈和安裝。 - 除了經過
package.json
的main
指定,一般的模塊也能夠導出一個 loader 來使用。 - Loader 能夠訪問配置。
- 插件可讓 loader 擁有更多特性。
- Loader 能夠分發出附加的任意文件。
Loader 自己也是運行在 node.js 環境中的 JavaScript 模塊,它一般會返回一個函數。大多數狀況下,咱們經過 npm 來管理 loader,可是你也能夠在項目中本身寫 loader 模塊。
按照慣例,而非必須,loader 通常以 xxx-loader
的方式命名,xxx
表明了這個 loader 要作的轉換功能,好比 json-loader
。
在引用 loader 的時候可使用全名 json-loader
,或者使用短名 json
。這個命名規則和搜索優先級順序在 webpack 的 resolveLoader.moduleTemplates
api 中定義。
什麼是vue-loader(browerify下對應vueify工具):
vue-loader是even you爲了支持web組件在一個.vue文件中組織js,css,html的夢幻開發模式,首創性地定義了一種文件類型component.vue, 這個vue文件中用script,style, template來分別表明js,css,html,這種文件格式是瀏覽器不認識的哦,webpack構建工具也是不認識的哦,要能使用必須先編譯打包成webpakc bundle,而在webpack生態系統中,vue-loader就是幹這個用的。一旦webpack遇到.vue文件就會調用這個vue-loader分別將js,css,html抽取出來,而且調用對應的代碼transpiler工具:好比css可能用less,也可能用sass;js可能用coffee,也可能用jsx;html可能用yaml等都須要轉換。這個工做就是vue-loader來完成的。
簡單一句話,vue-loader就是將一個.vue文件轉換爲一個js模塊的
chrome下vue dev-tool沒法顯示問題
Vue.config.devtools = true; //在new Vue()以前執行 //Vue.config.debug = true; //window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = Vue;
directive的webpack引用
// diretive定義 module.exports = { update: function(newValue, oldValue) { console.log(newValue) console.log(oldValue) }, bind: function() { }, unbind: function() { } } // component定義應用directive module.exports = { template: require('./template.html'), directives: { currency: require('./directives/currency.js') } } //在template中 <div v-currency>$1.00</div>
VueX
http://skyronic.com/2016/01/03/vuex-basics-tutorial/
vuex對於構建大型應用是很是重要的,主要解決數據狀態維護和傳遞的問題(好比不相干的多個組件須要更新數據,同時須要通知多個不相干的組件來應對這些數據的更新來更新視圖):整個應用只有一個地方負責對狀態數據的更新,任何一個地方均可以引用這個數據,須要更新則發消息給vuex來完成更新操做,其餘任何引用了這個狀態的地方都會reactive更新(computed屬性)
主要的思路是保持single source of truth,好比有兩個vue instance vmA和vmB,都依賴於一個狀態,這時如何保持狀態的同步就會是一個問題
var sourceOfTruth = {} var vmA = new Vue({ data: sourceOfTruth }) var vmB = new Vue({ data: sourceOfTruth })
動態建立而且mount組件到dom中
import ExecPlayer from './components/pages/exercise/exec-player.vue'; //這裏exec-player只是export了一個component option object var execplayercomp = Vue.extend(ExecPlayer); //這裏經過傳入定義好的component option object做爲Vue.extend的參數,以寫程序的方式得到了component的構造函數 var execplayer = new execplayercomp({ // 'el': '#execplayer', //調用new xxx的方式來建立組件,若是給了el配置項,則無需再調用$mount // 須要注意的是這種方式建立的組件在devtool中並不會顯示爲rootvm的子組件,可是實際上他們是有父子關係的!!! // 若是在html代碼中直接以 <exec-player></exec-player>調用的方式來composite組件的話,則在devtool中可以正確顯示父子關係~! created(){ this.exercises = rootvm.exercises; } }).$mount(document.getElementById('execplayer'));
vm.$set vs Vue.set vs Object.assign
1. For Vue instances, you can use the $set(path, value) instance method: vm.$set('b', 2) // `vm.b` and `data.b` are now reactive 2. For plain data objects, you can use the global Vue.set(object, key, value) method Vue.set(data, 'c', 3) // `vm.c` and `data.c` are now reactive 3. 若是想使用.assign來一次性給一個object添加多個屬性和value,須要注意: // instead of `Object.assign(this.someObject, { a: 1, b: 2 })` this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
vm.$nextTick() vs Vue.nextTick(callback)
到底nextTick是幹什麼的:
看一個牛人的回答: it's a way to execute a callback function after the data is set.
To add an example to that, lets say you have a jQuery plugin that creates a pie chart. The data on those charts are fetched and set by vuejs. You can't initialize the charts until after the data is set / until the "next tick". Here's a quick example...
If you try to initialize the charts without nextTick()
, it won't work because the data has not been changed yet.
<div id="example">{{msg}}</div> var vm = new Vue({ el: '#example', data: { msg: '123' } }) vm.msg = 'new message' // change data vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true }) // 對於component下面的調用方法 Vue.component('example', { template: '<span>{{msg}}</span>', data: function () { return { msg: 'not updated' } }, methods: { updateMessage: function () { this.msg = 'updated' console.log(this.$el.textContent) // => 'not updated' this.$nextTick(function () { console.log(this.$el.textContent) // => 'updated' }) } } })
經過extends選項繼承基礎組件,使用js代碼建立新組件,做爲獨立.vue文件方式構建新組建的替代方案,無須多個.vue文件
var CompA = { ... } // extend CompA without having to call Vue.extend on either var CompB = { extends: CompA, ... }
心法:在同一類組件須要在同一個mount point加載時,這種場景下最好的方案就是使用dynamic component。而使用動態組件又有兩種方案:
1. 直接生成多個.vue組件,好比component-typeA.vue,component-typeB.vue,這種方式比較適合組件代碼差別巨大的場景,具體應用時在使用該dynamic組件的父組件template中直接import這些.vue文件,而且components option中引用這些組件的定義
import CompTypeA from 'comp-type-a.vue' import CompTypeB from 'comp-type-b.vue' export default { ... components: [ CompTypeA, CompTypeB ] } // html: <component :is="comptype" :optionstoComp></component>
2. 只生成一個generic的.vue組件,隨後其餘組件extends這個generic組件,在引用該dynamic組件的父組件的script中直接使用js代碼方式"聲明"新的組件,並在父組件的template中使用:
import Comp from 'generic-comp.vue' var CompTypeA = { extends: Comp, templates: 'A', methods: ..A.. } var CompTypeB = { extends: Comp, templates: 'B', methods: ..B.. } export default { ... components: [ CompTypeA, CompTypeB ] } // html: <component :is="comptype" :optionstoComp></component>
vuejs中重用代碼的幾種方案
1. 建立自包含的組件,能夠任意重用
2. 建立抽象組件,好比node,實體組件extends這個抽象組件,再添加本身的options,造成新的concret組件
3. mixins: 部分代碼能夠被任意無關的類,組件共用,這部分最適合使用mixin
// mixin引用方法 import VuexStateGetterMixin from './mixin.js' export default { mixins: [ VuexStateGetterMixin ], }
4. 只負責對DOM操做的功能,則能夠抽象爲directive來重用
javascript中用到的各個版本術語 ES5,ES6,ES2016,ECMAScript
- ECMAScript:一個由 ECMA International 進行標準化,TC39 委員會進行監督的語言。一般用於指代標準自己。
- JavaScript:ECMAScript 標準的各類實現的最經常使用稱呼。這個術語並不侷限於某個特定版本的 ECMAScript 規範,而且可能被用於任何不一樣程度的任意版本的 ECMAScript 的實現。
- ECMAScript 5 (ES5):ECMAScript 的第五版修訂,於 2009 年完成標準化。這個規範在全部現代瀏覽器中都至關徹底的實現了。
- ECMAScript 6 (ES6) / ECMAScript 2015 (ES2015):ECMAScript 的第六版修訂,於 2015 年完成標準化。這個標準被部分實現於大部分現代瀏覽器。能夠查閱這張兼容性表來查看不一樣瀏覽器和工具的實現狀況。
- ECMAScript 2016:預計的第七版 ECMAScript 修訂,計劃於明年夏季發佈。這份規範具體將包含哪些特性尚未最終肯定
- ECMAScript Proposals:被考慮加入將來版本 ECMAScript 標準的特性與語法提案,他們須要經歷五個階段:Strawman(稻草人),Proposal(提議),Draft(草案),Candidate(候選)以及 Finished (完成)。
ES6中的=> arrow function
// ES5 var total = values.reduce(function (a, b) { return a + b; }, 0); // ES6 var total = values.reduce((a, b) => a + b, 0); // ES5 $("#confetti-btn").click(function (event) { playTrumpet(); fireConfettiCannon(); }); // ES6 $("#confetti-btn").click(event => { playTrumpet(); fireConfettiCannon(); });
arrow函數在vuejs event bus handler中的應用
vuejs2.0開始提倡使用event bus這個機制來實現代碼解耦和任何模塊之間的數據通訊,很是強大,可是使用中可能會遇到這樣的困惑:在event handler中的this指針怎麼纔可以引用到event handler所在的vue instance呢?這時ES6的=>arrow函數就起到了做用,由於它不會改變this指針:
this.pcomBus.$on('toggle-checked-all-for-role', e => { // 這裏this指針就指向的是定義這個pcomBus.$on調用的那個vue instance了! }
ES6 destruturing解構
const obj = { first: 'Jane', last: 'Doe' }; const {first: f, last: l} = obj; // f = 'Jane'; l = 'Doe' // {prop} 是 {prop: prop} 的縮寫 const {first, last} = obj; // first = 'Jane'; last = 'Doe'
強烈建議參考 segmentfault.com/a/119000000… 講解ES6基礎語法
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions
npm安裝vue, vuex等next分支上的最新版本:
npm install vue@next --save npm install vuex@next --save
vue2.0版本文件以及開發環境工具不匹配帶來的問題
$ npm run dev Vue packages version mismatch: - vue@2.0.0-rc.2 - vue-template-compiler@2.0.0-beta.7 This may cause things to work incorrectly. Make sure to use the same version for both. If you are using vue-loader or vueify, re-installing them should bump vue-template-compiler to the latest.
解決辦法npm install vue-loader@next --save-dev
vuex mapActions導入methods和local methods混用的語法
import { mapActions } from 'vuex' export default { methods:{ doJob: function(){ this.changeme(content) }, ...mapActions([ 'changeme' ]) } }
vuejs1.0 v-for track-by vs vuejs2.0 :key="item.id"
在vue v-for渲染一個列表時,若是不用被操做的item數據id做爲key的話,那麼vuejs會默認使用數組index做爲key,而這時若是數據列表發生變化,好比一個item被刪除,則可能會發生數據不一樣步的怪異錯誤,強烈建議永遠在v-for處理列表數據時,加上:key="item.id"!!!
v-for="skill in pskills" :key="skill.id"
什麼是抽象組件以(transition組件爲例)?
抽象組件具備如下特色:
1. 它是以引入功能而不是構建視圖爲目的而存在的
2. 它不會在DOM中有任何節點
3. 它也不會在inspect component hierarchy中顯示出來
抽象組件和無template的組件有什麼區別??
很是典型的例子是vuejs2.0中引入的transition組件:
<transition> <div v-if="ok">toggled content</div> </transition>
transition組件支持如下props:
1. name: 用於自動產生transition class name,好比若是name=fade,則對應的transition class將有.fade-enter,.fade-enter-active,.fade-leave,.fade-leave-active
2. appear: 是否在初次渲染時引用transition,默認爲false;
3. css: 是否引用css transition class。默認爲true,若是是false的話,則僅觸發javascript hooks
4. type: 指定須要等待何種event來決定transition的結束。好比: "transition",或者"animation"
5. mode: 控制leaving/entering transition的timing sequence,可能的選項是:in-out或者out-in
6.enterClass,leaveClss,enterActiveClass,leaveActiveClass,appearClass,appearActiveClass分別用於設置對應的css class爲客制內容.
好比:
<transition name="fade" mode="out-in" appear> <!-- classname 爲 fade,fade-active,fade-leave,fade-leave-active爲transition class --> <!-- 模式爲先出再入,而且首次渲染時也應用transition --> <component :is="view"></component> </transition>
支持的事件:
before-enter,enter,after-enter,before-leave,leave,after-leave,before-appear,appear,after-appear
例子:
<transition @after-enter="transitionComplete"> <div v-show="ok">toggled content</div> </transition>
<transition-group>組件
若是須要對多個元素執行transition效果,則須要使用transition-group組件。和transition組件所不一樣的是該組件須插入dom
Vue.component('fade', { functional: true, render (createElement, { children }) { const data = { props: { name: 'fade' }, on: { beforeEnter () { /* ... */ }, // <-- Note hooks use camelCase in JavaScript (same as 1.x) afterEnter () { /* ... */ } } } return createElement('transition', data, children) } }) <fade> <div v-if="ok">toggled content</div> </fade>
參考 http://vuejs.org/guide/render-function#Functional-Components
vuejs compile和mount過程
當vuejs app啓動時,首先入口程序根據#el來找到本應用對應的html片斷,該片斷做爲root的template,開始啓動編譯連接過程。每找到一個componennt,就開始walk through該組件的template,繼續compile,直到編譯完成。在vuejs2.0中因爲沒有了compiled鉤子,咱們能夠理解created事件就是按照上面的timing來完成的。若是對應有v-for,則開始啓動loop過程,就像普通的js代碼同樣,先把第一次Loop的數據傳給對應組件開始編譯,直到全部的loop都完成了,這時compile過程能夠認爲是徹底完成了。mount過程則和compile徹底相反,由compile造成的dom node map反序逐級mount。也就是說先子組件後父組件直到root組件被mounted.
好比,若是咱們有下面的html結構,以及外圍loop兩次,a組件template內須要loop三次,則會按照下面的timing圖來執行:
<!-- html content for root management partial --> <div id="root"> <div class="root" v-for="(item,index) of outerloop2times"><!-- 外圍須要loop 2次 --> <a></a> <b></b> </div> <c></c> <!-- 注意c組件是最後一個created和mounted to root的元素 --> </div> <!-- root結束 --> <script type="x/template" id="a-template"> <div class="a-root"> <a-sub v-for="(item,index) of innerloop3times"></a-sub> <!-- 須要loop 3次 --> </div> </script> <script type="x/template" id="b-template"> <div class="b-root"> <!-- standard html partial --> </div> </script>
全部watcher代碼都mounted以後纔可能被觸發。
關於vue組件的styling探討
1. vuejs組件須要有一個container class來負責該vue組件的position, layout,display,float等style設置;
2. vue-root, vue-root-{skin}做爲root元素的class
3. {skin}能夠考慮經過global vuex state驅動傳入,方便換膚
新增屬性reactive注意事項:
新增單個屬性,可使用:
Vue.set(vm.someobj,'newprop',propvalue)
新增多個屬性對象,可使用:
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
對於對象的操做總體替換,修改等強烈建議使用Vue.set由於這樣可以確保vuejs可以感知到這個變化
基於jqueryui select2組件在vuejs2.0(不支持prop.sync)下如何實現v-model模式的功能?
咱們知道vuejs2.0一個重大的變化是再也不支持prop.sync功能,也就是說從外部傳入的數據是不能經過vue組件內部邏輯直接來實現更新的。而咱們在封裝jquery-ui的select2組件造成咱們本身的select2組件時,又必需要實現v-model的功能,腫麼辦?還有就是若是銷燬vuejs select2組件,而其底層的jqueryuiselect2控件並不會自動銷燬,腫麼辦?
咱們看看官方的封裝代碼,解讀一下:
Vue.component('select2', { props: ['options', 'value'], template: '#select2-template', mounted: function () { var vm = this $(this.$el) .val(this.value) // init select2 .select2({ data: this.options }) // emit event on change. .on('change', function () { vm.$emit('input', this.value) //這個很是重要,經過emit一個input事件來更新v-model數據 }) }, watch: { value: function (value) { // update value $(this.$el).select2('val', value) }, options: function (options) { // update options $(this.$el).select2({ data: options }) } }, destroyed: function () { // 這裏也很重要,在vue組件銷燬時,jquery ui本身生成的dom並不會銷燬,咱們要經過jquery ui select2的api來實現銷燬 $(this.$el).off().select2('destroy') } }) html <select2 v-model="selectedValue" <!-- 這裏selectedValue會做爲value屬性傳入select2組件, 組件更新數據時經過$emit('input', select2internalValue)傳給selectedValue --> > </select2>
v-model internal
v-model不管用在input元素上仍是custom component上,其工做機制是同樣的:
<input v-model="message"> <!-- roughly the same as: --> <input v-bind:value="message" @input="message = $event.target.value"> <my-component v-model="message"></my-component> <!-- roughly the same as: --> <my-component v-bind:value="message" @input="setMessage"> </my-component> <!-- setMessage provided by v-model, doing essentially: function (valuefrom$emittedInputEvent) { message = valuefrom$emittedInputEvent } -->
vuejs2.0動態渲染模版
https://cinwell.com/post/vue-2-dynamic-template/
經過router-view給對應組件傳遞參數
<router-view class="view" :users="users"></router-view>
使用ES6的特性建立動態key值的對象
看下面的代碼:
export default{ data: function () { return { ["thisrootstore_"+this.$options.name]: { bus: new Vue() } } } }
上面這段代碼能夠做爲mixin來被每個組件重用,動態建立包含本組件名稱的本地全局數據變量 thisrootstore_xxname,其中的bus就能夠用於本組件和其子孫組件事件通訊
而且能夠被vuejs安全地merge,也就是說你能夠再手工建立其餘須要共享的數據在這個對象中
如何給webpack構建出來的bundle瘦身?
用過webpack你必定會發現每每就幾行代碼加到entry裏面最終作出來的bundle卻好幾百k,對於帶寬資源很是昂貴的主機來講是一個噩夢,一方面加載龜速,另外一方面若是是手機訪問更是噩夢。如何給他瘦身?首先找到哪些是咱們須要的,哪些是不該該出現的,對解決問題就作對了一半:
source-map-explorer 這個工具能夠對build出來的sourcemap作分析,給出依賴關係,你能夠慢慢研究
webpack-bundle-analyzer : 這個也很棒,他能夠可視化地列出你的bundle最終是由哪些個js代碼構成的,都是哪些臃腫的代碼致使了你最終的bundle過於龐大了
http://www.tuicool.com/articles/BjIrEj6
如何在production build中自動將console.log清除掉?
strip-loader能夠完成這個工做
如何獲取vuejs powered plain data?
有時候咱們可能須要一個未經vuejs define propery(get/set)處理過的原始js數據,一個workaround方案:
JSON.parse(JSON.stringify($vm.action.roles))
如何偵聽custom component的native事件,好比click? (使用.native修飾符)
<my-component v-on:click.native="doTheThing"></my-component>
如何綁定html元素屬性要麼爲一個值,要麼就沒有?
<a :href="shouldhavehref ? 'http://xx.com/yy' : null">
上面的代碼中判斷shouldhavehref data值,若是該值爲真則在a元素上存在href="http://xx.com/yy",若是該值爲假,則返回null,從而a元素上就不存在href屬性!
心法:只要expression的值爲null,就會被清除掉!
v-if和v-for共存
若是v-if和v-for在同一個element上出現,則和angular相似,也有一個directive優先級的問題。在vuejs中,v-for的優先級要高於v-if,也就是說先作v-for的loop,隨後對每個loop再應用v-if判斷,好比下面的代碼:對loop後的每個todo,只有在todo沒有完成的狀況下,咱們才render這個todo!!
<li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo }} </li>
什麼時候使用inline-template
一般,組件用於重用,可是有些時候,咱們可能只須要在一個頁面增長一些交互性,這時咱們可能使用Inline-template的組件就比較合適了,緣由是其佈置很是方便
// 將這段代碼放在你的html頁面中 <complete-progress inline-template> You have complete {{ count }} lessons <complete-progress> // 這段代碼放在html頁面引用的js文件中 Vue.component('complete-progress',{ data: function(){ return { count: 50 } } })