Vue中那些容易被忽略的~

哦啦吧啦丶發表於2018-07-15

記錄一些在使用Vue開發中常見的坑。

宣告本人菜鳥一隻,一些描述可能會有偏頗,大佬輕錘,歡迎拍磚。

小葵花課堂開課啦!

插槽相關:slotslot-scope

單個插槽 | 匿名插槽

以上兩種叫法也就說明了它的含義:

  • 一個元件中只能有一個該類插槽
  • 該類插槽不設定name屬性
<!-- 父元件 -->
<template>
  <div class="father">
    <h3>父元件</h3>
    <child>
      <div class="tmpl">
        <span>test1</span>
        <span>test2</span>
        <span>test3</span>
      </div>
    </child>
  </div>
</template>

<!-- 子元件 -->
<template>
  <div class="child">
    <h3>子元件</h3>
    <slot></slot>
  </div>
</template>
複製程式碼

子元件中定義的slot將被父元件中child內包裹的html模板所替代,即渲染後成:

<div class="father">
  <h3>父元件</h3>
  <div class="child">
    <h3>子元件</h3>
    <div class="tmpl">
      <span>test1</span>
      <span>test2</span>
      <span>test3</span>
    </div>
  </div>
</div>
複製程式碼
具名插槽

該插槽name屬性不為空,且可以在一個元件中出現多次。

<!-- 父元件 -->
<template>
  <div class="father">
    <h3>父元件</h3>
    <child>
      <div class="tmpl" slot="first">
        <span>test1</span>
        <span>test2</span>
        <span>test3</span>
      </div>
      <div class="tmpl" slot="second">
        <span>test4</span>
        <span>test5</span>
        <span>test6</span>
      </div>
      <div class="tmpl">
        <span>test7</span>
        <span>test8</span>
        <span>test9</span>
      </div>
    </child>
  </div>
</template>

<!-- 子元件 -->
<template>
  <div class="child">
    <h3>子元件</h3>
     <!-- 具名插槽 -->
    <slot name="first"></slot>
     <!-- 具名插槽 -->
    <slot name="second"></slot>
     <!-- 匿名插槽 -->
    <slot></slot>
  </div>
</template>
複製程式碼

元件中匿名插槽可與具名插槽同時使用,並且:

  • 宣告瞭name的具名插槽將在父元件中找到與其對應名稱的slot屬性,並將html模版替換到其中
  • 未宣告name的將找到對應的未宣告slot屬性的html模版做替換。
作用域插槽

觀察可以發現,以上兩種插槽的html模版的內容都是由父元件指定的,那如果我想展示來自子元件的內容該怎麼辦呢?

所以就有了第三種插槽,我們稱之為作用域插槽,坊間也叫攜帶資料的插槽,顧名思義,這種插槽可以攜帶資料給到父元件中的html模板。

<!-- 父元件 -->
<template>
  <div class="father">
    <h3>父元件</h3>
    <child>
      <template slot-scope='scope'>
        <div class="tmpl">
          <span v-for='u in scope.data'>test1</span>
        </div>
      </template>
    </child>
  </div>
</template>

<!-- 子元件 -->
<template>
  <div class="child">
    <h3>子元件</h3>
     <!-- 作用域插槽 -->
    <slot :data="arr"></slot>
  </div>
</template>
<script>
export default {
  data () {
    return {
      arr: ['test1','test2','test3']
    }
  }
}
</script>
複製程式碼

子元件中定義了一個陣列data傳給父元件展示,父元件以${slotScopeName}.data的格式去接收這個來自子元件的陣列加以展示。

想給第三方UI庫Event加自定義引數時

<el-date-picker v-model="editForm[item.name]" 
    placeholder="選擇日期"
    :editable="false" style="width:100%"
    @change="(value) => changeHandler(value, item.name)">
</el-date-picker>
複製程式碼

如上,使用箭頭函式,return一個新的自定義函式,在自定義函式changeHandler中加入你想要的引數。

同路由不同引數時

如果由 app/12345 跳轉到 app/23456,兩個路由可能都是app/:id,用的同一個元件,所以vue並不會重新走一遍生命週期,導致一些可能的資料沒更新的bug

所以,一個比較好的解決方案是監控路由的變化來重新整理一個必要的引數。

watch: {
    '$route.params': function (newValue) {
      this.init()
    }
}
複製程式碼

watch

如上是我們常用的 watch,但可能你不知道watch 還有兩個比較實用的配置:deepimmediate

deep

deep,預設值是 false。顧名思義即表示深入觀察,watch會將obj一層層往下遍歷,給物件的所有屬性都新增了監聽。可想而知,這一做法的效能耗費比較大。

watch: {
    obj: {
        handler (newVal, oldVal) {
            console.log('obj的屬性變化了')
        },
        deep: true
    },
    
}
// 建議使用
watch: {
    'obj.key': {
        handler (newVal, oldVal) {
            console.log('obj的屬性key變化了')
        }
    },
    
}
複製程式碼
immediate

