Vue全家桶之Vue基礎(2)

1. Vue常用特性

1.1 表單輸入綁定

Vue全家桶之Vue基礎(1) 一文中我們已經學習過 v-model 的用法。我們可以用 v-model 指令在表單 <input><textarea><select> 元素上創建 雙向數據綁定。它會根據控件類型自動選取正確的方法來更新元素。儘管有些神奇,但 v-model 本質上不過是語法糖。它負責監聽用戶的輸入事件以更新數據,並對一些極端場景進行一些特殊處理。

v-model 會忽略所有表單元素的 valuecheckedselected attribute 的初始值而總是將 Vue 實例的數據作爲數據來源。你應該通過 JavaScript 在組件的 data 選項中聲明初始值。如下圖所示:
在這裏插入圖片描述
v-model 在內部爲不同的輸入元素使用不同的 property 並拋出不同的事件:

  1. text 和 textarea 元素使用 value property 和 input 事件
  2. checkbox 和 radio 使用 checked property 和 change 事件
  3. select 字段將 value 作爲 prop 並將 change 作爲事件

對於需要使用輸入法 (如中文、日文、韓文等) 的語言,你會發現 v-model 不會在輸入法組合文字過程中得到更新。如果你也想處理這個過程,請使用 input 事件。
在這裏插入圖片描述
在後續關於表單輸入綁定操作中用到的 js 代碼如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            message: "", //文本以及多行文本
            checked: false, //單個複選框綁定到bool值
            checkedNames: [], //多個複選框綁定到同一個數組
            picked: "", //單選按鈕
            selected: "", //選擇框單選
            selected2: [], //選擇框多選
            selected3: "A", //動態渲染選項
            options: [{
                text: 'One',
                value: 'A'
            }, {
                text: 'Two',
                value: 'B'
            }, {
                text: 'Three',
                value: 'C'
            }]
        }
    });
</script>

文本

<!-- placeholder:向用戶顯示描述性說明或者提示信息 -->
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

演示效果如下:
在這裏插入圖片描述
多行文本

<span>Multiline message is:</span>
<!-- white-space: pre-line: 合併空白符序列,但是保留換行符。 -->
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

演示效果如下:
在這裏插入圖片描述
在文本區域插值 <textarea>{{text}}</textarea>) 並不會生效,應用 v-model 來代替。

單個複選框,綁定到布爾值

<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

演示效果如下:
在這裏插入圖片描述
多個複選框,綁定到同一個數組

<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>

演示效果如下:
在這裏插入圖片描述
單選按鈕

<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>

演示效果如下:
在這裏插入圖片描述
補充:

  1. <label> 標記用於在表單元素中定義標籤,這些標籤可以對其他一些表單控件元素(如單行文本框、密碼框等)進行說明。
  2. <label> 標記可以指定 id/style/class 等核心屬性,也可以指定 onclick 等事件屬性。除此之外,<label> 標記還有一個 for 屬性,該屬性指定 <label> 標記與哪個表單控件相關聯。
  3. 雖然 <label> 標記定義的標籤只是輸出普通的文本,但 <label> 標記生成的標籤還有一個另外的作用,那就是當用戶單擊 <label> 生成的標籤時,和該標籤關聯的表單控件元素就會 獲得焦點。也就是說,當用戶選擇
  4. 使標籤和表單控件相關聯主要有兩種方式:
    1. 隱式關聯
      使用 for 屬性,指定 <label> 標記的 for 屬性值爲所關聯的表單控件的 id 屬性值。
    2. 顯式關聯
      將普通文本、表單控件一起放在

選擇框單選時

 <select v-model="selected">
   <option disabled value="">請選擇</option>
   <option>A</option>
   <option>B</option>
   <option>C</option>
 </select>
 <span>Selected: {{ selected }}</span>

演示效果如下:
在這裏插入圖片描述
如果 v-model 表達式的初始值未能匹配任何選項,<select> 元素將被渲染爲 未選中 狀態。在 iOS 中,這會使用戶無法選擇第一個選項。因爲這樣的情況下,iOS不會觸發 change 事件。因此,更推薦像上面這樣提供一個 值爲空的禁用選項

多選時(綁定到一個數組):

<!-- multiple: 表示列表/菜單內容可多選 -->
<select v-model="selected2" multiple style="width: 50px;">
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>
<br>
<span>Selected: {{ selected2 }}</span>

演示效果如下:
在這裏插入圖片描述
v-for 渲染的動態選項:

<select v-model="selected3">
	<option v-for="option in options" v-bind:value="option.value">
	  {{ option.text }}
	</option>
</select>
<span>Selected: {{ selected3 }}</span>

演示效果如下:
在這裏插入圖片描述
修飾符:
在默認情況下,v-model 在每次 input 事件觸發後將輸入框的值與數據進行同步 (除了上述輸入法組合文字時)。你可以添加 lazy 修飾符,從而轉爲在 change 事件_之後_進行同步:

<!-- 在"change"時而非"input"時更新 -->
<input v-model.lazy="msg">

如以下例子所示:
在這裏插入圖片描述
演示效果如下:
在這裏插入圖片描述
如果想自動將用戶的輸入值轉爲數值類型,可以給 v-model 添加 number 修飾符:

<input v-model.number="age" type="number">

這通常很有用,因爲即使在 type="number" 時,HTML 輸入元素的值也總會返回字符串。如果這個值無法被 parseFloat() 解析,則會返回原始的值。如果要自動過濾用戶輸入的首尾空白字符,可以給 v-model 添加 trim 修飾符:

<input v-model.trim="msg">

1.2 自定義指令

爲何需要自定義指令?

除了核心功能默認內置的指令 (v-model 和 v-show),Vue 也允許註冊自定義指令。注意,在 Vue2.0 中,代碼複用和抽象的主要形式是 組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。舉個聚焦輸入框的例子,如下:
在這裏插入圖片描述
當頁面加載時,該元素將獲得焦點 (注意:autofocus 在移動版 Safari 上不工作)。事實上,只要你在打開這個頁面後還沒點擊過任何內容,這個輸入框就應當還是處於聚焦狀態。現在讓我們用指令來實現這個功能:

//註冊一個全局指令focus
Vue.directive("focus", {
    //當被綁定的元素插入到DOM中時
    inserted: function(el) {
        //聚焦元素
        el.focus();
    }
})

自定義指令用法:
在這裏插入圖片描述
一個指令定義對象可以提供如下幾個鉤子函數 (均爲可選):

  1. bind:只調用一次,指令第一次綁定到元素時調用。在這裏可以進行一次性的初始化設置。
  2. inserted:被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被插入文檔中)。
  3. update:所在組件的 VNode 更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新 (詳細的鉤子函數參數見下)。
  4. componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新後調用。
  5. unbind:只調用一次,指令與元素解綁時調用。

接下來我們來看一下鉤子函數的參數 (即 el、binding、vnode 和 oldVnode)。 指令鉤子函數會被傳入以下參數:

  1. el:指令所綁定的元素,可以用來直接操作 DOM。
  2. binding:一個對象,包含以下 property:
    1. name:指令名,不包括 v- 前綴。
    2. value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值爲 2
    3. oldValue:指令綁定的前一個值,僅在 updatecomponentUpdated 鉤子中可用。無論值是否改變都可用。
    4. expression:字符串形式的指令表達式。例如 v-my-directive="1 + 1" 中,表達式爲 "1 + 1"
    5. arg:傳給指令的參數,可選。例如 v-my-directive:foo 中,參數爲 "foo"
    6. modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象爲 { foo: true, bar: true }
  3. vnode:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。
  4. oldVnode:上一個虛擬節點,僅在 updatecomponentUpdated 鉤子中可用。

除了 el 之外,其它參數都應該是隻讀的,切勿進行修改。如果需要在鉤子之間共享數據,建議通過元素的 dataset來進行。 下面通過一個例子來說明:
在這裏插入圖片描述
演示效果如下:
在這裏插入圖片描述
如果想註冊局部指令,組件中也接受一個 directives 的選項:

directives: {
  focus: {
    // 指令的定義
    inserted: function (el) {
      el.focus()
    }
  }
}

1.3 計算屬性

1.3.1 基本使用

模板內的表達式非常便利,但是設計它們的初衷是用於簡單運算的。在模板中放入太多的邏輯會讓模板過重且難以維護。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

在這個地方,模板不再是簡單的聲明式邏輯。你必須看一段時間才能意識到,這裏是想要顯示變量 message 的翻轉字符串。當你想要在模板中多包含此處的翻轉字符串時,就會更加難以處理。所以,對於任何複雜邏輯,你都應當使用 計算屬性。基礎例子如下:

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>計算屬性</title>
</head>

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

</html>
<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#example",
        data: {
            message: "Hello"
        },
        computed: {
            //計算屬性的getter
            reversedMessage: function() {
                //this指向vm實例
                return this.message.split("").reverse().join("");
            }
        }

    });
</script>

演示效果如下:
在這裏插入圖片描述
這裏我們聲明瞭一個計算屬性 reversedMessage。我們提供的函數將用作 property vm.reversedMessage 的 getter 函數:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你可以打開瀏覽器的控制檯,自行修改例子中的 vmvm.reversedMessage 的值始終取決於 vm.message 的值。如下圖所示:
在這裏插入圖片描述
你可以像綁定普通 property 一樣在模板中綁定計算屬性。Vue 知道 vm.reversedMessage 依賴於 vm.message,因此當 vm.message 發生改變時,所有依賴 vm.reversedMessage 的綁定也會更新。而且最妙的是我們已經以聲明的方式創建了這種依賴關係:計算屬性的 getter 函數是沒有副作用 (side effect) 的,這使它更易於測試和理解。

1.3.2 計算屬性緩存 vs 方法

你可能已經注意到我們可以通過在表達式中調用方法來達到同樣的效果:

<p>Reversed message: "{{ reversedMessage() }}"</p>

js 代碼如下:

methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

我們可以將同一函數定義爲一個方法而不是一個計算屬性。兩種方式的最終結果確實是完全相同的。然而,不同的是計算屬性是基於它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們纔會重新求值。這就意味着只要 message 還沒有發生改變,多次訪問 reversedMessage 計算屬性會立即返回之前的計算結果,而不必再次執行函數。

這也同樣意味着下面的計算屬性將不再更新,因爲 Date.now() 不是響應式依賴:

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

相比之下,每當觸發重新渲染時,調用方法將總會再次執行函數。我們爲什麼需要緩存?假設我們有一個性能開銷比較大的計算屬性 A,它需要遍歷一個巨大的數組並做大量的計算。然後我們可能有其他的計算屬性依賴於 A。如果沒有緩存,我們將不可避免的多次執行 A 的 getter!如果你不希望有緩存,請用方法來替代。

1.4 偵聽屬性及偵聽器

1.4.1 基本使用

Vue 提供了一種更通用的方式來觀察和響應 Vue 實例上的數據變動:偵聽屬性。當你有一些數據需要隨着其它數據變動而變動時,你很容易濫用 watch——特別是如果你之前使用過 AngularJS。然而,通常更好的做法是使用 計算屬性 命令式的 watch 回調。細想一下這個例子:

html 核心代碼如下:

<body>
    <div id="app">
        <div>
            <span>姓:</span>
            <span>
                <input type="text" v-model="xing">
            </span>
        </div>
        <div>
            <span>名:</span>
            <span>
                <input type="text" v-model="ming">
            </span>
        </div>
        <div>全名: {{xingming}}</div>
    </div>
</body>

js 核心代碼如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            xing: "Amo",
            ming: "Xiang",
            xingming: "Amo Xiang"
        },
        //在這添加一個watch屬性
        watch: {
            //watch中的屬性 一定是data中已經存在的數據
            //這裏的xing對應着data中的xing 當data中xing的值發生改變時 會自動觸發watch
            xing: function(val) {
                this.xingming = val + " " + this.ming;
            },
            //這裏的ming對應着data中的ming 當data中ming的值發生改變時 會自動觸發watch
            ming: function(val) {
                this.xingming = this.xing + " " + val;
            }
        }
    });
</script>

演示效果如下:
在這裏插入圖片描述
上面代碼是命令式且重複的。將它與計算屬性的版本進行比較:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            xing: "Amo",
            ming: "Xiang",
        },
        computed: {
            xingming: function() {
                return this.xing + " " + this.ming;
            }
        }
    });
</script>

好得多了,不是嗎?雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。這就是爲什麼 Vue 通過 watch 選項提供了一個更通用的方法,來響應數據的變化。當需要在數據變化時 執行異步開銷較大 的操作時,這個方式是最有用的。html 結構如下:

<body>
    <div id="app">
        <span>用戶名: </span>
        <span>
            <!-- 使用修飾符將input事件改爲change事件 -->
            <input type="text" v-model.lazy="uname">
        </span>
        <span>{{tip}}</span>
    </div>
</body>

js 代碼如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            uname: "",
            tip: ""
        },
        methods: {
            checkName: function(uname) {
                // 調用接口,但是可以使用定時任務的方式模擬接口調用
                // 因爲定時器內部的this是指向window的 所以這裏要使用一個變量來存儲this指向的vm實例
                let that = this;
                setTimeout(function() {
                    if (uname == "admin") {
                        that.tip = "用戶名已經存在,請更改....";
                    } else {
                        that.tip = "用戶名可以使用";
                    }
                }, 2000);
            }
        },
        watch: {
            uname: function(val) {
                this.checkName(val); //調用函數校驗用戶名的合法性
                this.tip = "正在檢驗用戶名是否可用...."
            }
        }
    });
</script>

演示效果如下:
在這裏插入圖片描述
當需要監聽一個對象的改變時,普通的 watch 方法無法監聽到對象內部屬性的改變,只有 data 中的數據才能夠監聽到變化,此時就需要 deep 屬性對對象進行深度監聽 請看下面這個例子:html 結構如下:

<body>
    <div id="app">
        <input type="text" v-model="personInfo.name">
        <h3>{{personInfo.name}}</h3>
    </div>
</body>

js 代碼如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            personInfo: {
                name: "Amo",
                age: 18,
                isBoy: true
            }
        },
        watch: {
            personInfo: function(value) {
                console.log("監聽成功");
            }
        }
    });
</script>

演示效果如下:
在這裏插入圖片描述
從上面的結果我們可以看出,是沒有監聽成功的,我們只需要將上面 js 代碼中 watch 部分更改一下即可,如下:
在這裏插入圖片描述
演示效果如下:
在這裏插入圖片描述

1.5 過濾器

所謂的 過濾器,大家可以這樣理解類似於我們家裏的 淨水器淨水器是按對 的使用要求對水質進行深度過濾、淨化處理,Vue 中則是按照對數據的使用要求進行過濾處理,其本質就是對數據的處理,而在 Vue 中數據都是綁定到 Vue實例的 data 屬性中,即過濾器處理 data 屬性中的數據然後將其返回。

1.5.1 基本使用

Vue.js 允許你自定義過濾器,可被用於一些常見的文本格式化。過濾器可以用在兩個地方:雙花括號插值v-bind 表達式 (後者從 2.1.0+ 開始支持)。過濾器 應該被添加在 JavaScript 表達式的尾部,由 管道 符號指示:

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

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

你可以在一個組件的選項中定義本地的過濾器:

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    //return必須寫,charAt(0):獲取字符串中第一個字符
    //toUpperCase():將小寫字母轉換爲大寫字母
    //slice(1): 表示字符串從索引1開始截取到最後的子串
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

或者在創建 Vue 實例之前全局定義過濾器:

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

new Vue({
  // ...
})

注意:

  1. 當全局過濾器和局部過濾器重名時,會採用局部過濾器。
  2. 全局註冊時是 filter,沒有 s 的。而局部過濾器是filters,是有 s

下面這個例子用到了 capitalize 過濾器:
在這裏插入圖片描述
通過上述 gif 演示的效果圖,大家可以看出 過濾器 不改變真正的 data,而只是改變渲染的結果,並返回過濾後的版本。核心代碼如下:
在這裏插入圖片描述
過濾器函數總接收表達式的值(之前的操作鏈的結果)作爲第一個參數。在上述例子中,capitalize 過濾器函數將會收到 message 的值作爲第一個參數。過濾器可以串聯:

{{ message | filterA | filterB }}

在這個例子中,filterA 被定義爲接收單個參數的過濾器函數,表達式 message 的值將作爲參數傳入到函數中。然後繼續調用同樣被定義爲接收單個參數的過濾器函數 filterB,將 filterA 的結果傳遞到 filterB 中。過濾器是 JavaScript 函數,因此可以接收參數:

{{ message | filterA('arg1', arg2) }}

這裏,filterA 被定義爲接收三個參數的過濾器函數。其中 message 的值作爲第一個參數,普通字符串 'arg1' 作爲第二個參數,表達式 arg2 的值作爲第三個參數。格式化日期例子,核心代碼如下:

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

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

<body>
    <div id="app">
        <div>{{date | format('yyyy-MM-dd hh:mm:ss')}}</div>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        Vue.filter('format', function(value, arg) {
            function dateFormat(date, format) {
                if (typeof date === "string") {
                    var mts = date.match(/(\/Date\((\d+)\)\/)/);
                    if (mts && mts.length >= 3) {
                        date = parseInt(mts[2]);
                    }
                }
                date = new Date(date);
                if (!date || date.toUTCString() == "Invalid Date") {
                    return "";
                }
                var map = {
                    "M": date.getMonth() + 1, //月份 
                    "d": date.getDate(), //日 
                    "h": date.getHours(), //小時 
                    "m": date.getMinutes(), //分 
                    "s": date.getSeconds(), //秒 
                    "q": Math.floor((date.getMonth() + 3) / 3), //季度 
                    "S": date.getMilliseconds() //毫秒 
                };

                format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
                    var v = map[t];
                    if (v !== undefined) {
                        if (all.length > 1) {
                            v = '0' + v;
                            v = v.substr(v.length - 2);
                        }
                        return v;
                    } else if (t === 'y') {
                        return (date.getFullYear() + '').substr(4 - all.length);
                    }
                    return all;
                });
                return format;
            }
            return dateFormat(value, arg);
        })
        var vm = new Vue({
            el: '#app',
            data: {
                date: new Date()
            }
        });
    </script>
</body>
</html>

這裏,format 被定義爲接收二個參數的過濾器函數。其中 date 的值作爲第一個參數,普通字符串 'yyyy-MM-dd hh:mm:ss' 作爲第二個參數。程序結果爲:
在這裏插入圖片描述

1.6 音樂播放器

1.6.1 功能演示

音樂播放器小案例.mp4

1.6.2 前置知識

HTML5 中,新增了兩個元素——video 元素與 audio 元素。video 元素專門用來播放網絡上的視頻或電影,而 audio 元素專門用來播放網絡上的音頻數據。使用這兩個元素,就不再需要使用其他任何插件了,只要使用支持 HTML5 的瀏覽器就可以了。這兩個元素的使用方法都很簡單,本文使用到的是audio,所以這裏 audio 元素爲例,只要把播放音頻的 URL給指定元素的 src 屬性就可以了,audio 元素使用方法如下所示。

<audio src="./Music/麥小兜-下山.flac">您的瀏覽器不支持audio元素!</audio>

通過這種方法,可以把指定的音頻數據直接嵌入在網頁上,其中 您的瀏覽器不支持audio元素! 爲在不支持 audio 元素的瀏覽器中所顯示的替代文字。本案例用到的屬性及事件介紹如下:

  1. src 屬性用於指定媒體數據的 URL 地址。
  2. autoplay 屬性用於指定媒體是否在頁面加載後自動播放。使用方法如下:
    <audio src="./Music/汪蘇瀧-小星星.flac" autoplay></audio>
    
  3. controls 屬性指定是否爲視頻或音頻添加瀏覽器自帶的播放用的控制條。控制條中具有播放、暫停等按鈕。使用方法如下:
    <audio src="./Music/汪蘇瀧-小星星.flac" controls></audio>
    
  4. ended 事件:播放由於媒介結束而停止

1.6.3 示例代碼

html 結構如下:

<body>
    <div id="app">
        <audio :src="currentSrc" controls autoplay @ended="handleEnded"></audio>
        <ul>
            <li :class="{active:index===currentIndex}" v-for="(item,index) in musicData" :key="item.id" @click="handle(item,index)">
                <h2>{{item.id}}--歌名:{{item.name}}</h2>
                <p>{{item.author}}</p>
            </li>
        </ul>
        <button @click="handlePre">上一首</button>
        <button @click="handleNext">下一首</button>
    </div>
</body>

js 代碼如下:

<script>
    const musicData = [{
        id: 1,
        name: '下山',
        author: '麥小兜',
        songSrc: './Music/麥小兜-下山.flac'
    }, {
        id: 2,
        name: 'Please Dont Go',
        author: 'Joel Adams',
        songSrc: './Music/Joel Adams - Please Dont Go.mp3'
    }, {
        id: 3,
        name: '小星星',
        author: '汪蘇瀧',
        songSrc: './Music/汪蘇瀧-小星星.flac'
    }, {
        id: 4,
        name: '芒種',
        author: '趙方婧',
        songSrc: './Music/音闕詩聽,趙方婧-芒種.flac'
    }];
    let vm = new Vue({
        el: "#app",
        data: {
            musicData,
            currentSrc: "./Music/麥小兜-下山.flac",
            currentIndex: 0
        },
        methods: {
            handle(item, index) {
                this.currentSrc = item.songSrc;
                this.currentIndex = index;
            },
            handleEnded() {
                this.handleNext();
            },
            handlePre() {
                this.currentIndex--;
                if (this.currentIndex < 0) {
                    this.currentIndex = this.musicData.length - 1;
                }
                this.currentSrc = this.musicData[this.currentIndex].songSrc;
            },
            handleNext() {
                this.currentIndex++;
                if (this.currentIndex === this.musicData.length) {
                    this.currentIndex = 0;
                }
                this.currentSrc = this.musicData[this.currentIndex].songSrc;
            }
        }
    });
</script>

2. 綜合案例

2.1 Vue之數組更新檢測

2.1.1 變更方法

Vue 將被偵聽的數組的變更方法進行了包裹,所以它們也將會觸發視圖更新。這些被包裹過的方法包括:

  1. push()
  2. pop()
  3. shift()
  4. unshift()
  5. splice()
  6. sort()
  7. reverse()

這裏的話,我們通過一個案例來進行演示,案例演示效果如下:
在這裏插入圖片描述
html 代碼結構如下:

<body>
    <div id="app">
        <div>
            <span>
                <input type="text" v-model="person" autofocus>
                <button @click="add">添加</button>
                <button @click="del">刪除</button>
                <button @click="modify">修改</button>
            </span>
        </div>
        <ul>
            <li v-for="(item,index) in personList" :key="index">{{item}}</li>
        </ul>
    </div>
</body>

js 代碼如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            personList: ["Amo", "Jerry", "Paul"],
            person: ""
        },
        methods: {
            add: function() {
                this.personList.push(this.person);
            },
            del: function() {
                this.personList.pop();
            },
            modify: function() {
                //將新的數組賦值給原有的personList
                this.personList = this.personList.slice(0, 2);
            }
        }
    });
</script>

2.1.2 替換數組

變更方法,顧名思義,會變更調用了這些方法的原始數組。相比之下,也有非變更方法,例如 filter()concat()slice()。它們不會變更原始數組,而 總是返回一個新數組。當使用非變更方法時,可以用新數組替換舊數組:

this.personList = this.personList.slice(0, 2);

你可能認爲這將導致 Vue 丟棄現有 DOM 並重新渲染整個列表。幸運的是,事實並非如此。Vue 爲了使得 DOM 元素得到最大範圍的重用而實現了一些智能的啓發式方法,所以用一個含有相同元素的數組去替換原來的數組是非常高效的操作。

2.1.3 注意事項

由於 JavaScript 的限制,Vue 不能檢測數組和對象的變化。儘管如此我們還是有一些辦法來回避這些限制並保證它們的響應性。對於數組: Vue 不能檢測以下數組的變動:

  1. 當你利用索引直接設置一個數組項時,例如:vm.items[indexOfItem] = newValue
  2. 當你修改數組的長度時,例如:vm.items.length = newLength

舉個例子:

let vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是響應性的
vm.items.length = 2 // 不是響應性的

爲了解決第一類問題,以下兩種方式都可以實現和 vm.items[indexOfItem] = newValue 相同的效果,同時也將在響應式系統內觸發狀態更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可以使用 vm.$set 實例方法,該方法是全局方法 Vue.set 的一個別名:

vm.$set(vm.items, indexOfItem, newValue)

爲了解決第二類問題,你可以使用 splice

vm.items.splice(newLength)

對於對象: Vue 無法檢測 property 的添加或移除。由於 Vue 會在初始化實例時對 property 執行 getter/setter 轉化,所以 property 必須在 data 對象上存在才能讓 Vue 將它轉換爲響應式的。例如:

let vm = new Vue({
  data:{
    a:1
  }
})
// `vm.a` 是響應式的
vm.b = 2
// `vm.b` 是非響應式的

對於已經創建的實例,Vue 不允許動態添加根級別的響應式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套對象添加響應式 property。例如,對於:

Vue.set(vm.someObject, 'b', 2)

讀者還可以使用 vm.$set 實例方法,這也是全局 Vue.set 方法的別名:

this.$set(this.someObject,'b',2)

有時你可能需要爲已有對象賦值多個新 property,比如使用 Object.assign()_.extend()。但是,這樣添加到對象上的新 property 不會觸發更新。在這種情況下,你應該用原對象與要混合進去的對象的 property 一起創建一個新的對象。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

2.2 圖書管理

2.2.1 圖書列表

  1. 實現靜態列表效果
  2. 基於數據實現模板效果
  3. 處裏每行的操作按鈕
    在這裏插入圖片描述
    示例代碼如下:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>圖書管理</title>
    <style>
        .grid {
            margin: auto;
            width: 500px;
            text-align: center;
        }
        
        .grid table {
            width: 100%;
            border-collapse: collapse;
        }
        
        .grid th,
        td {
            padding: 10;
            border: 1px dashed orange;
            height: 35px;
            line-height: 35px;
        }
        
        .grid th {
            background-color: orange;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="grid">
            <table>
                <thead>
                    <tr>
                        <th>編號</th>
                        <th>名稱</th>
                        <th>時間</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr :key='item.id' v-for='item in books'>
                        <td>{{item.id}}</td>
                        <td>{{item.name}}</td>
                        <td>{{item.date}}</td>
                        <td>
                            <a href="" @click.prevent>修改</a>
                            <span>|</span>
                            <a href="" @click.prevent>刪除</a>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</body>

</html>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
    let vm = new Vue({
        el: '#app',
        data: {
            books: [{
                id: 1,
                name: '三國演義',
                date: ''
            }, {
                id: 2,
                name: '水滸傳',
                date: ''
            }, {
                id: 3,
                name: '紅樓夢',
                date: ''
            }, {
                id: 4,
                name: '西遊記',
                date: ''
            }]
        }
    });
</script>

這個案例之前的案例差別不大,主要是對前面基礎的知識進行復習,也比較簡單,筆者就不再一一寫下去了。後面如果有時間,在進行補充。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章