日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

vue 2实战系列 —— 复习Vue

發布時間:2024/1/2 vue 35 coder
生活随笔 收集整理的這篇文章主要介紹了 vue 2实战系列 —— 复习Vue 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

復習Vue

近期需要接手 vue 2的項目,許久未寫,語法有些陌生。本篇將較全面復習 vue 2。

Tip: 項目是基于 ant-design-vue-pro

ant-design-vue-pro

由于 cms 是基于這個項目開發的,所以筆者就將其下載下來。

下載后運行

// 按照依賴
yarn install

// 本地啟動
yarn run serve

根據提示輸入 admin/admin 即可登錄。

全局概覽

直接通過script引入vue

就像這樣:

<body>
    <div id='app'></div>
    <!-- V -->
    <script type="text/x-template" id="tpl">
        <section>
            {{ message }}
            <p v-if="seen">現在你看到我了</p>
        </section>
    </script>

    <script>
    // VM
    var app = new Vue({
        el: '#app',
        template: '#tpl',
        // M
        data: {
            message: 'Hello Vue!',
            seen: true
        }
    })
    </script>
</body>

有以下幾點需要注意:

  • vue 是一個 mvvm 的框架,分數據模型M視圖V視圖模型VM三部分
  • Vue 實例作為 ViewModel,充當連接 Model 和 View 的橋梁。它負責將 Model 的數據綁定到 View 上,并且在數據發生變化時更新 View。因此,整個 Vue 實例是 ViewModel
  • VM部分主要是new Vue中的實參,該對象有 template、data
  • 模板中的 seen 屬性直接取自 Vue 實例中的 data,沒有添加什么 this
  • {{ message }} 屬于模板語法,用于插入值。

將上述示例轉成單文件組件。就像這樣:

<template>
  <section>
    {{ message }}
    <p v-if="seen">現在你看到我了</p>
  </section>
</template>

<script>
// es6 導出模塊語法
export default {
  data() {
    return {
      message: 'Hello Vue!',
      seen: true,
    };
  },
};
</script>

<style scoped>
section {
  color: red;
}
</style>

單文件組件通常由三部分組成:template、script、style。

工作中通常是單文件組件開發,相對于通過script引入vue的方式,其處理復雜項目更加方便。

前面我們知道單文件組件中的 template、script、style 會被 vue-loader 解析,然后交給不同的 loader 處理。

單文件組件在寫法上和script引入vue的方式有差異。比如在單文件組件中 data 屬性是一個方法,里面返回一個對象,而在script引入vue中,data選項可以直接是一個對象,而不需要通過函數返回。

template

vue 單文件組件中 template 有兩種含義:

  • 根級的 <template> 標簽用作整個模板的容器
  • 作為內置標簽<template>,用于邏輯分組的容器,其自身不會被渲染
<template>
  <div>
    <template v-if="condition1">
      <h1>條件 1 成立</h1>
      <template v-if="nestedCondition1">
        <p>內嵌條件 1 成立</p>
      </template>
      <template v-else>
        <p>內嵌條件 1 不成立</p>
      </template>
    </template>
    
    <template v-else>
      <h1>條件 1 不成立</h1>
      <template v-if="condition2">
        <h2>條件 2 成立</h2>
        <template v-if="nestedCondition2">
          <p>內嵌條件 2 成立</p>
        </template>
        <template v-else>
          <p>內嵌條件 2 不成立</p>
        </template>
      </template>
      <template v-else>
        <h2>條件 2 不成立</h2>
        <p>默認內容</p>
      </template>
    </template>
  </div>
</template>

幾點需要注意:

  • v-show 不能用于 <template>
  • v-if 和 v-for 可用于 <template>

Tip: 在 Vue 3 中,模板可以包含多個根級別的元素,這樣更加靈活。但在 Vue 2 中,仍然需要使用一個根元素來包裹組件的內容。

{{ }}

可以在 {{ }} 進行簡單運算、三元運算

<p>{{ 5 + 10 }}</p> 
<p>{{ message.split('').reverse().join('') }}</p> 
<p>{{ value1 > value2 ? value1 : value2 }}</p>

{{}} 期望的是一個單個表達式,而不是語句、流控制如 if、for

不能這樣:

{{ var book = "西游記" }}
{{ if (value > 5) value = 10 }}
{{ for(let i=0; i<5; i++) total += i }}

正確的方式應該是使用 Vue 的指令:

<!-- 條件渲染 -->
<div v-if="value > 5">value is greater than 5</div>
<div v-else>value is not greater than 5</div>

<!-- 列表渲染 -->
<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.text }}
  </li>
</ul>

指令

指令是 vue 模板中最常見的一項功能,前綴是 v-。比如 v-if

指令的主要職責是當表達式的值改變時,相應的某些行為應用到 dom 上。

比如 v-bind 最基本用途是動態更新html元素上的屬性,比如 id、class:

<div v-bind:id="dynamicId"></div>

<!-- 縮寫成 -->
<div :id="dynamicId"></div>

只掛載一次

el 或 .$mount() 在單文件組件項目中一般只出現一次,用來將 Vue 實例掛載到特定的 DOM 元素上。

直接通過script引入vue會這樣寫:

var app = new Vue({
    el: '#app',
    ...
})

單文件組件這么寫:

new Vue({
  router,
  store,
  created: bootstrap,
  render: h => h(App),
  ...
}).$mount('#app')

生命周期

vue 中常用的生命周期鉤子:

  • created: 常用于請求 ajax;DOM 還未生成
  • mounted:可以操作 dom 元素
  • beforeDestroy:銷毀前,比如清除定時器

計算屬性

對于任何復雜邏輯,你都應當使用計算屬性。請看示例:

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>

