Vue 學(xué)習(xí)筆記

[TOC]

簡(jiǎn)介

Vue 是一個(gè)用于構(gòu)建 Web 用戶界面的漸進(jìn)式 JavaScript 框架。其核心庫只關(guān)注視圖層(view layer),同時(shí)具備良好的第三方支持庫生態(tài)用以應(yīng)付構(gòu)建復(fù)雜大型單頁應(yīng)用(SPA:Single-Page Application)。

MVVM 模型

Vue 雖然沒有完全遵循 MVVM(Model-View-ViewModel)模型,當(dāng)其設(shè)計(jì)也受到了 MVVM 的啟發(fā),以數(shù)據(jù)驅(qū)動(dòng)界面,如下圖所示:

Vue MVVM

Vue 中,充當(dāng) ViewModel 的是一個(gè) Vue 實(shí)例(new Vue({})),該 Vue實(shí)例 作用于某一個(gè) HTML 元素上,全權(quán)代理該元素節(jié)點(diǎn)的所有操作。

Vue實(shí)例 內(nèi)部通過 DOM Listeners 可以觀測(cè)到頁面上 DOM 元素的變化,從而將該種變化同步更改到 Model 中的對(duì)應(yīng)數(shù)據(jù)。

同時(shí)通過 Data Bindings,當(dāng) Model 中的數(shù)據(jù)改變時(shí),則會(huì)對(duì)相應(yīng)視圖上的顯示進(jìn)行更改,從而實(shí)現(xiàn)了 View 和 Model 的數(shù)據(jù)雙向綁定。

:傳統(tǒng)的 Web 編程模型是 結(jié)構(gòu)驅(qū)動(dòng),即要對(duì)一個(gè) DOM 節(jié)點(diǎn)進(jìn)行操作,第一步就是要獲取該 DOM 節(jié)點(diǎn)對(duì)象,然后再修改數(shù)據(jù)更新到節(jié)點(diǎn)上。
Vue 的中心思想是 數(shù)據(jù)驅(qū)動(dòng),要更改界面,其實(shí)就是要更改數(shù)據(jù)。
簡(jiǎn)而言之,在 Vue 中,不應(yīng)當(dāng)考慮操作 DOM,而是專注于 操作數(shù)據(jù)

安裝

Vue 的安裝有多種方法,這里主要介紹兩種方法:

  1. 通過<script>標(biāo)簽直接引入:
<!-- 對(duì)于制作原型或?qū)W習(xí),你可以這樣使用最新版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 或者-->
<!-- 對(duì)于生產(chǎn)環(huán)境,我們推薦鏈接到一個(gè)明確的版本號(hào)和構(gòu)建文件,以避免新版本造成的不可預(yù)期的破壞:-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>

:通過<script>標(biāo)簽引入,Vue會(huì)被注冊(cè)為一個(gè)全局變量

  1. 直接使用官方提供的快速搭建復(fù)雜單頁面應(yīng)用 (SPA) 的腳手架 vue-cli
  • 首先全局安裝該腳手架 vue-cli
npm install -g @vue/cli
vue create <project-name>

:使用 vue-cli 前需確保系統(tǒng)已安裝 nodejs

以上兩步操作完成,我們便創(chuàng)建完成一個(gè) Vue 項(xiàng)目。

在項(xiàng)目的 package.json 中,可以看到 vue-cli 提供了兩個(gè)腳本命令讓我們運(yùn)行與打包項(xiàng)目:

  • npm run serve:運(yùn)行項(xiàng)目
  • npm run build:打包項(xiàng)目到 dist 文件夾

組件化

Vue 的兩大特性為 數(shù)據(jù)驅(qū)動(dòng)組件化

通常一個(gè)大的頁面可以劃分為許多個(gè)小區(qū)塊,這些小區(qū)塊有些結(jié)構(gòu)是相似的,我們可以將這些相似的區(qū)塊抽象出一個(gè)統(tǒng)一的結(jié)構(gòu),方便復(fù)用,這種抽象結(jié)構(gòu)的方法即稱為組件化。

在實(shí)際項(xiàng)目開發(fā)中,一個(gè)大的頁面通常都是由許多個(gè)小的組件構(gòu)造而成的,如下圖所示:

組件化

Vue 提供了兩種組件定義的方式:

<body>
    <div id="app">
        <my-component />
    </div>

    <script>
        const myComponent = Vue.component('my-component',{
            data(){
                return {
                    'message': 'Hello Global Component'
                }
            },
            template:`
            <h1>{{message}}</h1>
            `
        });
        const vm = new Vue({
            el: "#app",
        });
    </script>
</body>
  • 局部組件:對(duì)于全局組件來說,即使頁面沒有使用該組件,組件也會(huì)被注入到最終的構(gòu)建結(jié)果中,導(dǎo)致了 JavaScript 文件的無謂增加。而局部組件可以做到按需加載,需要哪些組件,按需引入即可,更加靈活高效。
    定義方式:通過一個(gè)普通的 JavaScript 對(duì)象來定義組件,然后 Vue實(shí)例 按需引入需要的組件即可。
