Vue開發的實用代碼技巧

常被忽略的watch技巧

vm.$watch( expOrFn, callback, [options] )

  • 參數html

    • {string | Function} expOrFn
    • {Function | Object} callback
    • {Object} [options]
  • {boolean} deepvue

    • {boolean} immediate
  • 返回值{Function} unwatch
  • 用法

    觀察 Vue 實例變化的一個表達式或計算屬性函數。回調函數獲得的參數爲新值和舊值。表達式只接受監督的鍵路徑。對於更復雜的表達式,用一個函數取代。webpack

例如咱們在某種獲取搜索列表的場景就至關適合使用web

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
</head>

<body>
    <div id="app">
        <input type="text" v-model="searchWord">
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                searchWord: ''
            },
            created() {
                // 獲取搜索列表
                this.getSearchList()
            },
            watch: {
                // 監聽數據變化獲取搜索列表
                searchWord(oldVal, newVal) {
                    this.getSearchList(oldVal, newVal)
                }
            },
            methods: {
                getSearchList(oldVal, newVal) {
                    console.log('得到值: ', oldVal, newVal)
                    console.log('getSearchList')
                }
            }
        })
    </script>
</body>

</html>

咱們能夠優化成正則表達式

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
</head>

<body>
    <div id="app">
        <input type="text" v-model="searchWord">
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                searchWord: ''
            },
            watch: {
                // 監聽數據變化獲取搜索列表
                searchWord: {
                    handler: 'getSearchList',
                    immediate: true,
                    deep: true
                }
            },
            methods: {
                getSearchList(oldVal, newVal) {
                    console.log('得到值: ', oldVal, newVal)
                    console.log('getSearchList')
                }
            }
        })
    </script>
</body>

</html>
  1. handler 聲明調用方法名
  2. immediate 表示組件建立的時候當即執行一次(效果等同create生命週期內調用)
  3. deep 爲了發現對象內部值的變化,數組不須要添加

Vue 的父組件和子組件生命週期鉤子函數執行順序

加載過程vue-router

父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

更新過程express

父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

銷燬過程npm

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

響應式數據(2.6.0新增)

咱們習慣於用Vuex去解決狀態的共享問題,可是在小項目中使用就會有增大代碼體積和將代碼複雜化的煩惱,因此在後來的版本中Vue新增api

Vue.observable( object )

讓一個對象可響應,Vue 內部會用它來處理 data 函數返回的對象。數組

返回的對象能夠直接用於渲染函數計算屬性 內,而且會在發生改變時觸發相應的更新。也能夠做爲最小化的跨組件狀態存儲器,用於簡單的場景:

官方示例

const state = Vue.observable({ count: 0 })

const Demo = {
  render(h) {
    return h('button', {
      on: { click: () => { state.count++ }}
    }, `count is: ${state.count}`)
  }
}

長靜態列表性能優化

咱們都知道Vue2.x版本是利用Object.definedProperty對數據進行劫持達到視圖響應變化的效果,可是大多數時候咱們不須要變更的數據相似列表頁基本不存在這個需求,咱們就能夠利用object.freeze將其 "凍結"禁止修改.

便可以減小劫持數據的性能損耗,但不影響其從新賦值,例如

var app = new Vue({
    el: '#app',
    data: {
        list: []
    },
    async created() {
        const list = await this.getData()
        this.list = Object.freeze(list)
    },
    methods: {
        handleChange() {
            this.list[0].id = 1 // 無效
            this.list = [] // 能夠修改
        }
    }
})

函數式組件

若是有學過React的話應該就能發現一些相同的煩惱,在默寫很簡單的組件若是都必須實例化一遍是至關煩惱的操做,因此在一些簡單而且不須要任何生命週期處理的組件咱們能夠將狀態做爲傳參這麼寫

// List組件
<template functional>
    <ul>
        <li v-for="item in props.list" @click="props.handleChange">{{item}}</li>
    </ul>
</template>

// 父組件調用方式
<div>
    <List :list="list" :handle-change="handleChange" />
</div>

監聽組件生命週期