data: {
  message: 'Hello'
},
computed: {
  // 計算屬性的 getter
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

Tip: 兩個小技巧:

  • 計算屬性可以依賴其他計算屬性
  • 計算屬性不僅可以依賴當前 Vue 實例的數據,還可以依賴其他實例的數據。
緩存

計算屬性是基于它們的響應式依賴進行緩存的

下面的計算屬性將不再更新,因為 Date.now() 不是響應式依賴:

computed: {
  now: function () {
    return Date.now()
  }
}

我們為什么需要緩存?假設我們有一個性能開銷比較大的計算屬性 A,它需要遍歷一個巨大的數組并做大量的計算。然后我們可能有其他的計算屬性依賴于 A。如果沒有緩存,我們將不可避免的多次執行 A 的 getter!如果你不希望有緩存,請用方法來替代。

setter

計算屬性默認只有 getter,不過在需要時你也可以提供一個 setter(業務中很少用到):

/ ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

現在再運行 vm.fullName = 'John Doe' 時,setter 會被調用,vm.firstName 和 vm.lastName 也會相應地被更新。

過濾器

vue3 中廢除了過濾器,vue2 可以使用計算屬性替代。

下面是一個過濾器簡單示例:

<!-- 在雙花括號中 -->
{{ message | capitalize }}

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

內置指令

v-html

有時候想輸出 html,可以使用 v-html。

<div v-html="message"></div>

<script>
export default {
  data() {
    return {
      message: '<h1>Hello, Vue!</h1>'
    };
  }
}
</script>

如果將用戶產生的內容使用 v-html 輸出,可能導致 XSS 攻擊,所以要在服務端對用戶提交的內容進行處理,一般可將尖括號(<>)轉義。

v-pre

如果只想顯示 {{ }} 標簽,而不是進行取值,使用 v-pre 可跳過這個元素和它的子元素的編譯過程。

瀏覽器會原樣顯示綁定的內容,包括文本插值、指令和其他 Vue 特性

當有大量的靜態內容需要處理,使用 v-pre 可以減少 Vue 編譯的時間,從而提升性能

<div id="app">
  <p v-pre>{{ message }}</p>
  <p>{{ message }}</p>
</div>

<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: 'Hello, Vue!'
    }
  });
</script>

v-if

v-if、v-else-if、v-else 與 if、else、else if 類似。

v-else-if 需要緊跟 v-if,v-else 需要緊跟 v-else-if 或 v-if。

比如阻隔了,就會報錯:

<p v-if="aFlag == 1">早上好!</p>
<p>阻隔</p>
<p v-else-if="aFlag == 2">下午好!</p>
<p v-else>晚上好!</p>
用 key 管理可復用的元素

vue 渲染元素時,處于效率考慮,會盡可能復用已有元素而非重新渲染,比如:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address">
</template>

切換 loginType 將不會清除用戶已經輸入的內容。因為兩個模板使用了相同的元素,<input> 不會被替換掉——僅僅是替換了它的 placeholder。

Vue 提供了一種方式來表達“這兩個元素是完全獨立的,不要復用它們”。只需添加一個具有唯一值的 key 屬性即可:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

v-show

v-show 用法和v-if類似,只不過 v-show 只改變元素的 css 屬性 display。

v-if 是真正的條件渲染,而 v-show 總是渲染。

如果需要非常頻繁地切換,則使用 v-show 較好;如果在運行時條件很少改變,則使用 v-if 較好

v-for

v-for 可以迭代數組對象,用法和 for...in 類似。

// for...in 循環
let obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
  console.log(key); // 輸出 a, b, c
}

  • 遍歷數組:
<li v-for="item in items" :key="item.message">
    {{ item.message }}
</li>

data: {
  items: [
    { message: 'Foo' },
    { message: 'Bar' }
  ]
}
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>

data: {
  parentMessage: 'Parent',
  items: [
    { message: 'Foo' },
    { message: 'Bar' }
  ]
}
  • 遍歷對象:
<li v-for="value in object">
  {{ value }}
</li>

data: {
  object: {
    title: 'How to do lists in Vue',
    author: 'Jane Doe',
    publishedAt: '2016-04-10'
  }
}

/*
輸出:
How to do lists in Vue
Jane Doe
2016-04-10
*/
<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
</div>

/*
輸出:
title: How to do lists in Vue
author: Jane Doe
publishedAt: 2016-04-10
*/
<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
</div>

/*
輸出:
0. title: How to do lists in Vue
1. author: Jane Doe
2. publishedAt: 2016-04-10
*/

Tip: v-for 還可以迭代整數

// 輸出:“Item 1”, “Item 2”, “Item 3”, “Item 4”, “Item 5”
<div id="app">
  <div v-for="i in 5" :key="i">
    Item {{ i }}
  </div>
</div>

數組更新

vue2 只有這7個方法將會觸發視圖更新:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

filter()、concat() 和 slice() 不會觸發視圖更新。這些非變異方法總是返回一個新數組。

app.books[3] = {...} 不會觸發視圖更新,可以使用 vm.$set(this.$set(app.books, 3, {...}))觸發

排序和過濾

計算屬性非常適合用來處理排序和過濾邏輯

<div id="app">
  <ul>
    <li v-for="person in sortedPeople" :key="person.id">
      {{ person.name }}
    </li>
  </ul>
</div>

data: {
  people: [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Eve' }
  ]
},
computed: {
  sortedPeople() {
    return this.people.slice().sort((a, b) => a.name.localeCompare(b.name));
  }
}

事件

v-on 類似原生js 的 onclick 方法。

例如每點擊一次,數字就會增加1:

<button @click="count++">{{count}}</button>

<button @click="show = false">點擊隱藏</button>

Tip:@click 類似于 v-on:click,一個語法糖。

在大部分業務場景中,如果不需要傳入參數,可以不寫括號:@click=greet。以下是幾種寫法的區別:

<!-- 什么都不傳 -->
<button v-on:click="greet()">Greet</button>
<!-- 默認會傳遞一個原生事件對象 event -->
<button v-on:click="greet">Greet</button>
<!-- $event 是Vue 提供的一個特殊變量,表示原生事件對象 -->
<button v-on:click="greet('hello', $event)">Greet</button>
事件修飾符

在事件處理程序中調用 event.preventDefault() 或 event.stopPropagation() 是非常常見的需求,為了解決這個問題,Vue.js 為 v-on 提供了事件修飾符。修飾符是由開頭的指令后綴來表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

具體用法:

