Vue 是一個建立 UI 的 progressive framework。核心 library 只處理 view layer,易於跟其他 project/library 整合。
Vue.js 是 Evan You 在 2013 的業餘專案,因在 google 工作的 AngularJS 經驗,開發了 Vue.js,後來因為 Laravel 的作者 Taylor Otwell 的推崇,使得 Vue.js 越來越知名。2015 年推出 1.0 正式版,類似 AngularJS v1,2016年10月推出 2.0版,參考 React 的 Virtual DOM 機制,將 template 改為 render function,並回傳 virtual DOM。2020/9/18 推出 3.0版,底層改用 TypeScript 重寫,提高效能。
Vue 核心只有「宣告式渲染 declarative rendering」與「元件系統 component system」,傳統的 JQuery 是「指令式渲染」,用 js 指令修改 html 元件內容,Vue 會在 JS 的資料物件狀態異動時,直接同步更新 html。
傳統的網頁是 DOM Model,但 Vue Component 合併了 html, js 與 css,每個元件有自己的 template, code,將元件組合起來就是 component tree,也可編排成為網頁。
progressive framework 的意思是,Vue 以兩個核心概念為基礎,不斷提出相關的工具,例如前端路由 Vue Router,狀態管理 Vuex,Vue-CLI 內建整合了 webpack。developer 可根據專案的需求,漸進地採用 Vue 的眾多整合工具。最基本只需要 Vue.js 核心即可。
安裝
只需要在 html 引用 js 或是 下載 相關的 js
<script src="https://unpkg.com/vue@next"></script>
但文件中提到這種引用方式是開發階段使用
如果是在nodejs 使用 Vue,index.js 可得知是透過 process.env.NODE_ENV 判斷是不是用 prod
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/vue.cjs.prod.js')
} else {
module.exports = require('./dist/vue.cjs.js')
}
一文弄懂如何在 Vue 中配置 process.env.NODE_ENV
如果要使用特定版本的 Vue.js,可改用這個方式
<!-- 開發版 -->
<script src="https://unpkg.com/vue@3.2.10/dist/vue.global.js"></script>
<!-- production -->
<script src="https://unpkg.com/vue@3.2.10/dist/vue.global.prod.js"></script>
在 nodejs 可用 npm 安裝
npm install vue@next
或使用 Vue CLI
npm install -g @vue/cli
HelloWorld
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello Vue</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
</head>
<body>
<div id="app">
{{ message }}
</div>
<div id="app2">
{{ message }}
</div>
<!-- <script src="https://unpkg.com/vue@3.2.10"></script> -->
<!-- <script src="https://unpkg.com/vue@3.2.10/dist/vue.global.js"></script> -->
<script src="https://unpkg.com/vue@3.2.10/dist/vue.global.prod.js"></script>
<script>
// Vue 3.0 with options-base style
const vm = Vue.createApp({
data() {
return {
message: 'Hello Vue 3.0!'
}
}
});
// mount
vm.mount('#app');
//////////////////////
// Vue 3.0 with Composition API Style
const { createApp, ref } = Vue;
const vm2 = createApp({
setup() {
const message = ref('Hello Vue 3.0 in app2!');
return {
message
}
}
});
// mount
vm2.mount('#app2');
</script>
</body>
</html>
如果把 script 程式寫在 head 裡面,必須用 DOMContentLoaded
event listener 判斷是否 DOM ready
const vm = Vue.createApp({
//
});
document.addEventListener("DOMContentLoaded", () =>
// DOM ready
vm.mount('#app');
});
Introduction
DeclarativeRendering
Vue 會直接以宣告方式,利用 template 語法,將資料顯示在 DOM 元件裡面
<div id="counter">
Counter: {{ counter }}
</div>
<script>
const Counter = {
data() {
return {
counter: 0
}
},
mounted() {
setInterval(() => {
this.counter++
}, 1000)
}
}
Vue.createApp(Counter).mount('#counter')
</script>
也可以處理 element attributes
<div id="bind-attribute">
<span v-bind:title="message">
Hover your mouse over me for a few seconds to see my dynamically bound
title!
</span>
</div>
<script type="text/javascript">
const AttributeBinding = {
data() {
return {
message: 'You loaded this page on ' + new Date().toLocaleString()
}
}
}
Vue.createApp(AttributeBinding).mount('#bind-attribute')
</script>
UserInput
<div id="event-handling">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>
<script type="text/javascript">
const EventHandling = {
data() {
return {
message: 'Hello Vue.js!'
}
},
methods: {
reverseMessage() {
this.message = this.message
.split('')
.reverse()
.join('')
}
}
}
Vue.createApp(EventHandling).mount('#event-handling')
</script>
<div id="two-way-binding">
<p>{{ message }}</p>
<input v-model="message" />
</div>
<script type="text/javascript">
const TwoWayBinding = {
data() {
return {
message: 'Hello Vue!'
}
}
}
Vue.createApp(TwoWayBinding).mount('#two-way-binding')
</script>
Conditionals
<!---- conditional ------>
<div id="conditional-rendering">
<span v-if="seen">Now you see me</span>
</div>
<script type="text/javascript">
const ConditionalRendering = {
data() {
return {
seen: true
}
}
}
Vue.createApp(ConditionalRendering).mount('#conditional-rendering')
</script>
<!---- List ------>
<div id="list-rendering">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
<script type="text/javascript">
const ListRendering = {
data() {
return {
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
}
}
}
Vue.createApp(ListRendering).mount('#list-rendering')
</script>
ComponentSystem
component 就是有 pre-defined option 的 instance
產生 component object 後,定義在 components option 裡面
將 component 定義為,可以接受 prop
用 v-bind 將 todo 傳入 repeated component
<!---- component ------>
<div id="todo-list-app">
<ol>
<!--
Now we provide each todo-item with the todo object
it's representing, so that its content can be dynamic.
We also need to provide each component with a "key",
which will be explained later.
-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item>
</ol>
</div>
<script type="text/javascript">
const TodoList = {
data() {
return {
groceryList: [
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
}
}
const app = Vue.createApp(TodoList)
app.component('todo-item', {
props: ['todo'],
template: `<li>{{ todo.text }}</li>`
})
app.mount('#todo-list-app')
</script>
Component 類似 W3C Web Components Spec 裡面的 Custom Elements
Application & Component Instances
產生 Application Instance
每一個 Vue Application 都必須要從 application instance 開始,要用 createApp
function
const app = Vue.createApp({
/* options */
})
app instance 是用來註冊 "globals",然後就能在 applicaiton 裡面使用 components
const app = Vue.createApp({})
app.component('SearchInput', SearchInputComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)
//也可用 chaining 語法,因為 function 都會回傳 app instance
Vue.createApp({})
.component('SearchInput', SearchInputComponent)
.directive('focus', FocusDirective)
.use(LocalePlugin)
Root Component
傳給 createApp
的 options 用來設定 root component,該 component 用在 mount application 的 rendering 的起始點。
application 必須被 mounted 到一個 DOM element。ex: <div id="app"></div>
要傳 #app
const RootComponent = {
/* options */
}
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')
mount 不會回傳 application,而是回傳 root component instance,通常會命名為 vm
(view model)。
真實的 application 會是 nested, reusable component
Root Component
└─ TodoList
├─ TodoItem
│ ├─ DeleteTodoButton
│ └─ EditTodoButton
└─ TodoListFooter
├─ ClearTodosButton
└─ TodoListStatistics
每個 component 都有自己的 component instance,所有 component instance 共享一個 application instance
Component Instance Properties
在 data()
裡面定義的 properties 會透過 component instance 外顯
const app = Vue.createApp({
data() {
return { count: 4 }
}
})
const vm = app.mount('#app')
console.log(vm.count) // => 4
有多個 component options,可被 component 的 template 使用
methods
, props
, computed
, inject
, setup
Vue 有提供以 $
開頭的 built-in properties $attrs
及 $emit
,避免跟 user-defined property name 重複
Lifecycle Hooks
每個 component instance 會經過 setup data observation, compile the template, mount to the DOM, update DOM when data changes 的處理過程。也會執行 lifecycle hooks
functions,可增加自訂的處理流程。
// created 是 instance 被產生時會被呼叫
Vue.createApp({
data() {
return { count: 1 }
},
created() {
// `this` points to the vm instance
console.log('count is: ' + this.count) // => "count is: 1"
}
})
created
, mounted
, updated
, unmounted
該 function 都可使用 this,代表 current active component instance
不要在 option peoperty 使用 ES6 的 arrow function,因為 arrow function 缺少了 this
Lifecycle Diagram
Template Syntax
使用 HTML-based template syntax,可用 html browser 直接瀏覽
Vue 會編譯 template 為 Virtual DOM render functions,加上 reactivity system。Vue 可自行判斷 minimal number of components to re-render,及 minimal amount of DOM manipulation
Interpolations
- Text
最基本是用雙括號 mustache syntax,該標籤會自動被 msg
property 的值取代
<span>Message: {{ msg }}</span>
也可以用 one-time interpolation
<span v-once>This will never change: {{ msg }}</span>
- raw html
double mustaches 將 data 以純文字的方式處理,可用 v-html
將 data 以 html 處理
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
但動態 render html 可能會遇到 XSS 問題
- Attributes
mustanches 不能用在 html attributes 裡面,要用 v-bind
<div v-bind:id="dynamicId"></div>
如果 bound value 為 null 或 undefined,則該 attribute 不會 included 到 rendered element 裡面
當 isButtonDisabled 確實有值的時候,就會出現 disabled attribute
<button v-bind:disabled="isButtonDisabled">Button</button>
- Using JS Expressions
可在 data binding 使用 js 語法,但限制只能有單一 expression
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
<!-- this is a statement, not an expression: -->
{{ var a = 1 }}
<!-- flow control won't work either, use ternary expressions -->
{{ if (ok) { return message } }}
Directives
directives 是以 v-
開頭的特殊 attributes,除了 v-for
v-on
以外,都是一個單一 js expression。directive 可 reactively 在該 expression 改變時,對 DOM 套用變化。
ex: v-if
可動態根據 seen 決定要 remove/insert <p>
<p v-if="seen">Now you see me</p>
- Arguments
directive 可以在 name 後面加上 : 參數
ex: href 是 element 的 href attribute
<a v-bind:href="url"> ... </a>
ex: listens to DOM event
<a v-on:click="doSomething"> ... </a>
- Dynamic Arguments
可在 directive argument 使用 js expression,但要用 square bracket []
ex: [attributeName]
會用 js expression 運算一次,如果 component instance 有 data property: attributeName
,值為 href
,會讓以下程式跟 v-bind:href
一樣
<a v-bind:[attributeName]="url"> ... </a>
ex: 如果有 eventName
的值為 focus
,v-on:[eventName]
就等同 v-on:focus
<a v-on:[eventName]="doSomething"> ... </a>
- Modifiers
modifier 是 dot .
後面的 postfix,代表 directive 應該 bound in some special way
ex: .prevent
modifier 讓 v-on
directive 在 triggered event 呼叫 event.preventDefault()
<form v-on:submit.prevent="onSubmit">...</form>
Shorthands
v-
prefix 用來識別 vue 專用的 attributes,當使用 vue 開發 single-page application 時,v-
變得不重要了,因此 vue 提供特殊的縮寫
- v-bind shorthand
<!-- full syntax -->
<a v-bind:href="url"> ... </a>
<!-- shorthand -->
<a :href="url"> ... </a>
<!-- shorthand with dynamic argument -->
<a :[key]="url"> ... </a>
- v-on shorthand
<!-- full syntax -->
<a v-on:click="doSomething"> ... </a>
<!-- shorthand -->
<a @click="doSomething"> ... </a>
<!-- shorthand with dynamic argument -->
<a @[event]="doSomething"> ... </a>
- caveats
dynamic argument expression 有語法限制,因為某些特殊的字元 ex: spaces, quotes,不能用在 html attribute names
ex:
<!-- This will trigger a compiler warning. -->
<a v-bind:['foo' + bar]="value"> ... </a>
建議在這種狀況,改用 computed property
在使用 in-DOM template 時,要避免命名為大寫字元,因為 browser 會自動把 attribute name 轉成小寫字元
<!--
This will be converted to v-bind:[someattr] in in-DOM templates.
Unless you have a "someattr" property in your instance, your code won't work.
-->
<a v-bind:[someAttr]="value"> ... </a>
JS expression 只能使用 globalsWhitelist.ts 列出的 global function,無法使用 user 自訂的 global fuctions
import { makeMap } from './makeMap'
const GLOBALS_WHITE_LISTED =
'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' +
'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' +
'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt'
export const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED)
Data Properties and Methods
Data Properties
component 的 data
option 是 function,Vue 會在產生 new component instance 時,呼叫這個 function。該 function 會回傳一個 物件,然後 vue 會儲存到 $data
const app = Vue.createApp({
data() {
return { count: 4 }
}
})
const vm = app.mount('#app')
console.log(vm.$data.count) // => 4
console.log(vm.count) // => 4
// Assigning a value to vm.count will also update $data.count
vm.count = 5
console.log(vm.$data.count) // => 5
// ... and vice-versa
vm.$data.count = 6
console.log(vm.count) // => 6
這些 instance properties 只會在產生 instance 時,同時被加入。因此要確認所有資料都有在 data
function 裡面回傳的物件中。必要時,要用 null, undefined 或其他值,先定義 property
後續還是可以增加 component instance 的 property,不放在 data
裡面,但無法自動被 vue reactivity system 處理
vue 使用 $
prefix 為 built-in API 的 prefix,並使用 _
prefix 為 internal properties,要避免在 data
裡面使用這兩個字元
Methods
使用 methods
option 增加 component instance 的 methods
const app = Vue.createApp({
data() {
return { count: 4 }
},
methods: {
increment() {
// `this` will refer to the component instance
this.count++
}
}
})
const vm = app.mount('#app')
console.log(vm.count) // => 4
vm.increment()
console.log(vm.count) // => 5
vue 會自動 bind this
為 component instance,要避免在 method 使用 arrow function,因為 arrow function 沒有 this
methods 可在 component 的 template 呼叫
ex: 按下按鈕時,呼叫 increment
<button @click="increment">Up vote</button>
可直接在 template 呼叫 method,這樣寫比使用 computed property 好
ex: 可在支援 js expression 的地方,呼叫 method toTitleDate, formateDate
<span :title="toTitleDate(date)">
{{ formatDate(date) }}
</span>
Debouncing and Throttling
vue 不內建支援 debouncing or throttling,但可使用其他 library ex: Lodash
<script src="https://unpkg.com/lodash@4.17.20/lodash.min.js"></script>
<script>
Vue.createApp({
methods: {
// Debouncing with Lodash
click: _.debounce(function() {
// ... respond to click ...
}, 500)
}
}).mount('#app')
</script>
但這樣寫會讓所有 component 共用 debounced function,為了讓 component 各自獨立,可在 created
lifecycle hook 加上 debounced function
app.component('save-button', {
created() {
// Debouncing with Lodash
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// Cancel the timer when the component is removed
this.debouncedClick.cancel()
},
methods: {
click() {
// ... respond to click ...
}
},
template: `
<button @click="debouncedClick">
Save
</button>
`
})
ref: Debounce & Throttle — 那些前端開發應該要知道的小事(一)
沒有留言:
張貼留言