vue踩坑總結 & 優化點


最近連着做了好幾個vue項目,從0到版本迭代,vue教程算是好理解,把vue官方文檔看一遍下來,基本上不會有什么大問題(嗯如果你看的夠仔細夠透徹的話),此篇是記錄vue踩過的坑以及可以優化的地方。

1.踩坑

1.1.深拷貝/淺拷貝

這實際上算不上vue的問題,算是js基礎沒打好的坑吧。

先來看一個簡單的例子:

let obj = {name:'fiona-SUN'};
let copyObj = obj;
copyObj.name = 'fiona';
console.log(copyObj.name);  // 'fiona'
console.log(obj.name);     // 'fiona'

在js中也有棧(stack)和堆(heap)的概念:

  • 棧:自動分配的內存空間,大小確定會自動釋放。存放變量/局部變量/形參等。在js中存放簡單數據段(五種基本數據類型:Number、String、Boolean、Null、Undefined),他們是按值存放的,可以直接訪問。
  • 堆:動態分配的內存,大小不定並且不會自動釋放。存放在堆內存中的對象,棧中的變量實際保存的是一個指針,這個指針指向堆中的某一個位置。

所以上述例子中,屬於淺拷貝,當我們聲明一個對象,由於他不屬於五種基本數據類型(即非簡單數據段),棧中會存放一個我們聲明的obj變量,它指向了堆中實際的這個對象的地址。當我們把這個引用地址賦值給了copyObj,實際它獲得的是一個與obj一致的指向堆中的地址。當copyOjb改變了指向的對象地址的實際的值的時候,obj拿到的值也就自然而然變化了。看圖理解⬇

嗯,道理我都懂,但是寫代碼我就自然而然的忽略了,該反思。。。

深拷貝的方法

  • 方法一:逐個去拿到簡單數據項(網上可以搜到遞歸解決,思路類似)
let obj = {name:'fiona-SUN'};

let copyFunc = (originObj) => {
  let copyObj = {};
  for(let key in originObj){
    copyObj[key] = originObj[key];
  }
  return copyObj;
};

let copyObj = copyFunc(obj);
copyObj.name = 'fiona';
console.log(copyObj.name);  // 'fiona'
console.log(obj.name);     // 'fiona-SUN'
  • 方法二:通過JSON去解析
let obj = {name:'fiona-SUN'};

let copyObj = JSON.parse(JSON.stringify(obj));

copyObj.name = 'fiona';
console.log(copyObj.name);  // 'fiona'
console.log(obj.name);     // 'fiona-SUN'
  • 方法三:es6之展開Object.assign(拷貝obj的內容到一個新的堆內存,copyObj存儲新內存的引用)
let obj = {name:'fiona-SUN'};

let copyObj = Object.assign({}, obj);

copyObj.name = 'fiona';
console.log(copyObj.name);  // 'fiona'
console.log(obj.name);     // 'fiona-SUN'
  • 方法四:es6之展開運算符(僅用於數組)
let arr = [1,2,3];
let copyArr = [...obj];
copyArr[2] = 0;
console.log(copyArr[2]);  // 0
console.log(arr[2]);     // 2

1.2.列表更新檢測

1.2.1數組更新檢測

以下摘自vue官網API

由於 JavaScript 的限制,Vue 不能檢測以下變動的數組:

  1. 當你利用索引直接設置一個項時,例如:vm.items[indexOfItem] = newValue
  2. 當你修改數組的長度時,例如:vm.items.length = newLength

為了解決第一類問題,以下兩種方式都可以實現和 vm.items[indexOfItem] = newValue 相同的效果,同時也將觸發狀態更新:

// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)

為了解決第二類問題,你可以使用 splice:

example1.items.splice(newLength)

觸發視圖更新的方法:

  1. 變異方法
  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
  1. vue提供的set方法
  • Vue.set( target, key, value )
1.2.2對象更新檢測