<body>
    <div id="app">
        <component-a></component-a>
        <component-b />
    </div>
    <script>
        const componentA = {
            template: `
            <h1>Component A</h1>
            `
        };
        const componentB = {
            template: `
            <h1>Component B</h1>
            `
        };

        const vm = new Vue({
            el: '#app',
            components: {  // 按需引入需要的組件
                componentA,
                componentB
            }
        });
    </script>
</body>

最佳實(shí)踐:在 Vue 中,組件通常都定義到一個(gè)單獨(dú)的.vue文件中,其他組件需要時(shí),導(dǎo)入相應(yīng)組件的.vue文件即可。

// MyComponent.vue
<template>
  <h1>{{message}}</h1>
</template>

<script>
export default {
  name: "MyCompoent",
  data() {
    return {
      message: "Hello MyComponent!"
    };
  }
};
</script>

<style scoped>
h1 {
  background: red;
}
</style>

可以直接使用以下命令直接運(yùn)行.vue文件,查看組件展示效果:

vue serve MyComponent.vue --open

也可以在其他組件內(nèi)導(dǎo)入該組件,進(jìn)行使用:

<template>
  <div id="app">
    <h1>Parent Component</h1>
    <!-- 使用組件 -->
    <my-component />
  </div>
</template>

<script>
// 導(dǎo)入組件
import MyComponent from "./MyComponent.vue";

export default {
  name: "app",
  components: {
    MyComponent // 引入組件
  }
};
</script>

:在 Vue 中,組件實(shí)質(zhì)是帶有一個(gè)名字的 Vue實(shí)例,其性質(zhì)與 Vue實(shí)例 基本一致(遵循 Vue實(shí)例 的生命周期等內(nèi)容),特點(diǎn)是多了個(gè)組件復(fù)用功能。

Vue實(shí)例

Vue 實(shí)例充當(dāng) ViewModel 角色,負(fù)責(zé) View 和 Model 之間的數(shù)據(jù)綁定:

new Vue(Options)

當(dāng)創(chuàng)建一個(gè) Vue 實(shí)例時(shí),你可以傳入一個(gè)選項(xiàng)對(duì)象Options,該Options的選項(xiàng)列表有如下可選:

下面列舉一些Options常用選項(xiàng)

<body>
    <div id="app">
        <h1>{{message}}</h1>
    </div>
    <script>
        const vm = new Vue({
            el: "#app",
            data: {
                message: "Hello Vue!"
            }
        });
    </script>
</body>

:Vue 將會(huì)遞歸將data的屬性轉(zhuǎn)換為getter/setter,從而讓data的屬性能夠響應(yīng)數(shù)據(jù)變化。比如在控制臺(tái)輸入vm.message = 'Hi Vue!!!,可以觀察到頁面數(shù)據(jù)發(fā)生了更改。

組件 中的data屬性必須是Fcuntion類型,其返回一個(gè)Object,原因是組件復(fù)用時(shí),保證每個(gè)新組件都有獨(dú)一的一份數(shù)據(jù)拷貝。

// MyComponent.vue
<template>
    <h1>{{message}}</h1>
</template>

<script>
export default {
    name: 'MyComponent',
    data(){       // 函數(shù)類型
        return {  // 返回?cái)?shù)據(jù)對(duì)象
            'message': 'Hello Vue!'
        }
    }
}
</script>
  • 選項(xiàng) / 數(shù)據(jù)props
    描述:該屬性用于接收來自父組件的數(shù)據(jù)。
    類型:Array<string> | Object
    當(dāng)傳遞的是Object類型時(shí),則可以基于對(duì)象的語法使用以下選項(xiàng):
    ?type:指定數(shù)據(jù)類型,該值可以為原生類型(StringNumberBooleanArrayObjectDateFunctionSymbol),自定義構(gòu)造函數(shù),或上述內(nèi)容組成的數(shù)組。
    ? default:any:為該prop指定一個(gè)默認(rèn)值。如果該prop沒有被傳入,則使用該默認(rèn)值。對(duì)象或數(shù)組的默認(rèn)值必須從一個(gè)工廠函數(shù)返回。
    ? required: Boolean:定義該prop是否為必填項(xiàng)。
    ? validator: Function:自定義驗(yàn)證函數(shù),對(duì)該prop進(jìn)行校驗(yàn)。
Vue.component('my-component', {
  props: {
    // 基礎(chǔ)的類型檢查 (`null` 和 `undefined` 會(huì)通過任何類型驗(yàn)證)
    propA: Number,
    // 多個(gè)可能的類型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 帶有默認(rèn)值的數(shù)字
    propD: {
      type: Number,
      default: 100
    },
    // 帶有默認(rèn)值的對(duì)象
    propE: {
      type: Object,
      // 對(duì)象或數(shù)組默認(rèn)值必須從一個(gè)工廠函數(shù)獲取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗(yàn)證函數(shù)
    propF: {
      validator: function (value) {
        // 這個(gè)值必須匹配下列字符串中的一個(gè)
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})
var Comp = Vue.extend({
  props: ['msg'],
  template: '<div>{{ msg }}</div>'
})

var vm = new Comp({
  propsData: {
    msg: 'hello'
  }
})

propsData屬性只能用于new創(chuàng)建的實(shí)例中。

  • 選項(xiàng) / 數(shù)據(jù)methods
    描述:方法定義,Vue實(shí)例 可以直接訪問這些方法,或在指令表達(dá)式中直接調(diào)用這些方法。
    類型:{ [key: string]: Function }
var vm = new Vue({
  data: { a: 1 },
  methods: {
    plus: function () {
      this.a++
    }
  }
})
vm.plus()
vm.a // 2

methods中的this自動(dòng)綁定到當(dāng)前 Vue實(shí)例。

  • 選項(xiàng) / 數(shù)據(jù)computed
    描述:計(jì)算屬性,主要用于對(duì)data數(shù)據(jù)進(jìn)行計(jì)算轉(zhuǎn)換。
    類型:{ [key: string]: Function | { get: Function, set: Function } }
<template>
  <div>
    <h1>獲取數(shù)據(jù): {{computedData}}</h1>
    <h1>設(shè)置數(shù)據(jù):{{setData = 3}}</h1>
    <h1>獲取數(shù)據(jù): {{setData}}</h1>
  </div>
</template>

<script>
export default {
  name: "Computed",
  data() {
    return {
      count: 1
    };
  },
  computed: {
    // 只讀取 data
    computedData() {
      return this.count + 10;
    },
    // 讀取和設(shè)置 data
    setData: {
      get() {
        return this.count + 1;
      },
      set(value) {
        this.count = value;
      }
    }
  }
};
</script>

computed類型為Object,其具有如下特點(diǎn):

  1. computed內(nèi)部定義的屬性為訪問器屬性,即具備gettersetter,且其內(nèi)部this自動(dòng)綁定到當(dāng)前 Vue實(shí)例。
  2. computed會(huì)自動(dòng) 緩存 計(jì)算結(jié)果,只有當(dāng)依賴的響應(yīng)式屬性變化時(shí),computed才會(huì)重新進(jìn)行計(jì)算。
    緩存computedmethods的最大區(qū)別之處,methods每次調(diào)用一定會(huì)運(yùn)行函數(shù),而computed則不一定。
  • 選項(xiàng) / 數(shù)據(jù)watch
    描述:偵聽屬性,用于監(jiān)控datacomputed的數(shù)據(jù),當(dāng)數(shù)據(jù)變更時(shí)進(jìn)行回調(diào)通知。
    類型:{ [key: string]: string | Function | Object | Array }
    watch類型為Object,其內(nèi)部屬性的類型有多種:string | Function | Object | Array,這里簡(jiǎn)單介紹 3 種:
  1. string:字符串表示回調(diào)函數(shù)名,當(dāng)數(shù)據(jù)改變時(shí),回調(diào)該函數(shù):
const vm = new Vue({
    el: '#app',
    data: {
        a: 1
    },
    methods: {
        aChanged(value, oldValue) {
            console.log(`a changed: new:${value} --> old:${oldValue}`);
        }
    },
    watch: {
        a: 'aChanged'
    }
});
  1. Function:當(dāng)數(shù)據(jù)改變時(shí),直接回調(diào)該函數(shù):
const vm = new Vue({
    el: '#app',
    data: {
        a: 1
    },
    watch: {
        a(value, oldValue) {
            console.log(`a changed: new:${value} --> old:${oldValue}`);
        }
    }
});
  1. Object:對(duì)監(jiān)控的屬性為對(duì)象時(shí),Vue 默認(rèn)只能監(jiān)控到對(duì)象重新被賦值的變化,而如果需要監(jiān)聽對(duì)象內(nèi)部屬性的變化,則可使用該選項(xiàng),其中:
    handler代表回調(diào)函數(shù)。
    deep用來控制監(jiān)聽對(duì)象屬性的層級(jí),deep=true時(shí)只要對(duì)象內(nèi)部 property 改變(不管嵌套有多深),都會(huì)監(jiān)聽到。
    immediate用來設(shè)置是否立即產(chǎn)生回調(diào)。當(dāng)immediate=true時(shí),回調(diào)函數(shù)會(huì)立即被調(diào)用,傳遞的是屬性當(dāng)前的值。
const vm = new Vue({
    el: '#app',
    data: {
        a: {
            aa: {
                aaa: 3
            }
        }
    },
    watch: {
        a: {
            handler(value, oldValue) {
                console.log(`a changed: new:${value} --> old:${oldValue}`);
            },
            deep: true // 被監(jiān)聽對(duì)象的 property 改變時(shí)被調(diào)用,無論嵌套的有多深
        }
    }
});

:大多數(shù)情況下,觀察和響應(yīng)數(shù)據(jù)變更使用計(jì)算屬性(computed)便足夠了,但是當(dāng)在數(shù)據(jù)變化時(shí)需要執(zhí)行異步或開銷較大的操作時(shí),則此時(shí)使用偵聽屬性(watch)會(huì)更加適合。

  • 選項(xiàng) / DOMel
    描述:設(shè)置 Vue實(shí)例 的掛載目標(biāo)節(jié)點(diǎn)
    類型:string | Element
new Vue({
    el: '#app'
});

:如果在實(shí)例化時(shí)存在這個(gè)選項(xiàng),實(shí)例將立即進(jìn)入編譯過程,否則,需要顯式調(diào)用vm.$mount()手動(dòng)開啟編譯。

<div id="app"></div>

const vm = new Vue({
    el: '#app',
    template: '<h1>template</h1>' // <div> 會(huì)被 <h1> 完全覆蓋
})
  • 選項(xiàng) / DOMrender
    描述:字符串模板的代替方案,允許你發(fā)揮 JavaScript 最大的編程能力。該渲染函數(shù)接收一個(gè)createElement方法作為第一個(gè)參數(shù)用來創(chuàng)建VNode
    類型:(createElement: () => VNode) => VNode
<div id="app"></div>

new Vue({
    render(createElement) {
        return createElement('div', {
            class: 'rendered'
        },
            [
                createElement('h1', {
                    domProps: {
                        innerHTML: 'div>h1 rendered by vue'
                    }
                })
            ]
        );
    }}).$mount('#app');

Vue 推薦在絕大多數(shù)情況下使用模板來創(chuàng)建你的 HTML,只有在一些特殊場(chǎng)景下,比如模板冗長(zhǎng)且具備重復(fù)元素,則此時(shí)使用渲染函數(shù)render通過編寫 JavaScript 代碼來渲染出頁面會(huì)更加方便簡(jiǎn)潔。

生命周期

每個(gè) Vue實(shí)例 在掛載到頁面時(shí),都會(huì)經(jīng)歷一系列的初始化過程,例如,需要設(shè)置數(shù)據(jù)監(jiān)聽、編譯模板、將實(shí)例掛載到 DOM 并在數(shù)據(jù)變化時(shí)更新 DOM 等。在創(chuàng)建 Vue實(shí)例 的這整個(gè)過程中,Vue 為我們預(yù)留出了一些 Hook 點(diǎn),方便我們?cè)?Vue實(shí)例 創(chuàng)建過程的某個(gè)生命周期中進(jìn)行一些操作。如下圖所示:

vue lifecycle

:圖片來源于網(wǎng)上,侵刪。

這些預(yù)留的生命周期鉤子函數(shù)總共有如下幾個(gè):

  • beforeCreate:Vue實(shí)例 初始化之后,此時(shí)datamethods中的數(shù)據(jù)還未進(jìn)行初始化,因此無法獲取。

  • created:表示 Vue實(shí)例 創(chuàng)建完成,但還未掛載到頁面上,此時(shí)datamethods都已經(jīng)初始化成功,可以對(duì)其進(jìn)行調(diào)用獲取,而掛載階段未開始,所以$el屬性目前不可見。

  • beforeMount:在掛載開始之前被調(diào)用,此時(shí)模板已在內(nèi)存中被編譯完成,只是尚未掛載到頁面上,因此,此時(shí)頁面上顯示的還是未渲染的結(jié)構(gòu)。

  • mounted:掛載完成,此時(shí)頁面會(huì)顯示我們渲染的視圖。如果想要操作頁面上的 DOM 節(jié)點(diǎn),最早的時(shí)間就是該處。
    mounted不會(huì) 承諾所有的子組件也都一起被掛載。如果你希望等到整個(gè)視圖都渲染完畢,可以用vm.$nextTick替換掉mounted

mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}
  • beforeUpdate:數(shù)據(jù)更新時(shí)調(diào)用,此時(shí)內(nèi)存中data數(shù)據(jù)已更新,但頁面中顯示的數(shù)據(jù)還未更新,數(shù)據(jù)與頁面不同步。這里適合在更新之前訪問現(xiàn)有的 DOM,比如手動(dòng)移除已添加的事件監(jiān)聽器。

  • updated:新數(shù)據(jù)成功渲染到頁面,此時(shí)數(shù)據(jù)與頁面處于同步狀態(tài)。
    updated不會(huì) 承諾所有的子組件也都一起被掛載。如果你希望等到整個(gè)視圖都渲染完畢,可以用vm.$nextTick替換掉mounted

  • activated:激活狀態(tài),表示當(dāng)前組件處于前臺(tái)頁面,用戶可與該組件進(jìn)行交互。
    :只有組件被內(nèi)置組件keep-alive包裹時(shí),該鉤子才有可能被調(diào)用。

<template>
    <keep-alive>
        <my-component />
    </keep-alive>
</template>
  • deactivated:停用狀態(tài),表示當(dāng)前組件處于后臺(tái)頁面,用戶不能與之交互。
    :只有組件被內(nèi)置組件keep-alive包裹時(shí),該鉤子才有可能被調(diào)用。

  • beforeDestroy:實(shí)例銷毀之前調(diào)用。在這一步,實(shí)例仍然完全可用,即此時(shí)實(shí)例的datamethods等所有數(shù)據(jù)完全可用。

  • destroyed:Vue 實(shí)例銷毀后調(diào)用。此時(shí) Vue實(shí)例 指向的所有東西都會(huì)解綁定,所有的事件監(jiān)聽器會(huì)被移除,所有的子實(shí)例也會(huì)被銷毀。

  • errorCaptured:當(dāng)捕獲一個(gè)來自子孫組件的錯(cuò)誤時(shí)被調(diào)用。此鉤子會(huì)收到三個(gè)參數(shù):錯(cuò)誤對(duì)象、發(fā)生錯(cuò)誤的組件實(shí)例以及一個(gè)包含錯(cuò)誤來源信息的字符串。此鉤子可以返回false以阻止該錯(cuò)誤繼續(xù)向上傳播。

指令

指令 (Directives) 是帶有v-前綴的特殊屬性。

Vue 提供了以下內(nèi)置的指令:

  • v-text:更新元素的textContent,該指令與使用{{ Mustache }}插值效果一樣。
    類型:string
<span v-text="msg"></span>
<!-- 和下面的一樣 -->
<span>{{msg}}</span>
  • v-html:更新元素的innerHTML
    類型:string
<template>
  <div id="app">
    <div v-html="html"></div>
  </div>
</template>

<script>
export default {
  name: "app",
  data() {
    return {
      html: '<a  >baidu</a>'
    };
  },
};
</script>

v-html的內(nèi)容只會(huì)按普通 HTML 插入,不會(huì)作為 Vue 模板進(jìn)行編譯。
:在單文件組件里,scoped 的樣式不會(huì)應(yīng)用在 v-html 內(nèi)部,因?yàn)槟遣糠?HTML 沒有被 Vue 的模板編譯器處理。
:在網(wǎng)站上動(dòng)態(tài)渲染任意 HTML 是非常危險(xiǎn)的,因?yàn)槿菀讓?dǎo)致 XSS 攻擊。只在可信內(nèi)容上使用v-html,永不用在用戶提交的內(nèi)容上。

  • v-show:條件渲染,根據(jù)表達(dá)式之真假值,切換元素的displayCSS 屬性。
    類型:any