<!-- 阻止單擊事件繼續傳播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重載頁面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修飾符可以串聯 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修飾符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件監聽器時使用事件捕獲模式 -->
<!-- 即內部元素觸發的事件先在此處理,然后才交由內部元素進行處理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只當在 event.target 是當前元素自身時觸發處理函數 -->
<!-- 即事件不是從內部元素觸發的 -->
<div v-on:click.self="doThat">...</div>

<!-- 點擊事件將只會觸發一次 -->
<a v-on:click.once="doThis"></a>

v-once

定義它的元素和組件只渲染一次。在業務中很少使用,當需要進一步優化性能,可能會用到。

自定義指令

內置指令能滿足我們大部分需求,但一些特殊功能,需要對 dom 進行底層操作,這時可以使用自定義指令。

一個自定義指令對象的基本結構是:

{
  bind(el, binding, vnode) {
    // 在指令綁定到元素時的邏輯
  },
  inserted(el, binding, vnode) {
    // 在元素插入到DOM中的邏輯
  },
  update(el, binding, vnode, oldVnode) {
    // 在包含組件的VNode更新時的邏輯
  },
  componentUpdated(el, binding, vnode, oldVnode) {
    // 在包含組件的VNode和其子VNode更新之后的邏輯
  },
  unbind(el, binding, vnode) {
    // 在指令與元素解綁時的邏輯
  }
}

比如我們在 ant-desigin-vue-pro 項目中增加一個 v-focus 的自定義指令:

// 定義指令文件
// ant-design-vue-pro-master\src\core\directives\focus.js
import Vue from 'vue'
const action = Vue.directive('focus', {
    // 當被綁定的元素插入到 DOM 中時……
    inserted: function (el) {
      // 聚焦元素
      el.focus()
    }
  })

export default action
// 引入指令。全局都可以使用
// ant-design-vue-pro-master\src\core\lazy_use.js
import './directives/focus'

用法很簡單:<input v-focus/>。頁面加載后就會自動聚焦到這個 input 元素。

樣式

樣式相關的是 class 和內聯樣式,都是屬性,可以用 v-bind 處理。通過表達式計算,字符串拼接的方式比較麻煩,所以 v-bind 用于 class 和 style 時,Vue 專門做了增強,表達式除了是字符串,還可以是對象或數組。

class

對象語法
<div v-bind:class="{ active: isActive }"></div>

// isActive 為 true,渲染為
<div class="active"></div>
<div v-bind:class="classObject"></div>

data: {
  classObject: {
    active: true,
    'text-danger': false
  }
}

當 :class 表達式過長或邏輯復雜,還可以綁定計算屬性,這是一個常用且強大的模式:

<div v-bind:class="classObject"></div>

computed: {
  classObject: function () {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal'
    }
  }
}
數組語法
<div v-bind:class="[activeClass, errorClass]"></div>

data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

// 渲染為
<div class="active text-danger"></div>

可以用三元表達式切換列表中的 class

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

當有多個條件 class 時這樣寫有些繁瑣。所以在數組語法中也可以使用對象語法:

<div v-bind:class="[{ active: isActive }, errorClass]"></div>
用在組件上

當在一個自定義組件上使用 class 時,這些 class 將被添加到該組件的根元素上面。

Vue.component('my-component', {
  template: '<p class="foo bar">Hi</p>'
})

<my-component class="baz boo"></my-component>

最終渲染:<p class="foo bar baz boo">Hi</p>。對于帶數據綁定 class 也同樣適用,例如:

<my-component v-bind:class="{ active: isActive }"></my-component>

// 當 isActive 為 true 時,HTML 將被渲染成為
<p class="foo bar active">Hi</p>

內聯樣式

:style 給元素添加內聯樣式,也有對象語法數組語法,實際業務中數組語法不那么常見。

// 無效。<p>組件寬度</p>
<p style="{color: 'red'}" >組件寬度</p>

// 有效:<p style="color: red;">組件寬度</p>
<p :style="{color: 'red'}" >組件寬度</p>

看著非常像 CSS,但其實是一個 JavaScript 對象。CSS property 名可以用駝峰式 (camelCase) 或短橫線分隔 (kebab-case,記得用引號括起來) 來命名

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

data: {
  activeColor: 'red',
  fontSize: 30
}

直接綁定到一個樣式對象或computed通常更好,這會讓模板更清晰:

<div v-bind:style="styleObject"></div>

data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

數組語法:

<div v-bind:style="[baseStyles, overridingStyles]"></div>

:當 v-bind:style 使用需要添加瀏覽器引擎前綴的 CSS property 時,如 transform,Vue.js 會自動偵測并添加相應的前綴 —— 筆者使用 <p style="transform: scale(2, 0.5);">test</p> 在chrome和edge 中沒看見有什么前綴。

表單

原生表單比較難用,一些框架對表單做一些神奇的事情來減輕程序員的負擔,有的需要我們了解很多框架的內部實現,有的很容易使用但是可能不夠靈活。

下面我們看看在 vue 中如何使用表單。你對比原生(input、textarea、select)和vue提供的這種 v-model 方式,你會發現 vue 的這種方式和原生的套路有點不同,但確實方便一些。

可以用 v-model 指令在表單 <input><textarea><select> 元素上創建雙向數據綁定。

// 可以不用單獨指定 type="text", 因為 type="text" 是默認值。在瀏覽器中,如果沒有明確指定 input 標簽的 type 屬性時,它的默認值就是 text
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

data: {
  message: ''
}

在輸入框中輸入時,{{ message }} 內容也會實時更新。

對于 textare 也是相同用法:

<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

Tip: 使用 v-model 后,表單控件的顯示值只依賴綁定的數據,不在關心初始化時的 value,對于 <textarea></textarea>之間的插值,也不會生效。

// 初始值是 a
<input v-model="message" placeholder="edit me" value="2"> 
data() {
  return {
    message: 'a'
  }
},

v-model

v-model 本質上不過是語法糖(無需死機,按照 vue 提供的這種語法來寫即可):

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段將 value 作為 prop 并將 change 作為事件
<input v-model="message" placeholder="edit me"> 
等于
<input type="text" :value="message" @input="message = $event.target.value" placeholder="edit me">