還是由於 JavaScript 的限制,Vue 不能檢測對象屬性的添加或刪除:

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` 現在是響應式的

vm.b = 2
// `vm.b` 不是響應式的

解決方法:

  1. vue提供的set方法
  • Vue.set( target, key, value )
  1. Object.assign()
  • bject.assign({}, target, {key:value})

1.3.頁面刷新vuex被清空

這真的是遇到一個很坑的問題,同一個頁面(router未改變),一旦刷新(刷新或深度刷新),存儲的vuex就馬上和你說拜拜

  • localStorage
    網上推薦最多的方法就是用localStorage。但是我個人覺得不太合適,還得看項目吧。localStorage是永久存儲的。

  • 數據重新獲取
    我使用的方法是在需要某些數據之前先判斷一下數據是否存在,如果不存在重新獲取。

1.4nextTick適當使用

將回調延遲到下次 DOM 更新循環之后執行。在修改數據之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一樣,不同的是回調的 this 自動綁定到調用它的實例上。

簡而言之,等待DOM更新之后再進行操作。

1.5.異步問題

這個是一個亘古不變的話題。

請求后台數據異步,常不經意的帶來了問題。(處理異步的方法就不詳細描述了,網上一搜一大堆)

1.6.組件之間的調用方式

1.6.1.父子組件
  • prop向下傳遞,事件向上傳遞
  • 子組件添加ref屬性,父組件可以獲取到子組件的實例(不建議)
  • 插槽slot 作用域插槽
1.6.2.非父子組件
  • 使用狀態管理
  • 實例化一個公共vue實例

1.7.計算屬性設置值

計算屬性是基於它們的依賴進行緩存的,一旦依賴發生變化,計算屬性會重新計算

想要改變計算屬性的值。要通過set方法去觸發它所依賴的變量,(類似於觸發它重新計算,單純賦予一個新值,在取的時候也是不會被改變的)

1.8.vue文件中內聯樣式中有無scoped屬性的差別

  • 有scoped屬性:
    當前僅當該vue文件可以使用這個樣式。
  • 無scoped屬性:
    影響其他文件

1.9 v-for v-key

當 Vue.js 用v-for 正在更新已渲染過的元素列表時,它默認用“就地復用”策略。如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序, 而是簡單復用此處每個元素,並且確保它在特定索引下顯示已被渲染過的每個元素。

為了給 Vue 一個提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一 key 屬性。理想的 key 值是每項都有的且唯一的 id。這個特殊的屬性相當於 Vue 1.x 的 track-by ,但它的工作方式類似於一個屬性,所以你需要用 v-bind 來綁定動態值 (在這里使用簡寫):

1.10 v-for 和 v-if

當它們處於同一節點,v-for的優先級比v-if更高。

1.11 js文件中引入的css不會自動加前綴(新的腳手架已解決該問題)

無論是開發環境還是生成環境都不會自動加前綴,因為vue-loader只管.vue文件里面的樣式,沒有自動執行autoprefixer loader

參考鏈接

在build/utils.js下引入postcss-loader

var postcssLoader = {
    loader: 'postcss-loader',
    options: {
        plugins: (loader) => [
            require('autoprefixer')()
        ],
        sourceMap: true
    }
}

如果還有問題在改成

var postcssLoader = {
    loader: 'postcss-loader',
    options: {
        plugins: (loader) => [
            require('autoprefixer')({
                browsers: [
                    // 加這個后可以出現額外的兼容性前綴
                    "> 0.01%"
                ]
            })
        ],
        sourceMap: true
    }
}

1.12 組件、prop大小寫不敏感,事件敏感

對於組件和prop而言,html上用kebab-case (短橫線分隔命名) ,其對應的js上要用

(HTML 中的特性名是大小寫不敏感的,所以瀏覽器會把所有大寫字符解釋為小寫字符。這意味着當你使用 DOM 中的模板時,camelCase (駝峰命名法) 的 prop 名需要使用其等價的 kebab-case (短橫線分隔命名) 命名:)

【但是:如果你使用字符串模板,那么這個限制就不存在了。】

components: {
    kebabCase
}

---

prop: ['kebabCase']

跟組件和 prop 不同,事件名不存在任何自動化的大小寫轉換。而是觸發的事件名需要完全匹配監聽這個事件所用的名稱。

跟組件和 prop 不同,事件名不會被用作一個 JavaScript 變量名或屬性名,所以就沒有理由使用 camelCase 或 PascalCase 了。並且 v-on 事件監聽器在 DOM 模板中會被自動轉換為全小寫 (因為 HTML 是大小寫不敏感的),所以 v-on:myEvent 將會變成 v-on:myevent——導致 myEvent 不可能被監聽到。

因此,我們推薦你始終使用 kebab-case 的事件名。

1.13 refs是靜態節點

refs是靜態節點

1.14 prop值的改變--不是立即

如果父組件中給子組件傳遞了一個prop的值,然后調用子組件的方法去獲取該值,會發現值沒有立即改變。

解決方法:

  1. 可以監聽值的改變去調用相應子組件的方法
  2. 將子組件相關方法的調用放在nextTick里面

1.16 fetch兼容

在vue-cli中使用fetch方式請求的時候。存在一定的兼容問題。

可以在build/webpack.base.conf.js中添加一個plugins

plugins:[
new webpack.ProvidePlugin({
  fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch',
  Promise:  'imports-loader?this=>global!exports-loader?global.Promise!es6-promise',
})
]

1.17 字符串模板

字符串模板

1.18 對象中某屬性值的監聽

普通的watch中只能監聽到某對象的變化才會調用,當想監聽對象以及對象中屬性的變化都調用函數時,可以使用deep:true

data() {
  return {
    bet: {
      pokerState: 53,
      pokerHistory: 'local'
    }   
    }
},
watch: {
  bet: {
    handler(newValue, oldValue) {
      console.log(newValue)
    },
    deep: true
  }
}

1.19 this.$forceUpdate

強制刷新頁面,觸發頁面重新渲染。

1.20 vue中的beforeRouteUpdate

xxx/detail/123xxx/edit/123都用了同一個組件,beforeRouteUpdate不生效,但是watch $route是生效的?

擴展: 可以考慮在路由定義處使用別名 alias

alias DOC

官方解釋

beforeRouteUpdate (to, from, next) {
    // 在當前路由改變,但是該組件被復用時調用
    // 舉例來說,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
    // 由於會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鈎子就會在這個情況下被調用。
    // 可以訪問組件實例 `this`
  },

相關鏈接

1.21組件自身調用自身

example

在js中對組件命名(設置name屬性)即可以調用自身

1.22 vue會使用一種最大限度減少動態元素並且盡可能的嘗試修復/再利用相同類型元素的算法。

  • 解決方法:使用key,它會基於key的變化重新排列元素順序,並且會移除key不存在的元素。(key特殊屬性主要用在vue的虛擬DOM算法,在新舊nodes對比時辨別VNodes)

常見的場景:

  1. v-for下,有相同父元素的子元素必須有獨特的key。重復的key會造成渲染錯誤。
  2. 它也可以用於強制替換元素/組件而不是重復使用它
    1. 完整地觸發組件的聲明周期鈎子
    2. 觸發過渡
  3. 幾乎完全一樣/一樣的兩個html結構元素

1.23 父組件的自定義事件

若在父組件上定義一個事件,相當於定義一個監聽子組件的監聽器。若想要單獨使用父組件的事件而不是監聽,加一個.native事件修飾符即可。

2.優化

2.1.錯誤處理

錯誤處理很重要但是這是最容易讓開發忽略的點。

2.1.1.請求接口錯誤

由於我的請求是使用axios插件或者fetch單獨寫在了一個js,可以對其進行響應攔截。一旦失敗,或者后台報錯,就進行相應的錯誤處理以及友好提示,也避免了重復的代碼,提高可維護性

2.1.2.頁面錯誤處理(404)
  1. nginx未匹配到路由走404路由
  2. router.beforeEach是否匹配到響應的路由,否則走錯誤路由。
2.1.3 錯誤提示封裝

將錯誤提示模塊化,通過vuex來操作錯誤的顯示以及信息等內容。

2.2.減少不必要的依賴包

性能優化是很重要的,特別是對於vue這種首屏加載時間長的。

例如有些項目用到了圖表(echarts),可以選擇加載依賴包,不用加載整個echarts庫。

2.3.不發送多個相同的請求

不發送多個相同的請求,在點擊觸發請求的同時鎖定請求,直至給出響應/錯誤解鎖。

以上內容,如有錯誤請指出,不甚感激。
如需轉載,請注明出處

2.4.組件中引入css的css依賴 -- sass-resources-loader

為了讓SCSS之類的文件在CSS中引入中不需要每次都引入var.scss文件,可以引入一個sass-resources-loader解決。

2.4.為了解決組件內引入的外部css文件沒有做css兼容處理 -- postcss-loader

在build/utils中引入postcss-loader

loader: 'postcss-loader',
options:{
  plugins: (loader) => [
      require('autoprefixer')()
  ],
  sourceMap: true 
}

2.5兼容ie9-11

  • babel-polyfill模塊
entry: {
    // app: './src/main.js'
    app: ["babel-polyfill", "./src/main.js"]
}
// build/webpack.base.config.js
// ./src/main.js是入口文件,可能存在差異
  • promise兼容
plugins: [
    new webpack.ProvidePlugin({
        // 如果用了fetch可以使用以下的進行兼容
        // fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch',
        Promise: 'imports-loader?this=>global!exports-loader?global.Promise!es6-promise',
    })
]

2.6 vue項目配置ico

doc

3.混淆點

3.1 camelCase vs kebab-case - prop/事件

  1. prop

HTML 中的特性名是大小寫不敏感的,所以瀏覽器會把所有大寫字符解釋為小寫字符。這意味着當你使用 DOM 中的模板時,camelCase (駝峰命名法) 的 prop 名需要使用其等價的 kebab-case (短橫線分隔命名) 命名:

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>

重申一次,如果你使用字符串模板,那么這個限制就不存在了。

  1. 事件

不同於組件和 prop,事件名不存在任何自動化的大小寫轉換。而是觸發的事件名需要完全匹配監聽這個事件所用的名稱。舉個例子,如果觸發一個 camelCase 名字的事件:

this.$emit('myEvent')

則監聽這個名字的 kebab-case 版本是不會有任何效果的:

<my-component v-on:my-event="doSomething"></my-component>

不同於組件和 prop,事件名不會被用作一個 JavaScript 變量名或屬性名,所以就沒有理由使用 camelCasePascalCase 了。並且 v-on 事件監聽器在 DOM 模板中會被自動轉換為全小寫 (因為 HTML 是大小寫不敏感的),所以 v-on:myEvent 將會變成 v-on:myevent——導致 myEvent 不可能被監聽到

因此,我們推薦你始終使用 kebab-case 的事件名。

4.vue常見面試題

doc

以上內容,如有錯誤請指出,不甚感激。
如需轉載,請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM