iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 2
5
Modern Web

勇者鬥Vue龍系列 第 2

Vue.js Core 30天屠龍記(第2天): 初探 Vue.js

基本原理

Vue.js 是處理 View Layer 的 Library ,使用 Vue.js 時,我們會操作 View Model (也就是 Vue.js 的實體)使其依照從商業邏輯取得的資料做改變,配合在 HTML 中 Vue.js 提供的模板語法來改變配置,重新渲染後使畫面產生變化。

下圖是一個 Vue.js 簡單的架構圖:

vue mvvm like

  • View : 透過 HTML 及模板語法渲染出來的畫面。
  • View Model : 使用 Vue.js 所建立起來的實體。
  • Model : 後端程式中的商業邏輯。

圖中的綠色文字代表範例情境:

  1. 使用者按下按鈕。
  2. 透過 HTML 中模板語法的綁定觸發 Vue 實體中註冊的事件。
  3. 事件中叫用 AJAX 向伺服器請求資料。
  4. 伺服器取得資料後傳回給 Vue 實體。
  5. Vue 實體修改綁定的 View Model 。
  6. View Model 改變後觸發模板重新渲染。
  7. 使用者看到改變後的畫面。

上述的情境是在高層次的面上做說明,實際上 Vue.js 的運作原理還要比這個深奧許多,後面有機會再來分享,現在這階段,用這樣的架構去說明就可以有比較具體的概念了。

如果以語言來分的話, View 就是 HTML , View Model 是 JS ,而 Model 就是像 Node 、 Java 這類的後端語言。

第一支 Vue.js

這一節會用上節的情境來開發一個簡單的 Vue.js 應用,讓我們對 Vue.js 的流程有個初步的了解。

首先要建立一個 Vue 實體,再來在 HTML 中編寫模板語法,之後將 HTML 上欲做變化的元素登錄在 Vue 實體中,接著綁定 Click 事件至後端取得資料,最後修改 Vue 實體中的資料藉以讓畫面產生變化。

引入 Vue.js 庫

引入 Vue 的方式有很多種,在一般的專案中通常都是使用 Webpack 來引入,但本系列文為了 Demo 方便,使用 <script> 元素來引入。

<!DOCTYPE html>
<html>
<head>
    ...
    <script src="https://unpkg.com/vue"></script>
</head>
<body>
    ...
</body>
</html>

這樣就引入了 Vue.js 庫了。

Vue.js 有分不同的版本: FullRuntime-only , Runtime-only 版本比 Full 少了 Compiler ,這是將模板編譯成 render function 的編譯器,因此如果只使用 Render Function 來渲染頁面的話可以只載入 Runtime-only 就好,在後面的章節會介紹 Render Function,那時候會做比較詳細的說明,而版本差別的詳細說明可以參考官網的介紹

建立 Vue 實體

接下來要建立 Vue 實體,就是上圖中間圓圈的部分, Vue 實體是整個 Vue.js 應用必備的物件,要建立 Vue 實體很簡單,如下程式碼:

<!DOCTYPE html>
<html>
<head>
    ...
    <script src="https://unpkg.com/vue"></script>
</head>
<body>
    ...
    <script>
        var vm = new Vue({
            ...
        });
        ...
    </script>
</body>
</html>

<script> 中的 new Vue({...}) 會建立 Vue 的實體,而 vm 變數取得的就是 Vue 實例化的物件。

通常 Vue 實例化物件會以 vm 當作其變數名稱,這也呼應了上面介紹: Vue 實例化物件在架構上代表著 View Model 層。

Vue 的第一個參數是 Options,它用來登錄 Vue 實體所需的對象。

以這個例子來說有三個部分需要設定:

  • 註冊一個 Vue 實例的掛載目標。
  • 需要一個輸出取得結果的資料。
  • 一個向後端取得資料的 Click 事件。

所以 Options 物件會像下面這樣:

var vm = new Vue({
    el: '#app',
    data: {
        message: "This is local data.",
    },
    methods: {
        getRemoteMessage() {
            Promise.resolve("Get remote data.")
                .then((res) => {
                    this.message = res;
                });
        },
    },
});
  • el : 將這個 Vue 實體掛載到這裡設置的元素上。
  • data : 登錄資料,當這些資料改變時,畫面會依照變化做改變。
  • methods : 登錄方法,這些方法可以藉由 DOM 事件觸發,也可以在 Vue 實例中被叫用。

上面的程式碼做了下面三件事:

  • 將頁面上 ID 為 app 的元素當作這個 Vue 實例的掛載目標。
  • 初始一個 message 的畫面響應資料。
  • 定義一個 getRemoteMessage 方法,該方法會以非同步的方式取得資料,然後將取得的資料設置於 message 上。

這裡直接使用 Promise.resolve 當作範例,實際上這裡可以用 AJAX 取得資料。

這樣就把 Vue 實例建立起來了。

編寫模板

最後要使用 Vue 提供的模板語法來綁定 Vue 實例中的物件,我們需要下面這些部分:

  • 一個 ID 為 app<div>
  • 一個顯示訊息的 <p>
  • 一顆取回非同步資料的 <button>

配置會像下面這樣:

<!DOCTYPE html>
<html>
<head>
    ...
    <script src="https://unpkg.com/vue"></script>
</head>
<body>
    <div id="app">
        <p>{{message}}</p>
        <button v-on:click="getRemoteMessage">Click</button>
    </div>
    ...
</body>
</html>

這裡有兩個 Vue 的模板語法:

  • {{message}} : 綁定 Vue 實例中的 message 資料。
  • v-on:click : 綁定 Vue 實例中的 getRemoteMessage 方法至 Click 事件中。

如此一來我們就完成了所有的配置,當你按下按鈕後就會看到 This is local data. 變為 Get remote data. 了。

再進一步

上面完成了我們第一個 Vue 的應用程式,但如果現在的情境是 舊系統裡面要使用 Vue.js 來擴充功能 呢? 例如說取得資料的按鈕並不存在於 Vue 實體中,那要怎麼辦呢?

我們假設以下是我們目前的頁面配置:

<div id="app">
    <p>{{message}}</p>
    ...
</div>
<button ...>Outside Button</button>

因為按鈕在 Vue 實例(div#app)的範圍外,並不在 Vue 模板中,所以並不能使用像是 v-on:click 這樣的模板語法綁定事件。

這裡可以使用 vm 變數, vm 變數就是 Vue 實例的物件,因此修改 vm 中的 message 也可以達到同樣的效果:

<div id="app">
    <p>{{message}}</p>
    ...
</div>
<button onclick="clickButton()">Outside Button</button>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            message: "This is local data.",
        },
        ...
    });

    function clickButton() {
        Promise.resolve("Get remote data by outside button.")
            .then((res) => {
                vm.message = res;
            });
    }
</script>

這樣就算系統中其他部分不是使用 Vue.js ,也可以很輕鬆的整合。

完整程式碼

以下是完整的程式碼,包含了 Vue 實例內及實例外的按鈕範例。

<!DOCTYPE html>
<html>
<head>
    <title>Hello Vue.js</title>
    <script src="https://unpkg.com/vue"></script>
</head>
<body>
    <!-- 7. Output View after rerender -->
    <div id="app">
        <p>{{message}}</p>  <!-- 6. Rerender View -->
        <button v-on:click="getRemoteMessage">Inside Button</button>   <!-- 1. Click Button -->
    </div>
    <button onclick="clickButton()">Outside Button</button>   <!-- 1. Click Button -->
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                message: "This is local data.",
            },
            methods: {
                getRemoteMessage() {    // 2. trigger event in Vue instance
                    Promise.resolve("Get remote data by inside button.") // 3. Get data asynchronously
                        .then((res) => {    // 4. Return result
                            this.message = res; // 5. Change View Model
                        });
                },
            },
        });

        function clickButton() {    // 2. trigger event in Vue instance
            Promise.resolve("Get remote data by outside button.") // 3. Get data asynchronously
                .then((res) => {    // 4. Return result
                    vm.message = res; // 5. Change View Model
                });
        }
    </script>
</body>
</html>

註解可以搭配文章一開始圖片上的數字做比較具體的理解。

Demo

程式碼

小結

Vue.js 只要單純的設置就能改變頁面的配置,在範例中我們並沒有修改 DOM 元素,而是專注在對 View Model 做改變,由於不用直接接觸頁面的配置,因此變換邏輯跟頁面沒有直接的關係,所以就算修改了頁面的配置,只要模板語法的設定正確,不需要修改邏輯就可以完成修改,這樣的特性使頁面可以更容易的被抽換。

例如我們今天想要修改 message<p><div> ,如果是 JQuery 的寫法,除了 HTML 的配置要做修改外,還必須要修改 JavaScript 的程式碼,可是在 Vue.js 中我們只要把 <p> 改為 <div> 就好,只要 View Model 依然是 message ,我們就不需要修改任何的 JavaScript 碼。

而對於舊系統的相容也可以用 vm 變數做到對 Vue 實例的修改,在整合原有專案上有很大的優勢。

參考資料


上一篇
Vue.js Core 30天屠龍記(第1天): 前言
下一篇
Vue.js Core 30天屠龍記(第3天): Vue 實例
系列文
勇者鬥Vue龍32

2 則留言

1
marlin12
iT邦新手 3 級 ‧ 2018-10-19 00:29:39

在clickButton函數裏,為何vm.$data.messagevm.message都是指向data欄裏的message
如果原因是跟你(第3天)所說,Vue實體會將選項定義的常用屬性提升到實體的最上層中。用vm.$data.message不是已經可以存取相關的屬性嗎?為何要多此一舉呢?

看更多先前的回應...收起先前的回應...

Vue 會把 data 上的物件 proxy 給 Vue 實體,所以對實體上的物件做事實際上是直接對 data 做處理。

詳細的說明可以看 API 文件。

如果想要知道如何實作的話,可以看 Vue 原碼的 state.js:335 ,這裡在 Vue 實體上設定一個 $data 物件,然後看到 dataDefget 是取用 this._data ,所以 $data 的資料是從 _data 而來。

知道 $data 是從 _data 來之後,可以看到 state.js:L147 ,這裡把 _data proxy 給了 vue 的實體(vm),最後看到 proxy 內部的實作 其實只是直接回傳 _data 而已,所以 vm.meesage === vm._data.message === vm.$data.message

應該說將 data 代理到實體是為了要讓開發者比較方便存取,在之後會介紹到的 methods 或是 computed 等等的屬性都會代理到實體上,減少一層的程式碼其實可以讓開發更方便,在 vuex 也有像是 mapState 的設計,也是為了讓開發者少寫些代碼。

謝謝大大的回覆!
/images/emoticon/emoticon41.gif
要連續30天寫文章已經不容易,我沒想清楚便來添亂,確實不應該,拍謝!
/images/emoticon/emoticon70.gif
我覺得[讓開發者可以簡化代碼,把vm.$data.message化名為vm.message]這個說法比較合理。
但是避免混淆,可以只開放vm.message給開發者使用,vm.$data.message就封包(encapsulate)在Vue物件裏面。

不會拉,我也有想過這個問題,除了剛開始學習 Vue 外,在專案中的確很少有去使用 $data 的時機。

不只 data 有代理到實體上, computedmethods 等等的物件幾乎都會代理在實體上(還有很多以 _ 開頭的私有變數),所以我覺得可能是有某些需求是要巡覽 data 的所有資料所以才會多加一個 $data 的。

Homura iT邦高手 2 級‧ 2019-03-27 15:16:47 檢舉

這問題以前學的時候也有想過
Peter Chen
感謝解答/images/emoticon/emoticon41.gif

0
yutao116
iT邦新手 5 級 ‧ 2019-01-21 13:11:55

MVVM的图,说说我的看法:

  1. Modle:data数据
  2. view:DOM模板
  3. VM:数据的绑定与监听

VM监听到M的改变之后更新V即为数据驱动,VM也能监听V的改变而更新M,为双向绑定

我要留言

立即登入留言