在某些須要監聽組件生命週期的場景中可能大部分人都是經過傳遞方法而後在組件生命週期內調用,其實還有一種方法能夠直接在父組件就進行調用方式---hook

<!DOCTYPE html>
<html lang="en">

<body>
    <div id="app">
        <child @mounted="doSomethings">child</child>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        Vue.component('child', {
            mounted() {
                this.$emit("mounted");
            },
            render(createElement) {
                return createElement('p', this.$slots.default)
            }
        })
        var app = new Vue({
            el: '#app',
            data: {
                status: 0
            },
            methods: {
                doSomethings() {
                    console.log('doSomethings')
                }
            }
        })
    </script>
</body>

</html>

動態組件

如下面的一組狀態判斷按鈕爲例,咱們很容易就下意識地在模板內寫下這種代碼

<button v-if="status === 1" class="btn1" :class="status === 1" @click="">未開始</button>
<button v-if="status === 2" class="btn2" :class="status === 2" @click="">進行中</button>
<button v-if="status === 3" class="btn3" :class="status === 3" @click="">可領取</button>
<button v-if="status === 4" class="btn4" :class="status === 4" @click="">已領取</button>

可是若是咱們利用渲染函數能夠將上面的代碼抽取成優雅的使用組件

<!DOCTYPE html>
<html lang="en">

<body>
    <div id="app">
        <child :status="status"></child>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        Vue.component('child', {
            props: {
                status: {
                    type: Number,
                    required: true
                }
            },
            render(createElement) {
                const innerHTML = ['未開始', '進行中', '可領取', '已領取'][this.status]
                return createElement('button', {
                    class: {
                        active: this.status
                    },
                    attrs: {
                        id: 'btn'
                    },
                    domProps: {
                        innerHTML
                    },
                    on: {
                        click: () => console.log(this.status)
                    }
                })
            }
        })
        var app = new Vue({
            el: '#app',
            data: {
                status: 0
            }
        })
    </script>
</body>

</html>

咱們將全部的邏輯封裝進渲染函數內,外部只須要傳遞一個狀態參數便可改變

<child :status="status"></child>

路由緩存

假設咱們有一個詳情路由,咱們從 /detail/a 跳轉到 /detail/b ,可是由於vue-router 認爲這是同一個組件,因此不會從新執行 created內的代碼,咱們通常的解決辦法是監聽 $router 的變化執行代碼

created() {
    this.init()
},
watch: {
    '$route'() {
        this.init()
    }
},
methods: {
    init() {}
}

可是咱們其實能夠在路由組件設置惟一的 key 解決緩存問題

<router-view :key="$route.fullpath"></router-view>

注意這個方法會增長額外消耗

刷新當前路由

原始方法有幾種:

location.reload()

從新加載頁面,弊端也很明顯,從新運行全部流程,全部數據不做特殊處理會丟失,有可能被重定向到其餘路由

this.$router.go(0)

有些瀏覽器會原路由刷新,有些瀏覽器會返回首頁,具體緣由沒去探究

跳轉回當前路由API

頁面不會進行刷新,可是能夠結合上面的路由緩存作處理

provide / inject

這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。

<router-view v-if="isRouterAlive" />

// 父級組件提供 'foo'
var Provider = {
  provide() {
    return {
      reload: this.reload
    }
  },
  data() {
    return {
      isRouterAlive: true
    }
  },
  // ...
}

// 子組件注入 'foo'
var Child = {
  inject: ['reload'],
  created () {
    this.reload()
  }
  // ...
}

高階組件

咱們在父子組件進行 props 傳遞時,一般須要聲明一大堆屬性

//父組件
<BaseInput 
  :value="value"
  label="密碼"
  placeholder="請填寫密碼"
  @input="handleInput"
  @focus="handleFocus>
</BaseInput>

//子組件
<template>
 <label>
  {{ label }}
  <input
   :value="value"
   :placeholder="placeholder"
   @focus=$emit('focus', $event)"
   @input="$emit('input', $event.target.value)"
  >
 </label>
</template>

inheritAttrs