<h1 v-show="ok">Hello!</h1>

v-show不支持<template>元素,也不支持v-else

  • v-if:條件渲染,根據(jù)表達(dá)式的值的真假條件渲染元素。在切換時(shí)元素及它的數(shù)據(jù)綁定 / 組件被銷毀并重建。如果元素是<template>,將提出它的內(nèi)容作為條件塊。
    類型:any
<h1 v-if="awesome">Vue is awesome!</h1>

:當(dāng)和v-if一起使用時(shí),v-for的優(yōu)先級(jí)比v-if更高。
v-show = false時(shí)只是把元素設(shè)置為:display:none,元素還留著 DOM 樹上。
v-if = false時(shí),元素會(huì)被整個(gè)移除,其上綁定的數(shù)據(jù)/組件都會(huì)被銷毀。

  • v-elsev-ifv-if-else的分支。
    類型:無
<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>
<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>
  • v-for:遍歷源數(shù)據(jù),渲染元素列表。
    類型:Array | Object | number | string | Iterable
<div v-for="item in items">
  {{ item.text }}
</div>
<!-- 另外也可以為數(shù)組索引指定別名 (或者用于對(duì)象的鍵):-->
<div v-for="(item, index) in items"></div>
<div v-for="(val, key) in object"></div>
<div v-for="(val, name, index) in object"></div>

