本章將繼續和大家分享Vue的一些基礎知識,話不多說,下面我們直接上代碼:
本文內容大部分摘自Vue的官網:https://v2.cn.vuejs.org/v2/guide/
首先我們先來看一下Demo的目錄結構,如下所示:

一、偵聽器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue中的偵聽器</title> <script src=https://www.cnblogs.com/xyh9039/archive/2023/02/26/"/js/lib/vue.js"></script> <script src=https://www.cnblogs.com/xyh9039/archive/2023/02/26/"/js/lib/axios.js"></script> <script src=https://www.cnblogs.com/xyh9039/archive/2023/02/26/"/js/lib/lodash.js"></script> </head> <body> <div id="app"> <div desc="偵聽屬性"> <p> 請輸入您的問題: <input v-model="question"> </p> <p>{{ answer }}</p> </div> </div> <script> var vm = new Vue({ el: '#app', //掛載點 data: { question: '', answer: '在您提出問題之前,我不能給您答案!', student: { name: '張三', age: 18 } }, watch: { //簡單監聽 //如果 `question` 發生改變,這個函式就會運行 question: function (newQuestion, oldQuestion) { var _this = this; _this.answer = '正在等待您停止輸入...' _this.debouncedGetAnswer(); }, //對物件進行深度監聽 //普通的watch方法無法監聽到物件內部屬性的變化 student: { handler(newValue, oldValue) { // 注意:在嵌套的變更中, // 只要沒有替換物件本身, // 那么這里的 `newValue` 和 `oldValue` 相同,都是新值 console.log(newValue); console.log(oldValue); }, deep: true, // 深度監聽 immediate: true // 強制立即執行回呼(一般用于父組件向子組件動態傳值時) }, //對物件的某一個屬性進行深度監聽 //如果想要監聽物件的某一個屬性,并且希望獲取該屬性變化前后的值則需要用該方式進行監聽 'student.age': { handler(newValue, oldValue) { console.log(newValue); console.log(oldValue); }, deep: true, // 深度監聽 immediate: true // 強制立即執行回呼(一般用于父組件向子組件動態傳值時) } }, created: function () { // `_.debounce` 是一個通過 Lodash 限制操作頻率的函式, // 在這個例子中,我們希望限制訪問 介面 的頻率 // AJAX 請求直到用戶輸入完畢才會發出,想要了解更多關于 // `_.debounce` 函式 (及其近親 `_.throttle`) 的知識, // 請參考:https://lodash.com/docs#debounce var _this = this; _this.debouncedGetAnswer = _.debounce(_this.getAnswer, 1500); //debouncedGetAnswer 方法名可自定義 }, methods: { getAnswer: function () { var _this = this; if (_this.question.indexOf('?') === -1) { _this.answer = '問題通常包含問號!'; return; } _this.answer = '資料獲取中...'; axios.get('https://autumnfish.cn/api/joke') .then(function (response) { _this.answer = response.data; }) .catch(function (error) { _this.answer = '請求介面例外:' + error; }); } } }); </script> </body> </html>
二、Vue組件基礎
自定義組件 <button-counter> 代碼如下:
define([ 'axios' ], function (axios) { /* 因為組件是可復用的 Vue 實體,所以它們與 new Vue 接收相同的選項,例如 data、computed、watch、methods 以及生命周期鉤子等, 僅有的例外是像 el 這樣根實體特有的選項, */ return { template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>', props: [], //一個組件的 data 選項必須是一個函式,因此每個實體可以維護一份被回傳物件的獨立的拷貝 data: function () { return { count: 0 } }, mounted: function () { }, methods: { }, watch: { } }; });
自定義組件 <blog-post> 代碼如下:
define([ 'axios' ], function (axios) { /* 因為組件是可復用的 Vue 實體,所以它們與 new Vue 接收相同的選項,例如 data、computed、watch、methods 以及生命周期鉤子等, 僅有的例外是像 el 這樣根實體特有的選項, */ return { //每個組件必須只有一個根元素 template: ` <div class="blog-post" desc="根元素"> <h3>{{ post.title }}</h3> <p>子組件中的titleNew:<input type="text" v-model="titleNew"></p> <slot></slot> <button @click="handleEnlargeText">Enlarge text</button> <div v-html="post.content"></div> </div> `, /* 1、通過 Prop 向子組件傳遞資料, 2、一個組件默認可以擁有任意數量的 prop,任何值都可以傳遞給任何 prop, 在上述模板中,你會發現我們能夠在組件實體中訪問這個值,就像訪問 data 中的值一樣, 3、所有的 prop 都使得其父子 prop 之間形成了一個單向下行系結:父級 prop 的更新會向下流動到子組件中,但是反過來則不行, 這樣會防止從子組件意外變更父級組件的狀態,從而導致你的應用的資料流向難以理解, */ props: ['post', 'title'], //一個組件的 data 選項必須是一個函式,因此每個實體可以維護一份被回傳物件的獨立的拷貝 data: function () { return { enlargeFontSize: 0.1, //需要放大字體的大小 titleNew: this.title, //初始值為props中父組件傳遞過來的值 } }, mounted: function () { }, methods: { //處理放大文本字體 handleEnlargeText: function () { var _this = this; //子組件可以通過呼叫內建的 $emit 方法并傳入事件名稱來觸發一個父組件的事件 //第二個引數為呼叫父組件事件所需傳的引數 //enlarge-text為自定義事件 _this.$emit('enlarge-text', _this.enlargeFontSize); } }, watch: { //監聽器完整寫法 title: { handler(newValue, oldValue) { this.titleNew = newValue; }, //deep: true, // 深度監聽 immediate: true // 強制立即執行回呼(一般用于父組件向子組件動態傳值時) }, //監聽器簡寫,當需要設定 deep 或者 immediate 時需使用完整寫法 titleNew: function (newValue, oldValue) { /* 注意在 JavaScript 中物件和陣列是通過參考傳入的,所以對于一個陣列或物件型別的 prop 來說, 在子組件中改變變更這個物件或陣列本身將會影響到父組件的狀態, 這種情況下就不需要以 update:myPropName 的模式觸發更新事件了, */ this.$emit('update:title', newValue); //更新父組件title屬性系結的值 /* 自定義事件 .sync 修飾符: 在有些情況下,我們可能需要對一個 prop 進行“雙向系結”, 不幸的是,真正的雙向系結會帶來維護上的問題,因為子組件可以變更父組件,且在父組件和子組件兩側都沒有明顯的變更來源, 這也是為什么我們推薦以 update:myPropName 的模式觸發事件取而代之, 舉個例子,在一個包含 title prop 的假設的組件中,我們可以用以下方法表達對其賦新值的意圖: this.$emit('update:title', newTitle) 然后父組件可以監聽那個事件并根據需要更新一個本地的資料 property,例如: <text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event"> </text-document> 為了方便起見,我們為這種模式提供一個縮寫,即 .sync 修飾符: <text-document :title.sync="doc.title"></text-document> */ } } }; });
Vue組件基礎.html 代碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue組件基礎</title> </head> <body> <div id="app"> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> <div :style="{ fontSize: postFontSize + 'em' }"> <blog-post v-for="post in posts" :key="post.id" :title.sync="post.title" :post="post" v-on:enlarge-text="onEnlargeText"> <!-- 默認插槽的內容 --> <template v-slot:default> <p>父組件中的post.title:<input type="text" v-model="post.title" /></p> </template> </blog-post> </div> </div> <script src=https://www.cnblogs.com/xyh9039/archive/2023/02/26/"/js/lib/require.js"></script> <script src=https://www.cnblogs.com/xyh9039/archive/2023/02/26/"/js/common/require_config.js"></script> <script src=https://www.cnblogs.com/xyh9039/archive/2023/02/26/"/js/ComponentsDemo.js"></script> </body> </html>
其中 ComponentsDemo.js 代碼如下:
//Vue組件基礎 require(['../common/base', '../components/blogPost'], function (base, blogPost) { let axios = base.axios; var vm = new base.vue({ el: '#app', //掛載點 mixins: [base.mixin], //混入,類似基類的概念 components: { 'blog-post': blogPost //區域注冊組件,注意區域注冊的組件在其子組件中不可用, }, data: { posts: [ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ], postFontSize: 1 }, //created鉤子函式 created: function () { console.log('This is index created'); }, //mounted鉤子函式 mounted: function () { console.log('This is index mounted'); }, //方法 methods: { //放大文本 onEnlargeText: function (enlargeFontSize) { var _this = this; _this.postFontSize += enlargeFontSize } } }); });
其中 require_config.js 代碼如下:
//主要用來配置模塊的加載位置(設定短模塊名) require.config({ baseUrl: '/js/lib', //設定根目錄 paths: { //如果沒有設定根目錄則需要填寫完整路徑 'vue': 'vue', 'axios': 'axios', 'jquery': 'jquery-3.6.3', //paths還有一個重要的功能,就是可以配置多個路徑,如果遠程cdn庫沒有加載成功,可以加載本地的庫,如下: //'jquery': ['http://libs.baidu.com/jquery/2.0.3/jquery', '/js/lib/jquery-3.6.3'], } });
其中 base.js 代碼如下:
//define用來自定義模塊 //第一個引數:加載依賴模塊,可以是require_config中定義的短模塊名,也可以是完整的模塊路徑(去掉.js后綴名) //第二個引數:執行加載完后的回呼函式 define(['vue', 'axios', '../components/buttonCounter'], function (vue, axios, buttonCounter) { //TODO 此處可以處理一些公共的邏輯 //vue.component('component-a', { /* ... */ }); //全域注冊組件 //vue.mixin({...}); //全域混入 /* 定義組件名的方式有兩種: 1、使用 kebab-case (短橫線分隔命名) 當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在參考這個自定義元素時使用 kebab-case,例如 <my-component-name> 2、使用 PascalCase (首字母大寫命名) 當使用 PascalCase (首字母大寫命名) 定義一個組件時,你在參考這個自定義元素時兩種命名法都可以使用, 也就是說 <my-component-name> 和 <MyComponentName> 都是可接受的, 注意,盡管如此,直接在 DOM (即非字串的模板) 中使用時只有 kebab-case 是有效的, */ //Vue.component(...) 的第一個引數為組件名, vue.component('button-counter', buttonCounter); //全域注冊 return { vue: vue, axios: axios, //Vue混入 mixin: { //資料 data: function () { return { domain: '', //域名 } }, //組件 components: { }, //created鉤子函式 created: function () { console.log('This is base created'); }, //mounted鉤子函式 mounted: function () { console.log('This is base mounted'); }, //方法 methods: { //測驗 doTest: function () { console.log('This is base doTest'); }, //獲取域名 getDomain: function () { var _this = this; _this.domain = 'https://www.baidu.com'; }, } }, }; });
運行結果如下:

三、組件注冊
1、組件名大小寫
定義組件名的方式有兩種:
1)使用 kebab-case
Vue.component('my-component-name', { /* ... */ })
當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在參考這個自定義元素時使用 kebab-case,例如 <my-component-name>,
2)使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
當使用 PascalCase (首字母大寫命名) 定義一個組件時,你在參考這個自定義元素時兩種命名法都可以使用,也就是說 <my-component-name> 和 <MyComponentName> 都是可接受的,注意,盡管如此,直接在 DOM (即非字串的模板) 中使用時只有 kebab-case 是有效的,
2、全域注冊
到目前為止,我們用過 Vue.component 來創建組件:
Vue.component('my-component-name', { // ... 選項 ... })
這些組件是全域注冊的,也就是說它們在注冊之后可以用在任何新創建的 Vue 根實體 (new Vue) 的模板中,
3、區域注冊
全域注冊往往是不夠理想的,比如,如果你使用一個像 webpack 這樣的構建系統,全域注冊所有的組件意味著即便你已經不再使用一個組件了,它仍然會被包含在你最終的構建結果中,這造成了用戶下載的 JavaScript 的無謂的增加,
在這些情況下,你可以通過一個普通的 JavaScript 物件來定義組件:
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }
然后在 components 選項中定義你想要使用的組件:
new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
對于 components 物件中的每個 property 來說,其 property 名就是自定義元素的名字,其 property 值就是這個組件的選項物件,
注意區域注冊的組件在其子組件中不可用,例如,如果你希望 ComponentA 在 ComponentB 中可用,則你需要這樣寫:
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }
四、組件中的Prop
1、Prop 的大小寫 (camelCase vs kebab-case)
HTML 中的 attribute 名是大小寫不敏感的,所以瀏覽器會把所有大寫字符解釋為小寫字符,這意味著當你使用 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>
重申一次,如果你使用字串模板,那么這個限制就不存在了,
2、Prop 型別
到這里,我們只看到了以字串陣列形式列出的 prop:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
但是,通常你希望每個 prop 都有指定的值型別,這時,你可以以物件形式列出 prop,這些 property 的名稱和值分別是 prop 各自的名稱和型別:
props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor }
這不僅為你的組件提供了檔案,還會在它們遇到錯誤的型別時從瀏覽器的 JavaScript 控制臺提示用戶,
3、單向資料流
所有的 prop 都使得其父子 prop 之間形成了一個單向下行系結:父級 prop 的更新會向下流動到子組件中,但是反過來則不行,這樣會防止從子組件意外變更父級組件的狀態,從而導致你的應用的資料流向難以理解,
額外的,每次父級組件發生變更時,子組件中所有的 prop 都將會重繪為最新的值,這意味著你不應該在一個子組件內部改變 prop,如果你這樣做了,Vue 會在瀏覽器的控制臺中發出警告,
這里有兩種常見的試圖變更一個 prop 的情形:
1)這個 prop 用來傳遞一個初始值;這個子組件接下來希望將其作為一個本地的 prop 資料來使用,
在這種情況下,最好定義一個本地的 data property 并將這個 prop 用作其初始值:
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
2)這個 prop 以一種原始的值傳入且需要進行轉換,
在這種情況下,最好使用這個 prop 的值來定義一個計算屬性:
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
注意在 JavaScript 中物件和陣列是通過參考傳入的,所以對于一個陣列或物件型別的 prop 來說,在子組件中改變變更這個物件或陣列本身將會影響到父組件的狀態,
五、自定義事件
1、事件名
不同于組件和 prop,事件名不存在任何自動化的大小寫轉換,而是觸發的事件名需要完全匹配監聽這個事件所用的名稱,舉個例子,如果觸發一個 camelCase 名字的事件:
this.$emit('myEvent')
則監聽這個名字的 kebab-case 版本是不會有任何效果的:
<!-- 沒有效果 --> <my-component v-on:my-event="doSomething"></my-component>
不同于組件和 prop,事件名不會被用作一個 JavaScript 變數名或 property 名,所以就沒有理由使用 camelCase 或 PascalCase 了,并且 v-on 事件監聽器在 DOM 模板中會被自動轉換為全小寫 (因為 HTML 是大小寫不敏感的),所以 v-on:myEvent 將會變成 v-on:myevent——導致 myEvent 不可能被監聽到,
因此,我們推薦你始終使用 kebab-case 的事件名,
2、.sync 修飾符
在有些情況下,我們可能需要對一個 prop 進行“雙向系結”,不幸的是,真正的雙向系結會帶來維護上的問題,因為子組件可以變更父組件,且在父組件和子組件兩側都沒有明顯的變更來源,
這也是為什么我們推薦以 update:myPropName 的模式觸發事件取而代之,舉個例子,在一個包含 title prop 的假設的組件中,我們可以用以下方法表達對其賦新值的意圖:
this.$emit('update:title', newTitle)
然后父組件可以監聽那個事件并根據需要更新一個本地的資料 property,例如:
<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event" ></text-document>
為了方便起見,我們為這種模式提供一個縮寫,即 .sync 修飾符:
<text-document v-bind:title.sync="doc.title"></text-document>
六、插槽
1、具名插槽
有時我們需要多個插槽,例如對于一個帶有如下模板的 <base-layout> 組件:
<div class="container"> <header> <!-- 我們希望把頁頭放這里 --> </header> <main> <!-- 我們希望把主要內容放這里 --> </main> <footer> <!-- 我們希望把頁腳放這里 --> </footer> </div>
對于這樣的情況,<slot> 元素有一個特殊的 attribute:name,這個 attribute 可以用來定義額外的插槽:
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
一個不帶 name 的 <slot> 出口會帶有隱含的名字“default”,
在向具名插槽提供內容的時候,我們可以在一個 <template> 元素上使用 v-slot 指令,并以 v-slot 的引數的形式提供其名稱:
<base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <template v-slot:default> <p>A paragraph for the main content.</p> <p>And another one.</p> </template> <template v-slot:footer> <p>Here's some contact info</p> </template> </base-layout>
現在 <template> 元素中的所有內容都將會被傳入相應的插槽,
最終渲染結果如下所示:
<div class="container"> <header> <h1>Here might be a page title</h1> </header> <main> <p>A paragraph for the main content.</p> <p>And another one.</p> </main> <footer> <p>Here's some contact info</p> </footer> </div>
注意 v-slot 只能添加在 <template> 上 (只有一種例外情況),這一點和已經廢棄的 slot attribute 不同,
2、后備內容
有時為一個插槽設定具體的后備 (也就是默認的) 內容是很有用的,它只會在沒有提供內容的時候被渲染,例如在一個 <submit-button> 組件中:
<button type="submit"> <slot></slot> </button>
我們可能希望這個 <button> 內絕大多數情況下都渲染文本“Submit”,為了將“Submit”作為后備內容,我們可以將它放在 <slot> 標簽內:
<button type="submit"> <slot>Submit</slot> </button>
現在當我在一個父級組件中使用 <submit-button> 并且不提供任何插槽內容時:
<submit-button></submit-button>
后備內容“Submit”將會被渲染:
<button type="submit"> Submit </button>
但是如果我們提供內容:
<submit-button>
Save
</submit-button>
則這個提供的內容將會被渲染從而取代后備內容:
<button type="submit"> Save </button>
3、動態插槽名
動態指令引數也可以用在 v-slot 上,來定義動態的插槽名:
<base-layout> <template v-slot:[dynamicSlotName]> ... </template> </base-layout>
4、具名插槽的縮寫
跟 v-on 和 v-bind 一樣,v-slot 也有縮寫,即把引數之前的所有內容 (v-slot:) 替換為字符 #,例如 v-slot:header 可以被重寫為 #header:
<base-layout> <template #header> <h1>Here might be a page title</h1> </template> <template #default> <p>A paragraph for the main content.</p> <p>And another one.</p> </template> <template #footer> <p>Here's some contact info</p> </template> </base-layout>
七、混入
1、基礎
混入 (mixin) 提供了一種非常靈活的方式,來分發 Vue 組件中的可復用功能,一個混入物件可以包含任意組件選項,當組件使用混入物件時,所有混入物件的選項將被“混合”進入該組件本身的選項,
例子:
// 定義一個混入物件 var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } } } // 定義一個使用混入物件的組件 var Component = Vue.extend({ mixins: [myMixin] }) var component = new Component() // => "hello from mixin!"
2、選項合并
當組件和混入物件含有同名選項時,這些選項將以恰當的方式進行“合并”,
比如,資料物件在內部會進行遞回合并,并在發生沖突時以組件資料優先,
var mixin = { data: function () { return { message: 'hello', foo: 'abc' } } } new Vue({ mixins: [mixin], data: function () { return { message: 'goodbye', bar: 'def' } }, created: function () { console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" } } })
同名鉤子函式將合并為一個陣列,因此都將被呼叫,另外,混入物件的鉤子將在組件自身鉤子之前呼叫,
var mixin = { created: function () { console.log('混入物件的鉤子被呼叫') } } new Vue({ mixins: [mixin], created: function () { console.log('組件鉤子被呼叫') } }) // => "混入物件的鉤子被呼叫" // => "組件鉤子被呼叫"
值為物件的選項,例如 methods、components 和 directives,將被合并為同一個物件,兩個物件鍵名沖突時,取組件物件的鍵值對,
var mixin = { methods: { foo: function () { console.log('foo') }, conflicting: function () { console.log('from mixin') } } } var vm = new Vue({ mixins: [mixin], methods: { bar: function () { console.log('bar') }, conflicting: function () { console.log('from self') } } }) vm.foo() // => "foo" vm.bar() // => "bar" vm.conflicting() // => "from self"
注意:Vue.extend() 也使用同樣的策略進行合并,
3、全域混入
混入也可以進行全域注冊,使用時格外小心!一旦使用全域混入,它將影響每一個之后創建的 Vue 實體,使用恰當時,這可以用來為自定義選項注入處理邏輯,
// 為自定義的選項 'myOption' 注入一個處理器, Vue.mixin({ created: function () { var myOption = this.$options.myOption if (myOption) { console.log(myOption) } } }) new Vue({ myOption: 'hello!' }) // => "hello!"
請謹慎使用全域混入,因為它會影響每個單獨創建的 Vue 實體 (包括第三方組件),大多數情況下,只應當應用于自定義選項,就像上面示例一樣,推薦將其作為插件發布,以避免重復應用混入,
Demo原始碼:
鏈接:https://pan.baidu.com/s/1jc5SGf3_8qb6pZT4T-5mxA 提取碼:broa
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/17139196.html
著作權宣告:如有雷同純屬巧合,如有侵權請及時聯系本人修改,謝謝!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/545150.html
標籤:其他
下一篇:js-惰性函式
