使用vue開發已經有一段時間了,本文主要是記錄平時使用過程當中踩過的一些坑,以及一些心得。一方面能夠自我總結提升,另外一方面能夠將本身的經驗分享出來。css
1、爲何要用框架html
如今前端行業發展飛快,咱們在選用技術棧的時候,一方面要易於上手,另外一方面要適合本身的項目。vue就是這樣一個前端框架,易於上手,有成熟的文檔能夠參考、一樣有成熟的社區能夠討論問題。最不濟能夠閱讀源碼,vue的源碼仍是比較易讀的。前端
爲何咱們開發的時候須要使用框架?之前的jquery很差嗎?原生js開發很差嗎?使用這些框架可以解決哪些痛點?我就來講說我本身的一些淺見。vue
一、組件化node
組件化開發能夠解耦,便於複用,我跟人而言組件化是模塊化更進一步的解耦。最初咱們在開發前端項目的時候,瀏覽器原生並不支持模塊開發,咱們須要使用一些庫來給模塊化的js提供一個運行平臺,例如以前的 requireJs、seaJs等。若是你經歷過原始的開發再轉型到模塊化開發,你就會明白模塊化的好處。由於當你在沒有模塊的開發過程中,你會發現全部的變量都是全局的,全部的方法都是全局的(固然局部變量除外)。當你在你的js文件當中使用了某個變量的時候,你都找不到這個變量是定義在當前的js文件當中仍是定義在其餘的js文件中,這就很頭疼了。還有就是你引用了別人的js文件,你沒法確認你寫的方法是否是覆蓋了別人的方法。當你使用了模塊化開發的時候,你就能經過模塊引入的方式來進行你須要依賴的方法、類、對象、常量、變量的引入。而且模塊中全部定義的方法變量都不是全局的,這樣你會少了不少麻煩。jquery
上面簡要談了談模塊化的好處,模塊化對你的js邏輯來講基本上是夠用了,可是咱們前端不只有js邏輯,還有html、css這些界面樣式。這些東西怎麼去解耦呢?怎麼擁有本身的局部做用域呢?怎麼去複用呢?組件就很好地解決了這個問題。試想一下你須要在頁面上作一個擁有提交功能的按鈕,你須要在html中寫入元素,而後再在js中給這個按鈕添加事件。當你的頁面上有不少這種按鈕的時候,你須要去複製多份,這樣一沒有起到解耦的效果,二沒法複用。如今若是瀏覽器廠商特地爲你的需求定製了一個標籤,你只須要在你的html文件中使用這個標籤就能完成你所須要的功能,這樣是否是很簡單,若是你須要多份這樣的按鈕,你要作的只是將這個標籤寫到其餘地方。固然瀏覽器不可能爲你定製這樣一個標籤,因此你須要本身爲本身定製這樣一個標籤。這就是組件化的思想。web
二、數據綁定element-ui
這個能夠說是前端三大框架真正可以解決痛點的地方了,由於前面所說的組件化,瀏覽器立刻就要原生支持 webcomponent 了。在原來的開發模式中咱們修改頁面中的某個元素中的value值,咱們須要獲取到相應的元素,而後再修改元素的value。獲取元素的過程就是操做DOM的過程,瀏覽器爲js提供了操做DOM的接口,前端就是展現頁面,也就是改變html及css。改變html和css須要使用腳本語言才能動態地修改,這就是js存在的重要意義。可是操做DOM的過程是繁瑣的,而數據綁定能夠幫咱們從操做DOM的過程當中解放出來。試想一下咱們若是隻須要改變 value的值就可讓頁面發生相應的更改,這樣咱們就能夠免去操做DOM的麻煩。咱們稱這種改變UI的方式爲數據綁定。數組
無論框架是如何實現數據綁定的,咱們須要關心的只有數據,由於UI是被數據所驅動的。只要數據發生改變UI就能發生相應的更改。其實細想起來,咱們作的UI頁面就是一個html DOM樹,這顆DOM樹徹底能夠經過內存中的對象來實現數據映射。框架要作的工做就是將內存中的對象與DOM樹發生綁定關係,其實內存中的這個對象就是咱們常常看到的虛擬DOM。瀏覽器
2、vue的數據綁定原理
vue這個框架很好地實現了以上兩個優勢。我曾經讀過一些vue的源碼,下面說說我對vue發生數據綁定的一些理解,若是掌握了vue的工做原理,咱們能夠在工做當中很好地解釋一些難以理解的問題,我認爲仍是頗有必要的。
通常來講咱們改變了數據瀏覽器並不知道須要從新渲染哪些DOM,瀏覽器只知道當咱們顯式地改變了DOM的時候纔會去從新渲染。很顯然改變數據而後通知瀏覽器去渲染就是咱們的vue起的做用。那麼vue是怎麼知道咱們改變了數據的呢?
第一個問題:vue如何知道咱們修改了數據?
其實vue並非萬能的,它並不知道我隨手寫的一個變量是否發生了改變。也就是說vue所監控的變量是有要求的,這個要求就是顯式地聲明在data和props中的對象屬性。也就是說咱們改變data對象或者對象中的屬性的時候vue是知道咱們修改了這些屬性的。vue是如何知道的呢?
在咱們實例化Vue這個類的時候,它會遞歸地對data對象設置監聽,監聽的方式是 使用瀏覽器支持的 Object.defineProperty() 這個類靜態方法對這些屬性或對象設置get、set方法。這兩個方法有點相似鉤子函數,當你去改變data中的某個屬性時就會觸發set方法,當你去獲取data中的某個屬性時就會觸發get方法。很顯然你能夠在get和set方法中執行一些操做去修改頁面中的DOM。
vue就是這樣對咱們的數據進行監控的。前面說了 vue是在初始化的時候遞歸地對屬性進行監聽的,當你改變了data中某個屬性的引用的時候vue會從新對新的對象進行遞歸地監聽。注意這裏說的是改變引用的時候纔會觸發監聽綁定,也就是說當你的對象引用沒有發生改變,只是給對象增長了一個屬性的時候vue是沒法對新的屬性進行監聽的。這也就是爲何你在沒有在data中聲明屬性而是後面添加的屬性vue沒有辦法監聽到的緣由。一般咱們還會犯一個錯誤,就是咱們把開始聲明的對象引用改變了,可是新的引用中的屬性跟原來的引用的屬性有所區別,這樣原來的屬性就會丟失引用。例如
1 data () { 2 return { 3 pro1: { // 初始聲明的引用具備a b兩個屬性 4 a: 1, 5 b: 2 6 } 7 } 8 } 9 10 ... 11 12 // 後面的某個時刻 13 this.pro1 = {a: 3, c: 4}; 14 15 // 在這裏你會發現原來 模板中與 pro1.b 發生綁定關係已經被丟失了
就上面的問題而言,咱們平時的工做當中如何避免這種狀況呢。現階段我採用的方法是 使用 Object.assign()靜態方法或者本身編寫一個merge方法,進行數據的合併操做。因爲Object.assign()方法存在侷限性,咱們本身編寫的merge方法能夠更加靈活,因此咱們會採用merge方法進行vue的數據變動。merge方法的大概實現以下:
/** * 數據合併方法 * @param {Object} target 目標對象 * @param {Object} origin 源對象 * @param {String} stand 合併標準,默認爲左樹標準 * @returns {void} 無返回值 */ export function mergeData (target, origin, stand = 'left') { if (!target || !origin) { return } if (Utils.dataType(target) !== Utils.dataType(origin)) { console.error('目標對象與源對象的數據類型不一樣,沒法實現合併') return } let flag = stand === 'left' for (let prop in target) { if (Utils.dataType(target[prop]) === 'object') { // target[prop] = (target[prop].constructor === Array) ? [] : {}// 三元運算,將s[prop]初始化爲數組或者對象 mergeData(target[prop], origin[prop]) } else if (Utils.dataType(target[prop]) === 'array') { // 兼容處理 if (!origin[prop]) { origin[prop] = [] } if (origin[prop].length > 0) { // 該條件是爲了剔除重複的數據 target[prop].length = 0 } target[prop].push(...origin[prop]) } else { let defaultVal switch (Utils.dataType(target[prop])) { case 'object': defaultVal = flag ? (target[prop] || {}) : (origin[prop] || {}) break case 'array': // defaultVal = target[prop] || [] defaultVal = flag ? (target[prop] || []) : (origin[prop] || []) break case 'string': // defaultVal = target[prop] || '' defaultVal = flag ? (target[prop] || '') : (origin[prop] || '') break case 'number': // defaultVal = target[prop] || 0 defaultVal = flag ? (target[prop] || 0) : (origin[prop] || 0) break case 'boolean': // defaultVal = target[prop] || false defaultVal = flag ? (target[prop] || false) : (origin[prop] || false) break } target[prop] = (origin[prop] || defaultVal) } }; }
第二個問題:Vue怎麼知道咱們數據改變以後須要對哪些UI狀態作出修改?
3、組件數據共享
分組件開發固然能簡化咱們的工做,數據綁定又讓咱們只用關注於數據,由於vue接管了咱們的UI,它能夠將咱們操做的數據動做映射爲UI狀態的改變。因此咱們在平時開發的過程中須要關心數據的改變已經數據的傳遞。數據的傳遞指的是組件之間數據的傳遞,由於咱們在開發工做當中每每都會有多個組件共享數據的狀況。這些組件之間的關係多是父子組件、兄弟組件、祖前後代組件這些關係。這裏我想說一下最簡單的父子組件之間的數據傳遞方式。由於兄弟組件和祖前後代組件這種組件關係Vue提供了Vuex和事件總線這種解決方案。
從個人工做經驗來講父子組件之間的數據傳遞方式有兩大類,一類就是Vue官方推薦的作法,父傳子經過props來進行傳遞,子傳父經過$emit這種發佈訂閱模式來進行。第二類就是父傳子經過props進行傳遞,子傳父經過改變對象屬性引用的方式來進行.
// 父組件模板 <ChildComponent :parent-props="testProp" /> // 父組件數據 data: { testProp: { pro1: 'a', pro2: 'b' } } // 子組件 props: ['testProp'], methods: { doing () { this.testProp.a = 'c' } }
<el-form :model="formData" :rules="formRules"> <el-form-item prop="name"> <el-input v-model="formData.name" /> </el-form-item> </el-form> // 數據 data: { formData: { name: '' }, formRules: { name: [ // ... ] } }