v-for渲染元素時(shí),默認(rèn)使用“就地更新”策略,即當(dāng)列表數(shù)據(jù)改變時(shí),Vue 不會(huì)移動(dòng)當(dāng)前 DOM 元素來重新匹配數(shù)據(jù)項(xiàng),而是根據(jù)索引位置重新渲染數(shù)據(jù)。比如:

現(xiàn)在我們有數(shù)據(jù)項(xiàng):

data() {
    return {
        datas: [
            { id: 1, name: "one" },
            { id: 2, name: "two" },
            { id: 3, name: "three" }
        ]
    }

將這些數(shù)據(jù)項(xiàng)渲染到頁面上:

<template>
  <div>
    <ul>
      <li v-for="(item,index) in datas">
        <input type="checkbox" />
        {{item.name}}
      </li>
    </ul>
</template>

我們使用v-for將每條數(shù)據(jù)項(xiàng)渲染到一個(gè)<li>上,此時(shí)顯示效果如下:

如果此時(shí)我們勾選第一個(gè)<li>checkbox,即one勾選上,然后再往數(shù)據(jù)列表前面添加一個(gè)數(shù)據(jù):this.datas.unshift({ id: 4, name: "four" }),則可以看到顯示效果如下:

unshift

可以看到,我們想要的是one被勾選了,但是效果是數(shù)據(jù)列表首位被勾選。出現(xiàn)這種現(xiàn)象的原因就是v-for默認(rèn)采用的“就地更新”策略:它會(huì)復(fù)用已渲染完成的 DOM 元素,然后只對(duì)變化的數(shù)據(jù)進(jìn)行修改,比如這里復(fù)用了第一條<li><checkbox>one</li>,添加數(shù)據(jù)項(xiàng),對(duì)第一條<li>來說,他的數(shù)據(jù)改變了,但是<checkbox>不包含在數(shù)據(jù)項(xiàng)里,因此只會(huì)修改數(shù)據(jù),將one修改為four,而checkbox仍保持勾選狀態(tài)。

因此,“就地更新”策略是高效的,但是 只適用于不依賴子組件狀態(tài)或臨時(shí) DOM 狀態(tài)(例如:表單輸入值) 的列表渲染輸出

而要解決上述問題,只需為v-for提供一個(gè)key屬性(key必須是唯一的),這樣 Vue 就可以識(shí)別出數(shù)據(jù)項(xiàng)對(duì)應(yīng)的渲染條目,從而重用和重新排序現(xiàn)有元素:

<template>
  <div>
    <ul>
      <li v-for="(item,index) in datas" :key="item.id">
        <input type="checkbox" />
        {{item.name}}
      </li>
    </ul>
</template>

由于新添加的數(shù)據(jù)id=4,當(dāng)前已存在的<li>沒有與之對(duì)應(yīng)的標(biāo)識(shí)key,因此 Vue 會(huì)重新渲染一個(gè)新的<li>,并將其與id=4對(duì)應(yīng)起來,結(jié)果如下圖所示:

v-for key

:“就地更新”策略其實(shí)就是使用索引作為節(jié)點(diǎn)標(biāo)識(shí),即:key=index

  • v-on:綁定事件監(jiān)聽器。
    縮寫:@
    類型:Function | Inline Statement | Object
    參數(shù):event
    修飾符:
    ? .stop:調(diào)用event.stopPropagation(),停止事件分發(fā)。
    ? .prevent:調(diào)用event.preventDefault(),取消事件的默認(rèn)動(dòng)作。
    ? .capture:添加事件偵聽器時(shí)使用capture(捕獲)模式。
    ? .self:只有當(dāng)事件是從偵聽器綁定的元素本身觸發(fā)時(shí)才觸發(fā)回調(diào)。
    ? .{keyCode | keyAlias}:只有當(dāng)事件是從特定鍵觸發(fā)時(shí)才觸發(fā)回調(diào)。
    ? .native:監(jiān)聽組件根元素的原生事件。
    ? .once:只觸發(fā)一次回調(diào)。
    ? .left: 只有當(dāng)點(diǎn)擊鼠標(biāo)左鍵時(shí)觸發(fā)。
    ? .right: 只有當(dāng)點(diǎn)擊鼠標(biāo)右鍵時(shí)觸發(fā)。
    ? .middle: 只有當(dāng)點(diǎn)擊鼠標(biāo)中鍵時(shí)觸發(fā)。
    ? .passive:以{ passive: true }模式添加偵聽器
<!-- 方法處理器 -->
<button v-on:click="doThis"></button>

<!-- 動(dòng)態(tài)事件 (2.6.0+) -->
<button v-on:[event]="doThis"></button>

<!-- 內(nèi)聯(lián)語句 -->
<button v-on:click="doThat('hello', $event)"></button>

<!-- 縮寫 -->
<button @click="doThis"></button>

<!-- 動(dòng)態(tài)事件縮寫 (2.6.0+) -->
<button @[event]="doThis"></button>

<!-- 停止冒泡 -->
<button @click.stop="doThis"></button>

<!-- 阻止默認(rèn)行為 -->
<button @click.prevent="doThis"></button>

<!-- 阻止默認(rèn)行為,沒有表達(dá)式 -->
<form @submit.prevent></form>

<!--  串聯(lián)修飾符 -->
<button @click.stop.prevent="doThis"></button>

<!-- 鍵修飾符,鍵別名 -->
<input @keyup.enter="onEnter">

<!-- 鍵修飾符,鍵代碼 -->
<input @keyup.13="onEnter">

<!-- 點(diǎn)擊回調(diào)只會(huì)觸發(fā)一次 -->
<button v-on:click.once="doThis"></button>

<!-- 對(duì)象語法 (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>

v-on用在普通元素上時(shí),只能監(jiān)聽 原生 DOM 事件。用在自定義組件上時(shí),也可以監(jiān)聽子組件觸發(fā)的自定義事件

<my-component @my-event="handleThis"></my-component>

<!-- 內(nèi)聯(lián)語句 -->
<my-component @my-event="handleThis(123, $event)"></my-component>

<!-- 組件中的原生事件 -->
<my-component @click.native="onClick"></my-component>
  • v-bind:動(dòng)態(tài)綁定
    縮寫::
    類型:any (with argument) | Object (without argument)
    參數(shù):attrOrProp (optional)
    修飾符
    ? .prop:被用于綁定 DOM 屬性 (property)
    ? .camel:將 kebab-case 特性名轉(zhuǎn)換為 camelCase(駝峰式)
    ? .sync:會(huì)擴(kuò)展成一個(gè)更新父組件綁定值的 v-on 偵聽器
<!-- 綁定一個(gè)屬性 -->
<img :src="imageSrc">

<!-- 動(dòng)態(tài)特性名 (2.6.0+) -->
<button :[key]="value"></button>

<!-- class 綁定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]">

<!-- style 綁定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>

<!-- 綁定一個(gè)有屬性的對(duì)象 -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

<!-- 通過 prop 修飾符綁定 DOM 屬性 -->
<div v-bind:text-content.prop="text"></div>

<!-- prop 綁定。“prop”必須在 my-component 中聲明。-->
<my-component :prop="someThing"></my-component>

<!-- 通過 $props 將父組件的 props 一起傳給子組件 -->
<child-component v-bind="$props"></child-component>

<!-- 支持綁定駝峰命名屬性 -->
<svg :view-box.camel="viewBox"></svg>
  • v-model:表單控件與數(shù)據(jù)屬性的雙向綁定。
    修飾符
    ? .lazy:使用<input>change事件進(jìn)行同步。
    ? .number:自動(dòng)將字符串轉(zhuǎn)為數(shù)字。
    ? .trim:輸入首尾空格過濾。
<input type="text" v-model="message" />

<script>
export default {
  name: 'VModel',
  data() {
    return {
      message: ''
    };
  }
};
</script>
  • v-slot:插槽。
    縮寫:#
    當(dāng)我們定義組件的時(shí)候,有些內(nèi)容可能需要由父組件傳入,因此,此時(shí)可以使用插槽,預(yù)留出位置給到父組件進(jìn)行自定義內(nèi)容傳入:
// 子組件:預(yù)留插槽
<template>
  <div>
    <h1>Son Component</h1>
    <!-- 預(yù)留插槽 -->
    <slot></slot>
  </div>
</template>

// 父組件:傳入插槽內(nèi)容
<template>
  <div>
    <h1>Parent Component</h1>
    <son-component>
        <h2>slot: content from Parent Component</h2>
    </son-component>
  </div>
</template>

? 后備內(nèi)容:可以通過為<slot>內(nèi)部提供默認(rèn)內(nèi)容,只有當(dāng)父組件顯示傳入內(nèi)容時(shí),才會(huì)覆蓋默認(rèn)內(nèi)容:

<slot>
    <h1>Default Content</h1>
</slot>

? 具名插槽:我們可以給插槽進(jìn)行命名(使用name屬性),這樣父組件就可指定名字(使用v-slot指令)對(duì)特定的插槽進(jìn)行覆蓋:

// 子組件模板
<template>
  <div>
    <h1>Son Component</h1>
    <!-- 預(yù)留命名插槽 -->
    <slot name="header"></slot>
    <main>
      <!-- name="default" -->
      <slot></slot>
    </main>
    <slot name="footer"></slot>
  </div>
</template>

// 父組件
<template>
  <div>
    <h1>Parent Component</h1>
    <son-component>
      <template v-slot:header>
        <h2>替換 header 插槽</h2>
      </template>
      <h3>替換默認(rèn)插槽</h3>
      <template #footer>
        <h2>替換 footer 插槽</h2>
      </template>
    </son-component>
  </div>
</template>

v-slot只能添加在一個(gè)<template>或 組件 上。
:默認(rèn)插槽其實(shí)也是一個(gè)具名插槽,其名稱為:default

? 插槽 prop:使用 插槽 prop 可以傳遞子組件的數(shù)據(jù)給到父組件,使父組件可以在覆蓋插槽的內(nèi)容上使用子組件的數(shù)據(jù):

// 子組件
<slot :msg="message"></slot>

<script>
export default {
  name: 'SonComponent',
  data() {
    return {
      message: 'Hello from Son Component!'
    };
  }
};
</script>

// 父組件:slotProps 接收子組件的 插槽props
<son-component #default="slotProps">
    {{slotProps.msg}}
</son-component>
  • v-pre:跳過該元素及其子元素的編譯過程。可以用來顯示原始 Mustache 標(biāo)簽。
    類型:無
<span v-pre>{{ this will not be compiled }}</span>
  • v-cloak:這個(gè)指令保持在元素上直到關(guān)聯(lián)實(shí)例結(jié)束編譯。通常結(jié)合 CSS 規(guī)則來達(dá)到隱藏未編譯的 Mustache 標(biāo)簽直到實(shí)例準(zhǔn)備完畢。
    類型:無
[v-cloak] {
  display: none;
}

<div v-cloak>
  {{ message }}
</div>
  • v-once:只渲染元素和組件 一次。隨后的重新渲染,元素/組件及其所有的子節(jié)點(diǎn)將被視為靜態(tài)內(nèi)容并跳過。這可以用于優(yōu)化更新性能。
<span v-once>This will never change: {{msg}}</span>
  • 自定義指令:在 Vue2.0 中,代碼復(fù)用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對(duì)普通 DOM 元素進(jìn)行底層操作,這時(shí)候就會(huì)用到自定義指令。

Vue 提供了兩種自定義指令的方式:

  1. 全局指令:使用Vue.directive
// 注冊(cè)一個(gè)全局自定義指令 `v-focus`
Vue.directive('focus', {...})
  1. 局部指令:組件中定義一個(gè)directives屬性:
// 注冊(cè)一個(gè)局部自定義指令 `v-focus`
directives: {
  focus: {...}
}
  • 鉤子函數(shù):一個(gè)指令定義對(duì)象可以提供如下幾個(gè)鉤子函數(shù) (均為可選):
    ? bind:指令第一次綁定到元素時(shí)調(diào)用。改鉤子只會(huì)被調(diào)用一次,可在此做一些初始化設(shè)置。
    ? inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在,但不一定已被插入文檔中)。
    ? update:所在組件的 VNode 更新時(shí)調(diào)用,但是可能發(fā)生在其子 VNode 更新之前。
    ? componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用。
    ? unbind:指令與元素解綁時(shí)調(diào)用。改鉤子只會(huì)被調(diào)用一次,可在此做一些資源釋放操作。

