此小節節錄自 sunbu。
在說 MVVM 之前先介紹一下 MVC,MVC 本身也是一個經典的 web 開發架構,現在很多的開發架構就是用 MVC ,MVC 包含了前後端。
這邊 MVC 分別為 view、controller 及 model,那 view 指的就是我們看到的畫面,controller 就是控制器,model 指的就是模型,這裡也是資料介接的地方。
MVC 的架構中 controller 是做轉接的,model 是取得資料用的。
在一開始的時候會發送一個需求給 controller,比方說我們要取得資料列表的畫面,這個資料列表是網址下面帶有一個 list 的目錄下,此時使用者會對 list 本身發送一個請求說我要看這個 data 的內容,那 view 就會對 controller 發送一個需求,那在目前還沒有實際取得 data,只是發送一個請求說『我要看資料表』,此時 controller 就會對 model 說,請把資料表吐給我,那這個時候他就會去要求 model,model 得知此事後就會把 DB 裡的相關資料表取出來,比如說我要某某人的名字,那它把取出來並且把 data 返回給 controller,這個時候conroller 才會把整個網頁的內容呈現在某頁的樣板,一併回給畫面本身,這就是很基本的 MVC 架構。
那 MVVM 的概念會有點不一樣:
view 是視圖、ViewModel 是資料繫結器、model 是資料狀態,那這個 ViewModel 它是與畫面做綁定,它是綁定的狀態,它是一個連接器的感覺,在寫 vue 的時候並不會去寫 ViewModel,而是對 Model 做撰寫就可以了,那在寫 Model 的時候它在資料變動的同時它就會去控制視圖的變化。
所以我們在對某個 input 做輸入的時候 ViewModel 會幫我們把 model 的 text 做變動,相反的如果對 Model 的 text 做變更的話也會影響到 input 裡的文字。
此小節節錄自 勇者鬥 Vue 龍。
一個 Vue 實體有生老病死,而 Vue 實體會在各個生命階段提供鉤子事件( Hook Event )讓開發者可以在實體的不同階段做想要的處理,本文介紹各個 Hook 的叫用時機。
beforeCreate
: 實例初始化立即叫用,這時還未創建實例,所以任何 Vue 實體中的設定(例如: data
)都還未配置。created
: 完成創建實例,這時 Vue 實體中的配置除了 $el
外已全部配置,而 $el
要在掛載模板後才會配置。beforeMount
: 在 Vue 實體中的定義被掛載到目標元素之前叫用,這時的 $el
會是還未被 Vue 實體中的定義渲染的初始設定模板。mounted
: Vue 實體上的設置已經安裝上模板,這時的 $el
是已經藉由實體中的定義渲染而成的真正的頁面。beforeUpdate
: 當實體中的 data
產生變化後或是執行 vm.$forceUpdate() (opens new window)叫用,這時的頁面還未被重渲染為改變後的畫面。updated
: 在重新渲染頁面後叫用,這時的頁面已經被重渲染成改變後的畫面。beforeDestroy
: 在此實體被銷毀前時叫用,這時實體還是擁有完整的功能。destroyed
: 此實體被銷毀後叫用,這時實體中的任何定義( data
、 methods
…等)都已被解除綁定,代表在此做的任何操作都會失效。鉤子函式會因為引用了其他的工具(例如: vue-router (opens new window))或是 Vue 實體配置的不同(例如: keep-alive (opens new window))而有所增減。
接著我們實際來操作各個鉤子函數,然後印出 data 及 $el 看看在各階段會如何變化。
以下是我們使用的例子:
這個例子在之前的文章中也有使用,只有一個很單純 a 資料以及綁定 Vue 實體到
下面會將鉤子函數拆成四組來分析,分別是:
beforeCreate
及 created
: 創建實體。beforeMount
及 mounted
: 掛載目標元素。beforeUpdate
及 updated
: data
改變後的重渲染。beforeDestroy
及 destroyed
: 銷毀實體。beforeCreate
及 created
beforeCreate
: 在 beforeCreate
時因實體還沒創建,所以 a
跟 $el
都是 undefined
。created
: 到了 created
時已經創建實例,所以 a
已變為 1
,但是 $el
因為還未掛載至目標元素,所以依然是 undefined
。所以在
beforeCreate
是不能操作實體中的物件的。
beforeMount
及 mounted
beforeCreate
: 在 beforeCreate
時因實體還沒創建,所以 a
跟 $el
都是 undefined
。created
: 到了 created
時已經創建實例,所以 a
已變為 1
,但是 $el
因為還未掛載至目標元素,所以依然是 undefined
。所以在
beforeCreate
是不能操作實體中的物件的。
beforeMount
及 mounted
beforeMount
: 流程圖上有提到,在叫用 beforeMount
前 Vue 已經決定模板的樣式,所以在 beforeMount
中的 $el
已經有值了,只是它還未依照 Vue 實體上的定義所渲染,只是個初始設定的模板,因此可以看到 {{a}}
、 v-on
這些模板語法都還未被轉換。
mounted
: 在 mounted
被叫用時已經把 Vue 實體上的定義綁定到元素上,所以這裡看到的是經由 Vue 渲染後的配置。
所以在
beforeMount
前不能操作 DOM 元素。
beforeUpdate
及 updated
beforeUpdate
: a
改變後觸發 beforeUpdate
,可以看到 a
已經變為 2
了,可是頁面上還是 1
,表示這時雖然 data
已經改變,可是還沒有重新渲染畫面。updated
: 完成重新渲染的作業後觸發,這時可以看到畫面已經將 1
改為 2
了。
updated
時盡量避免修改data
,這樣有可能再次觸發update
造成無限循環,如果data
要連動變化可以使用後面的章節會介紹的 computed (opens new window)屬性。
beforeDestroy
及 destroyed
beforeDestroy
: 叫用 beforeDestroy
表示即將執行銷毀動作,如果有些物件要釋放資源可以在這處理。destroyed
: 叫用 destroyed
時,實體已經銷毀。由於 Vue 會將 Vue 實體綁定在
this
上,所以在 Vue 實例中只要有使用到this
的函式都不能使用箭頭函數,因箭頭函數的this
會綁定上層(父親)的內容,所以箭頭函數中的this
不會是期望的 Vue 實體。
預期:string
詳細:
更新元素的 textContent
。如果要更新部分的 textContent
,需要使用 {{ Mustache }}
插值。
示例:
預期:string
詳細:
更新元素的 innerHTML
。注意:內容按普通HTML插入-不會作為Vue模板進行編譯。如果試圖使用v-html
組合模板,可以重新考慮是否通過使用組件來替代。
在網站上動態渲染任意HTML是非常危險的,因為容易導致 XSS 攻擊。只在可信內容上使用 v-html
,永不用在用戶提交的內容上。
在單文件組件裡,scoped
的樣式不會應用在 v-html
內部,因為那部分HTML沒有被Vue的模板編譯器處理。如果你希望針對 v-html
的內容設置帶作用域的 CSS,你可以替換為 CSS Modules 或用一個額外的全局 <style>
元素手動設置類似 BEM 的作用域策略。
示例:
預期:隨表單控件類型不同而不同。
限制:
<input>
<select>
<textarea>
修飾符:
用法:
在表單控件或者組件上創建雙向綁定。
縮寫::
參數:attrOrProp (optional)
修飾符:
.prop
:作為一個 DOM property 綁定而不是作為 attribute 綁定。(差別在哪裡? ).camel
:將 kebab-case attribute 名轉換為 camelCase。.sync
:語法糖,會擴展成一個更新父組件綁定值的 v-on
偵聽器。用法:
動態地綁定一個或多個 attribute,或一個組件 prop 到表達式。
預期:Array | Object | number | string | Iterable
用法:
基於源數據多次渲染元素或模板塊。此指令之值,必須使用特定語法 alias in expression
,為當前遍歷的元素提供別名:
另外也可以為數組索引指定別名(或者用於對象的鍵):
v-for
的默認行為會嘗試原地修改元素而不是移動它們。要強制其重新排序元素,你需要用特殊 attribute key
來提供一個排序提示:
預期:any
用法:
根據表達式的值的truthiness來有條件地渲染元素。
當和
v-if
一起使用時,v-for
的優先級比v-if
更高。
縮寫:@
預期:Function | Inline Statement | Object
參數:event
修飾符:
.stop
:調用 event.stopPropagation()
。.prevent
:調用 event.preventDefault()
。.capture
:添加事件偵聽器時使用capture 模式。.self
:只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。.{keyCode | keyAlias}
: 只當事件是從特定鍵觸發時才觸發回調。.native
:監聽組件根元素的原生事件。.once
:只觸發一次回調。.left
:只當點擊鼠標左鍵時觸發。.right
:只當點擊鼠標右鍵時觸發。.middle
:只當點擊鼠標中鍵時觸發。.passive
:{ passive: true }
模式添加偵聽器用法:
綁定事件監聽器。事件類型由參數指定。表達式可以是一個方法的名字或一個內聯語句,如果沒有修飾符也可以省略。
一般寫法:
:class="computed 變數名"
物件寫法:
:class="{'class 名稱': 布林值 }"
@click="isTransform = !isTransform"
computed 用法:
computed 的值是一個物件,裡面寫入方法,方法名稱可以直接放入 {{}}
渲染於畫面上:
computed 是在監控資料更動後,重新運算結果呈現於畫面上。一般來說不會修改資料,只會回傳用於畫面呈現的資料
methods 就是互動的函式,需要觸發才會運作,會用來修改資料內容。
效能:
如果資料量大,computed 自然會比較慢,只要資料變動就會觸發,無形之中執行次數也會增加。因此在大量資料時,會建議透過 methods 減少不必要的運算喔。
Vue.component()
第一個參數為自定義的標籤名稱,第二參數是一個物件,裡面放入 data 和 template。(這裡的 data 是一個方法且回傳一個物件)
每個 <counter-component>
裡面的 counter 都是獨立的不會互相影響。
v-once
用於單次綁定,作用一次之後就不會再隨著 v-model
變動。{{ }}
雙大括號內的內容會被渲染成文字,如若想要插入 HTML 請使用 v-html
:**注意!**在網站上動態渲染任意HTML是非常危險的,因為容易導致 XSS 攻擊。只在可信內容上使用 v-html
,永不用在用戶提交的內容上。
input
標籤上動態綁定 disabled
屬性:{{ }}
雙大括號內可使用運算式:注意! 若 v-model
要寫入物件加上「點」運算子,屬性名稱不可帶有 dash -
,例如:
若屬性名稱帶有 dash -
,應使用中括號取物件值:
:style
:v-for
與其使用細節差別在於 key 的不同,陣列的 key 是索引值,物件的 key 是屬性名稱:
:key
『 Vue 正在更新使用 v-for 渲染的元素列表時,它默認使用「就地更新」的策略。如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,而是就地更新每個元素,並且確保它們在每個索引位置正確渲染。』
『為了給 Vue 一個提示,以便它能跟踪每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一 key 屬性』
filter()
方法會建立一個經指定之函式運算後,回傳由原陣列中通過該函式檢驗之元素所構成的新陣列。
match()
方法會檢驗是否包含指定字串,若無回傳 null
。
正確修改陣列中的元素方式為使用 Vue.set()
Vue.set(target, key, value)
template
標籤不會被渲染到畫面上
v-for
和 v-if
一起使用先執行 v-for
再執行 v-if
v-for
時,建議都加上 :key
,值盡量是不會重複的,例如 idv-if
與其使用細節v-if
& v-else
v-if
& v-show
的差異v-show
會將資料都渲染出來再利用 CSS display: none
的方式隱藏起來,v-if
則是連 DOM 元素都不會出現在 HTML 裡面。
watch 監控一個變數,當這個變數產生變化就執行特定事件
v-for
遍歷 option:input="checkbox"
加上 true-value & false-value{{ }}
中渲染出來的內容為數值。v-on
與頁面操作細節每個 Vue.js 的應用程式都是從 Vue 建構式 (vue constructor) 建立根實體 (root vue instance) 開始,再一個個元件 (Components) 搭建上去而來的,透過元件的方式能讓開發者將程式碼封裝而更好利用。此段落節錄自 Summer。桑莫。夏天 。
可使用全域或局部的方式註冊元件,但是不論全域註冊或是局部註冊,都必須在 Vue 實例
之前完成。
使用 Vue.component()
語法接收兩個參數,分別為「組件名稱」及「選項物件」:
全域註冊可以用在任何新創建的 Vue 根實例 ( new Vue
) 的模板中,因此全局註冊往往是不夠理想的。比如,如果你使用一個像 webpack 這樣的構建系統,全局註冊所有的組件意味著即便你已經不再使用一個組件了,它仍然會被包含在你最終的構建結果中。這造成了用戶下載的 JavaScript 的無謂的增加。
對於 components
物件中的每個 property 來說,其 property 名就是自定義元素的名字,其 property 值就是這個元件的選項物件。
字串模板
上述範例不管是全域註冊還是局部註冊都是使用字串模板的方式 :
X-template
然而,隨著專案規模的擴增,我們的 HTML 模板結構可能會變得越來越大,光是用
template
直接掛上 HTML 字串時,可能你的程式架構就會變得不是那麼好閱讀、管理,這時候,我們可以把整個 HTML 模板區塊透過<script id="xxx" type="text/x-template"> </script>
這樣的<script>
標籤來封裝我們的 HTML 模板,這種方式通常被稱為「X-Templates」。此段落節錄自 Kuro's Blog 。
官方文件的提醒:
这些可以用于模板特别大的 demo 或极小型的应用,但是其它情况下请避免使用,因为这会将模板和该组件的其它定义分离开。
載入外部的靜態檔案 (*.vue
檔案) 來做為你元件的內容
範例同樣可參考自 Kuro's Blog。
每個元件的資料狀態都是獨立的,透過 props 屬性可以將外部資料傳到內部
注意!props 大小寫命名方式,以下為官方文件說明:
HTML 中的 attribute 名是大小寫不敏感的,所以瀏覽器會把所有大寫字符解釋為小寫字符。這意味著當你使用 DOM 中的模板時,camelCase (駝峰命名法) 的 prop 名需要使用其等價的 kebab-case (短橫線分隔命名) 命名:
重申一次,如果你使用字符串模板,那麼這個限制就不存在了。
Prop 是單向的,只會從父層傳至子層,並且 Prop 的值會隨父層更動設定而改變。若要在子層做處理,可使用「計算屬性」(Computed)自動處理或一個 Local Variable 儲存值以供使用。若 Prop 的值是陣列或物件,記得使用深拷貝(Deep Copy),避免誤觸(Call by Address)的陷阱而更改了父層 Prop 的值。此段落節錄自 Summer。桑莫。夏天。
v-if
判別傳入的資料的某個屬性是否已經存在來正確渲染畫面:若要替 props 加上型別限制與預設值,則用物件寫入並加上 type
和 default
屬性:
**提醒!**若使用 :
動態寫入 props 的值為數字,那它的值就會是數值,所以上例第二個 <prop-type>
標籤的 :cash="123"
,最後渲染在畫面上時 ({{ typeof(cash) }}
) ,結果就為數值 Number。
目的:在元件內本身的 method 透過 this.$emit()
連結外部 data (Vue 實體)的 method 進而改變外部資料。
incrementCounter
,接著使用 this.$emit('increment')
傳向外層 。(觸發名稱自定義)<button-counter>
接收內部 emit 傳來的 @increment
事件,並將外部 method incrementTotal
與之綁定。
元件標籤裡面的內容完全會被模板覆蓋,所以可以使用插槽 Slot 來新增或替換元件標籤的內容。
小叮嚀 1:
<slot>
是寫在模板裡面。小叮嚀 2:
<slot>
標籤裡面可以放其他 HTML 標籤喔!
範例:
渲染結果:
渲染結果:
渲染結果:
反之:
渲染結果:
替元件標籤內的要替換的標籤新增 slot
屬性,模板內的 <slot>
新增 name
屬性進行配對。
注意!<slot>
與 <template>
標籤都不會被渲染出來。
範例:
:is
動態切換元件也可使用 v-if
寫法去判別(註解部分),但若要切換數量過多還是建議用 :is
目的:若兩個元件內容很相近,差異非常小,就可將重複的內容放入 extends。
範例:
childOne 和 childTwo 只有 template 綁定的對象不同而已
使用 extends:
**filters 的作用: 主要用來「處理格式化文字」,適合使用時機在「非資料處理,只用於畫面上的呈現」,並且可以全域使用。 **
用法:使用 |
符號代入 filter 變數。
範例:
注意 {{ data[1].cash }}
這段程式碼會無法正確渲染,因為此範例的 filter 屬性是註冊在元件內無法適用於外部,若要解決此問題可將 filter 全域註冊:
語法:vm.$set(target, key, value)
此範例點擊按鈕後,打開開發者工具觀看,資料確實已經寫入 data 裡的 item,但為什麼畫面無法正確渲染?其原因為一開始沒有被註冊到的物件不會響應式更新。要解決此問題可以一開始就將 item 屬性名稱寫入 data(註解一),或者使用 vm.$set()
語法寫入,更多詳情可參考:
mixins 與 extends 大同小異,用法也一樣,不過 mixins 是一個陣列可以放入多筆值:
目的:建立自定義指令。
語法:
在 HTML 標籤上加入自定義指令即可作用:
一個指令定義對象可以提供如下幾個鉤子函數(均為可選):
bind
:只調用一次,指令第一次綁定到元素時調用。在這裡可以進行一次性的初始化設置。inserted
:被綁定元素插入父節點時調用(僅保證父節點存在,但不一定已被插入文檔中)。update
:所在組件的VNode更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新。componentUpdated
:指令所在組件的 VNode**及其子 VNode **全部更新後調用。unbind
:只調用一次,指令與元素解綁時調用。補充:bind 和 inserted 的概念類似於 created 和 mounted。
接下來我們來看一下鉤子函數的參數(即el
、binding
、vnode
和oldVnode
)。
el
:指令所綁定的元素,可以用來直接操作 DOM。
binding
:一個對象,包含以下 property:
name
:指令名,不包括 v-
前綴。value
:指令的綁定值,例如:v-my-directive="1 + 1"
中,綁定值為 2
。oldValue
:指令綁定的前一個值,僅在 update
和 componentUpdated
鉤子中可用。無論值是否改變都可用。expression
:字符串形式的指令表達式。例如 v-my-directive="1 + 1"
中,表達式為 "1 + 1"
。arg
:傳給指令的參數,可選。例如 v-my-directive:foo
中,參數為 "foo"
。modifiers
:一個包含修飾符的對象。例如:v-my-directive.foo.bar
中,修飾符對象為 { foo: true, bar: true }
。vnode
:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。
oldVnode
:上一個虛擬節點,僅在 update
和 componentUpdated
鉤子中可用。
VUE CLI 是什麼?
不便於開發非 SPA 的網頁(此需求可用 CDN 模式)
Q&A
安裝:npm install -g vue-cli
(注意這是 2.x 版本)
使用:vue init <template-name> <project-name>
vue init webpack my-project
npm install --save axios vue-axios
package.json
中 dependencies
移除 "vue-axios",並安裝 2.x 版本:npm install --save vue-axios@2
三個重點:
main.js
router/index
.js**.vue
步驟:
先安裝:npm install vue-router --save
src 資料夾底下新增 router 資料夾,router 資料夾再新增 index.js 檔案
回到 main.js
,新增配置(註解部分):
回到 App.vue
,將模板標籤 <HelloWorld/>
改為 <view-router>
:
補充
<view-router>
與 router/index.js
的以下內容
相對應。
src/components
資料夾下新增 pages 資料夾,底下新增 page.vue
(自定義內容)
回 router/index.js
,配置:
分頁切換製作,回 App.vue
將超連結 a
標籤替換為 <routerLink>
、href
屬性改為 to="路徑名稱"
或者 :to="{ name: '路由名稱' }"
(router/index.js
的配置)
承上,
src/components/pages
底下新增三個 child 檔案,分別為 child.vue
、child2.vue
、 child3
回 page.vue
,加上 router-view
標籤:
回 router/index.js
,compontent: Page
加上 children 屬性:
回 page.vue
加上 routerLink
連結:
承上,以 child3 為例:
router/index.js
將 child3 更改為動態路由:
回 child3.vue
,this.$route
可以找到當前路由相關資訊
注意 this.$route.params.自定義名稱
需與路由 index.js
中相配對:
/page
路徑下載入兩個元件頁面,回 router/index.js
:App.vue
中配置 <router-view>
並新增 name 屬性與 router/index.js
中的 meue: Menu
配對:原本路徑:http://localhost:8080/#/page/child3
切換為 'history' mode 之後路徑不再帶有 #
字號:http://localhost:8080/page/child3
若使用 'history' 模式代表路由由後端提供,前後端必須搭配,建議不啟用此模式,使用預設模式即可。
默認值: "/"
應用的基路徑。例如,如果整個單頁應用服務在/app/
下,然後base
就應該設為"/app/"
。
類型: string
默認值: "router-link-active"
全局配置<router-link>
默認的激活的class。參考 router-link。
類型: string
默認值: "router-link-exact-active"
全局配置 <router-link>
默認的精確激活的class。可同時翻閱 router-link。
自定義方法:
更多自定義用法可參考 此篇 。