<input type="radio" v-model="picked" :value="avalue">
等于
<input type="radio" :checked="picked === avalue" @change="picked = avalue">

Tip:v-model 也可以在組件上創建雙向綁定。更多介紹請看下文的“組件”章節

單選按鈕

首先回顧 html 單選按鈕的基本用法:

<input type="radio" id="option1" name="color" value="red">
<label for="option1">Red</label>

<input type="radio" id="option2" name="color" value="blue" checked>
<label for="option2">Blue</label>

<input type="radio" id="option3" name="color" value="yellow">
<label for="option3">Yellow</label>

let selectedColor = document.querySelector("input[name='color']:checked").value;
console.log(selectedColor); // 輸出被選中的 Radio 按鈕的值,例如 `blue`

單選按鈕單獨使用時,不需要使用 v-model,為真時選中,否時不選中:

<input type="radio" :checked="picked">

data() {
  return {
    picked: true
  }
},

單選按鈕單獨使用時,如果需要綁定一個動態數據,可以使用 v-bind。比如下面這個例子,默認是未選中,選中后,picked 也變成 3。

// 選中后,picked 的值就是 avalue 的值
<input type="radio" v-model="picked" :value="avalue">
<p>picked={{ picked }}</p>
<p>value={{ value }}</p>

data() {
  return {
    picked: true,
    avalue: 3,
  }
},

組合實現互斥:

<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
{/*  */}
<span>Picked: {{ picked }}</span>

data: {
  picked: ''
}

復選框

復選框單獨使用,使用 v-model,選中時為 true,取消勾選為 false:

<p>{{ picked }}</p>
<input type="checkbox" v-model="picked" >

組合使用時,綁定到同一個數組。

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>

data: {
  checkedNames: []
}

都選中時,輸出Checked names: [ "Jack", "John", "Mike" ],都未選中,輸出Checked names: []

初始未勾選,勾選時 toggle 是 yes,取消勾選 toggle 是 no:

<p>{{ toggle }}</p>
<input type="checkbox" 
    v-model="toggle" 
    true-value="yes"
    false-value="no"
    />

return {
  toggle: true,
}

選擇框

首先回顧原生 select 用法:

<select id="mySelect">
  <option value="option1">Option 1</option>
  <option value="option2" selected>Option 2</option>
  <option value="option3">Option 3</option>
</select>

<script>
  const selectElement = document.getElementById('mySelect');
  const selectedValue = selectElement.value;
  console.log(selectedValue);
</script>
  • 單選示例:
<select v-model="selected">
  <option disabled value="">請選擇</option>
  <option>A</option>
  <option value="BBB">B</option>
  <option>C</option>
</select>
<span>Selected: {{ selected }}</span>

data() {
  return {
    // 首先和 value 熟悉匹配,如果沒有則和 option 中的內容匹配
    selected: 'BBB',
  }
},

初始時輸出 Selected: BBB,選中A則輸出Selected: A

Tip: 選中 123 時,selected 的值是{ number: 123 }

<select v-model="selected">
  <!-- 內聯對象字面量 -->
  <option v-bind:value="{ number: 123 }">123</option>
</select>
  • 多選示例:
<select v-model="selected" multiple>
  <option disabled value="">請選擇</option>
  <option>A</option>
  <option value="BBB">B</option>
  <option>C</option>
</select>
<span>Selected: {{ selected }}</span>
  
data() {
  return {
    selected: ['A', 'C'],
  }
},

多選增加 multiple 屬性,selected 變成數組,這里初始時選中A和C。

  • 用 for 渲染 option
<select v-model="selected">
  <option v-for="option in options" :value="option.value">
    {{ option.text }}
  </option>
</select>
<span>Selected: {{ selected }}</span>

data: {
  selected: 'A',
  options: [
    { text: 'One', value: 'A' },
    { text: 'Two', value: 'B' },
    { text: 'Three', value: 'C' }
  ]
}

Tip:select 因為樣式依賴瀏覽器,樣式無法統一,功能也受限,比如不支持搜索,所以常用 div 模擬。

修飾符

  • .lazy
<input v-model.lazy="message" placeholder="edit me">
    <p>Message is: {{ message }}</p>

失去焦點或回車時才更新。

Tip:比如有的同學做 cms(內容管理系統)時,頁面上部分是搜索條件,下部分是表格,輸入框輸入字符,去取消 axios 請求,另一些同學則是通過 lodash 做延遲請求,是否直接使用 .lazy 就可以?

  • .number
    即使是 type=number,再次改變數字后,將輸出 string。
<input v-model="number" type="number" min="0" max="100" step="5" />
<p>{{ typeof number }}</p>

data() {
  return {
    number: 10,
  }
},

使用修飾符 .numnber 可以將輸入轉為 Number。

  • .trim
    .trim 可以自動過濾首位空格:
<input v-model.trim="message" />
<p>{{ message.length }}</p>

data() {
  return {
    message: ''
  }
},

輸入 a,將輸出 1。

系統修飾符

.exact - 修飾符允許你控制由精確的系統修飾符組合觸發的事件。

<!-- 即使 Alt 或 Shift 被一同按下時也會觸發 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的時候才觸發 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 沒有任何系統修飾符被按下的時候才觸發 -->
<button v-on:click.exact="onClick">A</button>

鼠標按鈕修飾符 - 以下這些修飾符會限制處理函數僅響應特定的鼠標按鈕。

.left
.right
.middle
按鍵修飾符
<!-- 只有在 `key` 是 `Enter` 時調用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

<input v-on:keyup.delete="submit">

組件

props、事件以及 slot 內容分發構成了 Vue 組件的 3 個 API 來源。

第一個組件

vue 單文件組件中,一個 .vue 文件就是一個組件。

開發過程中,我們應該要有意識的將一些常用的模塊抽取成組件(組件的復用性),這對后續的迭代和維護是很有利的。

比如我們創建一個組件 MyComponent.vue:

<template>
    <div>
      <h1>{{ message }}</h1>
      <button @click="changeMessage">Change Message</button>
    </div>
  </template>
  
  <script>
  export default {
    data() {
      return {
        message: 'Hello, World!'
      };
    },
    methods: {
      changeMessage() {
        this.message = 'New Message!';
      }
    }
  };
  </script>

使用該組件也非常方便:

<template>
  <div>
    <my-component></my-component>
  </div>
</template>

<script>

import MyComponent from './MyComponent.vue';

export default {
  components: {
    MyComponent
  }
};
</script>

Tip: 比如 ant-design-vue-pro 在 src/components/index.js 就統一導出了這些組件。

// chart
import Bar from '@/components/Charts/Bar'
import ChartCard from '@/components/Charts/ChartCard'
import Liquid from '@/components/Charts/Liquid'
import MiniArea from '@/components/Charts/MiniArea'
import MiniSmoothArea from '@/components/Charts/MiniSmoothArea'
import MiniBar from '@/components/Charts/MiniBar'
import MiniProgress from '@/components/Charts/MiniProgress'
import Radar from '@/components/Charts/Radar'
import RankList from '@/components/Charts/RankList'
import TransferBar from '@/components/Charts/TransferBar'
import TagCloud from '@/components/Charts/TagCloud'

// pro components
import AvatarList from '@/components/AvatarList'
import Ellipsis from '@/components/Ellipsis'
import FooterToolbar from '@/components/FooterToolbar'
import NumberInfo from '@/components/NumberInfo'
import Tree from '@/components/Tree/Tree'
import Trend from '@/components/Trend'
import STable from '@/components/Table'
import MultiTab from '@/components/MultiTab'
import IconSelector from '@/components/IconSelector'
import TagSelect from '@/components/TagSelect'
import StandardFormRow from '@/components/StandardFormRow'
import ArticleListContent from '@/components/ArticleListContent'

import Dialog from '@/components/Dialog'

export {
  AvatarList,
  Bar,
  ChartCard,
  Liquid,
  MiniArea,
  MiniSmoothArea,
  MiniBar,
  MiniProgress,
  Radar,
  TagCloud,
  RankList,
  TransferBar,
  Trend,
  Ellipsis,
  FooterToolbar,
  NumberInfo,
  Tree,
  STable,
  MultiTab,
  IconSelector,
  TagSelect,
  StandardFormRow,
  ArticleListContent,
  Dialog
}

特殊的 is 屬性

我們知道在html中,有的標簽內規定只能是某些元素,比如 ul 子元素只能是 li。在 ul 內直接使用組件是無效的:

<ul>
  <my-component/>
</ul>

可以使用特殊的 is 屬性:

<ul>
  <li is="my-component"></li>
</ul>

最后渲染成:

<ul>
  <div>
    <h1>Hello, World!</h1>
    <button>Change Message</button>
  </div>
</ul>

:在單文件組件中不受此限制。

is 結合 <component> 實現動態掛載組件。就像這樣:

<div id='app'>
  <!-- vue 提供了 <component> 來動態的掛載組件 -->
  <component :is="currentComponent"></component>
  <button @click='switchHandle'>切換組件</button>
</div>

Tip: 更多介紹請看 vue 的基礎應用(下)-動態組件

props

通常父組件的模板中包含子組件,父組件要正向地向子組件傳遞數據,子組件接根據接收參數的不同來渲染或執行,這個過程就是通過 props 實現 —— props 用于接收來自父組件的數據。

在組件中通過 props 申明需要從父組件接收的參數:

props: {
  prefixCls: {
    // 類型
    type: String,
    // 默認值
    default: 'ant-pro-global-header-index-action'
  },
  isMobile: {
    type: Boolean,
    default: () => false
  },
  topMenu: {
    type: Boolean,
    required: true
  },
  theme: {
    type: String,
    required: true
  }
},
data () {
  return {
    showMenu: true,
    currentUser: {}
  }
},

Tip:props 還可以是數組,通常對象類型的更常用。例如 ant-design-vue-pro 就是對象類型的 props,除了可以定義名稱,還能定義類型和默認值。

// 數組類型
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

通常當你的組件提供給別人用時,推薦都進行數據校驗。更多用法請看官網。例如:

Vue.component('my-component', {
  props: {
    // 基礎的類型檢查 (`null` 和 `undefined` 會通過任何類型驗證)
    propA: Number,
    // 多個可能的類型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 帶有默認值的數字
    propD: {
      type: Number,
      default: 100
    },
    // 帶有默認值的對象
    propE: {
      type: Object,
      // 對象或數組默認值必須從一個工廠函數獲取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗證函數
    propF: {
      validator: function (value) {
        // 這個值必須匹配下列字符串中的一個
        // <ValidationComponent :propF="'info'" /> 則會報一個警告,因為 info 不在這3個枚舉中
        return ['success', 'warning', 'danger'].includes(value)
      }
    }
  }
})
null 和 undefined

基礎的類型檢查 (nullundefined 會通過任何類型驗證),意思是當你使用 Vue 的 props 屬性進行類型檢查時,null 和 undefined 值將通過任何類型的驗證。

請看示例:

<h1>{{ typeof postTitle }}</h1>
props: {
  postTitle: {
    type: String,
    default: ''
  },
},

通過下面三種方法使用組件:

  1. 控制臺報錯,提示期望字符串,傳過來的是數字
  2. 控制臺沒報錯,postTitle 的值是 null
  3. 控制臺沒報錯,postTitle 的值是 ''
// Expected String with value "123", got Number with value 123.
 <my-component :post-title="123"/> 

// object
<my-component :post-title="null"/>
    
// string
<my-component :post-title="undefined"/>
required

定義了 required:true,雖然同時定義了默認值,但是不傳該屬性,控制臺會報錯:

// Missing required prop: "postTitle"
<my-component />

props: {
  postTitle: {
    type: String,
    default: '',
    required: true
  },
},

傳遞 null 報錯:

// Invalid prop: type check failed for prop "postTitle". Expected String, got Null
<my-component :post-title="null"/>

:傳遞 undefined 不報錯,postTitle 會取默認值

<my-component :post-title="undefined"/>
prop 大小寫

定義的時使用駝峰,使用時使用 kebab-case (短橫線分隔命名) 命名。就像這樣:

props: {
  postTitle: {
    type: String,
    default: ''
  }
},

<my-component post-title="apple"/>

Tip:如果使用單文件組件就沒有這種限制,例如可以 <my-component postTitle="apple3"/>。ant-design-vue-pro 使用的就是 kebab-case 這種用風格。

動態 prop

使用 v-bind: 傳遞動態 prop:

// 靜態
<blog-post title="My journey with Vue"></blog-post>

// 動態
<blog-post v-bind:title="post.title"></blog-post>

// 動態賦予一個復雜表達式的值:字符串 `peng li`
<my-component :postTitle="'peng' + ' ' + 'li'"/>

:直接使用數字、布爾、對象、數組,而不使用 v-bind,傳遞的則是字符串。

// 字符串
<my-component number="10"/>

// 數字
<my-component :number="10"/>
單向數據流

vue2.x 與 vue1.x 比較大的一個改變是,vue2.x 通過 props 傳遞數據是單向的了。也就是父組件數據變化時會傳遞給子組件,但反過來就不行。而在 vue1.x 里提供的 .sync 修飾符來支持雙向。vue2.x 中的的 sync 本質是單向的。

這么設計是將父子組件解耦,避免子組件無意修改父組件的狀態。

業務中經常遇到兩種改變 prop 的情況,一種是父組件傳遞初始值進來,子組件將它作為一個初始值保存起來,然后在自己的作用域中隨意使用和修改。例如:

// 子組件
<div>
  <p>postTitle:{{ postTitle }}</p>
  <p>title:{{ title }}</p>
</div>

props: {
  postTitle: {
    type: String,
    default: '',
    required: true
  },
},
data() {
  return {
    title: this.postTitle
  };
},

父組件給子組件傳遞初始值 value 后,子組件只要維護 title,這樣就避免直接操作 PostTitle:

// 父組件
<input v-model="value"/>
<my-component :post-title="value"/>

另一種就是 prop 作為需要被轉變的原始值傳入。這種情況用計算屬性就可以了。請看示例:

<p :style="styleObject">組件寬度</p>

props: {
  width: {
    type: Number
  },
},
computed: {
  styleObject(){
    return {
      width: this.width + 'px',
      backgroundColor: 'pink',
    }
  }
},

隨著父組件更改 value 的值,子組件的寬度也會隨之變化:

<div>
  <input v-model.number="value"/>
  <my-component :width="value"/>
</div>

組件通信

組件的關系有父子、兄弟、子孫。

組件可以獨立存在,也可用來創建其他組件

組件更多的是以靈活的方式被使用,應該關注其獨立性和常常不帶任何負擔。所以組件只關注其父母和孩子,兄弟關系可以不管。

自定義事件

當子組件向父組件傳遞數據時,需要用到自定義事件。

js 中觀察者模式,有被觀察者和觀察者,其中有 addEventListener(注冊) 和 dispatchEvent(觸發) 這兩個方法。就像這樣:

// 創建一個被觀察者對象
const observer = {
  listeners: {},
  addEventListener(event, listener) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(listener);
  },
  removeEventListener(event, listener) {
    if (this.listeners[event]) {
      this.listeners[event] = this.listeners[event].filter(l => l !== listener);
    }
  },
  dispatchEvent(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(listener => listener(data));
    }
  }
};

// 創建觀察者函數
function observer1(data) {
  console.log('觀察者1收到通知:', data);
}

function observer2(data) {
  console.log('觀察者2收到通知:', data);
}

// 將觀察者注冊到被觀察者中
observer.addEventListener('event1', observer1);
observer.addEventListener('event2', observer2);

// 觸發事件并通知觀察者
observer.dispatchEvent('event1', 'Hello World');
observer.dispatchEvent('event2', { name: 'Alice', age: 25 });

// 注銷觀察者
observer.removeEventListener('event1', observer1);

// 再次觸發事件并通知觀察者
observer.dispatchEvent('event1', 'Hello Again');
observer.dispatchEvent('event2', { name: 'Bob', age: 30 });

Vue 組件也有一套類似模式,子組件用 $emit() 觸發事件,父組件用 $on() 來監聽子組件的事件。

我們從官網知道 vm.$on 監聽當前實例上的自定義事件。從示例也很容易看出來:

vm.$on('test', function (msg) {
  console.log(msg)
})
vm.$emit('test', 'hi')
// => "hi"

現在有一個疑惑:上面我們寫的觀察者模式示例,監聽和觸發的方法都在被觀察者中,而現在我們的用法是:在父組件中使用 v-on 注冊,在子組件中使用 $emit。為什么不是在同一處注冊和觸發?

// 父組件
<button-counter v-on:chang-count='changCount'></button-counter>

// 子組件
<button @click='triggerHandle'>觸發</button>
methods: {
  triggerHandle: function(){
    // 觸發事件
    this.$emit('chang-count', this.num++);
  }
}

要回答這個問題,我們得了解模板編譯和虛擬dom的一些事情,在模板編譯階段,會將模板編譯成渲染函數,隨后會創建 Vnode。在這個過程中會創建一些元素,也會判斷當前這個標簽是真的標簽(例如 div)還是一個組件:如果是組件,就會將子組件實例化,并傳遞一些參數,其中就包含父組件在模板中使用 v-on 注冊在子組件標簽上的事件;如果是平臺標簽(例如 div),則創建元素并插入到dom中,同時將 v-on 注冊的事件注冊到瀏覽器事件中。

簡單來說,如果 v-on 寫在組件標簽上,這個事件就會注冊到子組件 Vue 的事件系統中,如果是平臺標簽,例如 div,事件就注冊到瀏覽器事件中。

下面這個示例,子組件提供兩個按鈕,一個加1,一個減1,點擊時會通知父組件更新數據:

// 子組件
<template>
  <div>
    <p><button @click="handleIncrease">加1</button> <button @click="handleReduce">減1</button></p>
  </div>
</template>
  
  <script>