示例:使用自定義指令v-customtext模擬v-text

<template>
  <h1 v-customtext="msg"></h1>
</template>

<script>
export default {
  name: 'customeDirective',
  data() {
    return {
      msg: 'Hello Custom Directives!'
    };
  },
  directives: {
    customtext: {
      inserted(el, binding, vnode, oldVnode) {
        el.innerText = binding.value;
      }
    }
  }
};
</script>

其他

  • 組件間通信:

? 父?jìng)髯?/strong>:子組件通過props屬性可接收父組件傳遞過來的變量:

// ParentComponent.vue
<template>
  <son-component :msg="message" />
</template>

<script>
import SonComponent from './SonComponent';

export default {
  name: 'ParentComponent',
  components: {
    SonComponent
  },
  data() {
    return {
      message: 'data from Parent Component'
    };
  }
};
</script>

// SonComponent.vue
<template>
  <h1>{{msg}}</h1>
</template>

<script>
export default {
  name: 'SonComponent',
  props: {
    msg: {
      type: String,
      required: true
    }
  }
};
</script>

? 子傳父:子組件可以通過$emit發(fā)送自定義事件向父組件傳值,父組件直接注冊(cè)接收該事件即可:

// SonComponent.vue
<template>
  <button @click="sendEvent">點(diǎn)擊發(fā)送事件</button>