immediate,預設值為 false。顧名思義即表示立即監聽,在定義了 handler 方法後將立即執行一次。

watch: {
 firstName: {
  handler (newval, oldVal) {
   this.fullName = `${newval} ${this.lastName}`
  },
  immediate: true
 }
}
複製程式碼

定時器

很多時候我們可能會有寫定時器的需求(比如有個輪詢任務),當我們離開這個頁面時,你會發現定時任務還在跑,效能炸了。

於是我們可以這麼幹:

data() {
    return {
        timer: null
    }
}
methods: {
    timing() {
        this.timer = setInterval(() => {
            // dosomething
        }, 100)
    }
}
beforeDestroy() {
    clearInterval(this.timer) // 在元件即將銷燬時清掉定時任務
    this.timer = null // 變數釋放
}
複製程式碼

關鍵就在當我們離開這個頁面(元件銷燬前)時清掉定時任務。

路由懶載入

Vue的首屏渲染慢飽受詬病。

究其原因,在於webpack打出來的包太大,導致在進入首頁的時候需要載入的檔案大且多。

那麼我們就可以使用懶載入將頁面切分(vue-router支援webpack內建的非同步模組載入系統,使用較少的路由元件不再打包到bundles中,只在路由被訪問到時按需載入),隨用隨載,減少首頁載入的負擔。

常規非懶載入路由配置
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login'
import Home from '@/components/Home'
import Profile from '@/components/Profile'

Vue.use(Router)

export default new Router({
    routes: [{
        path: '/login',
        name: 'Login',
        compontent: Login
    }, {
        path: '/home',
        name: 'Home',
        compontent: Home
    }, {
        path: '/profile',
        name: 'Profile',
        compontent: Profile
    }]
})

複製程式碼
懶載入路由配置

以下列了常用的三種寫法,webpack都支援。

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
    routes: [{
        path: '/login',
        name: 'Login',
        compontent: resolve => require(['@/component/Login'], resolve)
    }, {
        path: '/home',
        name: 'Home',
        compontent: () => import(/* webpackChunkName: "home" */  '@/component/Home')
    }, {
        path: '/profile',
        name: 'Profile',
        compontent: r => require.ensure([], () => r(require('@/component/Profile')), 'profile')
    }]
})

複製程式碼

Computed

先來看一段程式碼:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8">
        <title>Computed</title>

    </head>

    <body>
        <div id="app">
            <label for="">FirstName:<input v-model="firstName"/></label><br>
            <label for="">LastName:<input v-model="lastName"/></label><br>
            <label for="">NickName<input v-model="nickName"/></label><br>
            <p>名稱:{{name}}</p>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script>
            new Vue({
                el: '#app',
                data: function() {
                    return {
                        firstName: '',
                        lastName: '',
                        nickName: ''
                    }
                },
                computed: {
                    name: function() {
                        console.log('觸發computed了')
                        if (this.nickName) {
                            return this.nickName
                        } else {
                            return this.firstName + '.' + this.lastName
                        }
                    }
                }
            })
        </script>
    </body>
</html>
複製程式碼

天真時候的我們可能會以為每當nickNamefirstNamelastName三者任一變化都將重新觸發name的變更。

其實不然,當nickName有值(不為空)後,重新觸發依賴收集,此後將移除對firstNamelastName的依賴;而當nickName再次為空值時,又將會收集到對firstNamelastName等的依賴。

也就是說,依賴收集是動態的。

re-render相關

還是看一段程式碼:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8">
        <title>Re-render</title>

    </head>

    <body>
        <div id="app">
            <div v-for="(item, index) in items" :key="index">
                <div v-if="index === 0">
                    <input v-model="formData[item.key]">
                </div>
                <div v-else>{{item.key}}:{{Date.now()}}</div>
            </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script>
            new Vue({
                el: '#app',
                data: function() {
                    return {
                        items: [{
                            key: 'input'
                        }, {
                            key: 'Re-render'
                        }],
                        formData: {
                            input: ''
                        }
                    }
                }
            })
        </script>
    </body>
</html>
複製程式碼

以上可能是一種常見的場景。我們會驚奇的發現,當input框中值發生變化,頁面re-render了一個新的時間戳。不難看出問題出在Date.now(),這是一個方法(雖然沒定義在例項的methods中),故而每當template重新渲染時會再次觸發一次method,進而引發意料之外的事故。

<div id="app">
    <div>
    	<label>input:<input v-model="model"/></label>
    </div>
    <div>
    	<child :items="makeItems()"></child>
    </div>
</div>
複製程式碼

以上可能更能說明問題,一些場景下我們可能沒深考慮或下意識地直接使用一個method做一些props傳給子元件。那麼當輸入值model變化時觸發父元件模板重新渲染,childitems是從method來的所以會被重新做一遍,產生不必要的麻煩。

所以,一些場景下為了避免這種不必要的麻煩,還是推薦使用computed,因為計算屬性是基於它們的依賴進行快取的,而method在觸發重新渲染時總會再次執行函式,不做快取。

...

未完待續。。。

相關文章