export default {
  props: {},
  data() {
    return {
      counter: 0,
    }
  },
  computed: {},
  methods: {
    handleIncrease() {
      this.counter++
      this.$emit('increase', this.counter)
    },
    handleReduce() {
      this.counter--
      this.$emit('reduce', this.counter)
    },
  },
}
</script>

父組件
<template>
  <div>
    <p>value: {{ value }}</p>
    <my-component @increase="update" @reduce="update" />
  </div>
</template>

<script>
import MyComponent from './MyComponent.vue'

export default {
  components: {
    MyComponent,
  },
  methods: {
    update(value) {
      this.value = value
    },
  },
  data() {
    return {
      value: 0,
    }
  },
}
</script>
.native

v-on 用在組件上監聽自定義事件,也可以監聽 dom 事件,這是可以用 .native 修飾符,表示監聽一個原生事件,監聽該組件的根元素。就像這樣:

// 父組件
<my-component @click.native="handleClick"/>

 methods: {
  handleClick(){
    console.log('apple');
  }
},

相當于在子組件的根元素上注冊了一個原生的 click 事件,點擊子組件,則會輸出 apple。

爺孫傳遞數據

需求:爺組件給孫組件傳遞數據,孫組件給爺組件傳遞數據。

父給子傳遞數據可以用 props,子組件給父組件傳遞數據用 emit。

這個需求我們當然可以借助 props 和 emit 來實現,但感覺父親轉發好累,而且假如某個屬性命名父親不用需要使用,但還是得在 props 中標明,容易給別人誤解,因為父親不需要使用這個屬性。

介紹兩個屬性:

  • vm.$listeners - 包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器
  • vm.$attrs - 包含了父作用域中不作為 prop 被識別 (且獲取) 的 attribute 綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外)

我們先將實現寫出來,再來分析這兩個屬性:

// 爺爺組件
<template>
    <Father :message="message" @changeMessage="handleMessage"/>
</template>

<script>
import Father from '@/views/result/Father'
export default {
    components:{
        Father,
    },
    data() {
        return {
            message: 'apple'
        }
    },
    methods: {
        handleMessage(v) {
            this.message = v
        }
    }
}
</script>
// 父親組件
<template>
    <Grandson v-bind="$attrs" v-on="$listeners"/>
</template>

<script>
import Grandson from '@/views/result/Grandson'
export default {
    components:{
        Grandson,
    },
}
</script>
// 孫子組件
<template>
    <div>
        <p>我是孫子:</p>
        <p>來自爺爺的 message: {{message }}</p>
        <button @click="handleClick">點擊改變 爺爺的 message</button>
    </div>
</template>

<script>
export default {
    props: {
        message: {
            type: [String, Date]
        }
    },
    methods: {
        handleClick(){
            this.$emit('changeMessage', new Date())
        }
    }
}
</script>

現在孫子組件接收到了來自爺爺的屬性,通過點擊按鈕,也能改變爺爺的屬性:

<div message="apple">
  <p>我是孫子:</p>
  <p>來自爺爺的 message: apple</p>
  <button>點擊改變 爺爺的 message</button>
</div>
$attrs 和 $listeners

上文提到 $attrs “包含了父作用域中不作為 prop 被識別”,什么意思?請看示例:

爺爺組件給父親組件多傳一個 tel 屬性

// 爺爺組件
<Father tel="131xxxx" :message="message" />

父親組件輸出 {tel: '131xxxx', message: 'apple'}

// 父親組件
created(){
    console.log(this.$attrs);
},

如果給父組件增加 props: ['tel'] 來接收 tel 屬性,父組件則輸出 {message: 'apple'},tel 屬性不在 $attrs 中。

Tip:attrs 還有不包含 class、style,也是同樣的意思。另外 $listeners 中不含 .native 也不在冗余介紹。

v-model

在上文“表單”中我們使用過 v-model,用在組件中,本質也是一樣的。請看示例:

// 父組件
<template>
  <div>
    <p>count: {{ count }}</p>
    <my-component v-model="count"/>
  </div>
</template>
  
<script>
  import MyComponent from './MyComponent.vue'
  export default {
    components: {
      MyComponent,
    },
    methods: {
    
    },
    data() {
      return {
        count: 10,
      }
    },
  }
</script>
// 子組件
<template>
  <div>
    <input :value="msg" @input="inputHandle" />
  </div>
</template>
  
<script>
export default {
  props: ['value'],
  data: function () {
    return {
      msg: this.value,
    }
  },
  methods: {
    inputHandle: function (e) {
      this.msg = e.target.value
      console.log('this.msg: ', this.msg);
      // 觸發事件
      this.$emit('input', this.msg)
    },
  },
}
</script>

父組件中使用 <my-component v-model="count"/> 綁定一個數據 count,子組件用 value 接收數據,子組件數據變化時通過 this.$emit('input', this.msg) 傳遞數據給父元素,實現雙向綁定。

sync

sync修飾符和v-model指令在某種程度上可以說是類似的,因為它們都用于在父子組件之間實現雙向數據綁定。

<input v-model="message" />
等同于
<input :value="message" @input="message = $event.target.value" />
<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event" ></text-document>
等同于
<text-document v-bind:title.sync="doc.title"></text-document>

通過在傳遞給子組件的屬性上使用.sync修飾符,Vue會自動將綁定的屬性以一個名為update:propertyName的事件形式傳遞給子組件。子組件只需要在需要更新屬性時觸發該事件即可。

請看 sync 示例:

// 父組件
<template>
  <div>
    父組件: name = {{ name }}
    <my-component :name.sync="name"></my-component>
  </div>
</template>
  
<script>
import MyComponent from './MyComponent.vue'
export default {
  components: {
    MyComponent,
  },
  data() {
    return {
      name: 'hello',
    }
  },
}
</script>
// 子組件
<template>
   <div>
        <p>子組件: name = {{name}}</p>
        <p><button @click='handleClick'>change name</button></p>
      </div>
</template>
  
<script>
export default {
  props: ['name'],

  methods: {
    handleClick: function(){
        // update:name 就是一個事件名
        this.$emit('update:name', new Date())
      }
  },
}
</script>

Tip: 更多介紹請看 vue 的基礎應用-.sync 修飾符