</template>

<script>
export default {
  name: 'SonComponent',
  methods: {
    sendEvent() {
      // 發(fā)送自定義事件
      this.$emit('eventFromChild', 'data from Son Component!!');
    }
  }
};
</script>

// ParentComponent.vue
<template>
  <div>
    <!-- 接收事件 -->
    <son-component @eventFromChild="recvChildEvent" />
    <h1>{{data}}</h1>
  </div>
</template>

<script>
import SonComponent from './SonComponent';

export default {
  name: 'ParentComponent',
  components: {
    SonComponent
  },
  data() {
    return {
      data: 'hhhh'
    };
  },
  methods: {
    recvChildEvent(data) {
      this.data = data;
    }
  }
};
</script>

? 父?jìng)髯訉O:父組件/祖先組件通過provide提供變量,子孫組件通過inject來接收該變量:

// ParentComponent
import SonComponent from './SonComponent.vue'
export default {
    name: 'ParentComponent',
    components: {
        SonComponent
    },
    provide: {
        message: 'data from Parent Component'
    }
}
// SonComponent
<template>
    <h1>{{message}}</h1>
</template>>

<script>
export default {
    name: 'SonComponent',
    inject: ['message']
}
</script>>

更多組件間通信方式,請(qǐng)參考:Vue組件間通信6種方式

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容