默認狀況下父做用域的不被認做 props 的特性綁定 (attribute bindings) 將會「回退」且做爲普通的 HTML 特性應用在子組件的根元素上。當撰寫包裹一個目標元素或另外一個組件的組件時,這可能不會老是符合預期行爲。經過設置 inheritAttrsfalse,這些默認行爲將會被去掉。而經過 (一樣是 2.4 新增的) 實例屬性 $attrs 可讓這些特性生效,且能夠經過 v-bind 顯性的綁定到非根元素上。

注意:這個選項不影響 classstyle 綁定。

$attrs

咱們能夠利用如下方式 $attrs 將原生屬性直接傳遞給子組件

<input
   v-bind="$attrs"
   :value="value"
   @focus=$emit('focus', $event)"
   @input="$emit('input', $event.target.value)"
>

這是Vue在2.4.0新增的屬性,包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (classstyle 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (classstyle 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件——在建立高級別的組件時很是有用。

$listeners

<input
  :value="value"
  v-bind="$attrs"
  v-on="listeners"
>
----------------------------------------------------------------------
computed: {
 listeners() {
  return {
   ...this.$listeners,
   input: event => 
    this.$emit('input', event.target.value)
  }
 }
}

包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners"傳入內部組件——在建立更高層次的組件時很是有用。

利用webpack自動註冊組件

當項目規模和組件化達到必定程度的時候,咱們最常開始的準備工做就是引入組件模塊 -> 聲明組件

若是咱們利用webpack 解析 require() 調用,而後提取出以下一些信息:

Directory: ./template
Regular expression: /^.*\.ejs$/

生成一個 context module(上下文模塊)。它包含目錄下的全部模塊的引用,是經過一個 request 解析出來的正則表達式,去匹配目錄下全部符合的模塊,而後都 require 進來。此 context module 包含一個 map 對象,會把 request 中全部模塊翻譯成對應的模塊 id。

例如

{
  "./table.ejs": 42,
  "./table-row.ejs": 43,
  "./directory/folder.ejs": 44
}

此 context module 還包含一些訪問這個 map 對象的 runtime 邏輯。

這意味着 webpack 可以支持動態地 require,但會致使全部可能用到的模塊都包含在 bundle 中。

咱們能夠經過 require.context() 函數來建立本身的 context。

require.context(directory, useSubdirectories = false, regExp = /^\.\//);

能夠給這個函數傳入三個參數:一個要搜索的目錄,一個標記表示是否還搜索其子目錄, 以及一個匹配文件的正則表達式。

webpack 會在構建中解析代碼中的 require.context()

/* 
  搜索當前目錄及其全部子目錄下vue後綴結尾文件,並返回webpack上下文函數以下
  webpackContext(req) {
    var id = webpackContextResolve(req);
    return __webpack_require__(id);
  } 
*/
const requireComponent = require.context('./', true, /\.vue$/)

// 獲取requireComponent的索引值獲得模塊路徑數組,例如 ['./A.vue', './B.vue', ...]
const components = requireComponent.keys()
// 遍歷註冊模塊組件
components.forEach(filePath => {
  // 組件路徑
  const config = requireComponent(filePath)
  // 提取文件名, filePath: ./A.vue -> fileName: B
  const fileName = validateFileName(filePath)
  const name =
    fileName.toLowerCase() === 'index'
      ? capitalizeFirstLetter(config.default.name) // 組件自定義name爲註冊名,例如 name: helloWorld -> HelloWorld
      : fileName // 不然用文件名

  // 調用Vue的API註冊組件,第二個參數就是組件的配置,相似 {data: ƒ, computed: {…}, activated: ƒ, deactivated: ƒ, methods: {…}, …}
  Vue.component(name, config.default || config)
})

// 拼湊大寫開頭單詞
function capitalizeFirstLetter (str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

// 提取文件名
function validateFileName (str) {
  return (
    //     匹配任何非空白字符的vue文件並提取匹配值
    /^\S+\.vue/.test(str) && str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => $1)
  )
}
相關文章
相關標籤/搜索