非父子組件通信

vue 1.x 中的 $dispatch 和 $broadcast 在 vue 2.x 中廢除。在 vue 2.x 中可以使用 *事件總線(bus) 來替代 —— 參考 vue 的基礎應用-非父子組件通信

除了 bus 還有,父鏈(vm.$parent)、子鏈(vm.$children),以及子組件索引(vm.$refs)來實現組件之間通信 —— 參考 vue 的基礎應用-父組件和子組件

vm.$refs - 一個對象,持有注冊過 ref attribute 的所有 DOM 元素和組件實例。

Tip:在 ant-design-vue-pro 中沒有使用 bus、$children,$parent 也只有一處。

插槽

ant-design-vue-pro 中沒有使用插槽。搜不到 v-scope。但稍微使用了 $slots:

// Ellipsis.vue
<script>
import Tooltip from 'ant-design-vue/es/tooltip'
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/util'
/*
    const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;

    const TooltipOverlayStyle = {
      overflowWrap: 'break-word',
      wordWrap: 'break-word',
    };
  */

export default {
  name: 'Ellipsis',
  components: {
    Tooltip
  },
  props: {
    prefixCls: {
      type: String,
      default: 'ant-pro-ellipsis'
    },
    tooltip: {
      type: Boolean
    },
    length: {
      type: Number,
      required: true
    },
    lines: {
      type: Number,
      default: 1
    },
    fullWidthRecognition: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    getStrDom (str, fullLength) {
      return (
        <span>{ cutStrByFullLength(str, this.length) + (fullLength > this.length ? '...' : '') }</span>
      )
    },
    getTooltip (fullStr, fullLength) {
      return (
        <Tooltip>
          <template slot="title">{ fullStr }</template>
          { this.getStrDom(fullStr, fullLength) }
        </Tooltip>
      )
    }
  },
  render () {
    const { tooltip, length } = this.$props
    const str = this.$slots.default.map(vNode => vNode.text).join('')
    const fullLength = getStrFullLength(str)
    const strDom = tooltip && fullLength > length ? this.getTooltip(str, fullLength) : this.getStrDom(str, fullLength)
    return (
      strDom
    )
  }
}
</script>

vm.$slots 用來訪問被插槽分發的內容。每個具名插槽有其相應的 property (例如:v-slot:foo 中的內容將會在 vm.$slots.foo 中被找到)。default property 包括了所有沒有被包含在具名插槽中的節點,或 v-slot:default 的內容。

vm.$slots 在業務中幾乎用不到,在用 render 函數創建組件時比較有用,主要用在獨立組件開發。

最簡單的 render() 單文件組件:

// 渲染出:<div>Hello, rendering function!</div>

<script>
export default {
  data() {
    return {
      message: 'Hello, rendering function!'
    };
  },
  render(createElement) {
    return createElement('div', this.message);
  }
};
</script>

Tip: 更多介紹請看 vue 的基礎應用-通過插槽分發內容

總結

以上是生活随笔為你收集整理的vue 2实战系列 —— 复习Vue的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 我和我的太阳泰剧在线观看泰剧 | 天天舔天天舔 | 玖草在线观看 | 嫩草av91| 亚洲一区二区偷拍 | 天天干天天透 | 西欧free性满足hd老熟妇 | 亚欧色视频 | 午夜肉体高潮免费毛片 | 高跟鞋丝袜猛烈xxxx | 麻豆传媒视频入口 | 免费成人美女女 | 99资源 | 性爱免费视频 | va免费视频| 在线播放一区二区三区 | 夜夜躁很很躁日日躁麻豆 | 国产中文在线播放 | 特黄一区二区 | 亚洲人午夜射精精品日韩 | 国产做爰全免费的视频软件 | 欧美性受xxxx黑人xyx性爽 | 综合色亚洲 | 理想之城连续剧40集免费播放 | 亚洲国产欧美另类 | 三级在线网址 | 日韩av片在线免费观看 | 无码h黄肉3d动漫在线观看 | 久久九九色 | 青青草成人在线 | 国产五区 | free性中国hd国语露脸 | 国产又色又爽无遮挡免费动态图 | 黄色网址哪里有 | 成人在线免费播放 | 久久一区二 | 久久av影院 | 一个色综合久久 | 伊人影院av | 国产精品7777777 | 天堂素人约啪 | 99视频在线精品 | 五月婷婷六月激情 | 97在线观看视频 | 短视频在线观看 | 国产无遮挡又黄又爽在线观看 | 十八禁毛片 | 一区二区免费视频 | 一区二区日韩电影 | 国产又粗又猛又爽又黄的视频在线观看动漫 | 亚洲色偷精品一区二区三区 | 一级片麻豆 | 内射国产内射夫妻免费频道 | 色一情一区二 | 91免费观看网站 | 天天操夜夜操 | 中文字幕亚洲欧美 | 国产情侣av自拍 | 欧美影院一区 | 亚洲最新 | 日韩激情视频网站 | 亚洲AV成人无码电影在线观看 | 久久久久久久九九九九 | 欧美拍拍视频 | 天天国产视频 | 超碰在线97观看 | 91视频免费看 | 在线观看一二三区 | 一区二区成人在线 | 亚洲欧洲成人精品久久一码二码 | 亚洲成人生活片 | 欧洲一区二区三区四区 | 国产老妇伦国产熟女老妇视频 | 成年人视频在线播放 | 精品产国自在拍 | 日本一区二区视频在线播放 | 成人人伦一区二区三区 | 免费日韩精品 | 久久成人毛片 | 一级黄色性生活视频 | 午夜黄网 | www一起操 | 欧美人妖老妇 | 一区二区三区欧美视频 | 香蕉视频99 | 免费av中文字幕 | 日本久久综合 | 99国产精品99久久久久久粉嫩 | avtt国产| 国产交换配乱淫视频免费 | 国内偷拍av | 国产精品久草 | 久久片 | 女人一区二区三区 | 99在线免费观看视频 | 亚洲高清精品视频 | 台湾三级伦理片 | 久久日本精品字幕区二区 | 99精品久久久久 |