# vue官方教程
# 安裝
### [](https://cn.vuejs.org/v2/guide/installation.html#%E5%85%BC%E5%AE%B9%E6%80%A7 "兼容性")兼容性
Vue?不支持?IE8 及以下版本,因為 Vue 使用了 IE8 無法模擬的 ECMAScript 5 特性。但它支持所有[兼容 ECMAScript 5 的瀏覽器](https://caniuse.com/#feat=es5)。
### [](https://cn.vuejs.org/v2/guide/installation.html#%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97 "更新日志")更新日志
最新穩定版本:2.5.17
每個版本的更新日志見?[GitHub](https://github.com/vuejs/vue/releases)。
## [Vue Devtools](https://cn.vuejs.org/v2/guide/installation.html#Vue-Devtools "Vue Devtools")
在使用 Vue 時,我們推薦在你的瀏覽器上安裝?[Vue Devtools](https://github.com/vuejs/vue-devtools#vue-devtools)。它允許你在一個更友好的界面中審查和調試 Vue 應用。
## [直接用?`<script>`?引入](https://cn.vuejs.org/v2/guide/installation.html#%E7%9B%B4%E6%8E%A5%E7%94%A8-lt-script-gt-%E5%BC%95%E5%85%A5 "直接用 引入")
直接下載并用?`<script>`?標簽引入,`Vue`?會被注冊為一個全局變量。
在開發環境下不要使用壓縮版本,不然你就失去了所有常見錯誤相關的警告!
[開發版本](https://vuejs.org/js/vue.js)包含完整的警告和調試模式
[生產版本](https://vuejs.org/js/vue.min.js)刪除了警告,30.90KB min+gzip
### [CDN](https://cn.vuejs.org/v2/guide/installation.html#CDN "CDN")
我們推薦鏈接到一個你可以手動更新的指定版本號:
|
script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js">script>
|
你可以在?[cdn.jsdelivr.net/npm/vue](https://cdn.jsdelivr.net/npm/vue/)?瀏覽 NPM 包的源代碼。
Vue 也可以在?[unpkg](https://unpkg.com/vue@2.5.17/dist/vue.js)?和?[cdnjs](https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js)?上獲取 (cdnjs 的版本更新可能略滯后)。
請確認了解[不同構建版本](https://cn.vuejs.org/v2/guide/installation.html#%E5%AF%B9%E4%B8%8D%E5%90%8C%E6%9E%84%E5%BB%BA%E7%89%88%E6%9C%AC%E7%9A%84%E8%A7%A3%E9%87%8A)并在你發布的站點中使用生產環境版本,把?`vue.js`?換成?`vue.min.js`。這是一個更小的構建,可以帶來比開發環境下更快的速度體驗。
## [NPM](https://cn.vuejs.org/v2/guide/installation.html#NPM "NPM")
在用 Vue 構建大型應用時推薦使用 NPM 安裝[[1]](https://cn.vuejs.org/v2/guide/installation.html#footnote-1)。NPM 能很好地和諸如?[webpack](https://webpack.js.org/)?或?[Browserify](http://browserify.org/)?模塊打包器配合使用。同時 Vue 也提供配套工具來開發[單文件組件](https://cn.vuejs.org/v2/guide/single-file-components.html)。
|
# 最新穩定版
$ npm install vue
|
## [命令行工具 (CLI)](https://cn.vuejs.org/v2/guide/installation.html#%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7-CLI "命令行工具 (CLI)")
Vue 提供了一個[官方的 CLI](https://github.com/vuejs/vue-cli),為單頁面應用 (SPA) 快速搭建繁雜的腳手架。它為現代前端工作流提供了 batteries-included 的構建設置。只需要幾分鐘的時間就可以運行起來并帶有熱重載、保存時 lint 校驗,以及生產環境可用的構建版本。更多詳情可查閱?[Vue CLI 的文檔](https://cli.vuejs.org/)。
CLI 工具假定用戶對 Node.js 和相關構建工具有一定程度的了解。如果你是新手,我們強烈建議先在不用構建工具的情況下通讀[指南](https://cn.vuejs.org/v2/guide/),在熟悉 Vue 本身之后再使用 CLI。
## [對不同構建版本的解釋](https://cn.vuejs.org/v2/guide/installation.html#%E5%AF%B9%E4%B8%8D%E5%90%8C%E6%9E%84%E5%BB%BA%E7%89%88%E6%9C%AC%E7%9A%84%E8%A7%A3%E9%87%8A "對不同構建版本的解釋")
在?[NPM 包的?`dist/`?目錄](https://cdn.jsdelivr.net/npm/vue/dist/)你將會找到很多不同的 Vue.js 構建版本。這里列出了它們之間的差別:
| | UMD | CommonJS | ES Module |
| --- | --- | --- | --- |
| 完整版 | vue.js | vue.common.js | vue.esm.js |
| 只包含運行時版 | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js |
| 完整版 (生產環境) | vue.min.js | - | - |
| 只包含運行時版 (生產環境) | vue.runtime.min.js | - | - |
### [術語](https://cn.vuejs.org/v2/guide/installation.html#%E6%9C%AF%E8%AF%AD "術語")
* 完整版:同時包含編譯器和運行時的版本。
* 編譯器:用來將模板字符串編譯成為 JavaScript 渲染函數的代碼。
* 運行時:用來創建 Vue 實例、渲染并處理虛擬 DOM 等的代碼。基本上就是除去編譯器的其它一切。
* [UMD](https://github.com/umdjs/umd):UMD 版本可以通過?`<script>`?標簽直接用在瀏覽器中。jsDelivr CDN 的?[https://cdn.jsdelivr.net/npm/vue](https://cdn.jsdelivr.net/npm/vue)?默認文件就是運行時 + 編譯器的 UMD 版本 (`vue.js`)。
* [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1):CommonJS 版本用來配合老的打包工具比如?[Browserify](http://browserify.org/)?或?[webpack 1](https://webpack.github.io/)。這些打包工具的默認文件 (`pkg.main`) 是只包含運行時的 CommonJS 版本 (`vue.runtime.common.js`)。
* [ES Module](http://exploringjs.com/es6/ch_modules.html):ES module 版本用來配合現代打包工具比如?[webpack 2](https://webpack.js.org/)?或?[Rollup](https://rollupjs.org/)。這些打包工具的默認文件 (`pkg.module`) 是只包含運行時的 ES Module 版本 (`vue.runtime.esm.js`)。
### [運行時 + 編譯器 vs. 只包含運行時](https://cn.vuejs.org/v2/guide/installation.html#%E8%BF%90%E8%A1%8C%E6%97%B6-%E7%BC%96%E8%AF%91%E5%99%A8-vs-%E5%8F%AA%E5%8C%85%E5%90%AB%E8%BF%90%E8%A1%8C%E6%97%B6 "運行時 + 編譯器 vs. 只包含運行時")
如果你需要在客戶端編譯模板 (比如傳入一個字符串給?`template`?選項,或掛載到一個元素上并以其 DOM 內部的 HTML 作為模板),就將需要加上編譯器,即完整版:
|
// 需要編譯器
new Vue({
template: '{{ hi }}'
})
// 不需要編譯器
new Vue({
render (h) {
return h('div', this.hi)
}
})
|
當使用?`vue-loader`?或?`vueify`?的時候,`*.vue`?文件內部的模板會在構建時預編譯成 JavaScript。你在最終打好的包里實際上是不需要編譯器的,所以只用運行時版本即可。
因為運行時版本相比完整版體積要小大約 30%,所以應該盡可能使用這個版本。如果你仍然希望使用完整版,則需要在打包工具里配置一個別名:
#### [](https://cn.vuejs.org/v2/guide/installation.html#webpack "webpack")webpack
|
module.exports = {
// ...
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 用 webpack 1 時需用 'vue/dist/vue.common.js'
}
}
}
|
#### [](https://cn.vuejs.org/v2/guide/installation.html#Rollup "Rollup")Rollup
|
const alias = require('rollup-plugin-alias')
rollup({
// ...
plugins: [
alias({
'vue': 'vue/dist/vue.esm.js'
})
]
})
|
#### [](https://cn.vuejs.org/v2/guide/installation.html#Browserify "Browserify")Browserify
添加到你項目的?`package.json`:
|
{
// ...
"browser": {
"vue": "vue/dist/vue.common.js"
}
}
|
#### [](https://cn.vuejs.org/v2/guide/installation.html#Parcel "Parcel")Parcel
在你項目的?`package.json`?中添加:
|
{
// ...
"alias": {
"vue" : "./node_modules/vue/dist/vue.common.js"
}
}
|
### [開發環境 vs. 生產環境模式](https://cn.vuejs.org/v2/guide/installation.html#%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83-vs-%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E6%A8%A1%E5%BC%8F "開發環境 vs. 生產環境模式")
對于 UMD 版本來說,開發環境/生產環境模式是硬編碼好的:開發環境下用未壓縮的代碼,生產環境下使用壓縮后的代碼。
CommonJS 和 ES Module 版本是用于打包工具的,因此我們不提供壓縮后的版本。你需要自行將最終的包進行壓縮。
CommonJS 和 ES Module 版本同時保留原始的?`process.env.NODE_ENV`?檢測,以決定它們應該運行在什么模式下。你應該使用適當的打包工具配置來替換這些環境變量以便控制 Vue 所運行的模式。把?`process.env.NODE_ENV`?替換為字符串字面量同時可以讓 UglifyJS 之類的壓縮工具完全丟掉僅供開發環境的代碼塊,以減少最終的文件尺寸。
#### [](https://cn.vuejs.org/v2/guide/installation.html#webpack-1 "webpack")webpack
在 webpack 4+ 中,你可以使用?`mode`?選項:
|
module.exports = {
mode: 'production'
}
|
但是在 webpack 3 及其更低版本中,你需要使用?[DefinePlugin](https://webpack.js.org/plugins/define-plugin/):
|
var webpack = require('webpack')
module.exports = {
// ...
plugins: [
// ...
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
})
]
}
|
#### [](https://cn.vuejs.org/v2/guide/installation.html#Rollup-1 "Rollup")Rollup
使用?[rollup-plugin-replace](https://github.com/rollup/rollup-plugin-replace):
|
const replace = require('rollup-plugin-replace')
rollup({
// ...
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
}).then(...)
|
#### [](https://cn.vuejs.org/v2/guide/installation.html#Browserify-1 "Browserify")Browserify
為你的包應用一次全局的?[envify](https://github.com/hughsk/envify)?轉換。
|
NODE_ENV=production browserify -g envify -e main.js | uglifyjs -c -m > build.js
|
也可以移步[生產環境部署](https://cn.vuejs.org/v2/guide/deployment.html)。
### [CSP 環境](https://cn.vuejs.org/v2/guide/installation.html#CSP-%E7%8E%AF%E5%A2%83 "CSP 環境")
有些環境,如 Google Chrome Apps,會強制應用內容安全策略 (CSP),不能使用?`new Function()`?對表達式求值。這時可以用 CSP 兼容版本。完整版本依賴于該功能來編譯模板,所以無法在這些環境下使用。
另一方面,運行時版本則是完全兼容 CSP 的。當通過?[webpack + vue-loader](https://github.com/vuejs-templates/webpack-simple)?或者?[Browserify + vueify](https://github.com/vuejs-templates/browserify-simple)?構建時,模板將被預編譯為?`render`?函數,可以在 CSP 環境中完美運行。
## [開發版本](https://cn.vuejs.org/v2/guide/installation.html#%E5%BC%80%E5%8F%91%E7%89%88%E6%9C%AC "開發版本")
重要: GitHub 倉庫的?`/dist`?文件夾只有在新版本發布時才會提交。如果想要使用 GitHub 上 Vue 最新的源碼,你需要自己構建!
|
git clone https://github.com/vuejs/vue.git node_modules/vue
cd node_modules/vue
npm install
npm run build
|
## [Bower](https://cn.vuejs.org/v2/guide/installation.html#Bower "Bower")
Bower 只提供 UMD 版本。
|
# 最新穩定版本
$ bower install vue
|
## [AMD 模塊加載器](https://cn.vuejs.org/v2/guide/installation.html#AMD-%E6%A8%A1%E5%9D%97%E5%8A%A0%E8%BD%BD%E5%99%A8 "AMD 模塊加載器")
所有 UMD 版本都可以直接用作 AMD 模塊。
譯者注
[1] 對于中國大陸用戶,建議將 NPM 源設置為[國內的鏡像](https://npm.taobao.org/),可以大幅提升安裝速度。
# 介紹
## [Vue.js 是什么](https://cn.vuejs.org/v2/guide/index.html#Vue-js-%E6%98%AF%E4%BB%80%E4%B9%88 "Vue.js 是什么")
Vue (讀音 /vju?/,類似于?view) 是一套用于構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。Vue 的核心庫只關注視圖層,不僅易于上手,還便于與第三方庫或既有項目整合。另一方面,當與[現代化的工具鏈](https://cn.vuejs.org/v2/guide/single-file-components.html)以及各種[支持類庫](https://github.com/vuejs/awesome-vue#libraries--plugins)結合使用時,Vue 也完全能夠為復雜的單頁應用提供驅動。
如果你想在深入學習 Vue 之前對它有更多了解,我們[制作了一個視頻](https://cn.vuejs.org/v2/guide/index.html#),帶您了解其核心概念和一個示例工程。
如果你已經是有經驗的前端開發者,想知道 Vue 與其它庫/框架有哪些區別,請查看[對比其它框架](https://cn.vuejs.org/v2/guide/comparison.html)。
## [起步](https://cn.vuejs.org/v2/guide/index.html#%E8%B5%B7%E6%AD%A5 "起步")
官方指南假設你已了解關于 HTML、CSS 和 JavaScript 的中級知識。如果你剛開始學習前端開發,將框架作為你的第一步可能不是最好的主意——掌握好基礎知識再來吧!之前有其它框架的使用經驗會有幫助,但這不是必需的。
嘗試 Vue.js 最簡單的方法是使用?[JSFiddle 上的 Hello World 例子](https://jsfiddle.net/chrisvfritz/50wL7mdz/)。你可以在瀏覽器新標簽頁中打開它,跟著例子學習一些基礎用法。或者你也可以[創建一個?`.html`?文件](https://gist.githubusercontent.com/chrisvfritz/7f8d7d63000b48493c336e48b3db3e52/raw/ed60c4e5d5c6fec48b0921edaed0cb60be30e87c/index.html),然后通過如下方式引入 Vue:
|
script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
|
或者:
|
script src="https://cdn.jsdelivr.net/npm/vue">script>
|
[安裝教程](https://cn.vuejs.org/guide/installation.html)給出了更多安裝 Vue 的方式。請注意我們不推薦新手直接使用?`vue-cli`,尤其是在你還不熟悉基于 Node.js 的構建工具時。
如果你喜歡交互式的東西,你也可以查閱[這個 Scrimba 上的系列教程](https://scrimba.com/playlist/pXKqta),它揉合了錄屏和代碼試驗田,并允許你隨時暫停和播放。
## [聲明式渲染](https://cn.vuejs.org/v2/guide/index.html#%E5%A3%B0%E6%98%8E%E5%BC%8F%E6%B8%B2%E6%9F%93 "聲明式渲染")
[在 Scrimba 上嘗試這節課](https://scrimba.com/p/pXKqta/cQ3QVcr)
Vue.js 的核心是一個允許采用簡潔的模板語法來聲明式地將數據渲染進 DOM 的系統:
|
div id="app">
{{ message }}
div>
|
|
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
|
Hello Vue!
我們已經成功創建了第一個 Vue 應用!看起來這跟渲染一個字符串模板非常類似,但是 Vue 在背后做了大量工作。現在數據和 DOM 已經被建立了關聯,所有東西都是響應式的。我們要怎么確認呢?打開你的瀏覽器的 JavaScript 控制臺 (就在這個頁面打開),并修改?`app.message`?的值,你將看到上例相應地更新。
除了文本插值,我們還可以像這樣來綁定元素特性:
|
div id="app-2">
span v-bind:title="message">
鼠標懸停幾秒鐘查看此處動態綁定的提示信息!
span>
div>
|
|
var app2 = new Vue({
el: '#app-2',
data: {
message: '頁面加載于 ' + new Date().toLocaleString()
}
})
|
鼠標懸停幾秒鐘查看此處動態綁定的提示信息!
這里我們遇到了一點新東西。你看到的?`v-bind`?特性被稱為指令。指令帶有前綴?`v-`,以表示它們是 Vue 提供的特殊特性。可能你已經猜到了,它們會在渲染的 DOM 上應用特殊的響應式行為。在這里,該指令的意思是:“將這個元素節點的?`title`?特性和 Vue 實例的?`message`?屬性保持一致”。
如果你再次打開瀏覽器的 JavaScript 控制臺,輸入?`app2.message = '新消息'`,就會再一次看到這個綁定了?`title`?特性的 HTML 已經進行了更新。
## [條件與循環](https://cn.vuejs.org/v2/guide/index.html#%E6%9D%A1%E4%BB%B6%E4%B8%8E%E5%BE%AA%E7%8E%AF "條件與循環")
[在 Scrimba 上嘗試這節課](https://scrimba.com/p/pXKqta/cEQe4SJ)
控制切換一個元素是否顯示也相當簡單:
|
div id="app-3">
p v-if="seen">現在你看到我了p>
div>
|
|
var app3 = new Vue({
el: '#app-3',
data: {
seen: true
}
})
|
現在你看到我了
繼續在控制臺輸入?`app3.seen = false`,你會發現之前顯示的消息消失了。
這個例子演示了我們不僅可以把數據綁定到 DOM 文本或特性,還可以綁定到 DOM?結構。此外,Vue 也提供一個強大的過渡效果系統,可以在 Vue 插入/更新/移除元素時自動應用[過渡效果](https://cn.vuejs.org/v2/guide/transitions.html)。
還有其它很多指令,每個都有特殊的功能。例如,`v-for`?指令可以綁定數組的數據來渲染一個項目列表:
|
div id="app-4">
ol>
li v-for="todo in todos">
{{ todo.text }}
li>
ol>
div>
|
|
var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: '學習 JavaScript' },
{ text: '學習 Vue' },
{ text: '整個牛項目' }
]
}
})
|
1. 學習 JavaScript
2. 學習 Vue
3. 整個牛項目
在控制臺里,輸入?`app4.todos.push({ text: '新項目' })`,你會發現列表最后添加了一個新項目。
## [處理用戶輸入](https://cn.vuejs.org/v2/guide/index.html#%E5%A4%84%E7%90%86%E7%94%A8%E6%88%B7%E8%BE%93%E5%85%A5 "處理用戶輸入")
[在 Scrimba 上嘗試這節課](https://scrimba.com/p/pXKqta/czPNaUr)
為了讓用戶和你的應用進行交互,我們可以用?`v-on`?指令添加一個事件監聽器,通過它調用在 Vue 實例中定義的方法:
|
div id="app-5">
p>{{ message }}p>
button v-on:click="reverseMessage">逆轉消息button>
div>
|
|
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
|
Hello Vue.js!
逆轉消息
注意在?`reverseMessage`?方法中,我們更新了應用的狀態,但沒有觸碰 DOM——所有的 DOM 操作都由 Vue 來處理,你編寫的代碼只需要關注邏輯層面即可。
Vue 還提供了?`v-model`?指令,它能輕松實現表單輸入和應用狀態之間的雙向綁定。
|
div id="app-6">
p>{{ message }}p>
input v-model="message">
div>
|
|
var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
})
|
Hello Vue!
## [組件化應用構建](https://cn.vuejs.org/v2/guide/index.html#%E7%BB%84%E4%BB%B6%E5%8C%96%E5%BA%94%E7%94%A8%E6%9E%84%E5%BB%BA "組件化應用構建")
[在 Scrimba 上嘗試這節課](https://scrimba.com/p/pXKqta/cEQVkA3)
組件系統是 Vue 的另一個重要概念,因為它是一種抽象,允許我們使用小型、獨立和通常可復用的組件構建大型應用。仔細想想,幾乎任意類型的應用界面都可以抽象為一個組件樹:

在 Vue 里,一個組件本質上是一個擁有預定義選項的一個 Vue 實例。在 Vue 中注冊組件很簡單:
|
// 定義名為 todo-item 的新組件
Vue.component('todo-item', {
template: '這是個待辦項'
})
|
現在你可以用它構建另一個組件模板:
|
ol>
todo-item>todo-item>
ol>
|
但是這樣會為每個待辦項渲染同樣的文本,這看起來并不炫酷。我們應該能從父作用域將數據傳到子組件才對。讓我們來修改一下組件的定義,使之能夠接受一個?[prop](https://cn.vuejs.org/v2/guide/components.html#Props):
|
Vue.component('todo-item', {
// todo-item 組件現在接受一個
// "prop",類似于一個自定義特性。
// 這個 prop 名為 todo。
props: ['todo'],
template: '{{ todo.text }}'
})
|
現在,我們可以使用?`v-bind`?指令將待辦項傳到循環輸出的每個組件中:
|
div id="app-7">
ol>
現在我們為每個 todo-item 提供 todo 對象
todo 對象是變量,即其內容可以是動態的。
我們也需要為每個組件提供一個“key”,稍后再
作詳細解釋。
-->
todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id">
todo-item>
ol>
div>
|
|
Vue.component('todo-item', {
props: ['todo'],
template: '{{ todo.text }}'
})
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '隨便其它什么人吃的東西' }
]
}
})
|
1. 蔬菜
2. 奶酪
3. 隨便其它什么人吃的東西
盡管這只是一個刻意設計的例子,但是我們已經設法將應用分割成了兩個更小的單元。子單元通過 prop 接口與父單元進行了良好的解耦。我們現在可以進一步改進?`<todo-item>`?組件,提供更為復雜的模板和邏輯,而不會影響到父單元。
在一個大型應用中,有必要將整個應用程序劃分為組件,以使開發更易管理。在[后續教程](https://cn.vuejs.org/v2/guide/components.html)中我們將詳述組件,不過這里有一個 (假想的) 例子,以展示使用了組件的應用模板是什么樣的:
|
div id="app">
app-nav>app-nav>
app-view>
app-sidebar>app-sidebar>
app-content>app-content>
app-view>
div>
|
### [與自定義元素的關系](https://cn.vuejs.org/v2/guide/index.html#%E4%B8%8E%E8%87%AA%E5%AE%9A%E4%B9%89%E5%85%83%E7%B4%A0%E7%9A%84%E5%85%B3%E7%B3%BB "與自定義元素的關系")
你可能已經注意到 Vue 組件非常類似于自定義元素——它是?[Web 組件規范](https://www.w3.org/wiki/WebComponents/)的一部分,這是因為 Vue 的組件語法部分參考了該規范。例如 Vue 組件實現了?[Slot API](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md)?與?`is`?特性。但是,還是有幾個關鍵差別:
1. Web Components 規范已經完成并通過,但未被所有瀏覽器原生實現。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 組件不需要任何 polyfill,并且在所有支持的瀏覽器 (IE9 及更高版本) 之下表現一致。必要時,Vue 組件也可以包裝于原生自定義元素之內。
2. Vue 組件提供了純自定義元素所不具備的一些重要功能,最突出的是跨組件數據流、自定義事件通信以及構建工具集成。
## [準備好了嗎?](https://cn.vuejs.org/v2/guide/index.html#%E5%87%86%E5%A4%87%E5%A5%BD%E4%BA%86%E5%90%97%EF%BC%9F "準備好了嗎?")
我們剛才簡單介紹了 Vue 核心最基本的功能——本教程的其余部分將涵蓋這些功能以及其它高級功能更詳細的細節,所以請務必讀完整個教程!
# Vue 實例
## [創建一個 Vue 實例](https://cn.vuejs.org/v2/guide/instance.html#%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA-Vue-%E5%AE%9E%E4%BE%8B "創建一個 Vue 實例")
每個 Vue 應用都是通過用?`Vue`?函數創建一個新的?Vue 實例開始的:
|
var vm = new Vue({
// 選項
})
|
雖然沒有完全遵循?[MVVM 模型](https://zh.wikipedia.org/wiki/MVVM),但是 Vue 的設計也受到了它的啟發。因此在文檔中經常會使用?`vm`?(ViewModel 的縮寫) 這個變量名表示 Vue 實例。
當創建一個 Vue 實例時,你可以傳入一個選項對象。這篇教程主要描述的就是如何使用這些選項來創建你想要的行為。作為參考,你也可以在?[API 文檔](https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E6%95%B0%E6%8D%AE)?中瀏覽完整的選項列表。
一個 Vue 應用由一個通過?`new Vue`?創建的根 Vue 實例,以及可選的嵌套的、可復用的組件樹組成。舉個例子,一個 todo 應用的組件樹可以是這樣的:
|
根實例
└─ TodoList
├─ TodoItem
│ ├─ DeleteTodoButton
│ └─ EditTodoButton
└─ TodoListFooter
├─ ClearTodosButton
└─ TodoListStatistics
|
我們會在稍后的[組件系統](https://cn.vuejs.org/v2/guide/components.html)章節具體展開。不過現在,你只需要明白所有的 Vue 組件都是 Vue 實例,并且接受相同的選項對象 (一些根實例特有的選項除外)。
## [數據與方法](https://cn.vuejs.org/v2/guide/instance.html#%E6%95%B0%E6%8D%AE%E4%B8%8E%E6%96%B9%E6%B3%95 "數據與方法")
當一個 Vue 實例被創建時,它向 Vue 的響應式系統中加入了其?`data`?對象中能找到的所有的屬性。當這些屬性的值發生改變時,視圖將會產生“響應”,即匹配更新為新的值。
|
// 我們的數據對象
var data = { a: 1 }
// 該對象被加入到一個 Vue 實例中
var vm = new Vue({
data: data
})
// 獲得這個實例上的屬性
// 返回源數據中對應的字段
vm.a == data.a // => true
// 設置屬性也會影響到原始數據
vm.a = 2
data.a // => 2
// ……反之亦然
data.a = 3
vm.a // => 3
|
當這些數據改變時,視圖會進行重渲染。值得注意的是只有當實例被創建時?`data`?中存在的屬性才是響應式的。也就是說如果你添加一個新的屬性,比如:
|
vm.b = 'hi'
|
那么對?`b`?的改動將不會觸發任何視圖的更新。如果你知道你會在晚些時候需要一個屬性,但是一開始它為空或不存在,那么你僅需要設置一些初始值。比如:
|
data: {
newTodoText: '',
visitCount: 0,
hideCompletedTodos: false,
todos: [],
error: null
}
|
這里唯一的例外是使用?`Object.freeze()`,這會阻止修改現有的屬性,也意味著響應系統無法再*追蹤*變化。
|
var obj = {
foo: 'bar'
}
Object.freeze(obj)
new Vue({
el: '#app',
data: obj
})
|
|
div id="app">
p>{{ foo }}p>
button v-on:click="foo = 'baz'">Change itbutton>
div>
|
除了數據屬性,Vue 實例還暴露了一些有用的實例屬性與方法。它們都有前綴?`$`,以便與用戶定義的屬性區分開來。例如:
|
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
// $watch 是一個實例方法
vm.$watch('a', function (newValue, oldValue) {
// 這個回調將在 `vm.a` 改變后調用
})
|
以后你可以在?[API 參考](https://cn.vuejs.org/v2/api/#%E5%AE%9E%E4%BE%8B%E5%B1%9E%E6%80%A7)中查閱到完整的實例屬性和方法的列表。
## [實例生命周期鉤子](https://cn.vuejs.org/v2/guide/instance.html#%E5%AE%9E%E4%BE%8B%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90 "實例生命周期鉤子")
每個 Vue 實例在被創建時都要經過一系列的初始化過程——例如,需要設置數據監聽、編譯模板、將實例掛載到 DOM 并在數據變化時更新 DOM 等。同時在這個過程中也會運行一些叫做生命周期鉤子的函數,這給了用戶在不同階段添加自己的代碼的機會。
比如?[`created`](https://cn.vuejs.org/v2/api/#created)?鉤子可以用來在一個實例被創建之后執行代碼:
|
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 實例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
|
也有一些其它的鉤子,在實例生命周期的不同階段被調用,如?[`mounted`](https://cn.vuejs.org/v2/api/#mounted)、[`updated`](https://cn.vuejs.org/v2/api/#updated)和?[`destroyed`](https://cn.vuejs.org/v2/api/#destroyed)。生命周期鉤子的?`this`?上下文指向調用它的 Vue 實例。
不要在選項屬性或回調上使用[箭頭函數](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions),比如?`created: () => console.log(this.a)`?或?`vm.$watch('a', newValue => this.myMethod())`。因為箭頭函數是和父級上下文綁定在一起的,`this`?不會是如你所預期的 Vue 實例,經常導致?`Uncaught TypeError: Cannot read property of undefined`?或?`Uncaught TypeError: this.myMethod is not a function`?之類的錯誤。
## [生命周期圖示](https://cn.vuejs.org/v2/guide/instance.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9B%BE%E7%A4%BA "生命周期圖示")
下圖展示了實例的生命周期。你不需要立馬弄明白所有的東西,不過隨著你的不斷學習和使用,它的參考價值會越來越高。

# 模板語法
Vue.js 使用了基于 HTML 的模板語法,允許開發者聲明式地將 DOM 綁定至底層 Vue 實例的數據。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循規范的瀏覽器和 HTML 解析器解析。
在底層的實現上,Vue 將模板編譯成虛擬 DOM 渲染函數。結合響應系統,Vue 能夠智能地計算出最少需要重新渲染多少組件,并把 DOM 操作次數減到最少。
如果你熟悉虛擬 DOM 并且偏愛 JavaScript 的原始力量,你也可以不用模板,[直接寫渲染 (render) 函數](https://cn.vuejs.org/v2/guide/render-function.html),使用可選的 JSX 語法。
## [插值](https://cn.vuejs.org/v2/guide/syntax.html#%E6%8F%92%E5%80%BC "插值")
### [文本](https://cn.vuejs.org/v2/guide/syntax.html#%E6%96%87%E6%9C%AC "文本")
數據綁定最常見的形式就是使用“Mustache”語法 (雙大括號) 的文本插值:
|
span>Message: {{ msg }}span>
|
Mustache 標簽將會被替代為對應數據對象上?`msg`?屬性的值。無論何時,綁定的數據對象上?`msg`?屬性發生了改變,插值處的內容都會更新。
通過使用?[v-once 指令](https://cn.vuejs.org/v2/api/#v-once),你也能執行一次性地插值,當數據改變時,插值處的內容不會更新。但請留心這會影響到該節點上的其它數據綁定:
|
span v-once>這個將不會改變: {{ msg }}span>
|
### [原始 HTML](https://cn.vuejs.org/v2/guide/syntax.html#%E5%8E%9F%E5%A7%8B-HTML "原始 HTML")
雙大括號會將數據解釋為普通文本,而非 HTML 代碼。為了輸出真正的 HTML,你需要使用?`v-html`?指令:
|
p>Using mustaches: {{ rawHtml }}p>
p>Using v-html directive: span v-html="rawHtml">span>p>
|
Using mustaches: This should be red.
Using v-html directive:?This should be red.
這個?`span`?的內容將會被替換成為屬性值?`rawHtml`,直接作為 HTML——會忽略解析屬性值中的數據綁定。注意,你不能使用?`v-html`?來復合局部模板,因為 Vue 不是基于字符串的模板引擎。反之,對于用戶界面 (UI),組件更適合作為可重用和可組合的基本單位。
你的站點上動態渲染的任意 HTML 可能會非常危險,因為它很容易導致?[XSS 攻擊](https://en.wikipedia.org/wiki/Cross-site_scripting)。請只對可信內容使用 HTML 插值,絕不要對用戶提供的內容使用插值。
### [特性](https://cn.vuejs.org/v2/guide/syntax.html#%E7%89%B9%E6%80%A7 "特性")
Mustache 語法不能作用在 HTML 特性上,遇到這種情況應該使用?[v-bind 指令](https://cn.vuejs.org/v2/api/#v-bind):
|
div v-bind:id="dynamicId">div>
|
在布爾特性的情況下,它們的存在即暗示為?`true`,`v-bind`?工作起來略有不同,在這個例子中:
|
button v-bind:disabled="isButtonDisabled">Buttonbutton>
|
如果?`isButtonDisabled`?的值是?`null`、`undefined`?或?`false`,則?`disabled`?特性甚至不會被包含在渲染出來的?`<button>`?元素中。
### [使用 JavaScript 表達式](https://cn.vuejs.org/v2/guide/syntax.html#%E4%BD%BF%E7%94%A8-JavaScript-%E8%A1%A8%E8%BE%BE%E5%BC%8F "使用 JavaScript 表達式")
迄今為止,在我們的模板中,我們一直都只綁定簡單的屬性鍵值。但實際上,對于所有的數據綁定,Vue.js 都提供了完全的 JavaScript 表達式支持。
|
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
div v-bind:id="'list-' + id">div>
|
這些表達式會在所屬 Vue 實例的數據作用域下作為 JavaScript 被解析。有個限制就是,每個綁定都只能包含單個表達式,所以下面的例子都不會生效。
|
{{ var a = 1 }}
{{ if (ok) { return message } }}
|
模板表達式都被放在沙盒中,只能訪問全局變量的一個白名單,如?`Math`?和?`Date`?。你不應該在模板表達式中試圖訪問用戶定義的全局變量。
## [指令](https://cn.vuejs.org/v2/guide/syntax.html#%E6%8C%87%E4%BB%A4 "指令")
指令 (Directives) 是帶有?`v-`?前綴的特殊特性。指令特性的值預期是單個 JavaScript 表達式?(`v-for`?是例外情況,稍后我們再討論)。指令的職責是,當表達式的值改變時,將其產生的連帶影響,響應式地作用于 DOM。回顧我們在介紹中看到的例子:
|
p v-if="seen">現在你看到我了p>
|
這里,`v-if`?指令將根據表達式?`seen`?的值的真假來插入/移除?`<p>`?元素。
### [參數](https://cn.vuejs.org/v2/guide/syntax.html#%E5%8F%82%E6%95%B0 "參數")
一些指令能夠接收一個“參數”,在指令名稱之后以冒號表示。例如,`v-bind`?指令可以用于響應式地更新 HTML 特性:
|
a v-bind:href="url">...a>
|
在這里?`href`?是參數,告知?`v-bind`?指令將該元素的?`href`?特性與表達式?`url`?的值綁定。
另一個例子是?`v-on`?指令,它用于監聽 DOM 事件:
|
a v-on:click="doSomething">...a>
|
在這里參數是監聽的事件名。我們也會更詳細地討論事件處理。
### [修飾符](https://cn.vuejs.org/v2/guide/syntax.html#%E4%BF%AE%E9%A5%B0%E7%AC%A6 "修飾符")
修飾符 (Modifiers) 是以半角句號?`.`?指明的特殊后綴,用于指出一個指令應該以特殊方式綁定。例如,`.prevent`?修飾符告訴?`v-on`?指令對于觸發的事件調用?`event.preventDefault()`:
|
form v-on:submit.prevent="onSubmit">...form>
|
在接下來對?[`v-on`](https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6)?和?[`v-for`](https://cn.vuejs.org/v2/guide/forms.html#%E4%BF%AE%E9%A5%B0%E7%AC%A6)?等功能的探索中,你會看到修飾符的其它例子。
## [縮寫](https://cn.vuejs.org/v2/guide/syntax.html#%E7%BC%A9%E5%86%99 "縮寫")
`v-`?前綴作為一種視覺提示,用來識別模板中 Vue 特定的特性。當你在使用 Vue.js 為現有標簽添加動態行為 (dynamic behavior) 時,`v-`?前綴很有幫助,然而,對于一些頻繁用到的指令來說,就會感到使用繁瑣。同時,在構建由 Vue.js 管理所有模板的[單頁面應用程序 (SPA - single page application)](https://en.wikipedia.org/wiki/Single-page_application)?時,`v-`?前綴也變得沒那么重要了。因此,Vue.js 為?`v-bind`?和?`v-on`?這兩個最常用的指令,提供了特定簡寫:
### [`v-bind`?縮寫](https://cn.vuejs.org/v2/guide/syntax.html#v-bind-%E7%BC%A9%E5%86%99 "v-bind 縮寫")
|
a v-bind:href="url">...a>
a :href="url">...a>
|
### [`v-on`?縮寫](https://cn.vuejs.org/v2/guide/syntax.html#v-on-%E7%BC%A9%E5%86%99 "v-on 縮寫")
|
a v-on:click="doSomething">...a>
a @click="doSomething">...a>
|
它們看起來可能與普通的 HTML 略有不同,但?`:`?與?`@`?對于特性名來說都是合法字符,在所有支持 Vue.js 的瀏覽器都能被正確地解析。而且,它們不會出現在最終渲染的標記中。縮寫語法是完全可選的,但隨著你更深入地了解它們的作用,你會慶幸擁有它們。
# 計算屬性和偵聽器
## [計算屬性](https://cn.vuejs.org/v2/guide/computed.html#%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7 "計算屬性")
模板內的表達式非常便利,但是設計它們的初衷是用于簡單運算的。在模板中放入太多的邏輯會讓模板過重且難以維護。例如:
|
div id="example">
{{ message.split('').reverse().join('') }}
div>
|
在這個地方,模板不再是簡單的聲明式邏輯。你必須看一段時間才能意識到,這里是想要顯示變量?`message`?的翻轉字符串。當你想要在模板中多次引用此處的翻轉字符串時,就會更加難以處理。
所以,對于任何復雜邏輯,你都應當使用計算屬性。
### [基礎例子](https://cn.vuejs.org/v2/guide/computed.html#%E5%9F%BA%E7%A1%80%E4%BE%8B%E5%AD%90 "基礎例子")
|
div id="example">
p>Original message: "{{ message }}"p>
p>Computed reversed message: "{{ reversedMessage }}"p>
div>
|
|
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 計算屬性的 getter
reversedMessage: function () {
// `this` 指向 vm 實例
return this.message.split('').reverse().join('')
}
}
})
|
結果:
Original message: "Hello"
Computed reversed message: "olleH"
這里我們聲明了一個計算屬性?`reversedMessage`。我們提供的函數將用作屬性?`vm.reversedMessage`?的 getter 函數:
|
console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
|
你可以打開瀏覽器的控制臺,自行修改例子中的 vm。`vm.reversedMessage`?的值始終取決于?`vm.message`?的值。
你可以像綁定普通屬性一樣在模板中綁定計算屬性。Vue 知道?`vm.reversedMessage`依賴于?`vm.message`,因此當?`vm.message`?發生改變時,所有依賴?`vm.reversedMessage`?的綁定也會更新。而且最妙的是我們已經以聲明的方式創建了這種依賴關系:計算屬性的 getter 函數是沒有副作用 (side effect) 的,這使它更易于測試和理解。
### [計算屬性緩存 vs 方法](https://cn.vuejs.org/v2/guide/computed.html#%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E7%BC%93%E5%AD%98-vs-%E6%96%B9%E6%B3%95 "計算屬性緩存 vs 方法")
你可能已經注意到我們可以通過在表達式中調用方法來達到同樣的效果:
|
p>Reversed message: "{{ reversedMessage() }}"p>
|
|
// 在組件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
|
我們可以將同一函數定義為一個方法而不是一個計算屬性。兩種方式的最終結果確實是完全相同的。然而,不同的是計算屬性是基于它們的依賴進行緩存的。只在相關依賴發生改變時它們才會重新求值。這就意味著只要?`message`?還沒有發生改變,多次訪問?`reversedMessage`?計算屬性會立即返回之前的計算結果,而不必再次執行函數。
這也同樣意味著下面的計算屬性將不再更新,因為?`Date.now()`?不是響應式依賴:
|
computed: {
now: function () {
return Date.now()
}
}
|
相比之下,每當觸發重新渲染時,調用方法將總會再次執行函數。
我們為什么需要緩存?假設我們有一個性能開銷比較大的計算屬性?A,它需要遍歷一個巨大的數組并做大量的計算。然后我們可能有其他的計算屬性依賴于?A?。如果沒有緩存,我們將不可避免的多次執行?A?的 getter!如果你不希望有緩存,請用方法來替代。
### [計算屬性 vs 偵聽屬性](https://cn.vuejs.org/v2/guide/computed.html#%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7-vs-%E4%BE%A6%E5%90%AC%E5%B1%9E%E6%80%A7 "計算屬性 vs 偵聽屬性")
Vue 提供了一種更通用的方式來觀察和響應 Vue 實例上的數據變動:偵聽屬性。當你有一些數據需要隨著其它數據變動而變動時,你很容易濫用?`watch`——特別是如果你之前使用過 AngularJS。然而,通常更好的做法是使用計算屬性而不是命令式的?`watch`?回調。細想一下這個例子:
|
div id="demo">{{ fullName }}div>
|
|
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
|
上面代碼是命令式且重復的。將它與計算屬性的版本進行比較:
|
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
|
好得多了,不是嗎?
### [計算屬性的 setter](https://cn.vuejs.org/v2/guide/computed.html#%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E7%9A%84-setter "計算屬性的 setter")
計算屬性默認只有 getter ,不過在需要時你也可以提供一個 setter :
|
// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
|
現在再運行?`vm.fullName = 'John Doe'`?時,setter 會被調用,`vm.firstName`?和?`vm.lastName`?也會相應地被更新。
## [偵聽器](https://cn.vuejs.org/v2/guide/computed.html#%E4%BE%A6%E5%90%AC%E5%99%A8 "偵聽器")
雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。這就是為什么 Vue 通過?`watch`?選項提供了一個更通用的方法,來響應數據的變化。當需要在數據變化時執行異步或開銷較大的操作時,這個方式是最有用的。
例如:
|
div id="watch-example">
p>
Ask a yes/no question:
input v-model="question">
p>
p>{{ answer }}p>
div>
|
|
script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js">script>
script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js">script>
script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 發生改變,這個函數就會運行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// `_.debounce` 是一個通過 Lodash 限制操作頻率的函數。
// 在這個例子中,我們希望限制訪問 yesno.wtf/api 的頻率
// AJAX 請求直到用戶輸入完畢才會發出。想要了解更多關于
// `_.debounce` 函數 (及其近親 `_.throttle`) 的知識,
// 請參考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
script>
|
結果:
Ask a yes/no question:?
I cannot give you an answer until you ask a question!
在這個示例中,使用?`watch`?選項允許我們執行異步操作 (訪問一個 API),限制我們執行該操作的頻率,并在我們得到最終結果前,設置中間狀態。這些都是計算屬性無法做到的。
除了?`watch`?選項之外,您還可以使用命令式的?[vm.$watch API](https://cn.vuejs.org/v2/api/#vm-watch)。
# Class 與 Style 綁定
操作元素的 class 列表和內聯樣式是數據綁定的一個常見需求。因為它們都是屬性,所以我們可以用?`v-bind`?處理它們:只需要通過表達式計算出字符串結果即可。不過,字符串拼接麻煩且易錯。因此,在將?`v-bind`?用于?`class`?和?`style`?時,Vue.js 做了專門的增強。表達式結果的類型除了字符串之外,還可以是對象或數組。
## [綁定 HTML Class](https://cn.vuejs.org/v2/guide/class-and-style.html#%E7%BB%91%E5%AE%9A-HTML-Class "綁定 HTML Class")
### [對象語法](https://cn.vuejs.org/v2/guide/class-and-style.html#%E5%AF%B9%E8%B1%A1%E8%AF%AD%E6%B3%95 "對象語法")
我們可以傳給?`v-bind:class`?一個對象,以動態地切換 class:
|
div v-bind:class="{ active: isActive }">div>
|
上面的語法表示?`active`?這個 class 存在與否將取決于數據屬性?`isActive`?的?[truthiness](https://developer.mozilla.org/zh-CN/docs/Glossary/Truthy)。
你可以在對象中傳入更多屬性來動態切換多個 class。此外,`v-bind:class`?指令也可以與普通的 class 屬性共存。當有如下模板:
|
div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }">
div>
|
和如下 data:
|
data: {
isActive: true,
hasError: false
}
|
結果渲染為:
|
div class="static active">div>
|
當?`isActive`?或者?`hasError`?變化時,class 列表將相應地更新。例如,如果?`hasError`?的值為?`true`,class 列表將變為?`"static active text-danger"`。
綁定的數據對象不必內聯定義在模板里:
|
div v-bind:class="classObject">div>
|
|
data: {
classObject: {
active: true,
'text-danger': false
}
}
|
渲染的結果和上面一樣。我們也可以在這里綁定一個返回對象的[計算屬性](https://cn.vuejs.org/v2/guide/computed.html)。這是一個常用且強大的模式:
|
div v-bind:class="classObject">div>
|
|
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
|
### [數組語法](https://cn.vuejs.org/v2/guide/class-and-style.html#%E6%95%B0%E7%BB%84%E8%AF%AD%E6%B3%95 "數組語法")
我們可以把一個數組傳給?`v-bind:class`,以應用一個 class 列表:
|
div v-bind:class="[activeClass, errorClass]">div>
|
|
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
|
渲染為:
|
div class="active text-danger">div>
|
如果你也想根據條件切換列表中的 class,可以用三元表達式:
|
div v-bind:class="[isActive ? activeClass : '', errorClass]">div>
|
這樣寫將始終添加?`errorClass`,但是只有在?`isActive`?是 truthy[[1]](https://cn.vuejs.org/v2/guide/class-and-style.html#footnote-1)?時才添加?`activeClass`。
不過,當有多個條件 class 時這樣寫有些繁瑣。所以在數組語法中也可以使用對象語法:
|
div v-bind:class="[{ active: isActive }, errorClass]">div>
|
### [用在組件上](https://cn.vuejs.org/v2/guide/class-and-style.html#%E7%94%A8%E5%9C%A8%E7%BB%84%E4%BB%B6%E4%B8%8A "用在組件上")
> 這個章節假設你已經對?[Vue 組件](https://cn.vuejs.org/v2/guide/components.html)有一定的了解。當然你也可以先跳過這里,稍后再回過頭來看。
當在一個自定義組件上使用?`class`?屬性時,這些類將被添加到該組件的根元素上面。這個元素上已經存在的類不會被覆蓋。
例如,如果你聲明了這個組件:
|
Vue.component('my-component', {
template: 'Hi'
})
|
然后在使用它的時候添加一些 class:
|
my-component class="baz boo">my-component>
|
HTML 將被渲染為:
|
p class="foo bar baz boo">Hip>
|
對于帶數據綁定 class 也同樣適用:
|
my-component v-bind:class="{ active: isActive }">my-component>
|
當?`isActive`?為 truthy[[1]](https://cn.vuejs.org/v2/guide/class-and-style.html#footnote-1)?時,HTML 將被渲染成為:
|
p class="foo bar active">Hip>
|
## [綁定內聯樣式](https://cn.vuejs.org/v2/guide/class-and-style.html#%E7%BB%91%E5%AE%9A%E5%86%85%E8%81%94%E6%A0%B7%E5%BC%8F "綁定內聯樣式")
### [對象語法](https://cn.vuejs.org/v2/guide/class-and-style.html#%E5%AF%B9%E8%B1%A1%E8%AF%AD%E6%B3%95-1 "對象語法")
`v-bind:style`?的對象語法十分直觀——看著非常像 CSS,但其實是一個 JavaScript 對象。CSS 屬性名可以用駝峰式 (camelCase) 或短橫線分隔 (kebab-case,記得用單引號括起來) 來命名:
|
div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">div>
|
|
data: {
activeColor: 'red',
fontSize: 30
}
|
直接綁定到一個樣式對象通常更好,這會讓模板更清晰:
|
div v-bind:style="styleObject">div>
|
|
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
|
同樣的,對象語法常常結合返回對象的計算屬性使用。
### [數組語法](https://cn.vuejs.org/v2/guide/class-and-style.html#%E6%95%B0%E7%BB%84%E8%AF%AD%E6%B3%95-1 "數組語法")
`v-bind:style`?的數組語法可以將多個樣式對象應用到同一個元素上:
|
div v-bind:style="[baseStyles, overridingStyles]">div>
|
### [自動添加前綴](https://cn.vuejs.org/v2/guide/class-and-style.html#%E8%87%AA%E5%8A%A8%E6%B7%BB%E5%8A%A0%E5%89%8D%E7%BC%80 "自動添加前綴")
當?`v-bind:style`?使用需要添加[瀏覽器引擎前綴](https://developer.mozilla.org/zh-CN/docs/Glossary/Vendor_Prefix)的 CSS 屬性時,如?`transform`,Vue.js 會自動偵測并添加相應的前綴。
### [多重值](https://cn.vuejs.org/v2/guide/class-and-style.html#%E5%A4%9A%E9%87%8D%E5%80%BC "多重值")
> 2.3.0+
從 2.3.0 起你可以為?`style`?綁定中的屬性提供一個包含多個值的數組,常用于提供多個帶前綴的值,例如:
|
div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">div>
|
這樣寫只會渲染數組中最后一個被瀏覽器支持的值。在本例中,如果瀏覽器支持不帶瀏覽器前綴的 flexbox,那么就只會渲染?`display: flex`。
譯者注
[1] truthy 不是?`true`,詳見?[MDN](https://developer.mozilla.org/zh-CN/docs/Glossary/Truthy)?的解釋。
# 條件渲染
## [`v-if`](https://cn.vuejs.org/v2/guide/conditional.html#v-if "v-if")
在字符串模板中,比如 Handlebars,我們得像這樣寫一個條件塊:
|
{{#if ok}}
h1>Yesh1>
{{/if}}
|
在 Vue 中,我們使用?`v-if`?指令實現同樣的功能:
|
h1 v-if="ok">Yesh1>
|
也可以用?`v-else`?添加一個“else 塊”:
|
h1 v-if="ok">Yesh1>
h1 v-else>Noh1>
|
### [在?`<template>`?元素上使用?`v-if`?條件渲染分組](https://cn.vuejs.org/v2/guide/conditional.html#%E5%9C%A8-lt-template-gt-%E5%85%83%E7%B4%A0%E4%B8%8A%E4%BD%BF%E7%94%A8-v-if-%E6%9D%A1%E4%BB%B6%E6%B8%B2%E6%9F%93%E5%88%86%E7%BB%84 "在 元素上使用 v-if 條件渲染分組")
因為?`v-if`?是一個指令,所以必須將它添加到一個元素上。但是如果想切換多個元素呢?此時可以把一個?`<template>`?元素當做不可見的包裹元素,并在上面使用?`v-if`。最終的渲染結果將不包含?`<template>`?元素。
|
template v-if="ok">
h1>Titleh1>
p>Paragraph 1p>
p>Paragraph 2p>
template>
|
### [`v-else`](https://cn.vuejs.org/v2/guide/conditional.html#v-else "v-else")
你可以使用?`v-else`?指令來表示?`v-if`?的“else 塊”:
|
div v-if="Math.random() > 0.5">
Now you see me
div>
div v-else>
Now you don't
div>
|
`v-else`?元素必須緊跟在帶?`v-if`?或者?`v-else-if`?的元素的后面,否則它將不會被識別。
### [`v-else-if`](https://cn.vuejs.org/v2/guide/conditional.html#v-else-if "v-else-if")
> 2.1.0 新增
`v-else-if`,顧名思義,充當?`v-if`?的“else-if 塊”,可以連續使用:
|
div v-if="type === 'A'">
A
div>
div v-else-if="type === 'B'">
B
div>
div v-else-if="type === 'C'">
C
div>
div v-else>
Not A/B/C
div>
|
類似于?`v-else`,`v-else-if`?也必須緊跟在帶?`v-if`?或者?`v-else-if`?的元素之后。
### [用?`key`?管理可復用的元素](https://cn.vuejs.org/v2/guide/conditional.html#%E7%94%A8-key-%E7%AE%A1%E7%90%86%E5%8F%AF%E5%A4%8D%E7%94%A8%E7%9A%84%E5%85%83%E7%B4%A0 "用 key 管理可復用的元素")
Vue 會盡可能高效地渲染元素,通常會復用已有元素而不是從頭開始渲染。這么做除了使 Vue 變得非常快之外,還有其它一些好處。例如,如果你允許用戶在不同的登錄方式之間切換:
|
template v-if="loginType === 'username'">
label>Usernamelabel>
input placeholder="Enter your username">
template>
template v-else>
label>Emaillabel>
input placeholder="Enter your email address">
template>
|
那么在上面的代碼中切換?`loginType`?將不會清除用戶已經輸入的內容。因為兩個模板使用了相同的元素,`<input>`?不會被替換掉——僅僅是替換了它的?`placeholder`。
自己動手試一試,在輸入框中輸入一些文本,然后按下切換按鈕:
Username?
Toggle login type
這樣也不總是符合實際需求,所以 Vue 為你提供了一種方式來表達“這兩個元素是完全獨立的,不要復用它們”。只需添加一個具有唯一值的?`key`?屬性即可:
|
template v-if="loginType === 'username'">
label>Usernamelabel>
input placeholder="Enter your username" key="username-input">
template>
template v-else>
label>Emaillabel>
input placeholder="Enter your email address" key="email-input">
template>
|
現在,每次切換時,輸入框都將被重新渲染。請看:
Username?
Toggle login type
注意,`<label>`?元素仍然會被高效地復用,因為它們沒有添加?`key`?屬性。
## [`v-show`](https://cn.vuejs.org/v2/guide/conditional.html#v-show "v-show")
另一個用于根據條件展示元素的選項是?`v-show`?指令。用法大致一樣:
|
h1 v-show="ok">Hello!h1>
|
不同的是帶有?`v-show`?的元素始終會被渲染并保留在 DOM 中。`v-show`?只是簡單地切換元素的 CSS 屬性?`display`。
注意,`v-show`?不支持?`<template>`?元素,也不支持?`v-else`。
## [`v-if`?vs?`v-show`](https://cn.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show "v-if vs v-show")
`v-if`?是“真正”的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建。
`v-if`?也是惰性的:如果在初始渲染時條件為假,則什么也不做——直到條件第一次變為真時,才會開始渲染條件塊。
相比之下,`v-show`?就簡單得多——不管初始條件是什么,元素總是會被渲染,并且只是簡單地基于 CSS 進行切換。
一般來說,`v-if`?有更高的切換開銷,而?`v-show`?有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用?`v-show`?較好;如果在運行時條件很少改變,則使用?`v-if`?較好。
## [`v-if`?與?`v-for`?一起使用](https://cn.vuejs.org/v2/guide/conditional.html#v-if-%E4%B8%8E-v-for-%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8 "v-if 與 v-for 一起使用")
不推薦同時使用?`v-if`?和?`v-for`。請查閱[風格指南](https://cn.vuejs.org/v2/style-guide/#%E9%81%BF%E5%85%8D-v-if-%E5%92%8C-v-for-%E7%94%A8%E5%9C%A8%E4%B8%80%E8%B5%B7-%E5%BF%85%E8%A6%81)以獲取更多信息。
當?`v-if`?與?`v-for`?一起使用時,`v-for`?具有比?`v-if`?更高的優先級。請查閱[列表渲染指南](https://cn.vuejs.org/v2/guide/list.html#v-for-with-v-if)?以獲取詳細信息。
# 列表渲染
## [用?`v-for`?把一個數組對應為一組元素](https://cn.vuejs.org/v2/guide/list.html#%E7%94%A8-v-for-%E6%8A%8A%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84%E5%AF%B9%E5%BA%94%E4%B8%BA%E4%B8%80%E7%BB%84%E5%85%83%E7%B4%A0 "用 v-for 把一個數組對應為一組元素")
我們用?`v-for`?指令根據一組數組的選項列表進行渲染。`v-for`?指令需要使用?`item in items`?形式的特殊語法,`items`?是源數據數組并且?`item`?是數組元素迭代的別名。
|
ul id="example-1">
li v-for="item in items">
{{ item.message }}
li>
ul>
|
|
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
|
結果:
* Foo
* Bar
在?`v-for`?塊中,我們擁有對父作用域屬性的完全訪問權限。`v-for`?還支持一個可選的第二個參數為當前項的索引。
|
ul id="example-2">
li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
li>
ul>
|
|
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
|
結果:
* Parent - 0 - Foo
* Parent - 1 - Bar
你也可以用?`of`?替代?`in`?作為分隔符,因為它是最接近 JavaScript 迭代器的語法:
|
div v-for="item of items">div>
|
## [一個對象的?`v-for`](https://cn.vuejs.org/v2/guide/list.html#%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%B1%A1%E7%9A%84-v-for "一個對象的 v-for")
你也可以用?`v-for`?通過一個對象的屬性來迭代。
|
ul id="v-for-object" class="demo">
li v-for="value in object">
{{ value }}
li>
ul>
|
|
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})
|
結果:
* John
* Doe
* 30
你也可以提供第二個的參數為鍵名:
|
div v-for="(value, key) in object">
{{ key }}: {{ value }}
div>
|
firstName: John
lastName: Doe
age: 30
第三個參數為索引:
|
div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
div>
|
0\. firstName: John
1\. lastName: Doe
2\. age: 30
在遍歷對象時,是按?`Object.keys()`?的結果遍歷,但是不能保證它的結果在不同的 JavaScript 引擎下是一致的。
## [`key`](https://cn.vuejs.org/v2/guide/list.html#key "key")
當 Vue.js 用?`v-for`?正在更新已渲染過的元素列表時,它默認用“就地復用”策略。如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序, 而是簡單復用此處每個元素,并且確保它在特定索引下顯示已被渲染過的每個元素。這個類似 Vue 1.x 的?`track-by="$index"`?。
這個默認的模式是高效的,但是只適用于不依賴子組件狀態或臨時 DOM 狀態 (例如:表單輸入值) 的列表渲染輸出。
為了給 Vue 一個提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一?`key`?屬性。理想的?`key`?值是每項都有的唯一 id。這個特殊的屬性相當于 Vue 1.x 的?`track-by`?,但它的工作方式類似于一個屬性,所以你需要用?`v-bind`?來綁定動態值 (在這里使用簡寫):
|
div v-for="item in items" :key="item.id">
div>
|
建議盡可能在使用?`v-for`?時提供?`key`,除非遍歷輸出的 DOM 內容非常簡單,或者是刻意依賴默認行為以獲取性能上的提升。
因為它是 Vue 識別節點的一個通用機制,`key`?并不與?`v-for`?特別關聯,key 還具有其他用途,我們將在后面的指南中看到其他用途。
## [數組更新檢測](https://cn.vuejs.org/v2/guide/list.html#%E6%95%B0%E7%BB%84%E6%9B%B4%E6%96%B0%E6%A3%80%E6%B5%8B "數組更新檢測")
### [變異方法](https://cn.vuejs.org/v2/guide/list.html#%E5%8F%98%E5%BC%82%E6%96%B9%E6%B3%95 "變異方法")
Vue 包含一組觀察數組的變異方法,所以它們也將會觸發視圖更新。這些方法如下:
* `push()`
* `pop()`
* `shift()`
* `unshift()`
* `splice()`
* `sort()`
* `reverse()`
你打開控制臺,然后用前面例子的?`items`?數組調用變異方法:`example1.items.push({ message: 'Baz' })`?。
### [替換數組](https://cn.vuejs.org/v2/guide/list.html#%E6%9B%BF%E6%8D%A2%E6%95%B0%E7%BB%84 "替換數組")
變異方法 (mutation method),顧名思義,會改變被這些方法調用的原始數組。相比之下,也有非變異 (non-mutating method) 方法,例如:`filter()`,?`concat()`?和?`slice()`?。這些不會改變原始數組,但總是返回一個新數組。當使用非變異方法時,可以用新數組替換舊數組:
|
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
|
你可能認為這將導致 Vue 丟棄現有 DOM 并重新渲染整個列表。幸運的是,事實并非如此。Vue 為了使得 DOM 元素得到最大范圍的重用而實現了一些智能的、啟發式的方法,所以用一個含有相同元素的數組去替換原來的數組是非常高效的操作。
### [注意事項](https://cn.vuejs.org/v2/guide/list.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9 "注意事項")
由于 JavaScript 的限制,Vue 不能檢測以下變動的數組:
1. 當你利用索引直接設置一個項時,例如:`vm.items[indexOfItem] = newValue`
2. 當你修改數組的長度時,例如:`vm.items.length = newLength`
舉個例子:
|
var 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`](https://vuejs.org/v2/api/#vm-set)?實例方法,該方法是全局方法?`Vue.set`?的一個別名:
|
vm.$set(vm.items, indexOfItem, newValue)
|
為了解決第二類問題,你可以使用?`splice`:
|
vm.items.splice(newLength)
|
## [對象更改檢測注意事項](https://cn.vuejs.org/v2/guide/list.html#%E5%AF%B9%E8%B1%A1%E6%9B%B4%E6%94%B9%E6%A3%80%E6%B5%8B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9 "對象更改檢測注意事項")
還是由于 JavaScript 的限制,Vue 不能檢測對象屬性的添加或刪除:
|
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 現在是響應式的
vm.b = 2
// `vm.b` 不是響應式的
|
對于已經創建的實例,Vue 不能動態添加根級別的響應式屬性。但是,可以使用?`Vue.set(object, key, value)`?方法向嵌套對象添加響應式屬性。例如,對于:
|
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
|
你可以添加一個新的?`age`?屬性到嵌套的?`userProfile`?對象:
|
Vue.set(vm.userProfile, 'age', 27)
|
你還可以使用?`vm.$set`?實例方法,它只是全局?`Vue.set`?的別名:
|
vm.$set(vm.userProfile, 'age', 27)
|
有時你可能需要為已有對象賦予多個新屬性,比如使用?`Object.assign()`?或?`_.extend()`。在這種情況下,你應該用兩個對象的屬性創建一個新的對象。所以,如果你想添加新的響應式屬性,不要像這樣:
|
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
|
你應該這樣做:
|
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
|
## [顯示過濾/排序結果](https://cn.vuejs.org/v2/guide/list.html#%E6%98%BE%E7%A4%BA%E8%BF%87%E6%BB%A4-%E6%8E%92%E5%BA%8F%E7%BB%93%E6%9E%9C "顯示過濾/排序結果")
有時,我們想要顯示一個數組的過濾或排序副本,而不實際改變或重置原始數據。在這種情況下,可以創建返回過濾或排序數組的計算屬性。
例如:
|
li v-for="n in evenNumbers">{{ n }}li>
|
|
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
|
在計算屬性不適用的情況下 (例如,在嵌套?`v-for`?循環中) 你可以使用一個 method 方法:
|
li v-for="n in even(numbers)">{{ n }}li>
|
|
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
|
## [一段取值范圍的?`v-for`](https://cn.vuejs.org/v2/guide/list.html#%E4%B8%80%E6%AE%B5%E5%8F%96%E5%80%BC%E8%8C%83%E5%9B%B4%E7%9A%84-v-for "一段取值范圍的 v-for")
`v-for`?也可以取整數。在這種情況下,它將重復多次模板。
|
div>
span v-for="n in 10">{{ n }} span>
div>
|
結果:
1?2?3?4?5?6?7?8?9?10
## [`v-for`?on a?`<template>`](https://cn.vuejs.org/v2/guide/list.html#v-for-on-a-lt-template-gt "v-for on a ")
類似于?`v-if`,你也可以利用帶有?`v-for`?的?`<template>`?渲染多個元素。比如:
|
ul>
template v-for="item in items">
li>{{ item.msg }}li>
li class="divider" role="presentation">li>
template>
ul>
|
## [`v-for`?with?`v-if`](https://cn.vuejs.org/v2/guide/list.html#v-for-with-v-if "v-for with v-if")
當它們處于同一節點,`v-for`?的優先級比?`v-if`?更高,這意味著?`v-if`?將分別重復運行于每個?`v-for`?循環中。當你想為僅有的*一些*項渲染節點時,這種優先級的機制會十分有用,如下:
|
li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
li>
|
上面的代碼只傳遞了未完成的 todos。
而如果你的目的是有條件地跳過循環的執行,那么可以將?`v-if`?置于外層元素 (或?[`<template>`](https://cn.vuejs.org/v2/guide/conditional.html#%E5%9C%A8-lt-template-gt-%E4%B8%AD%E9%85%8D%E5%90%88-v-if-%E6%9D%A1%E4%BB%B6%E6%B8%B2%E6%9F%93%E4%B8%80%E6%95%B4%E7%BB%84))上。如:
|
ul v-if="todos.length">
li v-for="todo in todos">
{{ todo }}
li>
ul>
p v-else>No todos left!p>
|
## [一個組件的?`v-for`](https://cn.vuejs.org/v2/guide/list.html#%E4%B8%80%E4%B8%AA%E7%BB%84%E4%BB%B6%E7%9A%84-v-for "一個組件的 v-for")
> 了解組件相關知識,查看?[組件](https://cn.vuejs.org/v2/guide/components.html)。完全可以先跳過它,以后再回來查看。
在自定義組件里,你可以像任何普通元素一樣用?`v-for`?。
|
my-component v-for="item in items" :key="item.id">my-component>
|
> 2.2.0+ 的版本里,當在組件中使用?`v-for`?時,`key`?現在是必須的。
然而,任何數據都不會被自動傳遞到組件里,因為組件有自己獨立的作用域。為了把迭代數據傳遞到組件里,我們要用?`props`?:
|
my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
>my-component>
|
不自動將?`item`?注入到組件里的原因是,這會使得組件與?`v-for`?的運作緊密耦合。明確組件數據的來源能夠使組件在其他場合重復使用。
下面是一個簡單的 todo list 的完整例子:
|
div id="todo-list-example">
form v-on:submit.prevent="addNewTodo">
label for="new-todo">Add a todolabel>
input
v-model="newTodoText"
id="new-todo"
placeholder="E.g. Feed the cat"
>
button>Addbutton>
form>
ul>
li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
>li>
ul>
div>
|
注意這里的?`is="todo-item"`?屬性。這種做法在使用 DOM 模板時是十分必要的,因為在?`<ul>`?元素內只有?`<li>`?元素會被看作有效內容。這樣做實現的效果與?`<todo-item>`?相同,但是可以避開一些潛在的瀏覽器解析錯誤。查看?[DOM 模板解析說明](https://cn.vuejs.org/v2/guide/components.html#%E8%A7%A3%E6%9E%90-DOM-%E6%A8%A1%E6%9D%BF%E6%97%B6%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)?來了解更多信息。
|
Vue.component('todo-item', {
template: '\
\
{{ title }}\
Remove\
\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
|
Add a todo??Add
* Do the dishes?Remove
* Take out the trash?Remove
* Mow the lawn?Remove
# 事件處理
## [監聽事件](https://cn.vuejs.org/v2/guide/events.html#%E7%9B%91%E5%90%AC%E4%BA%8B%E4%BB%B6 "監聽事件")
可以用?`v-on`?指令監聽 DOM 事件,并在觸發時運行一些 JavaScript 代碼。
示例:
|
div id="example-1">
button v-on:click="counter += 1">Add 1button>
p>The button above has been clicked {{ counter }} times.p>
div>
|
|
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
|
結果:
Add 1
The button above has been clicked 0 times.
## [事件處理方法](https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95 "事件處理方法")
然而許多事件處理邏輯會更為復雜,所以直接把 JavaScript 代碼寫在?`v-on`?指令中是不可行的。因此?`v-on`?還可以接收一個需要調用的方法名稱。
示例:
|
div id="example-2">
button v-on:click="greet">Greetbutton>
div>
|
|
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 對象中定義方法
methods: {
greet: function (event) {
// `this` 在方法里指向當前 Vue 實例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
// 也可以用 JavaScript 直接調用方法
example2.greet() // => 'Hello Vue.js!'
|
結果:
Greet
## [內聯處理器中的方法](https://cn.vuejs.org/v2/guide/events.html#%E5%86%85%E8%81%94%E5%A4%84%E7%90%86%E5%99%A8%E4%B8%AD%E7%9A%84%E6%96%B9%E6%B3%95 "內聯處理器中的方法")
除了直接綁定到一個方法,也可以在內聯 JavaScript 語句中調用方法:
|
div id="example-3">
button v-on:click="say('hi')">Say hibutton>
button v-on:click="say('what')">Say whatbutton>
div>
|
|
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
|
結果:
Say hi?Say what
有時也需要在內聯語句處理器中訪問原始的 DOM 事件。可以用特殊變量?`$event`?把它傳入方法:
|
button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
button>
|
|
// ...
methods: {
warn: function (message, event) {
// 現在我們可以訪問原生事件對象
if (event) event.preventDefault()
alert(message)
}
}
|
## [事件修飾符](https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6 "事件修飾符")
在事件處理程序中調用?`event.preventDefault()`?或?`event.stopPropagation()`?是非常常見的需求。盡管我們可以在方法中輕松實現這點,但更好的方式是:方法只有純粹的數據邏輯,而不是去處理 DOM 事件細節。
為了解決這個問題,Vue.js 為?`v-on`?提供了事件修飾符。之前提過,修飾符是由點開頭的指令后綴來表示的。
* `.stop`
* `.prevent`
* `.capture`
* `.self`
* `.once`
* `.passive`
|
a v-on:click.stop="doThis">a>
form v-on:submit.prevent="onSubmit">form>
a v-on:click.stop.prevent="doThat">a>
form v-on:submit.prevent>form>
div v-on:click.capture="doThis">...div>
div v-on:click.self="doThat">...div>
|
使用修飾符時,順序很重要;相應的代碼會以同樣的順序產生。因此,用?`v-on:click.prevent.self`?會阻止所有的點擊,而?`v-on:click.self.prevent`?只會阻止對元素自身的點擊。
> 2.1.4 新增
|
a v-on:click.once="doThis">a>
|
不像其它只能對原生的 DOM 事件起作用的修飾符,`.once`?修飾符還能被用到自定義的[組件事件](https://cn.vuejs.org/v2/guide/components-custom-events.html)上。如果你還沒有閱讀關于組件的文檔,現在大可不必擔心。
> 2.3.0 新增
Vue 還對應?[`addEventListener`?中的?`passive`?選項](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters)提供了?`.passive`?修飾符。
|
div v-on:scroll.passive="onScroll">...div>
|
這個?`.passive`?修飾符尤其能夠提升移動端的性能。
不要把?`.passive`?和?`.prevent`?一起使用,因為?`.prevent`?將會被忽略,同時瀏覽器可能會向你展示一個警告。請記住,`.passive`?會告訴瀏覽器你*不*想阻止事件的默認行為。
## [按鍵修飾符](https://cn.vuejs.org/v2/guide/events.html#%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6 "按鍵修飾符")
在監聽鍵盤事件時,我們經常需要檢查常見的鍵值。Vue 允許為?`v-on`?在監聽鍵盤事件時添加按鍵修飾符:
|
input v-on:keyup.13="submit">
|
記住所有的?`keyCode`?比較困難,所以 Vue 為最常用的按鍵提供了別名:
|
input v-on:keyup.enter="submit">
input @keyup.enter="submit">
|
全部的按鍵別名:
* `.enter`
* `.tab`
* `.delete`?(捕獲“刪除”和“退格”鍵)
* `.esc`
* `.space`
* `.up`
* `.down`
* `.left`
* `.right`
可以通過全局?`config.keyCodes`?對象[自定義按鍵修飾符別名](https://cn.vuejs.org/v2/api/#keyCodes):
|
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
|
### [自動匹配按鍵修飾符](https://cn.vuejs.org/v2/guide/events.html#%E8%87%AA%E5%8A%A8%E5%8C%B9%E9%85%8D%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6 "自動匹配按鍵修飾符")
> 2.5.0 新增
你也可直接將?[`KeyboardEvent.key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)?暴露的任意有效按鍵名轉換為 kebab-case 來作為修飾符:
|
input @keyup.page-down="onPageDown">
|
在上面的例子中,處理函數僅在?`$event.key === 'PageDown'`?時被調用。
有一些按鍵 (`.esc`?以及所有的方向鍵) 在 IE9 中有不同的?`key`?值, 如果你想支持 IE9,它們的內置別名應該是首選。
## [系統修飾鍵](https://cn.vuejs.org/v2/guide/events.html#%E7%B3%BB%E7%BB%9F%E4%BF%AE%E9%A5%B0%E9%94%AE "系統修飾鍵")
> 2.1.0 新增
可以用如下修飾符來實現僅在按下相應按鍵時才觸發鼠標或鍵盤事件的監聽器。
* `.ctrl`
* `.alt`
* `.shift`
* `.meta`
> 注意:在 Mac 系統鍵盤上,meta 對應 command 鍵 (?)。在 Windows 系統鍵盤 meta 對應 Windows 徽標鍵 (?)。在 Sun 操作系統鍵盤上,meta 對應實心寶石鍵 (◆)。在其他特定鍵盤上,尤其在 MIT 和 Lisp 機器的鍵盤、以及其后繼產品,比如 Knight 鍵盤、space-cadet 鍵盤,meta 被標記為“META”。在 Symbolics 鍵盤上,meta 被標記為“META”或者“Meta”。
例如:
|
input @keyup.alt.67="clear">
div @click.ctrl="doSomething">Do somethingdiv>
|
請注意修飾鍵與常規按鍵不同,在和?`keyup`?事件一起用時,事件觸發時修飾鍵必須處于按下狀態。換句話說,只有在按住?`ctrl`?的情況下釋放其它按鍵,才能觸發?`keyup.ctrl`。而單單釋放?`ctrl`?也不會觸發事件。如果你想要這樣的行為,請為?`ctrl`?換用?`keyCode`:`keyup.17`。
### [`.exact`?修飾符](https://cn.vuejs.org/v2/guide/events.html#exact-%E4%BF%AE%E9%A5%B0%E7%AC%A6 ".exact 修飾符")
> 2.5.0 新增
`.exact`?修飾符允許你控制由精確的系統修飾符組合觸發的事件。
|
button @click.ctrl="onClick">Abutton>
button @click.ctrl.exact="onCtrlClick">Abutton>
button @click.exact="onClick">Abutton>
|
### [鼠標按鈕修飾符](https://cn.vuejs.org/v2/guide/events.html#%E9%BC%A0%E6%A0%87%E6%8C%89%E9%92%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6 "鼠標按鈕修飾符")
> 2.2.0 新增
* `.left`
* `.right`
* `.middle`
這些修飾符會限制處理函數僅響應特定的鼠標按鈕。
## [為什么在 HTML 中監聽事件?](https://cn.vuejs.org/v2/guide/events.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%9C%A8-HTML-%E4%B8%AD%E7%9B%91%E5%90%AC%E4%BA%8B%E4%BB%B6 "為什么在 HTML 中監聽事件?")
你可能注意到這種事件監聽的方式違背了關注點分離 (separation of concern) 這個長期以來的優良傳統。但不必擔心,因為所有的 Vue.js 事件處理方法和表達式都嚴格綁定在當前視圖的 ViewModel 上,它不會導致任何維護上的困難。實際上,使用?`v-on`?有幾個好處:
1. 掃一眼 HTML 模板便能輕松定位在 JavaScript 代碼里對應的方法。
2. 因為你無須在 JavaScript 里手動綁定事件,你的 ViewModel 代碼可以是非常純粹的邏輯,和 DOM 完全解耦,更易于測試。
3. 當一個 ViewModel 被銷毀時,所有的事件處理器都會自動被刪除。你無須擔心如何清理它們。
# 表單輸入綁定
## [基礎用法](https://cn.vuejs.org/v2/guide/forms.html#%E5%9F%BA%E7%A1%80%E7%94%A8%E6%B3%95 "基礎用法")
你可以用?`v-model`?指令在表單?`<input>`、`<textarea>`?及?`<select>`?元素上創建雙向數據綁定。它會根據控件類型自動選取正確的方法來更新元素。盡管有些神奇,但?`v-model`?本質上不過是語法糖。它負責監聽用戶的輸入事件以更新數據,并對一些極端場景進行一些特殊處理。
`v-model`?會忽略所有表單元素的?`value`、`checked`、`selected`?特性的初始值而總是將 Vue 實例的數據作為數據來源。你應該通過 JavaScript 在組件的?`data`?選項中聲明初始值。
對于需要使用[輸入法](https://zh.wikipedia.org/wiki/%E8%BE%93%E5%85%A5%E6%B3%95)?(如中文、日文、韓文等) 的語言,你會發現?`v-model`?不會在輸入法組合文字過程中得到更新。如果你也想處理這個過程,請使用?`input`?事件。
### [文本](https://cn.vuejs.org/v2/guide/forms.html#%E6%96%87%E6%9C%AC "文本")
|
input v-model="message" placeholder="edit me">
p>Message is: {{ message }}p>
|
Message is:
### [多行文本](https://cn.vuejs.org/v2/guide/forms.html#%E5%A4%9A%E8%A1%8C%E6%96%87%E6%9C%AC "多行文本")
|
span>Multiline message is:span>
p style="white-space: pre-line;">{{ message }}p>
br>
textarea v-model="message" placeholder="add multiple lines">textarea>
|
Multiline message is:
在文本區域插值 (`<textarea></textarea>`) 并不會生效,應用?`v-model`?來代替。
### [復選框](https://cn.vuejs.org/v2/guide/forms.html#%E5%A4%8D%E9%80%89%E6%A1%86 "復選框")
單個復選框,綁定到布爾值:
|
input type="checkbox" id="checkbox" v-model="checked">
label for="checkbox">{{ checked }}label>
|
?false
多個復選框,綁定到同一個數組:
|
div id='example-3'>
input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
label for="jack">Jacklabel>
input type="checkbox" id="john" value="John" v-model="checkedNames">
label for="john">Johnlabel>
input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
label for="mike">Mikelabel>
br>
span>Checked names: {{ checkedNames }}span>
div>
|
|
new Vue({
el: '#example-3',
data: {
checkedNames: []
}
})
|
?Jack??John??Mike?
Checked names: []
### [單選按鈕](https://cn.vuejs.org/v2/guide/forms.html#%E5%8D%95%E9%80%89%E6%8C%89%E9%92%AE "單選按鈕")
|
div id="example-4">
input type="radio" id="one" value="One" v-model="picked">
label for="one">Onelabel>
br>
input type="radio" id="two" value="Two" v-model="picked">
label for="two">Twolabel>
br>
span>Picked: {{ picked }}span>
div>
|
|
new Vue({
el: '#example-4',
data: {
picked: ''
}
})
|
?One?
?Two?
Picked:
### [選擇框](https://cn.vuejs.org/v2/guide/forms.html#%E9%80%89%E6%8B%A9%E6%A1%86 "選擇框")
單選時:
|
div id="example-5">
select v-model="selected">
option disabled value="">請選擇option>
option>Aoption>
option>Boption>
option>Coption>
select>
span>Selected: {{ selected }}span>
div>
|
|
new Vue({
el: '...',
data: {
selected: ''
}
})
|
請選擇?A?B?C?Selected:
如果?`v-model`?表達式的初始值未能匹配任何選項,`<select>`?元素將被渲染為“未選中”狀態。在 iOS 中,這會使用戶無法選擇第一個選項。因為這樣的情況下,iOS 不會觸發 change 事件。因此,更推薦像上面這樣提供一個值為空的禁用選項。
多選時 (綁定到一個數組):
|
div id="example-6">
select v-model="selected" multiple style="width: 50px;">
option>Aoption>
option>Boption>
option>Coption>
select>
br>
span>Selected: {{ selected }}span>
div>
|
|
new Vue({
el: '#example-6',
data: {
selected: []
}
})
|
A?B?C?
Selected: []
用?`v-for`?渲染的動態選項:
|
select v-model="selected">
option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
option>
select>
span>Selected: {{ selected }}span>
|
|
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})
|
?? ?? ?One? ? ??? ?? ?Two? ? ??? ?? ?Three? ? ??Selected: A
## [值綁定](https://cn.vuejs.org/v2/guide/forms.html#%E5%80%BC%E7%BB%91%E5%AE%9A "值綁定")
對于單選按鈕,復選框及選擇框的選項,`v-model`?綁定的值通常是靜態字符串 (對于復選框也可以是布爾值):
|
input type="radio" v-model="picked" value="a">
input type="checkbox" v-model="toggle">
select v-model="selected">
option value="abc">ABCoption>
select>
|
但是有時我們可能想把值綁定到 Vue 實例的一個動態屬性上,這時可以用?`v-bind`?實現,并且這個屬性的值可以不是字符串。
### [復選框](https://cn.vuejs.org/v2/guide/forms.html#%E5%A4%8D%E9%80%89%E6%A1%86-1 "復選框")
|
input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
|
|
// 當選中時
vm.toggle === 'yes'
// 當沒有選中時
vm.toggle === 'no'
|
這里的?`true-value`?和?`false-value`?特性并不會影響輸入控件的?`value`?特性,因為瀏覽器在提交表單時并不會包含未被選中的復選框。如果要確保表單中這兩個值中的一個能夠被提交,(比如“yes”或“no”),請換用單選按鈕。
### [單選按鈕](https://cn.vuejs.org/v2/guide/forms.html#%E5%8D%95%E9%80%89%E6%8C%89%E9%92%AE-1 "單選按鈕")
|
input type="radio" v-model="pick" v-bind:value="a">
|
|
// 當選中時
vm.pick === vm.a
|
### [選擇框的選項](https://cn.vuejs.org/v2/guide/forms.html#%E9%80%89%E6%8B%A9%E6%A1%86%E7%9A%84%E9%80%89%E9%A1%B9 "選擇框的選項")
|
select v-model="selected">
option v-bind:value="{ number: 123 }">123option>
select>
|
|
// 當選中時
typeof vm.selected // => 'object'
vm.selected.number // => 123
|
## [修飾符](https://cn.vuejs.org/v2/guide/forms.html#%E4%BF%AE%E9%A5%B0%E7%AC%A6 "修飾符")
### [`.lazy`](https://cn.vuejs.org/v2/guide/forms.html#lazy ".lazy")
在默認情況下,`v-model`?在每次?`input`?事件觸發后將輸入框的值與數據進行同步 (除了[上述](https://cn.vuejs.org/v2/guide/forms.html#vmodel-ime-tip)輸入法組合文字時)。你可以添加?`lazy`?修飾符,從而轉變為使用?`change`事件進行同步:
|
input v-model.lazy="msg" >
|
### [`.number`](https://cn.vuejs.org/v2/guide/forms.html#number ".number")
如果想自動將用戶的輸入值轉為數值類型,可以給?`v-model`?添加?`number`?修飾符:
|
input v-model.number="age" type="number">
|
這通常很有用,因為即使在?`type="number"`?時,HTML 輸入元素的值也總會返回字符串。如果這個值無法被?`parseFloat()`?解析,則會返回原始的值。
### [`.trim`](https://cn.vuejs.org/v2/guide/forms.html#trim ".trim")
如果要自動過濾用戶輸入的首尾空白字符,可以給?`v-model`?添加?`trim`?修飾符:
|
input v-model.trim="msg">
|
## [在組件上使用?`v-model`](https://cn.vuejs.org/v2/guide/forms.html#%E5%9C%A8%E7%BB%84%E4%BB%B6%E4%B8%8A%E4%BD%BF%E7%94%A8-v-model "在組件上使用 v-model")
> 如果你還不熟悉 Vue 的組件,可以暫且跳過這里。
HTML 原生的輸入元素類型并不總能滿足需求。幸好,Vue 的組件系統允許你創建具有完全自定義行為且可復用的輸入組件。這些輸入組件甚至可以和?`v-model`?一起使用!要了解更多,請參閱組件指南中的[自定義輸入組件](https://cn.vuejs.org/v2/guide/components.html#%E5%9C%A8%E7%BB%84%E4%BB%B6%E4%B8%8A%E4%BD%BF%E7%94%A8-v-model)。
# 組件基礎
## [基本示例](https://cn.vuejs.org/v2/guide/components.html#%E5%9F%BA%E6%9C%AC%E7%A4%BA%E4%BE%8B "基本示例")
這里有一個 Vue 組件的示例:
|
// 定義一個名為 button-counter 的新組件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: 'You clicked me {{ count }} times.'
})
|
組件是可復用的 Vue 實例,且帶有一個名字:在這個例子中是?`<button-counter>`。我們可以在一個通過?`new Vue`?創建的 Vue 根實例中,把這個組件作為自定義元素來使用:
|
div id="components-demo">
button-counter>button-counter>
div>
|
|
new Vue({ el: '#components-demo' })
|
You clicked me 0 times.
因為組件是可復用的 Vue 實例,所以它們與?`new Vue`?接收相同的選項,例如?`data`、`computed`、`watch`、`methods`?以及生命周期鉤子等。僅有的例外是像?`el`這樣根實例特有的選項。
## [組件的復用](https://cn.vuejs.org/v2/guide/components.html#%E7%BB%84%E4%BB%B6%E7%9A%84%E5%A4%8D%E7%94%A8 "組件的復用")
你可以將組件進行任意次數的復用:
|
div id="components-demo">
button-counter>button-counter>
button-counter>button-counter>
button-counter>button-counter>
div>
|
You clicked me 0 times.?You clicked me 0 times.?You clicked me 0 times.
注意當點擊按鈕時,每個組件都會各自獨立維護它的?`count`。因為你每用一次組件,就會有一個它的新實例被創建。
### [`data`?必須是一個函數](https://cn.vuejs.org/v2/guide/components.html#data-%E5%BF%85%E9%A1%BB%E6%98%AF%E4%B8%80%E4%B8%AA%E5%87%BD%E6%95%B0 "data 必須是一個函數")
當我們定義這個?`<button-counter>`?組件時,你可能會發現它的?`data`?并不是像這樣直接提供一個對象:
|
data: {
count: 0
}
|
取而代之的是,一個組件的?`data`?選項必須是一個函數,因此每個實例可以維護一份被返回對象的獨立的拷貝:
|
data: function () {
return {
count: 0
}
}
|
如果 Vue 沒有這條規則,點擊一個按鈕就可能會像如下代碼一樣影響到*其它所有實例*:
You clicked me 0 times.?You clicked me 0 times.?You clicked me 0 times.
## [組件的組織](https://cn.vuejs.org/v2/guide/components.html#%E7%BB%84%E4%BB%B6%E7%9A%84%E7%BB%84%E7%BB%87 "組件的組織")
通常一個應用會以一棵嵌套的組件樹的形式來組織:

例如,你可能會有頁頭、側邊欄、內容區等組件,每個組件又包含了其它的像導航鏈接、博文之類的組件。
為了能在模板中使用,這些組件必須先注冊以便 Vue 能夠識別。這里有兩種組件的注冊類型:全局注冊和局部注冊。至此,我們的組件都只是通過?`Vue.component`?全局注冊的:
|
Vue.component('my-component-name', {
// ... options ...
})
|
全局注冊的組件可以用在其被注冊之后的任何 (通過?`new Vue`) 新創建的 Vue 根實例,也包括其組件樹中的所有子組件的模板中。
到目前為止,關于組件注冊你需要了解的就這些了,如果你閱讀完本頁內容并掌握了它的內容,我們會推薦你再回來把[組件注冊](https://cn.vuejs.org/v2/guide/components-registration.html)讀完。
## [通過 Prop 向子組件傳遞數據](https://cn.vuejs.org/v2/guide/components.html#%E9%80%9A%E8%BF%87-Prop-%E5%90%91%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BC%A0%E9%80%92%E6%95%B0%E6%8D%AE "通過 Prop 向子組件傳遞數據")
早些時候,我們提到了創建一個博文組件的事情。問題是如果你不能向這個組件傳遞某一篇博文的標題或內容之類的我們想展示的數據的話,它是沒有辦法使用的。這也正是 prop 的由來。
Prop 是你可以在組件上注冊的一些自定義特性。當一個值傳遞給一個 prop 特性的時候,它就變成了那個組件實例的一個屬性。為了給博文組件傳遞一個標題,我們可以用一個?`props`?選項將其包含在該組件可接受的 prop 列表中:
|
Vue.component('blog-post', {
props: ['title'],
template: '{{ title }}'
})
|
一個組件默認可以擁有任意數量的 prop,任何值都可以傳遞給任何 prop。在上述模板中,你會發現我們能夠在組件實例中訪問這個值,就像訪問?`data`?中的值一樣。
一個 prop 被注冊之后,你就可以像這樣把數據作為一個自定義特性傳遞進來:
|
blog-post title="My journey with Vue">blog-post>
blog-post title="Blogging with Vue">blog-post>
blog-post title="Why Vue is so fun">blog-post>
|
### My journey with Vue
### Blogging with Vue
### Why Vue is so fun
然而在一個典型的應用中,你可能在?`data`?里有一個博文的數組:
|
new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
})
|
并想要為每篇博文渲染一個組件:
|
blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
>blog-post>
|
如上所示,你會發現我們可以使用?`v-bind`?來動態傳遞 prop。這在你一開始不清楚要渲染的具體內容,比如[從一個 API 獲取博文列表](https://jsfiddle.net/chrisvfritz/sbLgr0ad)的時候,是非常有用的。
到目前為止,關于 prop 你需要了解的大概就這些了,如果你閱讀完本頁內容并掌握了它的內容,我們會推薦你再回來把?[prop](https://cn.vuejs.org/v2/guide/components-props.html)?讀完。
## [單個根元素](https://cn.vuejs.org/v2/guide/components.html#%E5%8D%95%E4%B8%AA%E6%A0%B9%E5%85%83%E7%B4%A0 "單個根元素")
當構建一個?`<blog-post>`?組件時,你的模板最終會包含的東西遠不止一個標題:
|
h3>{{ title }}h3>
|
最最起碼,你會包含這篇博文的正文:
|
h3>{{ title }}h3>
div v-html="content">div>
|
然而如果你在模板中嘗試這樣寫,Vue 會顯示一個錯誤,并解釋道?every component must have a single root element (每個組件必須只有一個根元素)。你可以將模板的內容包裹在一個父元素內,來修復這個問題,例如:
|
div class="blog-post">
h3>{{ title }}h3>
div v-html="content">div>
div>
|
看起來當組件變得越來越復雜的時候,我們的博文不只需要標題和內容,還需要發布日期、評論等等。為每個相關的信息定義一個 prop 會變得很麻煩:
|
blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
>blog-post>
|
所以是時候重構一下這個?`<blog-post>`?組件了,讓它變成接受一個單獨的?`post`prop:
|
blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
>blog-post>
|
|
Vue.component('blog-post', {
props: ['post'],
template: `
{{ post.title }}
`
})
|
上述的這個和一些接下來的示例使用了 JavaScript 的[模板字符串](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals)來讓多行的模板更易讀。它們在 IE 下并沒有被支持,所以如果你需要在不 (經過 Babel 或 TypeScript 之類的工具) 編譯的情況下支持 IE,請使用[折行轉義字符](https://css-tricks.com/snippets/javascript/multiline-string-variables-in-javascript/)取而代之。
現在,不論何時為?`post`?對象添加一個新的屬性,它都會自動地在?`<blog-post>`?內可用。
## [通過事件向父級組件發送消息](https://cn.vuejs.org/v2/guide/components.html#%E9%80%9A%E8%BF%87%E4%BA%8B%E4%BB%B6%E5%90%91%E7%88%B6%E7%BA%A7%E7%BB%84%E4%BB%B6%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF "通過事件向父級組件發送消息")
在我們開發?`<blog-post>`?組件時,它的一些功能可能要求我們和父級組件進行溝通。例如我們可能會引入一個可訪問性的功能來放大博文的字號,同時讓頁面的其它部分保持默認的字號。
在其父組件中,我們可以通過添加一個?`postFontSize`?數據屬性來支持這個功能:
|
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [/* ... */],
postFontSize: 1
}
})
|
它可以在模板中用來控制所有博文的字號:
|
div id="blog-posts-events-demo">
div :style="{ fontSize: postFontSize + 'em' }">
blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
>blog-post>
div>
div>
|
現在我們在每篇博文正文之前添加一個按鈕來放大字號:
|
Vue.component('blog-post', {
props: ['post'],
template: `
{{ post.title }}
Enlarge text
`
})
|
問題是這個按鈕不會做任何事:
|
button>
Enlarge text
button>
|
當點擊這個按鈕時,我們需要告訴父級組件放大所有博文的文本。幸好 Vue 實例提供了一個自定義事件的系統來解決這個問題。我們可以調用內建的?[`$emit`?方法](https://cn.vuejs.org/v2/api/#vm-emit)并傳入事件的名字,來向父級組件觸發一個事件:
|
button v-on:click="$emit('enlarge-text')">
Enlarge text
button>
|
然后我們可以用?`v-on`?在博文組件上監聽這個事件,就像監聽一個原生 DOM 事件一樣:
|
blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
>blog-post>
|
### My journey with Vue
Enlarge text
...content...
### Blogging with Vue
Enlarge text
...content...
### Why Vue is so fun
Enlarge text
...content...
### [使用事件拋出一個值](https://cn.vuejs.org/v2/guide/components.html#%E4%BD%BF%E7%94%A8%E4%BA%8B%E4%BB%B6%E6%8A%9B%E5%87%BA%E4%B8%80%E4%B8%AA%E5%80%BC "使用事件拋出一個值")
有的時候用一個事件來拋出一個特定的值是非常有用的。例如我們可能想讓?`<blog-post>`?組件決定它的文本要放大多少。這時可以使用?`$emit`?的第二個參數來提供這個值:
|
button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
button>
|
然后當在父級組件監聽這個事件的時候,我們可以通過?`$event`?訪問到被拋出的這個值:
|
blog-post
...
v-on:enlarge-text="postFontSize += $event"
>blog-post>
|
或者,如果這個事件處理函數是一個方法:
|
blog-post
...
v-on:enlarge-text="onEnlargeText"
>blog-post>
|
那么這個值將會作為第一個參數傳入這個方法:
|
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
|
### [在組件上使用?`v-model`](https://cn.vuejs.org/v2/guide/components.html#%E5%9C%A8%E7%BB%84%E4%BB%B6%E4%B8%8A%E4%BD%BF%E7%94%A8-v-model "在組件上使用 v-model")
自定義事件也可以用于創建支持?`v-model`?的自定義輸入組件。記住:
|
input v-model="searchText">
|
等價于:
|
input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
|
當用在組件上時,`v-model`?則會這樣:
|
custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
>custom-input>
|
為了讓它正常工作,這個組件內的?`<input>`?必須:
* 將其?`value`?特性綁定到一個名叫?`value`?的 prop 上
* 在其?`input`?事件被觸發時,將新的值通過自定義的?`input`?事件拋出
寫成代碼之后是這樣的:
|
Vue.component('custom-input', {
props: ['value'],
template: `
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
|
現在?`v-model`?就應該可以在這個組件上完美地工作起來了:
|
custom-input v-model="searchText">custom-input>
|
到目前為止,關于組件自定義事件你需要了解的大概就這些了,如果你閱讀完本頁內容并掌握了它的內容,我們會推薦你再回來把[自定義事件](https://cn.vuejs.org/v2/guide/components-custom-events.html)讀完。
## [通過插槽分發內容](https://cn.vuejs.org/v2/guide/components.html#%E9%80%9A%E8%BF%87%E6%8F%92%E6%A7%BD%E5%88%86%E5%8F%91%E5%86%85%E5%AE%B9 "通過插槽分發內容")
和 HTML 元素一樣,我們經常需要向一個組件傳遞內容,像這樣:
|
alert-box>
Something bad happened.
alert-box>
|
可能會渲染出這樣的東西:
Error!?Something bad happened.
幸好,Vue 自定義的?`<slot>`?元素讓這變得非常簡單:
|
Vue.component('alert-box', {
template: `
Error!
`
})
|
如你所見,我們只要在需要的地方加入插槽就行了——就這么簡單!
到目前為止,關于插槽你需要了解的大概就這些了,如果你閱讀完本頁內容并掌握了它的內容,我們會推薦你再回來把[插槽](https://cn.vuejs.org/v2/guide/components-slots.html)讀完。
## [動態組件](https://cn.vuejs.org/v2/guide/components.html#%E5%8A%A8%E6%80%81%E7%BB%84%E4%BB%B6 "動態組件")
有的時候,在不同組件之間進行動態切換是非常有用的,比如在一個多標簽的界面里:
HomePostsArchive
Home component
上述內容可以通過 Vue 的?`<component>`?元素加一個特殊的?`is`?特性來實現:
|
component v-bind:is="currentTabComponent">component>
|
在上述示例中,`currentTabComponent`?可以包括
* 已注冊組件的名字,或
* 一個組件的選項對象
你可以在[這里](https://jsfiddle.net/chrisvfritz/o3nycadu/)查閱并體驗完整的代碼,或在[這個版本](https://jsfiddle.net/chrisvfritz/b2qj69o1/)了解綁定組件選項對象,而不是已注冊組件名的示例。
到目前為止,關于動態組件你需要了解的大概就這些了,如果你閱讀完本頁內容并掌握了它的內容,我們會推薦你再回來把[動態和異步組件](https://cn.vuejs.org/v2/guide/components-dynamic-async.html)讀完。
## [解析 DOM 模板時的注意事項](https://cn.vuejs.org/v2/guide/components.html#%E8%A7%A3%E6%9E%90-DOM-%E6%A8%A1%E6%9D%BF%E6%97%B6%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9 "解析 DOM 模板時的注意事項")
有些 HTML 元素,諸如?`<ul>`、`<ol>`、`<table>`?和?`<select>`,對于哪些元素可以出現在其內部是有嚴格限制的。而有些元素,諸如?`<li>`、`<tr>`?和?`<option>`,只能出現在其它某些特定的元素內部。
這會導致我們使用這些有約束條件的元素時遇到一些問題。例如:
|
table>
blog-post-row>blog-post-row>
table>
|
這個自定義組件?`<blog-post-row>`?會被作為無效的內容提升到外部,并導致最終渲染結果出錯。幸好這個特殊的?`is`?特性給了我們一個變通的辦法:
|
table>
tr is="blog-post-row">tr>
table>
|
需要注意的是如果我們從以下來源使用模板的話,這條限制是*不存在*的:
* 字符串 (例如:`template: '...'`)
* [單文件組件 (`.vue`)](https://cn.vuejs.org/v2/guide/single-file-components.html)
* [`<script type="text/x-template">`](https://cn.vuejs.org/v2/guide/components-edge-cases.html#X-Templates)
到這里,你需要了解的解析 DOM 模板時的注意事項——實際上也是 Vue 的全部*必要內容*,大概就是這些了。恭喜你!接下來還有很多東西要去學習,不過首先,我們推薦你先休息一下,試用一下 Vue,自己隨意做些好玩的東西。
如果你感覺已經掌握了這些知識,我們推薦你再回來把完整的組件指南,包括側邊欄中組件深入章節的所有頁面讀完。
# 組件注冊
> 該頁面假設你已經閱讀過了[組件基礎](https://cn.vuejs.org/v2/guide/components.html)。如果你還對組件不太了解,推薦你先閱讀它。
## [組件名](https://cn.vuejs.org/v2/guide/components-registration.html#%E7%BB%84%E4%BB%B6%E5%90%8D "組件名")
在注冊一個組件的時候,我們始終需要給它一個名字。比如在全局注冊的時候我們已經看到了:
|
Vue.component('my-component-name', { /* ... */ })
|
該組件名就是?`Vue.component`?的第一個參數。
你給予組件的名字可能依賴于你打算拿它來做什么。當直接在 DOM 中使用一個組件 (而不是在字符串模板或[單文件組件](https://cn.vuejs.org/v2/guide/single-file-components.html)) 的時候,我們強烈推薦遵循?[W3C 規范](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name)中的自定義組件名 (字母全小寫且必須包含一個連字符)。這會幫助你避免和當前以及未來的 HTML 元素相沖突。
你可以在[風格指南](https://cn.vuejs.org/v2/style-guide/#%E5%9F%BA%E7%A1%80%E7%BB%84%E4%BB%B6%E5%90%8D-%E5%BC%BA%E7%83%88%E6%8E%A8%E8%8D%90)中查閱到關于組件名的其它建議。
### [組件名大小寫](https://cn.vuejs.org/v2/guide/components-registration.html#%E7%BB%84%E4%BB%B6%E5%90%8D%E5%A4%A7%E5%B0%8F%E5%86%99 "組件名大小寫")
定義組件名的方式有兩種:
#### [](https://cn.vuejs.org/v2/guide/components-registration.html#%E4%BD%BF%E7%94%A8-kebab-case "使用 kebab-case")使用 kebab-case
|
Vue.component('my-component-name', { /* ... */ })
|
當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,例如?`<my-component-name>`。
#### [](https://cn.vuejs.org/v2/guide/components-registration.html#%E4%BD%BF%E7%94%A8-PascalCase "使用 PascalCase")使用 PascalCase
|
Vue.component('MyComponentName', { /* ... */ })
|
當使用 PascalCase (駝峰式命名) 定義一個組件時,你在引用這個自定義元素時兩種命名法都可以使用。也就是說?`<my-component-name>`?和?`<MyComponentName>`?都是可接受的。注意,盡管如此,直接在 DOM (即非字符串的模板) 中使用時只有 kebab-case 是有效的。
## [全局注冊](https://cn.vuejs.org/v2/guide/components-registration.html#%E5%85%A8%E5%B1%80%E6%B3%A8%E5%86%8C "全局注冊")
到目前為止,我們只用過?`Vue.component`?來創建組件:
|
Vue.component('my-component-name', {
// ... 選項 ...
})
|
這些組件是全局注冊的。也就是說它們在注冊之后可以用在任何新創建的 Vue 根實例 (`new Vue`) 的模板中。比如:
|
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
new Vue({ el: '#app' })
|
|
div id="app">
component-a>component-a>
component-b>component-b>
component-c>component-c>
div>
|
在所有子組件中也是如此,也就是說這三個組件*在各自內部*也都可以相互使用。
## [局部注冊](https://cn.vuejs.org/v2/guide/components-registration.html#%E5%B1%80%E9%83%A8%E6%B3%A8%E5%86%8C "局部注冊")
全局注冊往往是不夠理想的。比如,如果你使用一個像 webpack 這樣的構建系統,全局注冊所有的組件意味著即便你已經不再使用一個組件了,它仍然會被包含在你最終的構建結果中。這造成了用戶下載的 JavaScript 的無謂的增加。
在這些情況下,你可以通過一個普通的 JavaScript 對象來定義組件:
|
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
|
然后在?`components`?選項中定義你想要使用的組件:
|
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
|
對于?`components`?對象中的每個屬性來說,其屬性名就是自定義元素的名字,其屬性值就是這個組件的選項對象。
注意局部注冊的組件在其子組件中*不可用*。例如,如果你希望?`ComponentA`?在?`ComponentB`?中可用,則你需要這樣寫:
|
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
|
或者如果你通過 Babel 和 webpack 使用 ES2015 模塊,那么代碼看起來更像:
|
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}
|
注意在 ES2015+ 中,在對象中放一個類似?`ComponentA`?的變量名其實是?`ComponentA: ComponentA`?的縮寫,即這個變量名同時是:
* 用在模板中的自定義元素的名稱
* 包含了這個組件選項的變量名
## [模塊系統](https://cn.vuejs.org/v2/guide/components-registration.html#%E6%A8%A1%E5%9D%97%E7%B3%BB%E7%BB%9F "模塊系統")
如果你沒有通過?`import`/`require`?使用一個模塊系統,也許可以暫且跳過這個章節。如果你使用了,那么我們會為你提供一些特殊的使用說明和注意事項。
### [在模塊系統中局部注冊](https://cn.vuejs.org/v2/guide/components-registration.html#%E5%9C%A8%E6%A8%A1%E5%9D%97%E7%B3%BB%E7%BB%9F%E4%B8%AD%E5%B1%80%E9%83%A8%E6%B3%A8%E5%86%8C "在模塊系統中局部注冊")
如果你還在閱讀,說明你使用了諸如 Babel 和 webpack 的模塊系統。在這些情況下,我們推薦創建一個?`components`?目錄,并將每個組件放置在其各自的文件中。
然后你需要在局部注冊之前導入每個你想使用的組件。例如,在一個假設的?`ComponentB.js`?或?`ComponentB.vue`?文件中:
|
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
|
現在?`ComponentA`?和?`ComponentC`?都可以在?`ComponentB`?的模板中使用了。
### [基礎組件的自動化全局注冊](https://cn.vuejs.org/v2/guide/components-registration.html#%E5%9F%BA%E7%A1%80%E7%BB%84%E4%BB%B6%E7%9A%84%E8%87%AA%E5%8A%A8%E5%8C%96%E5%85%A8%E5%B1%80%E6%B3%A8%E5%86%8C "基礎組件的自動化全局注冊")
可能你的許多組件只是包裹了一個輸入框或按鈕之類的元素,是相對通用的。我們有時候會把它們稱為[基礎組件](https://cn.vuejs.org/v2/style-guide/#%E5%9F%BA%E7%A1%80%E7%BB%84%E4%BB%B6%E5%90%8D-%E5%BC%BA%E7%83%88%E6%8E%A8%E8%8D%90),它們會在各個組件中被頻繁的用到。
所以會導致很多組件里都會有一個包含基礎組件的長列表:
|
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}
|
而只是用于模板中的一小部分:
|
BaseInput
v-model="searchText"
@keydown.enter="search"
/>
BaseButton @click="search">
BaseIcon name="search"/>
BaseButton>
|
幸好如果你使用了 webpack (或在內部使用了 webpack 的?[Vue CLI 3+](https://github.com/vuejs/vue-cli)),那么就可以使用?`require.context`?只全局注冊這些非常通用的基礎組件。這里有一份可以讓你在應用入口文件 (比如?`src/main.js`) 中全局導入基礎組件的示例代碼:
|
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其組件目錄的相對路徑
'./components',
// 是否查詢其子目錄
false,
// 匹配基礎組件文件名的正則表達式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 獲取組件配置
const componentConfig = requireComponent(fileName)
// 獲取組件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 剝去文件名開頭的 `./` 和結尾的擴展名
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
)
// 全局注冊組件
Vue.component(
componentName,
// 如果這個組件選項是通過 `export default` 導出的,
// 那么就會優先使用 `.default`,
// 否則回退到使用模塊的根。
componentConfig.default || componentConfig
)
})
|
記住全局注冊的行為必須在根 Vue 實例 (通過?`new Vue`) 創建之前發生。[這里](https://github.com/chrisvfritz/vue-enterprise-boilerplate/blob/master/src/components/_globals.js)有一個真實項目情景下的示例。
# Prop
> 該頁面假設你已經閱讀過了[組件基礎](https://cn.vuejs.org/v2/guide/components.html)。如果你還對組件不太了解,推薦你先閱讀它。
## [Prop 的大小寫 (camelCase vs kebab-case)](https://cn.vuejs.org/v2/guide/components-props.html#Prop-%E7%9A%84%E5%A4%A7%E5%B0%8F%E5%86%99-camelCase-vs-kebab-case "Prop 的大小寫 (camelCase vs kebab-case)")
HTML 中的特性名是大小寫不敏感的,所以瀏覽器會把所有大寫字符解釋為小寫字符。這意味著當你使用 DOM 中的模板時,camelCase (駝峰命名法) 的 prop 名需要使用其等價的 kebab-case (短橫線分隔命名) 命名:
|
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '{{ postTitle }}'
})
|
|
blog-post post-title="hello!">blog-post>
|
重申一次,如果你使用字符串模板,那么這個限制就不存在了。
## [Prop 類型](https://cn.vuejs.org/v2/guide/components-props.html#Prop-%E7%B1%BB%E5%9E%8B "Prop 類型")
到這里,我們只看到了以字符串數組形式列出的 prop:
|
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
|
但是,通常你希望每個 prop 都有指定的值類型。這時,你可以以對象形式列出 prop,這些屬性的名稱和值分別是 prop 各自的名稱和類型:
|
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object
}
|
這不僅為你的組件提供了文檔,還會在它們遇到錯誤的類型時從瀏覽器的 JavaScript 控制臺提示用戶。你會在這個頁面接下來的部分看到[類型檢查和其它 prop 驗證](https://cn.vuejs.org/v2/guide/components-props.html#Prop-%E9%AA%8C%E8%AF%81)。
## [傳遞靜態或動態 Prop](https://cn.vuejs.org/v2/guide/components-props.html#%E4%BC%A0%E9%80%92%E9%9D%99%E6%80%81%E6%88%96%E5%8A%A8%E6%80%81-Prop "傳遞靜態或動態 Prop")
像這樣,你已經知道了可以像這樣給 prop 傳入一個靜態的值:
|
blog-post title="My journey with Vue">blog-post>
|
你也知道 prop 可以通過?`v-bind`?動態賦值,例如:
|
blog-post v-bind:title="post.title">blog-post>
blog-post v-bind:title="post.title + ' by ' + post.author.name">blog-post>
|
在上述兩個示例中,我們傳入的值都是字符串類型的,但實際上*任何*類型的值都可以傳給一個 prop。
### [傳入一個數字](https://cn.vuejs.org/v2/guide/components-props.html#%E4%BC%A0%E5%85%A5%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97 "傳入一個數字")
|
blog-post v-bind:likes="42">blog-post>
blog-post v-bind:likes="post.likes">blog-post>
|
### [傳入一個布爾值](https://cn.vuejs.org/v2/guide/components-props.html#%E4%BC%A0%E5%85%A5%E4%B8%80%E4%B8%AA%E5%B8%83%E5%B0%94%E5%80%BC "傳入一個布爾值")
|
blog-post is-published>blog-post>
blog-post v-bind:is-published="false">blog-post>
blog-post v-bind:is-published="post.isPublished">blog-post>
|
### [傳入一個數組](https://cn.vuejs.org/v2/guide/components-props.html#%E4%BC%A0%E5%85%A5%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84 "傳入一個數組")
|
blog-post v-bind:comment-ids="[234, 266, 273]">blog-post>
blog-post v-bind:comment-ids="post.commentIds">blog-post>
|
### [傳入一個對象](https://cn.vuejs.org/v2/guide/components-props.html#%E4%BC%A0%E5%85%A5%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%B1%A1 "傳入一個對象")
|
blog-post v-bind:author="{ name: 'Veronica', company: 'Veridian Dynamics' }">blog-post>
blog-post v-bind:author="post.author">blog-post>
|
### [傳入一個對象的所有屬性](https://cn.vuejs.org/v2/guide/components-props.html#%E4%BC%A0%E5%85%A5%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%89%80%E6%9C%89%E5%B1%9E%E6%80%A7 "傳入一個對象的所有屬性")
如果你想要將一個對象的所有屬性都作為 prop 傳入,你可以使用不帶參數的?`v-bind`(取代?`v-bind:prop-name`)。例如,對于一個給定的對象?`post`:
|
post: {
id: 1,
title: 'My Journey with Vue'
}
|
下面的模板:
|
blog-post v-bind="post">blog-post>
|
等價于:
|
blog-post
v-bind:id="post.id"
v-bind:title="post.title"
>blog-post>
|
## [單向數據流](https://cn.vuejs.org/v2/guide/components-props.html#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81 "單向數據流")
所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。
額外的,每次父級組件發生更新時,子組件中所有的 prop 都將會刷新為最新的值。這意味著你不應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制臺中發出警告。
這里有兩種常見的試圖改變一個 prop 的情形:
1. 這個 prop 用來傳遞一個初始值;這個子組件接下來希望將其作為一個本地的 prop 數據來使用。在這種情況下,最好定義一個本地的 data 屬性并將這個 prop 用作其初始值:
|
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
|
2. 這個 prop 以一種原始的值傳入且需要進行轉換。在這種情況下,最好使用這個 prop 的值來定義一個計算屬性:
|
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
|
注意在 JavaScript 中對象和數組是通過引用傳入的,所以對于一個數組或對象類型的 prop 來說,在子組件中改變這個對象或數組本身將會影響到父組件的狀態。
## [Prop 驗證](https://cn.vuejs.org/v2/guide/components-props.html#Prop-%E9%AA%8C%E8%AF%81 "Prop 驗證")
我們可以為組件的 prop 指定驗證要求,例如你知道的這些類型。如果有一個需求沒有被滿足,則 Vue 會在瀏覽器控制臺中警告你。這在開發一個會被別人用到的組件時尤其有幫助。
為了定制 prop 的驗證方式,你可以為?`props`?中的值提供一個帶有驗證需求的對象,而不是一個字符串數組。例如:
|
Vue.component('my-component', {
props: {
// 基礎的類型檢查 (`null` 匹配任何類型)
propA: Number,
// 多個可能的類型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 帶有默認值的數字
propD: {
type: Number,
default: 100
},
// 帶有默認值的對象
propE: {
type: Object,
// 對象或數組默認值必須從一個工廠函數獲取
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數
propF: {
validator: function (value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
|
當 prop 驗證失敗的時候,(開發環境構建版本的) Vue 將會產生一個控制臺的警告。
注意那些 prop 會在一個組件實例創建之前進行驗證,所以實例的屬性 (如?`data`、`computed`?等) 在?`default`?或?`validator`?函數中是不可用的。
### [類型檢查](https://cn.vuejs.org/v2/guide/components-props.html#%E7%B1%BB%E5%9E%8B%E6%A3%80%E6%9F%A5 "類型檢查")
`type`?可以是下列原生構造函數中的一個:
* `String`
* `Number`
* `Boolean`
* `Array`
* `Object`
* `Date`
* `Function`
* `Symbol`
額外的,`type`?還可以是一個自定義的構造函數,并且通過?`instanceof`?來進行檢查確認。例如,給定下列現成的構造函數:
|
function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
|
你可以使用:
|
Vue.component('blog-post', {
props: {
author: Person
}
})
|
來驗證?`author`?prop 的值是否是通過?`new Person`?創建的。
## [非 Prop 的特性](https://cn.vuejs.org/v2/guide/components-props.html#%E9%9D%9E-Prop-%E7%9A%84%E7%89%B9%E6%80%A7 "非 Prop 的特性")
一個非 prop 特性是指傳向一個組件,但是該組件并沒有相應 prop 定義的特性。
因為顯式定義的 prop 適用于向一個子組件傳入信息,然而組件庫的作者并不總能預見組件會被用于怎樣的場景。這也是為什么組件可以接受任意的特性,而這些特性會被添加到這個組件的根元素上。
例如,想象一下你通過一個 Bootstrap 插件使用了一個第三方的?`<bootstrap-date-input>`?組件,這個插件需要在其?`<input>`?上用到一個?`data-date-picker`?特性。我們可以將這個特性添加到你的組件實例上:
|
bootstrap-date-input data-date-picker="activated">bootstrap-date-input>
|
然后這個?`data-date-picker="activated"`?特性就會自動添加到?`<bootstrap-date-input>`?的根元素上。
### [替換/合并已有的特性](https://cn.vuejs.org/v2/guide/components-props.html#%E6%9B%BF%E6%8D%A2-%E5%90%88%E5%B9%B6%E5%B7%B2%E6%9C%89%E7%9A%84%E7%89%B9%E6%80%A7 "替換/合并已有的特性")
想象一下?`<bootstrap-date-input>`?的模板是這樣的:
|
input type="date" class="form-control">
|
為了給我們的日期選擇器插件定制一個主題,我們可能需要像這樣添加一個特別的類名:
|
bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
>bootstrap-date-input>
|
在這種情況下,我們定義了兩個不同的?`class`?的值:
* `form-control`,這是在組件的模板內設置好的
* `date-picker-theme-dark`,這是從組件的父級傳入的
對于絕大多數特性來說,從外部提供給組件的值會替換掉組件內部設置好的值。所以如果傳入?`type="text"`?就會替換掉?`type="date"`?并把它破壞!慶幸的是,`class`和?`style`?特性會稍微智能一些,即兩邊的值會被合并起來,從而得到最終的值:`form-control date-picker-theme-dark`。
### [禁用特性繼承](https://cn.vuejs.org/v2/guide/components-props.html#%E7%A6%81%E7%94%A8%E7%89%B9%E6%80%A7%E7%BB%A7%E6%89%BF "禁用特性繼承")
如果你不希望組件的根元素繼承特性,你可以在組件的選項中設置?`inheritAttrs: false`。例如:
|
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
|
這尤其適合配合實例的?`$attrs`?屬性使用,該屬性包含了傳遞給一個組件的特性名和特性值,例如:
|
{
class: 'username-input',
placeholder: 'Enter your username'
}
|
有了?`inheritAttrs: false`?和?`$attrs`,你就可以手動決定這些特性會被賦予哪個元素。在撰寫[基礎組件](https://cn.vuejs.org/v2/style-guide/#%E5%9F%BA%E7%A1%80%E7%BB%84%E4%BB%B6%E5%90%8D-%E5%BC%BA%E7%83%88%E6%8E%A8%E8%8D%90)的時候是常會用到的:
|
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
{{ label }}
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
|
這個模式允許你在使用基礎組件的時候更像是使用原始的 HTML 元素,而不會擔心哪個元素是真正的根元素:
|
base-input
v-model="username"
class="username-input"
placeholder="Enter your username"
>base-input>
|
# 自定義事件
> 該頁面假設你已經閱讀過了[組件基礎](https://cn.vuejs.org/v2/guide/components.html)。如果你還對組件不太了解,推薦你先閱讀它。
## [事件名](https://cn.vuejs.org/v2/guide/components-custom-events.html#%E4%BA%8B%E4%BB%B6%E5%90%8D "事件名")
不同于組件和 prop,事件名不存在任何自動化的大小寫轉換。而是觸發的事件名需要完全匹配監聽這個事件所用的名稱。舉個例子,如果觸發一個 camelCase 名字的事件:
|
this.$emit('myEvent')
|
則監聽這個名字的 kebab-case 版本是不會有任何效果的:
|
my-component v-on:my-event="doSomething">my-component>
|
不同于組件和 prop,事件名不會被用作一個 JavaScript 變量名或屬性名,所以就沒有理由使用 camelCase 或 PascalCase 了。并且?`v-on`?事件監聽器在 DOM 模板中會被自動轉換為全小寫 (因為 HTML 是大小寫不敏感的),所以?`v-on:myEvent`?將會變成?`v-on:myevent`——導致?`myEvent`?不可能被監聽到。
因此,我們推薦你始終使用 kebab-case 的事件名。
## [自定義組件的?`v-model`](https://cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model "自定義組件的 v-model")
> 2.2.0+ 新增
一個組件上的?`v-model`?默認會利用名為?`value`?的 prop 和名為?`input`?的事件,但是像單選框、復選框等類型的輸入控件可能會將?`value`?特性用于[不同的目的](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#Value)。`model`?選項可以用來避免這樣的沖突:
|
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
|
現在在這個組件上使用?`v-model`?的時候:
|
base-checkbox v-model="lovingVue">base-checkbox>
|
這里的?`lovingVue`?的值將會傳入這個名為?`checked`?的 prop。同時當?`<base-checkbox>`?觸發一個?`change`?事件并附帶一個新的值的時候,這個?`lovingVue`?的屬性將會被更新。
注意你仍然需要在組件的?`props`?選項里聲明?`checked`?這個 prop。
## [將原生事件綁定到組件](https://cn.vuejs.org/v2/guide/components-custom-events.html#%E5%B0%86%E5%8E%9F%E7%94%9F%E4%BA%8B%E4%BB%B6%E7%BB%91%E5%AE%9A%E5%88%B0%E7%BB%84%E4%BB%B6 "將原生事件綁定到組件")
你可能有很多次想要在一個組件的根元素上直接監聽一個原生事件。這時,你可以使用?`v-on`?的?`.native`?修飾符:
|
base-input v-on:focus.native="onFocus">base-input>
|
在有的時候這是很有用的,不過在你嘗試監聽一個類似?`<input>`?的非常特定的元素時,這并不是個好主意。比如上述?`<base-input>`?組件可能做了如下重構,所以根元素實際上是一個?`<label>`?元素:
|
label>
{{ label }}
input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
label>
|
這時,父級的?`.native`?監聽器將靜默失敗。它不會產生任何報錯,但是?`onFocus`?處理函數不會如你預期地被調用。
為了解決這個問題,Vue 提供了一個?`$listeners`?屬性,它是一個對象,里面包含了作用在這個組件上的所有監聽器。例如:
|
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
|
有了這個?`$listeners`?屬性,你就可以配合?`v-on="$listeners"`?將所有的事件監聽器指向這個組件的某個特定的子元素。對于類似?`<input>`?的你希望它也可以配合?`v-model`?工作的組件來說,為這些監聽器創建一個類似下述?`inputListeners`?的計算屬性通常是非常有用的:
|
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 將所有的對象合并為一個新對象
return Object.assign({},
// 我們從父級添加所有的監聽器
this.$listeners,
// 然后我們添加自定義監聽器,
// 或覆寫一些監聽器的行為
{
// 這里確保組件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
{{ label }}
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
`
})
|
現在?`<base-input>`?組件是一個完全透明的包裹器了,也就是說它可以完全像一個普通的?`<input>`?元素一樣使用了:所有跟它相同的特性和監聽器的都可以工作。
## [`.sync`?修飾符](https://cn.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A5%B0%E7%AC%A6 ".sync 修飾符")
> 2.3.0+ 新增
在有些情況下,我們可能需要對一個 prop 進行“雙向綁定”。不幸的是,真正的雙向綁定會帶來維護上的問題,因為子組件可以修改父組件,且在父組件和子組件都沒有明顯的改動來源。
這也是為什么我們推薦以?`update:myPropName`?的模式觸發事件取而代之。舉個例子,在一個包含?`title`?prop 的假設的組件中,我們可以用以下方法表達對其賦新值的意圖:
|
this.$emit('update:title', newTitle)
|
然后父組件可以監聽那個事件并根據需要更新一個本地的數據屬性。例如:
|
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>
|
注意帶有?`.sync`?修飾符的?`v-bind`?不能和表達式一起使用 (例如?`v-bind:title.sync=”doc.title + ‘!’”`?是無效的)。取而代之的是,你只能提供你想要綁定的屬性名,類似?`v-model`。
當我們用一個對象同時設置多個 prop 的時候,也可以將這個?`.sync`?修飾符和?`v-bind`?配合使用:
|
text-document v-bind.sync="doc">text-document>
|
這樣會把?`doc`?對象中的每一個屬性 (如?`title`) 都作為一個獨立的 prop 傳進去,然后各自添加用于更新的?`v-on`?監聽器。
將?`v-bind.sync`?用在一個字面量的對象上,例如?`v-bind.sync=”{ title: doc.title }”`,是無法正常工作的,因為在解析一個像這樣的復雜表達式的時候,有很多邊緣情況需要考慮。
# 插槽
> 該頁面假設你已經閱讀過了[組件基礎](https://cn.vuejs.org/v2/guide/components.html)。如果你還對組件不太了解,推薦你先閱讀它。
## [插槽內容](https://cn.vuejs.org/v2/guide/components-slots.html#%E6%8F%92%E6%A7%BD%E5%86%85%E5%AE%B9 "插槽內容")
Vue 實現了一套內容分發的 API,這套 API 基于當前的?[Web Components 規范草案](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md),將?`<slot>`?元素作為承載分發內容的出口。
它允許你像這樣合成組件:
|
navigation-link url="/profile">
Your Profile
navigation-link>
|
然后你在?`<navigation-link>`?的模板中可能會寫為:
|
a
v-bind:href="url"
class="nav-link"
>
slot>slot>
a>
|
當組件渲染的時候,這個?`<slot>`?元素將會被替換為“Your Profile”。插槽內可以包含任何模板代碼,包括 HTML:
|
navigation-link url="/profile">
span class="fa fa-user">span>
Your Profile
navigation-link>
|
甚至其它的組件:
|
navigation-link url="/profile">
font-awesome-icon name="user">font-awesome-icon>
Your Profile
navigation-link>
|
如果?`<navigation-link>`?沒有包含一個?`<slot>`?元素,則任何傳入它的內容都會被拋棄。
## [具名插槽](https://cn.vuejs.org/v2/guide/components-slots.html#%E5%85%B7%E5%90%8D%E6%8F%92%E6%A7%BD "具名插槽")
有些時候我們需要多個插槽。例如,一個假設的?`<base-layout>`?組件的模板如下:
|
div class="container">
header>
header>
main>
main>
footer>
footer>
div>
|
對于這樣的情況,`<slot>`?元素有一個特殊的特性:`name`。這個特性可以用來定義額外的插槽:
|
div class="container">
header>
slot name="header">slot>
header>
main>
slot>slot>
main>
footer>
slot name="footer">slot>
footer>
div>
|
在向具名插槽提供內容的時候,我們可以在一個父組件的?`<template>`?元素上使用?`slot`?特性:
|
base-layout>
template slot="header">
h1>Here might be a page titleh1>
template>
p>A paragraph for the main content.p>
p>And another one.p>
template slot="footer">
p>Here's some contact infop>
template>
base-layout>
|
另一種?`slot`?特性的用法是直接用在一個普通的元素上:
|
base-layout>
h1 slot="header">Here might be a page titleh1>
p>A paragraph for the main content.p>
p>And another one.p>
p slot="footer">Here's some contact infop>
base-layout>
|
我們還是可以保留一個未命名插槽,這個插槽是默認插槽,也就是說它會作為所有未匹配到插槽的內容的統一出口。上述兩個示例渲染出來的 HTML 都將會是:
|
div class="container">
header>
h1>Here might be a page titleh1>
header>
main>
p>A paragraph for the main content.p>
p>And another one.p>
main>
footer>
p>Here's some contact infop>
footer>
div>
|
## [插槽的默認內容](https://cn.vuejs.org/v2/guide/components-slots.html#%E6%8F%92%E6%A7%BD%E7%9A%84%E9%BB%98%E8%AE%A4%E5%86%85%E5%AE%B9 "插槽的默認內容")
有的時候為插槽提供默認的內容是很有用的。例如,一個?`<submit-button>`?組件可能希望這個按鈕的默認內容是“Submit”,但是同時允許用戶覆寫為“Save”、“Upload”或別的內容。
你可以在組件模板里的?`<slot>`?標簽內部指定默認的內容來做到這一點。
|
button type="submit">
slot>Submitslot>
button>
|
如果父組件為這個插槽提供了內容,則默認的內容會被替換掉。
## [編譯作用域](https://cn.vuejs.org/v2/guide/components-slots.html#%E7%BC%96%E8%AF%91%E4%BD%9C%E7%94%A8%E5%9F%9F "編譯作用域")
當你想在插槽內使用數據時,例如:
|
navigation-link url="/profile">
Logged in as {{ user.name }}
navigation-link>
|
該插槽可以訪問跟這個模板的其它地方相同的實例屬性 (也就是說“作用域”是相同的)。但這個插槽不能訪問?`<navigation-link>`?的作用域。例如嘗試訪問?`url`?是不會工作的。牢記一條準則:
> 父組件模板的所有東西都會在父級作用域內編譯;子組件模板的所有東西都會在子級作用域內編譯。
## [作用域插槽](https://cn.vuejs.org/v2/guide/components-slots.html#%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%8F%92%E6%A7%BD "作用域插槽")
> 2.1.0+ 新增
有的時候你希望提供的組件帶有一個可從子組件獲取數據的可復用的插槽。例如一個簡單的?`<todo-list>`?組件的模板可能包含了如下代碼:
|
ul>
li
v-for="todo in todos"
v-bind:key="todo.id"
>
{{ todo.text }}
li>
ul>
|
但是在我們應用的某些部分,我們希望每個獨立的待辦項渲染出和?`todo.text`?不太一樣的東西。這也是作用域插槽的用武之地。
為了讓這個特性成為可能,你需要做的全部事情就是將待辦項內容包裹在一個?`<slot>`?元素上,然后將所有和其上下文相關的數據傳遞給這個插槽:在這個例子中,這個數據是?`todo`?對象:
|
ul>
li
v-for="todo in todos"
v-bind:key="todo.id"
>
slot v-bind:todo="todo">
{{ todo.text }}
slot>
li>
ul>
|
現在當我們使用?`<todo-list>`?組件的時候,我們可以選擇為待辦項定義一個不一樣的?`<template>`?作為替代方案,并且可以通過?`slot-scope`?特性從子組件獲取數據:
|
todo-list v-bind:todos="todos">
template slot-scope="slotProps">
span v-if="slotProps.todo.isComplete">?span>
{{ slotProps.todo.text }}
template>
todo-list>
|
> 在 2.5.0+,`slot-scope`?不再限制在?`<template>`?元素上使用,而可以用在插槽內的任何元素或組件上。
### [解構?`slot-scope`](https://cn.vuejs.org/v2/guide/components-slots.html#%E8%A7%A3%E6%9E%84-slot-scope "解構 slot-scope")
如果一個 JavaScript 表達式在一個函數定義的參數位置有效,那么這個表達式實際上就可以被?`slot-scope`?接受。也就是說你可以在支持的環境下 ([單文件組件](https://cn.vuejs.org/v2/guide/single-file-components.html)或[現代瀏覽器](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9)),在這些表達式中使用?[ES2015 解構語法](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%E8%A7%A3%E6%9E%84%E5%AF%B9%E8%B1%A1)。例如:
|
todo-list v-bind:todos="todos">
template slot-scope="{ todo }">
span v-if="todo.isComplete">?span>
{{ todo.text }}
template>
todo-list>
|
這會使作用域插槽變得更干凈一些。
# 動態組件 & 異步組件
> 該頁面假設你已經閱讀過了[組件基礎](https://cn.vuejs.org/v2/guide/components.html)。如果你還對組件不太了解,推薦你先閱讀它。
## [在動態組件上使用?`keep-alive`](https://cn.vuejs.org/v2/guide/components-dynamic-async.html#%E5%9C%A8%E5%8A%A8%E6%80%81%E7%BB%84%E4%BB%B6%E4%B8%8A%E4%BD%BF%E7%94%A8-keep-alive "在動態組件上使用 keep-alive")
我們之前曾經在一個多標簽的界面中使用?`is`?特性來切換不同的組件:
|
component v-bind:is="currentTabComponent">component>
|
當在這些組件之間切換的時候,你有時會想保持這些組件的狀態,以避免反復重渲染導致的性能問題。例如我們來展開說一說這個多標簽界面:
PostsArchive
* Cat Ipsum
* Hipster Ipsum
* Cupcake Ipsum
Click on a blog title to the left to view it.
你會注意到,如果你選擇了一篇文章,切換到?*Archive*?標簽,然后再切換回?*Posts*,是不會繼續展示你之前選擇的文章的。這是因為你每次切換新標簽的時候,Vue 都創建了一個新的?`currentTabComponent`?實例。
重新創建動態組件的行為通常是非常有用的,但是在這個案例中,我們更希望那些標簽的組件實例能夠被在它們第一次被創建的時候緩存下來。為了解決這個問題,我們可以用一個?`<keep-alive>`?元素將其動態組件包裹起來。
|
keep-alive>
component v-bind:is="currentTabComponent">component>
keep-alive>
|
來看看修改后的結果:
PostsArchive
* Cat Ipsum
* Hipster Ipsum
* Cupcake Ipsum
Click on a blog title to the left to view it.
現在這個?*Posts*?標簽保持了它的狀態 (被選中的文章) 甚至當它未被渲染時也是如此。你可以在[這個 fiddle](https://jsfiddle.net/chrisvfritz/Lp20op9o/)?查閱到完整的代碼。
注意這個?`<keep-alive>`?要求被切換到的組件都有自己的名字,不論是通過組件的?`name`?選項還是局部/全局注冊。
你可以在?[API 參考文檔](https://cn.vuejs.org/v2/api/#keep-alive)?查閱更多關于?`<keep-alive>`?的細節。
## [異步組件](https://cn.vuejs.org/v2/guide/components-dynamic-async.html#%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6 "異步組件")
在大型應用中,我們可能需要將應用分割成小一些的代碼塊,并且只在需要的時候才從服務器加載一個模塊。為了簡化,Vue 允許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件需要被渲染的時候才會觸發該工廠函數,且會把結果緩存起來供未來重渲染。例如:
|
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回調傳遞組件定義
resolve({
template: 'I am async!'
})
}, 1000)
})
|
如你所見,這個工廠函數會收到一個?`resolve`?回調,這個回調函數會在你從服務器得到組件定義的時候被調用。你也可以調用?`reject(reason)`?來表示加載失敗。這里的?`setTimeout`?是為了演示用的,如何獲取組件取決于你自己。一個推薦的做法是將異步組件和?[webpack 的 code-splitting 功能](https://webpack.js.org/guides/code-splitting/)一起配合使用:
|
Vue.component('async-webpack-example', function (resolve) {
// 這個特殊的 `require` 語法將會告訴 webpack
// 自動將你的構建代碼切割成多個包,這些包
// 會通過 Ajax 請求加載
require(['./my-async-component'], resolve)
})
|
你也可以在工廠函數中返回一個?`Promise`,所以把 webpack 2 和 ES2015 語法加在一起,我們可以寫成這樣:
|
Vue.component(
'async-webpack-example',
// 這個 `import` 函數會返回一個 `Promise` 對象。
() => import('./my-async-component')
)
|
當使用[局部注冊](https://cn.vuejs.org/v2/guide/components.html#%E6%9C%AC%E5%9C%B0%E6%B3%A8%E5%86%8C)的時候,你也可以直接提供一個返回?`Promise`?的函數:
|
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
|
如果你是一個?Browserify?用戶同時喜歡使用異步組件,很不幸這個工具的作者[明確表示](https://github.com/substack/node-browserify/issues/58#issuecomment-21978224)異步加載“并不會被 Browserify 支持”,至少官方不會。Browserify 社區已經找到了[一些變通方案](https://github.com/vuejs/vuejs.org/issues/620),這些方案可能會對已存在的復雜應用有幫助。對于其它的場景,我們推薦直接使用 webpack,以擁有內建的被作為第一公民的異步支持。
### [處理加載狀態](https://cn.vuejs.org/v2/guide/components-dynamic-async.html#%E5%A4%84%E7%90%86%E5%8A%A0%E8%BD%BD%E7%8A%B6%E6%80%81 "處理加載狀態")
> 2.3.0+ 新增
這里的異步組件工廠函數也可以返回一個如下格式的對象:
|
const AsyncComponent = () => ({
// 需要加載的組件 (應該是一個 `Promise` 對象)
component: import('./MyComponent.vue'),
// 異步組件加載時使用的組件
loading: LoadingComponent,
// 加載失敗時使用的組件
error: ErrorComponent,
// 展示加載時組件的延時時間。默認值是 200 (毫秒)
delay: 200,
// 如果提供了超時時間且組件加載也超時了,
// 則使用加載失敗時使用的組件。默認值是:`Infinity`
timeout: 3000
})
|
> 注意如果你希望在?[Vue Router](https://github.com/vuejs/vue-router)?的路由組件中使用上述語法的話,你必須使用 Vue Router 2.4.0+ 版本。
>
# 處理邊界情況
> 該頁面假設你已經閱讀過了[組件基礎](https://cn.vuejs.org/v2/guide/components.html)。如果你還對組件不太了解,推薦你先閱讀它。
這里記錄的都是和處理邊界情況有關的功能,即一些需要對 Vue 的規則做一些小調整的特殊情況。不過注意這些功能都是有劣勢或危險的場景的。我們會在每個案例中注明,所以當你使用每個功能的時候請稍加留意。
## [訪問元素 & 組件](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0-amp-%E7%BB%84%E4%BB%B6 "訪問元素 & 組件")
在絕大多數情況下,我們最好不要觸達另一個組件實例內部或手動操作 DOM 元素。不過也確實在一些情況下做這些事情是合適的。
### [訪問根實例](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E8%AE%BF%E9%97%AE%E6%A0%B9%E5%AE%9E%E4%BE%8B "訪問根實例")
在每個?`new Vue`?實例的子組件中,其根實例可以通過?`$root`?屬性進行訪問。例如,在這個根實例中:
|
// Vue 根實例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
|
所有的子組件都可以將這個實例作為一個全局 store 來訪問或使用。
|
// 獲取根組件的數據
this.$root.foo
// 寫入根組件的數據
this.$root.foo = 2
// 訪問根組件的計算屬性
this.$root.bar
// 調用根組件的方法
this.$root.baz()
|
對于 demo 或非常小型的有少量組件的應用來說這是很方便的。不過這個模式擴展到中大型應用來說就不然了。因此在絕大多數情況下,我們強烈推薦使用?[Vuex](https://github.com/vuejs/vuex)?來管理應用的狀態。
### [訪問父級組件實例](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E8%AE%BF%E9%97%AE%E7%88%B6%E7%BA%A7%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B "訪問父級組件實例")
和?`$root`?類似,`$parent`?屬性可以用來從一個子組件訪問父組件的實例。它提供了一種機會,可以在后期隨時觸達父級組件,以替代將數據以 prop 的方式傳入子組件的方式。
在絕大多數情況下,觸達父級組件會使得你的應用更難調試和理解,尤其是當你變更了父級組件的數據的時候。當我們稍后回看那個組件的時候,很難找出那個變更是從哪里發起的。
另外在一些*可能*適當的時候,你需要特別地共享一些組件庫。舉個例子,在和 JavaScript API 進行交互而不渲染 HTML 的抽象組件內,諸如這些假設性的 Google 地圖組件一樣:
|
google-map>
google-map-markers v-bind:places="iceCreamShops">google-map-markers>
google-map>
|
這個?`<google-map>`?組件可以定義一個?`map`?屬性,所有的子組件都需要訪問它。在這種情況下?`<google-map-markers>`?可能想要通過類似?`this.$parent.getMap`?的方式訪問那個地圖,以便為其添加一組標記。你可以在[這里](https://jsfiddle.net/chrisvfritz/ttzutdxh/)查閱這種模式。
請留意,盡管如此,通過這種模式構建出來的那個組件的內部仍然是容易出現問題的。比如,設想一下我們添加一個新的?`<google-map-region>`?組件,當?`<google-map-markers>`?在其內部出現的時候,只會渲染那個區域內的標記:
|
google-map>
google-map-region v-bind:shape="cityBoundaries">
google-map-markers v-bind:places="iceCreamShops">google-map-markers>
google-map-region>
google-map>
|
那么在?`<google-map-markers>`?內部你可能發現自己需要一些類似這樣的 hack:
|
var map = this.$parent.map || this.$parent.$parent.map
|
很快它就會失控。這也是我們針對需要向任意更深層級的組件提供上下文信息時推薦[依賴注入](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5)的原因。
### [訪問子組件實例或子元素](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E8%AE%BF%E9%97%AE%E5%AD%90%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B%E6%88%96%E5%AD%90%E5%85%83%E7%B4%A0 "訪問子組件實例或子元素")
盡管存在 prop 和事件,有的時候你仍可能需要在 JavaScript 里直接訪問一個子組件。為了達到這個目的,你可以通過?`ref`?特性為這個子組件賦予一個 ID 引用。例如:
|
base-input ref="usernameInput">base-input>
|
現在在你已經定義了這個?`ref`?的組件里,你可以使用:
|
this.$refs.usernameInput
|
來訪問這個?`<base-input>`?實例,以便不時之需。比如程序化地從一個父級組件聚焦這個輸入框。在剛才那個例子中,該?`<base-input>`?組件也可以使用一個類似的?`ref`?提供對內部這個指定元素的訪問,例如:
|
input ref="input">
|
甚至可以通過其父級組件定義方法:
|
methods: {
// 用來從父級組件聚焦輸入框
focus: function () {
this.$refs.input.focus()
}
}
|
這樣就允許父級組件通過下面的代碼聚焦?`<base-input>`?里的輸入框:
|
this.$refs.usernameInput.focus()
|
當?`ref`?和?`v-for`?一起使用的時候,你得到的引用將會是一個包含了對應數據源的這些子組件的數組。
`$refs`?只會在組件渲染完成之后生效,并且它們不是響應式的。這只意味著一個直接的子組件封裝的“逃生艙”——你應該避免在模板或計算屬性中訪問?`$refs`。
### [依賴注入](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5 "依賴注入")
在此之前,在我們描述[訪問父級組件實例](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E8%AE%BF%E9%97%AE%E7%88%B6%E7%BA%A7%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B)的時候,展示過一個類似這樣的例子:
|
google-map>
google-map-region v-bind:shape="cityBoundaries">
google-map-markers v-bind:places="iceCreamShops">google-map-markers>
google-map-region>
google-map>
|
在這個組件里,所有?`<google-map>`?的后代都需要訪問一個?`getMap`?方法,以便知道要跟那個地圖進行交互。不幸的是,使用?`$parent`?屬性無法很好的擴展到更深層級的嵌套組件上。這也是依賴注入的用武之地,它用到了兩個新的實例選項:`provide`和?`inject`。
`provide`?選項允許我們指定我們想要提供給后代組件的數據/方法。在這個例子中,就是?`<google-map>`?內部的?`getMap`?方法:
|
provide: function () {
return {
getMap: this.getMap
}
}
|
然后在任何后代組件里,我們都可以使用?`inject`?選項來接收指定的我們想要添加在這個實例上的屬性:
|
inject: ['getMap']
|
你可以在[這里](https://jsfiddle.net/chrisvfritz/tdv8dt3s/)看到完整的示例。相比?`$parent`?來說,這個用法可以讓我們在*任意*后代組件中訪問?`getMap`,而不需要暴露整個?`<google-map>`?實例。這允許我們更好的持續研發該組件,而不需要擔心我們可能會改變/移除一些子組件依賴的東西。同時這些組件之間的接口是始終明確定義的,就和?`props`?一樣。
實際上,你可以把依賴注入看作一部分“大范圍有效的 prop”,除了:
* 祖先組件不需要知道哪些后代組件使用它提供的屬性
* 后代組件不需要知道被注入的屬性來自哪里
然而,依賴注入還是有負面影響的。它將你的應用以目前的組件組織方式耦合了起來,使重構變得更加困難。同時所提供的屬性是非響應式的。這是出于設計的考慮,因為使用它們來創建一個中心化規模化的數據跟[使用?`$root`](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E8%AE%BF%E9%97%AE%E6%A0%B9%E5%AE%9E%E4%BE%8B)做這件事都是不夠好的。如果你想要共享的這個屬性是你的應用特有的,而不是通用化的,或者如果你想在祖先組件中更新所提供的數據,那么這意味著你可能需要換用一個像?[Vuex](https://github.com/vuejs/vuex)?這樣真正的狀態管理方案了。
你可以在?[API 參考文檔](https://cn.vuejs.org/v2/api/#provide-inject)學習更多關于依賴注入的知識。
## [程序化的事件偵聽器](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E7%A8%8B%E5%BA%8F%E5%8C%96%E7%9A%84%E4%BA%8B%E4%BB%B6%E4%BE%A6%E5%90%AC%E5%99%A8 "程序化的事件偵聽器")
現在,你已經知道了?`$emit`?的用法,它可以被?`v-on`?偵聽,但是 Vue 實例同時在其事件接口中提供了其它的方法。我們可以:
* 通過?`$on(eventName, eventHandler)`?偵聽一個事件
* 通過?`$once(eventName, eventHandler)`?一次性偵聽一個事件
* 通過?`$off(eventName, eventHandler)`?停止偵聽一個事件
你通常不會用到這些,但是當你需要在一個組件實例上手動偵聽事件時,它們是派得上用場的。它們也可以用于代碼組織工具。例如,你可能經常看到這種集成一個第三方庫的模式:
|
// 一次性將這個日期選擇器附加到一個輸入框上
// 它會被掛載到 DOM 上。
mounted: function () {
// Pikaday 是一個第三方日期選擇器的庫
this.picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
},
// 在組件被銷毀之前,
// 也銷毀這個日期選擇器。
beforeDestroy: function () {
this.picker.destroy()
}
|
這里有兩個潛在的問題:
* 它需要在這個組件實例中保存這個?`picker`,如果可以的話最好只有生命周期鉤子可以訪問到它。這并不算嚴重的問題,但是它可以被視為雜物。
* 我們的建立代碼獨立于我們的清理代碼,這使得我們比較難于程序化地清理我們建立的所有東西。
你應該通過一個程序化的偵聽器解決這兩個問題:
|
mounted: function () {
var picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
|
使用了這個策略,我甚至可以讓多個輸入框元素同時使用不同的 Pikaday,每個新的實例都程序化地在后期清理它自己:
|
mounted: function () {
this.attachDatepicker('startDateInput')
this.attachDatepicker('endDateInput')
},
methods: {
attachDatepicker: function (refName) {
var picker = new Pikaday({
field: this.$refs[refName],
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
}
|
查閱[這個 fiddle](https://jsfiddle.net/chrisvfritz/1Leb7up8/)?可以了解到完整的代碼。注意,即便如此,如果你發現自己不得不在單個組件里做很多建立和清理的工作,最好的方式通常還是創建更多的模塊化組件。在這個例子中,我們推薦創建一個可復用的?`<input-datepicker>`?組件。
想了解更多程序化偵聽器的內容,請查閱[實例方法 / 事件](https://cn.vuejs.org/v2/api/#%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95-%E4%BA%8B%E4%BB%B6)相關的 API。
注意 Vue 的事件系統不同于瀏覽器的?[EventTarget API](https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget)。盡管它們工作起來是相似的,但是?`$emit`、`$on`, 和?`$off`?并不是?`dispatchEvent`、`addEventListener`?和?`removeEventListener`?的別名。
## [循環引用](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8 "循環引用")
### [遞歸組件](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E9%80%92%E5%BD%92%E7%BB%84%E4%BB%B6 "遞歸組件")
組件是可以在它們自己的模板中調用自身的。不過它們只能通過?`name`?選項來做這件事:
|
name: 'unique-name-of-my-component'
|
當你使用?`Vue.component`?全局注冊一個組件時,這個全局的 ID 會自動設置為該組件的?`name`?選項。
|
Vue.component('unique-name-of-my-component', {
// ...
})
|
稍有不慎,遞歸組件就可能導致無限循環:
|
name: 'stack-overflow',
template: ''
|
類似上述的組件將會導致“max stack size exceeded”錯誤,所以請確保遞歸調用是條件性的 (例如使用一個最終會得到?`false`?的?`v-if`)。
### [組件之間的循環引用](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E7%BB%84%E4%BB%B6%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8 "組件之間的循環引用")
假設你需要構建一個文件目錄樹,像訪達或資源管理器那樣的。你可能有一個?`<tree-folder>`?組件,模板是這樣的:
|
p>
span>{{ folder.name }}span>
tree-folder-contents :children="folder.children"/>
p>
|
還有一個?`<tree-folder-contents>`?組件,模板是這樣的:
|
ul>
li v-for="child in children">
tree-folder v-if="child.children" :folder="child"/>
span v-else>{{ child.name }}span>
li>
ul>
|
當你仔細觀察的時候,你會發現這些組件在渲染樹中互為對方的后代*和*祖先——一個悖論!當通過?`Vue.component`?全局注冊組件的時候,這個悖論會被自動解開。如果你是這樣做的,那么你可以跳過這里。
然而,如果你使用一個*模塊系統*依賴/導入組件,例如通過 webpack 或 Browserify,你會遇到一個錯誤:
|
Failed to mount component: template or render function not defined.
|
為了解釋這里發生了什么,我們先把兩個組件稱為 A 和 B。模塊系統發現它需要 A,但是首先 A 依賴 B,但是 B 又依賴 A,但是 A 又依賴 B,如此往復。這變成了一個循環,不知道如何不經過其中一個組件而完全解析出另一個組件。為了解決這個問題,我們需要給模塊系統一個點,在那里“A?*反正*是需要 B 的,但是我們不需要先解析 B。”
在我們的例子中,把?`<tree-folder>`?組件設為了那個點。我們知道那個產生悖論的子組件是?`<tree-folder-contents>`?組件,所以我們會等到生命周期鉤子?`beforeCreate`?時去注冊它:
|
beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}
|
或者,在本地注冊組件的時候,你可以使用 webpack 的異步?`import`:
|
components: {
TreeFolderContents: () => import('./tree-folder-contents.vue')
}
|
這樣問題就解決了!
## [模板定義的替代品](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E6%A8%A1%E6%9D%BF%E5%AE%9A%E4%B9%89%E7%9A%84%E6%9B%BF%E4%BB%A3%E5%93%81 "模板定義的替代品")
### [內聯模板](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E5%86%85%E8%81%94%E6%A8%A1%E6%9D%BF "內聯模板")
當?`inline-template`?這個特殊的特性出現在一個子組件上時,這個組件將會使用其里面的內容作為模板,而不是將其作為被分發的內容。這使得模板的撰寫工作更加靈活。
|
my-component inline-template>
div>
p>These are compiled as the component's own template.p>
p>Not parent's transclusion content.p>
div>
my-component>
|
不過,`inline-template`?會讓你模板的作用域變得更加難以理解。所以作為最佳實踐,請在組件內優先選擇?`template`?選項或?`.vue`?文件里的一個?`<template>`?元素來定義模板。
### [X-Templates](https://cn.vuejs.org/v2/guide/components-edge-cases.html#X-Templates "X-Templates")
另一個定義模板的方式是在一個?`<script>`?元素中,并為其帶上?`text/x-template`的類型,然后通過一個 id 將模板引用過去。例如:
|
script type="text/x-template" id="hello-world-template">
p>Hello hello hellop>
script>
|
|
Vue.component('hello-world', {
template: '#hello-world-template'
})
|
這些可以用于模板特別大的 demo 或極小型的應用,但是其它情況下請避免使用,因為這會將模板和該組件的其它定義分離開。
## [控制更新](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E6%8E%A7%E5%88%B6%E6%9B%B4%E6%96%B0 "控制更新")
感謝 Vue 的響應式系統,它始終知道何時進行更新 (如果你用對了的話)。不過還是有一些邊界情況,你想要強制更新,盡管表面上看響應式的數據沒有發生改變。也有一些情況是你想阻止不必要的更新。
### [強制更新](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E5%BC%BA%E5%88%B6%E6%9B%B4%E6%96%B0 "強制更新")
如果你發現你自己需要在 Vue 中做一次強制更新,99.9% 的情況,是你在某個地方做錯了事。
你可能還沒有留意到[數組](https://cn.vuejs.org/v2/guide/list.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)或[對象](https://cn.vuejs.org/v2/guide/list.html#%E5%AF%B9%E8%B1%A1%E6%9B%B4%E6%94%B9%E6%A3%80%E6%B5%8B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)的變更檢測注意事項,或者你可能依賴了一個未被 Vue 的響應式系統追蹤的狀態。
然而,如果你已經做到了上述的事項仍然發現在極少數的情況下需要手動強制更新,那么你可以通過?[`$forceUpdate`](https://cn.vuejs.org/v2/api/#vm-forceUpdate)?來做這件事。
### [通過?`v-once`?創建低開銷的靜態組件](https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E9%80%9A%E8%BF%87-v-once-%E5%88%9B%E5%BB%BA%E4%BD%8E%E5%BC%80%E9%94%80%E7%9A%84%E9%9D%99%E6%80%81%E7%BB%84%E4%BB%B6 "通過 v-once 創建低開銷的靜態組件")
渲染普通的 HTML 元素在 Vue 中是非常快速的,但有的時候你可能有一個組件,這個組件包含了大量靜態內容。在這種情況下,你可以在根元素上添加?`v-once`?特性以確保這些內容只計算一次然后緩存起來,就像這樣:
|
Vue.component('terms-of-service', {
template: `
Terms of Service
... a lot of static content ...
`
})
|
再說一次,試著不要過度使用這個模式。當你需要渲染大量靜態內容時,極少數的情況下它會給你帶來便利,除非你非常留意渲染變慢了,不然它完全是沒有必要的——再加上它在后期會帶來很多困惑。例如,設想另一個開發者并不熟悉?`v-once`?或漏看了它在模板中,他們可能會花很多個小時去找出模板為什么無法正確更新。
# 進入/離開 & 列表過渡
## [概述](https://cn.vuejs.org/v2/guide/transitions.html#%E6%A6%82%E8%BF%B0 "概述")
Vue 在插入、更新或者移除 DOM 時,提供多種不同方式的應用過渡效果。
包括以下工具:
* 在 CSS 過渡和動畫中自動應用 class
* 可以配合使用第三方 CSS 動畫庫,如 Animate.css
* 在過渡鉤子函數中使用 JavaScript 直接操作 DOM
* 可以配合使用第三方 JavaScript 動畫庫,如 Velocity.js
在這里,我們只會講到進入、離開和列表的過渡,你也可以看下一節的?[管理過渡狀態](https://cn.vuejs.org/v2/guide/transitioning-state.html)。
## [單元素/組件的過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%8D%95%E5%85%83%E7%B4%A0-%E7%BB%84%E4%BB%B6%E7%9A%84%E8%BF%87%E6%B8%A1 "單元素/組件的過渡")
Vue 提供了?`transition`?的封裝組件,在下列情形中,可以給任何元素和組件添加進入/離開過渡
* 條件渲染 (使用?`v-if`)
* 條件展示 (使用?`v-show`)
* 動態組件
* 組件根節點
這里是一個典型的例子:
|
div id="demo">
button v-on:click="show = !show">
Toggle
button>
transition name="fade">
p v-if="show">hellop>
transition>
div>
|
|
new Vue({
el: '#demo',
data: {
show: true
}
})
|
|
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
|
Toggle
hello
當插入或刪除包含在?`transition`?組件中的元素時,Vue 將會做以下處理:
1. 自動嗅探目標元素是否應用了 CSS 過渡或動畫,如果是,在恰當的時機添加/刪除 CSS 類名。
2. 如果過渡組件提供了?[JavaScript 鉤子函數](https://cn.vuejs.org/v2/guide/transitions.html#JavaScript-%E9%92%A9%E5%AD%90),這些鉤子函數將在恰當的時機被調用。
3. 如果沒有找到 JavaScript 鉤子并且也沒有檢測到 CSS 過渡/動畫,DOM 操作 (插入/刪除) 在下一幀中立即執行。(注意:此指瀏覽器逐幀動畫機制,和 Vue 的?`nextTick`?概念不同)
### [過渡的類名](https://cn.vuejs.org/v2/guide/transitions.html#%E8%BF%87%E6%B8%A1%E7%9A%84%E7%B1%BB%E5%90%8D "過渡的類名")
在進入/離開的過渡中,會有 6 個 class 切換。
1. `v-enter`:定義進入過渡的開始狀態。在元素被插入之前生效,在元素被插入之后的下一幀移除。
2. `v-enter-active`:定義進入過渡生效時的狀態。在整個進入過渡的階段中應用,在元素被插入之前生效,在過渡/動畫完成之后移除。這個類可以被用來定義進入過渡的過程時間,延遲和曲線函數。
3. `v-enter-to`:?2.1.8版及以上?定義進入過渡的結束狀態。在元素被插入之后下一幀生效 (與此同時?`v-enter`?被移除),在過渡/動畫完成之后移除。
4. `v-leave`: 定義離開過渡的開始狀態。在離開過渡被觸發時立刻生效,下一幀被移除。
5. `v-leave-active`:定義離開過渡生效時的狀態。在整個離開過渡的階段中應用,在離開過渡被觸發時立刻生效,在過渡/動畫完成之后移除。這個類可以被用來定義離開過渡的過程時間,延遲和曲線函數。
6. `v-leave-to`:?2.1.8版及以上?定義離開過渡的結束狀態。在離開過渡被觸發之后下一幀生效 (與此同時?`v-leave`?被刪除),在過渡/動畫完成之后移除。

對于這些在過渡中切換的類名來說,如果你使用一個沒有名字的?`<transition>`,則?`v-`?是這些類名的默認前綴。如果你使用了?`<transition name="my-transition">`,那么?`v-enter`?會替換為?`my-transition-enter`。
`v-enter-active`?和?`v-leave-active`?可以控制進入/離開過渡的不同的緩和曲線,在下面章節會有個示例說明。
### [CSS 過渡](https://cn.vuejs.org/v2/guide/transitions.html#CSS-%E8%BF%87%E6%B8%A1 "CSS 過渡")
常用的過渡都是使用 CSS 過渡。
下面是一個簡單例子:
|
div id="example-1">
button @click="show = !show">
Toggle render
button>
transition name="slide-fade">
p v-if="show">hellop>
transition>
div>
|
|
new Vue({
el: '#example-1',
data: {
show: true
}
})
|
|
/* 可以設置不同的進入和離開動畫 */
/* 設置持續時間和動畫函數 */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
|
Toggle render
hello
### [CSS 動畫](https://cn.vuejs.org/v2/guide/transitions.html#CSS-%E5%8A%A8%E7%94%BB "CSS 動畫")
CSS 動畫用法同 CSS 過渡,區別是在動畫中?`v-enter`?類名在節點插入 DOM 后不會立即刪除,而是在?`animationend`?事件觸發時刪除。
示例:(省略了兼容性前綴)
|
div id="example-2">
button @click="show = !show">Toggle showbutton>
transition name="bounce">
p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.p>
transition>
div>
|
|
new Vue({
el: '#example-2',
data: {
show: true
}
})
|
|
.bounce-enter-active {
animation: bounce-in .5s;
}
.bounce-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
|
Toggle show
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.
### [自定義過渡的類名](https://cn.vuejs.org/v2/guide/transitions.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BF%87%E6%B8%A1%E7%9A%84%E7%B1%BB%E5%90%8D "自定義過渡的類名")
我們可以通過以下特性來自定義過渡類名:
* `enter-class`
* `enter-active-class`
* `enter-to-class`?(2.1.8+)
* `leave-class`
* `leave-active-class`
* `leave-to-class`?(2.1.8+)
他們的優先級高于普通的類名,這對于 Vue 的過渡系統和其他第三方 CSS 動畫庫,如?[Animate.css](https://daneden.github.io/animate.css/)?結合使用十分有用。
示例:
|
link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
div id="example-3">
button @click="show = !show">
Toggle render
button>
transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
p v-if="show">hellop>
transition>
div>
|
|
new Vue({
el: '#example-3',
data: {
show: true
}
})
|
Toggle render
hello
### [同時使用過渡和動畫](https://cn.vuejs.org/v2/guide/transitions.html#%E5%90%8C%E6%97%B6%E4%BD%BF%E7%94%A8%E8%BF%87%E6%B8%A1%E5%92%8C%E5%8A%A8%E7%94%BB "同時使用過渡和動畫")
Vue 為了知道過渡的完成,必須設置相應的事件監聽器。它可以是?`transitionend`?或?`animationend`?,這取決于給元素應用的 CSS 規則。如果你使用其中任何一種,Vue 能自動識別類型并設置監聽。
但是,在一些場景中,你需要給同一個元素同時設置兩種過渡動效,比如?`animation`很快的被觸發并完成了,而?`transition`?效果還沒結束。在這種情況中,你就需要使用?`type`?特性并設置?`animation`?或?`transition`?來明確聲明你需要 Vue 監聽的類型。
### [顯性的過渡持續時間](https://cn.vuejs.org/v2/guide/transitions.html#%E6%98%BE%E6%80%A7%E7%9A%84%E8%BF%87%E6%B8%A1%E6%8C%81%E7%BB%AD%E6%97%B6%E9%97%B4 "顯性的過渡持續時間")
> 2.2.0 新增
在很多情況下,Vue 可以自動得出過渡效果的完成時機。默認情況下,Vue 會等待其在過渡效果的根元素的第一個?`transitionend`?或?`animationend`?事件。然而也可以不這樣設定——比如,我們可以擁有一個精心編排的一系列過渡效果,其中一些嵌套的內部元素相比于過渡效果的根元素有延遲的或更長的過渡效果。
在這種情況下你可以用?`<transition>`?組件上的?`duration`?屬性定制一個顯性的過渡持續時間 (以毫秒計):
|
transition :duration="1000">...transition>
|
你也可以定制進入和移出的持續時間:
|
transition :duration="{ enter: 500, leave: 800 }">...transition>
|
### [JavaScript 鉤子](https://cn.vuejs.org/v2/guide/transitions.html#JavaScript-%E9%92%A9%E5%AD%90 "JavaScript 鉤子")
可以在屬性中聲明 JavaScript 鉤子
|
transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
transition>
|
|
// ...
methods: {
// --------
// 進入中
// --------
beforeEnter: function (el) {
// ...
},
// 當與 CSS 結合使用時
// 回調函數 done 是可選的
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},
// --------
// 離開時
// --------
beforeLeave: function (el) {
// ...
},
// 當與 CSS 結合使用時
// 回調函數 done 是可選的
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}
|
這些鉤子函數可以結合 CSS?`transitions/animations`?使用,也可以單獨使用。
當只用 JavaScript 過渡的時候,在?`enter`?和?`leave`?中必須使用?`done`?進行回調。否則,它們將被同步調用,過渡會立即完成。
推薦對于僅使用 JavaScript 過渡的元素添加?`v-bind:css="false"`,Vue 會跳過 CSS 的檢測。這也可以避免過渡過程中 CSS 的影響。
一個使用 Velocity.js 的簡單例子:
|
Velocity 和 jQuery.animate 的工作方式類似,也是用來實現 JavaScript 動畫的一個很棒的選擇
-->
script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js">script>
div id="example-4">
button @click="show = !show">
Toggle
button>
transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
>
p v-if="show">
Demo
p>
transition>
div>
|
|
new Vue({
el: '#example-4',
data: {
show: false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
el.style.transformOrigin = 'left'
},
enter: function (el, done) {
Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
Velocity(el, { fontSize: '1em' }, { complete: done })
},
leave: function (el, done) {
Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
Velocity(el, {
rotateZ: '45deg',
translateY: '30px',
translateX: '30px',
opacity: 0
}, { complete: done })
}
}
})
|
Toggle
## [初始渲染的過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%88%9D%E5%A7%8B%E6%B8%B2%E6%9F%93%E7%9A%84%E8%BF%87%E6%B8%A1 "初始渲染的過渡")
可以通過?`appear`?特性設置節點在初始渲染的過渡
|
transition appear>
transition>
|
這里默認和進入/離開過渡一樣,同樣也可以自定義 CSS 類名。
|
transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class" (2.1.8+)
appear-active-class="custom-appear-active-class"
>
transition>
|
自定義 JavaScript 鉤子:
|
transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook"
>
transition>
|
## [多個元素的過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%A4%9A%E4%B8%AA%E5%85%83%E7%B4%A0%E7%9A%84%E8%BF%87%E6%B8%A1 "多個元素的過渡")
我們之后討論[多個組件的過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%A4%9A%E4%B8%AA%E7%BB%84%E4%BB%B6%E7%9A%84%E8%BF%87%E6%B8%A1),對于原生標簽可以使用?`v-if`/`v-else`?。最常見的多標簽過渡是一個列表和描述這個列表為空消息的元素:
|
transition>
table v-if="items.length > 0">
table>
p v-else>Sorry, no items found.p>
transition>
|
可以這樣使用,但是有一點需要注意:
當有相同標簽名的元素切換時,需要通過?`key`?特性設置唯一的值來標記以讓 Vue 區分它們,否則 Vue 為了效率只會替換相同標簽內部的內容。即使在技術上沒有必要,給在?`<transition>`?組件中的多個元素設置 key 是一個更好的實踐。
示例:
|
transition>
button v-if="isEditing" key="save">
Save
button>
button v-else key="edit">
Edit
button>
transition>
|
在一些場景中,也可以通過給同一個元素的?`key`?特性設置不同的狀態來代替?`v-if`和?`v-else`,上面的例子可以重寫為:
|
transition>
button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
button>
transition>
|
使用多個?`v-if`?的多個元素的過渡可以重寫為綁定了動態屬性的單個元素過渡。例如:
|
transition>
button v-if="docState === 'saved'" key="saved">
Edit
button>
button v-if="docState === 'edited'" key="edited">
Save
button>
button v-if="docState === 'editing'" key="editing">
Cancel
button>
transition>
|
可以重寫為:
|
transition>
button v-bind:key="docState">
{{ buttonMessage }}
button>
transition>
|
|
// ...
computed: {
buttonMessage: function () {
switch (this.docState) {
case 'saved': return 'Edit'
case 'edited': return 'Save'
case 'editing': return 'Cancel'
}
}
}
|
### [過渡模式](https://cn.vuejs.org/v2/guide/transitions.html#%E8%BF%87%E6%B8%A1%E6%A8%A1%E5%BC%8F "過渡模式")
這里還有一個問題,試著點擊下面的按鈕:
off
在 “on” 按鈕和 “off” 按鈕的過渡中,兩個按鈕都被重繪了,一個離開過渡的時候另一個開始進入過渡。這是?`<transition>`?的默認行為 - 進入和離開同時發生。
在元素絕對定位在彼此之上的時候運行正常:
off
然后,我們加上 translate 讓它們運動像滑動過渡:
off
同時生效的進入和離開的過渡不能滿足所有要求,所以 Vue 提供了?過渡模式
* `in-out`:新元素先進行過渡,完成之后當前元素過渡離開。
* `out-in`:當前元素先進行過渡,完成之后新元素過渡進入。
用?`out-in`?重寫之前的開關按鈕過渡:
|
transition name="fade" mode="out-in">
transition>
|
off
只用添加一個簡單的特性,就解決了之前的過渡問題而無需任何額外的代碼。
`in-out`?模式不是經常用到,但對于一些稍微不同的過渡效果還是有用的。
將之前滑動淡出的例子結合:
off
很酷吧?
## [多個組件的過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%A4%9A%E4%B8%AA%E7%BB%84%E4%BB%B6%E7%9A%84%E8%BF%87%E6%B8%A1 "多個組件的過渡")
多個組件的過渡簡單很多 - 我們不需要使用?`key`?特性。相反,我們只需要使用[動態組件](https://cn.vuejs.org/v2/guide/components.html#%E5%8A%A8%E6%80%81%E7%BB%84%E4%BB%B6):
|
transition name="component-fade" mode="out-in">
component v-bind:is="view">component>
transition>
|
|
new Vue({
el: '#transition-components-demo',
data: {
view: 'v-a'
},
components: {
'v-a': {
template: 'Component A'
},
'v-b': {
template: 'Component B'
}
}
})
|
|
.component-fade-enter-active, .component-fade-leave-active {
transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active for below version 2.1.8 */ {
opacity: 0;
}
|
A?B
Component A
## [列表過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%88%97%E8%A1%A8%E8%BF%87%E6%B8%A1 "列表過渡")
目前為止,關于過渡我們已經講到:
* 單個節點
* 同一時間渲染多個節點中的一個
那么怎么同時渲染整個列表,比如使用?`v-for`??在這種場景中,使用?`<transition-group>`?組件。在我們深入例子之前,先了解關于這個組件的幾個特點:
* 不同于?`<transition>`,它會以一個真實元素呈現:默認為一個?`<span>`。你也可以通過?`tag`?特性更換為其他元素。
* [過渡模式](https://cn.vuejs.org/v2/guide/transitions.html#%E8%BF%87%E6%B8%A1%E6%A8%A1%E5%BC%8F)不可用,因為我們不再相互切換特有的元素。
* 內部元素?總是需要?提供唯一的?`key`?屬性值。
### [列表的進入/離開過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%88%97%E8%A1%A8%E7%9A%84%E8%BF%9B%E5%85%A5-%E7%A6%BB%E5%BC%80%E8%BF%87%E6%B8%A1 "列表的進入/離開過渡")
現在讓我們由一個簡單的例子深入,進入和離開的過渡使用之前一樣的 CSS 類名。
|
div id="list-demo" class="demo">
button v-on:click="add">Addbutton>
button v-on:click="remove">Removebutton>
transition-group name="list" tag="p">
span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
span>
transition-group>
div>
|
|
new Vue({
el: '#list-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
}
})
|
|
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to
/* .list-leave-active for below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
|
Add?Remove
123456789
這個例子有個問題,當添加和移除元素的時候,周圍的元素會瞬間移動到他們的新布局的位置,而不是平滑的過渡,我們下面會解決這個問題。
### [列表的排序過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%88%97%E8%A1%A8%E7%9A%84%E6%8E%92%E5%BA%8F%E8%BF%87%E6%B8%A1 "列表的排序過渡")
`<transition-group>`?組件還有一個特殊之處。不僅可以進入和離開動畫,還可以改變定位。要使用這個新功能只需了解新增的?`v-move`?特性,它會在元素的改變定位的過程中應用。像之前的類名一樣,可以通過?`name`?屬性來自定義前綴,也可以通過?`move-class`?屬性手動設置。
`v-move`?對于設置過渡的切換時機和過渡曲線非常有用,你會看到如下的例子:
|
script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js">script>
div id="flip-list-demo" class="demo">
button v-on:click="shuffle">Shufflebutton>
transition-group name="flip-list" tag="ul">
li v-for="item in items" v-bind:key="item">
{{ item }}
li>
transition-group>
div>
|
|
new Vue({
el: '#flip-list-demo',
data: {
items: [1,2,3,4,5,6,7,8,9]
},
methods: {
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
})
|
|
.flip-list-move {
transition: transform 1s;
}
|
Shuffle
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
這個看起來很神奇,內部的實現,Vue 使用了一個叫?[FLIP](https://aerotwist.com/blog/flip-your-animations/)?簡單的動畫隊列
使用 transforms 將元素從之前的位置平滑過渡新的位置。
我們將之前實現的例子和這個技術結合,使我們列表的一切變動都會有動畫過渡。
|
script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js">script>
div id="list-complete-demo" class="demo">
button v-on:click="shuffle">Shufflebutton>
button v-on:click="add">Addbutton>
button v-on:click="remove">Removebutton>
transition-group name="list-complete" tag="p">
span
v-for="item in items"
v-bind:key="item"
class="list-complete-item"
>
{{ item }}
span>
transition-group>
div>
|
|
new Vue({
el: '#list-complete-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
})
|
|
.list-complete-item {
transition: all 1s;
display: inline-block;
margin-right: 10px;
}
.list-complete-enter, .list-complete-leave-to
/* .list-complete-leave-active for below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
|
Shuffle?Add?Remove
123456789
需要注意的是使用 FLIP 過渡的元素不能設置為?`display: inline`?。作為替代方案,可以設置為?`display: inline-block`?或者放置于 flex 中
FLIP 動畫不僅可以實現單列過渡,多維網格也[同樣可以過渡](https://jsfiddle.net/chrisvfritz/sLrhk1bc/):
Lazy Sudoku
Keep hitting the shuffle button until you win.
Shuffle
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
### [列表的交錯過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%88%97%E8%A1%A8%E7%9A%84%E4%BA%A4%E9%94%99%E8%BF%87%E6%B8%A1 "列表的交錯過渡")
通過 data 屬性與 JavaScript 通信 ,就可以實現列表的交錯過渡:
|
script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js">script>
div id="staggered-list-demo">
input v-model="query">
transition-group
name="staggered-fade"
tag="ul"
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
li
v-for="(item, index) in computedList"
v-bind:key="item.msg"
v-bind:data-index="index"
>{{ item.msg }}li>
transition-group>
div>
|
|
new Vue({
el: '#staggered-list-demo',
data: {
query: '',
list: [
{ msg: 'Bruce Lee' },
{ msg: 'Jackie Chan' },
{ msg: 'Chuck Norris' },
{ msg: 'Jet Li' },
{ msg: 'Kung Fury' }
]
},
computed: {
computedList: function () {
var vm = this
return this.list.filter(function (item) {
return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
})
}
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
el.style.height = 0
},
enter: function (el, done) {
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(
el,
{ opacity: 1, height: '1.6em' },
{ complete: done }
)
}, delay)
},
leave: function (el, done) {
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(
el,
{ opacity: 0, height: 0 },
{ complete: done }
)
}, delay)
}
}
})
|
* Bruce Lee
* Jackie Chan
* Chuck Norris
* Jet Li
* Kung Fury
## [可復用的過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%8F%AF%E5%A4%8D%E7%94%A8%E7%9A%84%E8%BF%87%E6%B8%A1 "可復用的過渡")
過渡可以通過 Vue 的組件系統實現復用。要創建一個可復用過渡組件,你需要做的就是將?`<transition>`?或者?`<transition-group>`?作為根組件,然后將任何子組件放置在其中就可以了。
使用 template 的簡單例子:
|
Vue.component('my-special-transition', {
template: '\
name="very-special-transition"\
mode="out-in"\
v-on:before-enter="beforeEnter"\
v-on:after-enter="afterEnter"\
>\
\
\
',
methods: {
beforeEnter: function (el) {
// ...
},
afterEnter: function (el) {
// ...
}
}
})
|
函數組件更適合完成這個任務:
|
Vue.component('my-special-transition', {
functional: true,
render: function (createElement, context) {
var data = {
props: {
name: 'very-special-transition',
mode: 'out-in'
},
on: {
beforeEnter: function (el) {
// ...
},
afterEnter: function (el) {
// ...
}
}
}
return createElement('transition', data, context.children)
}
})
|
## [動態過渡](https://cn.vuejs.org/v2/guide/transitions.html#%E5%8A%A8%E6%80%81%E8%BF%87%E6%B8%A1 "動態過渡")
在 Vue 中即使是過渡也是數據驅動的!動態過渡最基本的例子是通過?`name`?特性來綁定動態值。
|
transition v-bind:name="transitionName">
transition>
|
當你想用 Vue 的過渡系統來定義的 CSS 過渡/動畫 在不同過渡間切換會非常有用。
所有過渡特性都可以動態綁定,但我們不僅僅只有特性可以利用,還可以通過事件鉤子獲取上下文中的所有數據,因為事件鉤子都是方法。這意味著,根據組件的狀態不同,你的 JavaScript 過渡會有不同的表現。
|
script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js">script>
div id="dynamic-fade-demo" class="demo">
Fade In: input type="range" v-model="fadeInDuration" min="0" v-bind:max="maxFadeDuration">
Fade Out: input type="range" v-model="fadeOutDuration" min="0" v-bind:max="maxFadeDuration">
transition
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
p v-if="show">hellop>
transition>
button
v-if="stop"
v-on:click="stop = false; show = false"
>Start animatingbutton>
button
v-else
v-on:click="stop = true"
>Stop it!button>
div>
|
|
new Vue({
el: '#dynamic-fade-demo',
data: {
show: true,
fadeInDuration: 1000,
fadeOutDuration: 1000,
maxFadeDuration: 1500,
stop: true
},
mounted: function () {
this.show = false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
},
enter: function (el, done) {
var vm = this
Velocity(el,
{ opacity: 1 },
{
duration: this.fadeInDuration,
complete: function () {
done()
if (!vm.stop) vm.show = false
}
}
)
},
leave: function (el, done) {
var vm = this
Velocity(el,
{ opacity: 0 },
{
duration: this.fadeOutDuration,
complete: function () {
done()
vm.show = true
}
}
)
}
}
})
|
Fade In:??Fade Out:?
hello
Start animating
最后,創建動態過渡的最終方案是組件通過接受 props 來動態修改之前的過渡。一句老話,唯一的限制是你的想象力。
# 狀態過渡
Vue 的過渡系統提供了非常多簡單的方法設置進入、離開和列表的動效。那么對于數據元素本身的動效呢,比如:
* 數字和運算
* 顏色的顯示
* SVG 節點的位置
* 元素的大小和其他的屬性
所有的原始數字都被事先存儲起來,可以直接轉換到數字。做到這一步,我們就可以結合 Vue 的響應式和組件系統,使用第三方庫來實現切換元素的過渡狀態。
## [狀態動畫與偵聽器](https://cn.vuejs.org/v2/guide/transitioning-state.html#%E7%8A%B6%E6%80%81%E5%8A%A8%E7%94%BB%E4%B8%8E%E4%BE%A6%E5%90%AC%E5%99%A8 "狀態動畫與偵聽器")
通過偵聽器我們能監聽到任何數值屬性的數值更新。可能聽起來很抽象,所以讓我們先來看看使用?[GreenSock](https://greensock.com/)?一個例子:
|
script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js">script>
div id="animated-number-demo">
input v-model.number="number" type="number" step="20">
p>{{ animatedNumber }}p>
div>
|
|
new Vue({
el: '#animated-number-demo',
data: {
number: 0,
tweenedNumber: 0
},
computed: {
animatedNumber: function() {
return this.tweenedNumber.toFixed(0);
}
},
watch: {
number: function(newValue) {
TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });
}
}
})
|
0
當你把數值更新時,就會觸發動畫。這個是一個不錯的演示,但是對于不能直接像數字一樣存儲的值,比如 CSS 中的 color 的值,通過下面的例子我們來通過?[Tween.js](https://github.com/tweenjs/tween.js)?和?[Color.js](https://github.com/brehaut/color-js)?實現一個例子:
|
script src="https://cdn.jsdelivr.net/npm/tween.js@16.3.4">script>
script src="https://cdn.jsdelivr.net/npm/color-js@1.0.3">script>
div id="example-7">
input
v-model="colorQuery"
v-on:keyup.enter="updateColor"
placeholder="Enter a color"
>
button v-on:click="updateColor">Updatebutton>
p>Preview:p>
span
v-bind:style="{ backgroundColor: tweenedCSSColor }"
class="example-7-color-preview"
>span>
p>{{ tweenedCSSColor }}p>
div>
|
|
var Color = net.brehaut.Color
new Vue({
el: '#example-7',
data: {
colorQuery: '',
color: {
red: 0,
green: 0,
blue: 0,
alpha: 1
},
tweenedColor: {}
},
created: function () {
this.tweenedColor = Object.assign({}, this.color)
},
watch: {
color: function () {
function animate () {
if (TWEEN.update()) {
requestAnimationFrame(animate)
}
}
new TWEEN.Tween(this.tweenedColor)
.to(this.color, 750)
.start()
animate()
}
},
computed: {
tweenedCSSColor: function () {
return new Color({
red: this.tweenedColor.red,
green: this.tweenedColor.green,
blue: this.tweenedColor.blue,
alpha: this.tweenedColor.alpha
}).toCSS()
}
},
methods: {
updateColor: function () {
this.color = new Color(this.colorQuery).toRGB()
this.colorQuery = ''
}
}
})
|
|
.example-7-color-preview {
display: inline-block;
width: 50px;
height: 50px;
}
|
?Update
Preview:
#000000
## [動態狀態過渡](https://cn.vuejs.org/v2/guide/transitioning-state.html#%E5%8A%A8%E6%80%81%E7%8A%B6%E6%80%81%E8%BF%87%E6%B8%A1 "動態狀態過渡")
就像 Vue 的過渡組件一樣,數據背后狀態過渡會實時更新,這對于原型設計十分有用。當你修改一些變量,即使是一個簡單的 SVG 多邊形也可實現很多難以想象的效果。
Sides: 10Minimum Radius: 50%Update Interval: 500 milliseconds
上述 demo 背后的代碼可以通過[這個 fiddle](https://jsfiddle.net/chrisvfritz/65gLu2b6/)?進行詳閱。
## [把過渡放到組件里](https://cn.vuejs.org/v2/guide/transitioning-state.html#%E6%8A%8A%E8%BF%87%E6%B8%A1%E6%94%BE%E5%88%B0%E7%BB%84%E4%BB%B6%E9%87%8C "把過渡放到組件里")
管理太多的狀態過渡會很快的增加 Vue 實例或者組件的復雜性,幸好很多的動畫可以提取到專用的子組件。
我們來將之前的示例改寫一下:
|
script src="https://cdn.jsdelivr.net/npm/tween.js@16.3.4">script>
div id="example-8">
input v-model.number="firstNumber" type="number" step="20"> +
input v-model.number="secondNumber" type="number" step="20"> =
{{ result }}
p>
animated-integer v-bind:value="firstNumber">animated-integer> +
animated-integer v-bind:value="secondNumber">animated-integer> =
animated-integer v-bind:value="result">animated-integer>
p>
div>
|
|
// 這種復雜的補間動畫邏輯可以被復用
// 任何整數都可以執行動畫
// 組件化使我們的界面十分清晰
// 可以支持更多更復雜的動態過渡
// 策略。
Vue.component('animated-integer', {
template: '{{ tweeningValue }}',
props: {
value: {
type: Number,
required: true
}
},
data: function () {
return {
tweeningValue: 0
}
},
watch: {
value: function (newValue, oldValue) {
this.tween(oldValue, newValue)
}
},
mounted: function () {
this.tween(0, this.value)
},
methods: {
tween: function (startValue, endValue) {
var vm = this
function animate () {
if (TWEEN.update()) {
requestAnimationFrame(animate)
}
}
new TWEEN.Tween({ tweeningValue: startValue })
.to({ tweeningValue: endValue }, 500)
.onUpdate(function (object) {
vm.tweeningValue = object.tweeningValue.toFixed(0)
})
.start()
animate()
}
}
})
// 所有的復雜度都已經從 Vue 的主實例中移除!
new Vue({
el: '#example-8',
data: {
firstNumber: 20,
secondNumber: 40
},
computed: {
result: function () {
return this.firstNumber + this.secondNumber
}
}
})
|
?+??= 60
20?+?40?=?60
我們能在組件中結合使用這一節講到各種過渡策略和 Vue?[內建的過渡系統](https://cn.vuejs.org/v2/guide/transitions.html)。總之,對于完成各種過渡動效幾乎沒有阻礙。
## [賦予設計以生命](https://cn.vuejs.org/v2/guide/transitioning-state.html#%E8%B5%8B%E4%BA%88%E8%AE%BE%E8%AE%A1%E4%BB%A5%E7%94%9F%E5%91%BD "賦予設計以生命")
只要一個動畫,就可以帶來生命。不幸的是,當設計師創建圖標、logo 和吉祥物的時候,他們交付的通常都是圖片或靜態的 SVG。所以,雖然 GitHub 的章魚貓、Twitter 的小鳥以及其它許多 logo 類似于生靈,它們看上去實際上并不是活著的。
Vue 可以幫到你。因為 SVG 的本質是數據,我們只需要這些動物興奮、思考或警戒的樣例。然后 Vue 就可以輔助完成這幾種狀態之間的過渡動畫,來制作你的歡迎頁面、加載指示、以及更加帶有情感的提示。
Sarah Drasner 展示了下面這個 demo,這個 demo 結合了時間和交互相關的狀態改變:
# 混入
## [基礎](https://cn.vuejs.org/v2/guide/mixins.html#%E5%9F%BA%E7%A1%80 "基礎")
混入 (mixins) 是一種分發 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!"
|
## [選項合并](https://cn.vuejs.org/v2/guide/mixins.html#%E9%80%89%E9%A1%B9%E5%90%88%E5%B9%B6 "選項合并")
當組件和混入對象含有同名選項時,這些選項將以恰當的方式混合。
比如,數據對象在內部會進行淺合并 (一層屬性深度),在和組件的數據發生沖突時以組件數據優先。
|
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()`?也使用同樣的策略進行合并。
## [全局混入](https://cn.vuejs.org/v2/guide/mixins.html#%E5%85%A8%E5%B1%80%E6%B7%B7%E5%85%A5 "全局混入")
也可以全局注冊混入對象。注意使用! 一旦使用全局混入對象,將會影響到?所有?之后創建的 Vue 實例。使用恰當時,可以為自定義對象注入處理邏輯。
|
// 為自定義的選項 'myOption' 注入一個處理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
|
謹慎使用全局混入對象,因為會影響到每個單獨創建的 Vue 實例 (包括第三方模板)。大多數情況下,只應當應用于自定義選項,就像上面示例一樣。也可以將其用作?[Plugins](https://cn.vuejs.org/v2/guide/plugins.html)?以避免產生重復應用
## [自定義選項合并策略](https://cn.vuejs.org/v2/guide/mixins.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%80%89%E9%A1%B9%E5%90%88%E5%B9%B6%E7%AD%96%E7%95%A5 "自定義選項合并策略")
自定義選項將使用默認策略,即簡單地覆蓋已有值。如果想讓自定義選項以自定義邏輯合并,可以向?`Vue.config.optionMergeStrategies`?添加一個函數:
|
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// return mergedVal
}
|
對于大多數對象選項,可以使用?`methods`?的合并策略:
|
var strategies = Vue.config.optionMergeStrategies
strategies.myOption = strategies.methods
|
更多高級的例子可以在?[Vuex](https://github.com/vuejs/vuex)?的 1.x 混入策略里找到:
|
const merge = Vue.config.optionMergeStrategies.computed
Vue.config.optionMergeStrategies.vuex = function (toVal, fromVal) {
if (!toVal) return fromVal
if (!fromVal) return toVal
return {
getters: merge(toVal.getters, fromVal.getters),
state: merge(toVal.state, fromVal.state),
actions: merge(toVal.actions, fromVal.actions)
}
}
|
# 自定義指令
## [簡介](https://cn.vuejs.org/v2/guide/custom-directive.html#%E7%AE%80%E4%BB%8B "簡介")
除了核心功能默認內置的指令 (`v-model`?和?`v-show`),Vue 也允許注冊自定義指令。注意,在 Vue2.0 中,代碼復用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。舉個聚焦輸入框的例子,如下:
當頁面加載時,該元素將獲得焦點 (注意:`autofocus`?在移動版 Safari 上不工作)。事實上,只要你在打開這個頁面后還沒點擊過任何內容,這個輸入框就應當還是處于聚焦狀態。現在讓我們用指令來實現這個功能:
|
// 注冊一個全局自定義指令 `v-focus`
Vue.directive('focus', {
// 當被綁定的元素插入到 DOM 中時……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
|
如果想注冊局部指令,組件中也接受一個?`directives`?的選項:
|
directives: {
focus: {
// 指令的定義
inserted: function (el) {
el.focus()
}
}
}
|
然后你可以在模板中任何元素上使用新的?`v-focus`?屬性,如下:
|
input v-focus>
|
## [鉤子函數](https://cn.vuejs.org/v2/guide/custom-directive.html#%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0 "鉤子函數")
一個指令定義對象可以提供如下幾個鉤子函數 (均為可選):
* `bind`:只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
* `inserted`:被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被插入文檔中)。
* `update`:所在組件的 VNode 更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新 (詳細的鉤子函數參數見下)。
* `componentUpdated`:指令所在組件的 VNode?及其子 VNode?全部更新后調用。
* `unbind`:只調用一次,指令與元素解綁時調用。
接下來我們來看一下鉤子函數的參數 (即?`el`、`binding`、`vnode`?和?`oldVnode`)。
## [鉤子函數參數](https://cn.vuejs.org/v2/guide/custom-directive.html#%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0 "鉤子函數參數")
指令鉤子函數會被傳入以下參數:
* `el`:指令所綁定的元素,可以用來直接操作 DOM 。
* `binding`:一個對象,包含以下屬性:
* `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](https://cn.vuejs.org/v2/api/#VNode-%E6%8E%A5%E5%8F%A3)?來了解更多詳情。
* `oldVnode`:上一個虛擬節點,僅在?`update`?和?`componentUpdated`?鉤子中可用。
除了?`el`?之外,其它參數都應該是只讀的,切勿進行修改。如果需要在鉤子之間共享數據,建議通過元素的?[`dataset`](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/dataset)?來進行。
這是一個使用了這些屬性的自定義鉤子樣例:
|
div id="hook-arguments-example" v-demo:foo.a.b="message">div>
|
|
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '' +
'value: ' + s(binding.value) + '' +
'expression: ' + s(binding.expression) + '' +
'argument: ' + s(binding.arg) + '' +
'modifiers: ' + s(binding.modifiers) + '' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
|
name: "demo"
value: "hello!"
expression: "message"
argument: "foo"
modifiers: {"a":true,"b":true}
vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder
## [函數簡寫](https://cn.vuejs.org/v2/guide/custom-directive.html#%E5%87%BD%E6%95%B0%E7%AE%80%E5%86%99 "函數簡寫")
在很多時候,你可能想在?`bind`?和?`update`?時觸發相同行為,而不關心其它的鉤子。比如這樣寫:
|
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
|
## [對象字面量](https://cn.vuejs.org/v2/guide/custom-directive.html#%E5%AF%B9%E8%B1%A1%E5%AD%97%E9%9D%A2%E9%87%8F "對象字面量")
如果指令需要多個值,可以傳入一個 JavaScript 對象字面量。記住,指令函數能夠接受所有合法的 JavaScript 表達式。
|
div v-demo="{ color: 'white', text: 'hello!' }">div>
|
|
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
|
# 渲染函數 & JSX
## [基礎](https://cn.vuejs.org/v2/guide/render-function.html#%E5%9F%BA%E7%A1%80 "基礎")
Vue 推薦在絕大多數情況下使用 template 來創建你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全編程的能力,這時你可以用?render 函數,它比 template 更接近編譯器。
讓我們深入一個簡單的例子,這個例子里?`render`?函數很實用。假設我們要生成錨點標題 (anchored headings):
|
h1>
a name="hello-world" href="#hello-world">
Hello world!
a>
h1>
|
對于上面的 HTML,我們決定這樣定義組件接口:
|
anchored-heading :level="1">Hello world!anchored-heading>
|
當我們開始寫一個只能通過?`level`?prop 動態生成 heading 標簽的組件時,你可能很快想到這樣實現:
|
script type="text/x-template" id="anchored-heading-template">
if="level === 1">
slot>
h1>
else-if="level === 2">
slot>
h2>
else-if="level === 3">
slot>
h3>
else-if="level === 4">
slot>
h4>
else-if="level === 5">
slot>
h5>
else-if="level === 6">
slot>
h6>
script>
|
|
Vue.component('anchored-heading', {
template: '#anchored-heading-template',
props: {
level: {
type: Number,
required: true
}
}
})
|
在這種場景中使用 template 并不是最好的選擇:首先代碼冗長,為了在不同級別的標題中插入錨點元素,我們需要重復地使用?`<slot></slot>`。
雖然模板在大多數組件中都非常好用,但是在這里它就不是很簡潔的了。那么,我們來嘗試使用?`render`?函數重寫上面的例子:
|
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 標簽名稱
this.$slots.default // 子元素數組
)
},
props: {
level: {
type: Number,
required: true
}
}
})
|
簡單清晰很多!簡單來說,這樣代碼精簡很多,但是需要非常熟悉 Vue 的實例屬性。在這個例子中,你需要知道,向組件中傳遞不帶?`slot`?特性的子元素時,比如?`anchored-heading`?中的?`Hello world!`,這些子元素被存儲在組件實例中的?`$slots.default`?中。如果你還不了解,在深入 render 函數之前推薦閱讀[實例屬性 API](https://cn.vuejs.org/v2/api/#%E5%AE%9E%E4%BE%8B%E5%B1%9E%E6%80%A7)。
## [節點、樹以及虛擬 DOM](https://cn.vuejs.org/v2/guide/render-function.html#%E8%8A%82%E7%82%B9%E3%80%81%E6%A0%91%E4%BB%A5%E5%8F%8A%E8%99%9A%E6%8B%9F-DOM "節點、樹以及虛擬 DOM")
在深入渲染函數之前,了解一些瀏覽器的工作原理是很重要的。以下面這段 HTML 為例:
|
div>
h1>My titleh1>
Some text content
TODO: 添加標簽行 -->
div>
|
當瀏覽器讀到這些代碼時,它會建立一個[“DOM 節點”樹](https://javascript.info/dom-nodes)來保持追蹤,如同你會畫一張家譜樹來追蹤家庭成員的發展一樣。
HTML 的 DOM 節點樹如下圖所示:

每個元素都是一個節點。每片文字也是一個節點。甚至注釋也都是節點。一個節點就是頁面的一個部分。就像家譜樹一樣,每個節點都可以有孩子節點 (也就是說每個部分可以包含其它的一些部分)。
高效的更新所有這些節點會是比較困難的,不過所幸你不必再手動完成這個工作了。你只需要告訴 Vue 你希望頁面上的 HTML 是什么,這可以是在一個模板里:
|
h1>{{ blogTitle }}h1>
|
或者一個渲染函數里:
|
render: function (createElement) {
return createElement('h1', this.blogTitle)
}
|
在這兩種情況下,Vue 都會自動保持頁面的更新,即便?`blogTitle`?發生了改變。
### [虛擬 DOM](https://cn.vuejs.org/v2/guide/render-function.html#%E8%99%9A%E6%8B%9F-DOM "虛擬 DOM")
Vue 通過建立一個虛擬 DOM?對真實 DOM 發生的變化保持追蹤。請仔細看這行代碼:
|
return createElement('h1', this.blogTitle)
|
`createElement`?到底會返回什么呢?其實不是一個*實際的*?DOM 元素。它更準確的名字可能是?`createNodeDescription`,因為它所包含的信息會告訴 Vue 頁面上需要渲染什么樣的節點,及其子節點。我們把這樣的節點描述為“虛擬節點 (Virtual Node)”,也常簡寫它為“VNode”。“虛擬 DOM”是我們對由 Vue 組件樹建立起來的整個 VNode 樹的稱呼。
## [`createElement`?參數](https://cn.vuejs.org/v2/guide/render-function.html#createElement-%E5%8F%82%E6%95%B0 "createElement 參數")
接下來你需要熟悉的是如何在?`createElement`?函數中生成模板。這里是?`createElement`?接受的參數:
|
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一個 HTML 標簽字符串,組件選項對象,或者
// 解析上述任何一種的一個 async 異步函數。必需參數。
'div',
// {Object}
// 一個包含模板相關屬性的數據對象
// 你可以在 template 中使用這些特性。可選參數。
{
// (詳情見下一節)
},
// {String | Array}
// 子虛擬節點 (VNodes),由 `createElement()` 構建而成,
// 也可以使用字符串來生成“文本虛擬節點”。可選參數。
[
'先寫一些文字',
createElement('h1', '一則頭條'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
|
### [深入 data 對象](https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5-data-%E5%AF%B9%E8%B1%A1 "深入 data 對象")
有一點要注意:正如在模板語法中,`v-bind:class`?和?`v-bind:style`,會被特別對待一樣,在 VNode 數據對象中,下列屬性名是級別最高的字段。該對象也允許你綁定普通的 HTML 特性,就像 DOM 屬性一樣,比如?`innerHTML`?(這會取代?`v-html`?指令)。
|
{
// 和`v-bind:class`一樣的 API
// 接收一個字符串、對象或字符串和對象組成的數組
'class': {
foo: true,
bar: false
},
// 和`v-bind:style`一樣的 API
// 接收一個字符串、對象或對象組成的數組
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML 特性
attrs: {
id: 'foo'
},
// 組件 props
props: {
myProp: 'bar'
},
// DOM 屬性
domProps: {
innerHTML: 'baz'
},
// 事件監聽器基于 `on`
// 所以不再支持如 `v-on:keyup.enter` 修飾器
// 需要手動匹配 keyCode。
on: {
click: this.clickHandler
},
// 僅用于組件,用于監聽原生事件,而不是組件內部使用
// `vm.$emit` 觸發的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定義指令。注意,你無法對 `binding` 中的 `oldValue`
// 賦值,因為 Vue 已經自動為你進行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽格式
// { name: props => VNode | Array }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果組件是其他組件的子組件,需為插槽指定名稱
slot: 'name-of-slot',
// 其他特殊頂層屬性
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函數中向多個元素都應用了相同的 ref 名,
// 那么 `$refs.myRef` 會變成一個數組。
refInFor: true
}
|
### [完整示例](https://cn.vuejs.org/v2/guide/render-function.html#%E5%AE%8C%E6%95%B4%E7%A4%BA%E4%BE%8B "完整示例")
有了這些知識,我們現在可以完成我們最開始想實現的組件:
|
var getChildrenTextContent = function (children) {
return children.map(function (node) {
return node.children
? getChildrenTextContent(node.children)
: node.text
}).join('')
}
Vue.component('anchored-heading', {
render: function (createElement) {
// 創建 kebab-case 風格的ID
var headingId = getChildrenTextContent(this.$slots.default)
.toLowerCase()
.replace(/\W+/g, '-')
.replace(/(^\-|\-$)/g, '')
return createElement(
'h' + this.level,
[
createElement('a', {
attrs: {
name: headingId,
href: '#' + headingId
}
}, this.$slots.default)
]
)
},
props: {
level: {
type: Number,
required: true
}
}
})
|
### [約束](https://cn.vuejs.org/v2/guide/render-function.html#%E7%BA%A6%E6%9D%9F "約束")
#### [](https://cn.vuejs.org/v2/guide/render-function.html#VNodes-%E5%BF%85%E9%A1%BB%E5%94%AF%E4%B8%80 "VNodes 必須唯一")VNodes 必須唯一
組件樹中的所有 VNodes 必須是唯一的。這意味著,下面的 render function 是無效的:
|
render: function (createElement) {
var myParagraphVNode = createElement('p', 'hi')
return createElement('div', [
// 錯誤-重復的 VNodes
myParagraphVNode, myParagraphVNode
])
}
|
如果你真的需要重復很多次的元素/組件,你可以使用工廠函數來實現。例如,下面這個例子 render 函數完美有效地渲染了 20 個相同的段落:
|
render: function (createElement) {
return createElement('div',
Array.apply(null, { length: 20 }).map(function () {
return createElement('p', 'hi')
})
)
}
|
## [使用 JavaScript 代替模板功能](https://cn.vuejs.org/v2/guide/render-function.html#%E4%BD%BF%E7%94%A8-JavaScript-%E4%BB%A3%E6%9B%BF%E6%A8%A1%E6%9D%BF%E5%8A%9F%E8%83%BD "使用 JavaScript 代替模板功能")
### [`v-if`?和?`v-for`](https://cn.vuejs.org/v2/guide/render-function.html#v-if-%E5%92%8C-v-for "v-if 和 v-for")
只要在原生的 JavaScript 中可以輕松完成的操作,Vue 的 render 函數就不會提供專有的替代方法。比如,在 template 中使用的?`v-if`?和?`v-for`:
|
ul v-if="items.length">
li v-for="item in items">{{ item.name }}li>
ul>
p v-else>No items found.p>
|
這些都會在 render 函數中被 JavaScript 的?`if`/`else`?和?`map`?重寫:
|
props: ['items'],
render: function (createElement) {
if (this.items.length) {
return createElement('ul', this.items.map(function (item) {
return createElement('li', item.name)
}))
} else {
return createElement('p', 'No items found.')
}
}
|
### [`v-model`](https://cn.vuejs.org/v2/guide/render-function.html#v-model "v-model")
render 函數中沒有與?`v-model`?的直接對應 - 你必須自己實現相應的邏輯:
|
props: ['value'],
render: function (createElement) {
var self = this
return createElement('input', {
domProps: {
value: self.value
},
on: {
input: function (event) {
self.$emit('input', event.target.value)
}
}
})
}
|
這就是深入底層的代價,但與?`v-model`?相比,這可以讓你更好地控制交互細節。
### [事件 & 按鍵修飾符](https://cn.vuejs.org/v2/guide/render-function.html#%E4%BA%8B%E4%BB%B6-amp-%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6 "事件 & 按鍵修飾符")
對于?`.passive`、`.capture`?和?`.once`事件修飾符, Vue 提供了相應的前綴可以用于?`on`:
| Modifier(s) | Prefix |
| --- | --- |
| `.passive` | `&` |
| `.capture` | `!` |
| `.once` | `~` |
| `.capture.once`?or
`.once.capture` | `~!` |
例如:
|
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
'~!mouseover': this.doThisOnceInCapturingMode
}
|
對于其他的修飾符,前綴不是很重要,因為你可以在事件處理函數中使用事件方法:
| Modifier(s) | Equivalent in Handler |
| --- | --- |
| `.stop` | `event.stopPropagation()` |
| `.prevent` | `event.preventDefault()` |
| `.self` | `if (event.target !== event.currentTarget) return` |
| Keys:
`.enter`,?`.13` | `if (event.keyCode !== 13) return`?(change?`13`?to?[another key code](http://keycode.info/)?for other key modifiers) |
| Modifiers Keys:
`.ctrl`,?`.alt`,?`.shift`,?`.meta` | `if (!event.ctrlKey) return`?(change?`ctrlKey`?to?`altKey`,?`shiftKey`, or?`metaKey`, respectively) |
這里是一個使用所有修飾符的例子:
|
on: {
keyup: function (event) {
// 如果觸發事件的元素不是事件綁定的元素
// 則返回
if (event.target !== event.currentTarget) return
// 如果按下去的不是 enter 鍵或者
// 沒有同時按下 shift 鍵
// 則返回
if (!event.shiftKey || event.keyCode !== 13) return
// 阻止?事件冒泡
event.stopPropagation()
// 阻止該元素默認的 keyup 事件
event.preventDefault()
// ...
}
}
|
### [插槽](https://cn.vuejs.org/v2/guide/render-function.html#%E6%8F%92%E6%A7%BD "插槽")
你可以通過?[`this.$slots`](https://cn.vuejs.org/v2/api/#vm-slots)?訪問靜態插槽的內容,得到的是一個 VNodes 數組:
|
render: function (createElement) {
// ``
return createElement('div', this.$slots.default)
}
|
也可以通過?[`this.$scopedSlots`](https://cn.vuejs.org/v2/api/#vm-scopedSlots)?訪問作用域插槽,得到的是一個返回 VNodes 的函數:
|
props: ['message'],
render: function (createElement) {
// ``
return createElement('div', [
this.$scopedSlots.default({
text: this.message
})
])
}
|
如果要用渲染函數向子組件中傳遞作用域插槽,可以利用 VNode 數據對象中的?`scopedSlots`?域:
|
render: function (createElement) {
return createElement('div', [
createElement('child', {
// 在數據對象中傳遞 `scopedSlots`
// 格式:{ name: props => VNode | Array }
scopedSlots: {
default: function (props) {
return createElement('span', props.text)
}
}
})
])
}
|
## [JSX](https://cn.vuejs.org/v2/guide/render-function.html#JSX "JSX")
如果你寫了很多?`render`?函數,可能會覺得下面這樣的代碼寫起來很痛苦:
|
createElement(
'anchored-heading', {
props: {
level: 1
}
}, [
createElement('span', 'Hello'),
' world!'
]
)
|
特別是模板如此簡單的情況下:
|
anchored-heading :level="1">
span>Hellospan> world!
anchored-heading>
|
這就是為什么會有一個?[Babel 插件](https://github.com/vuejs/babel-plugin-transform-vue-jsx),用于在 Vue 中使用 JSX 語法,它可以讓我們回到更接近于模板的語法上。
|
import AnchoredHeading from './AnchoredHeading.vue'
new Vue({
el: '#demo',
render: function (h) {
return (
1}>
Hello/span> world!
AnchoredHeading>
)
}
})
|
將?`h`?作為?`createElement`?的別名是 Vue 生態系統中的一個通用慣例,實際上也是 JSX 所要求的,如果在作用域中?`h`?失去作用,在應用中會觸發報錯。
更多關于 JSX 映射到 JavaScript,閱讀?[使用文檔](https://github.com/vuejs/babel-plugin-transform-vue-jsx#usage)。
## [函數式組件](https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6 "函數式組件")
之前創建的錨點標題組件是比較簡單,沒有管理或者監聽任何傳遞給他的狀態,也沒有生命周期方法。它只是一個接收參數的函數。
在這個例子中,我們標記組件為?`functional`,這意味它是無狀態 (沒有[響應式數據](https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E6%95%B0%E6%8D%AE)),無實例 (沒有?`this`?上下文)。
一個函數式組件就像這樣:
|
Vue.component('my-component', {
functional: true,
// Props 可選
props: {
// ...
},
// 為了彌補缺少的實例
// 提供第二個參數作為上下文
render: function (createElement, context) {
// ...
}
})
|
> 注意:在 2.3.0 之前的版本中,如果一個函數式組件想要接受 props,則?`props`選項是必須的。在 2.3.0 或以上的版本中,你可以省略?`props`?選項,所有組件上的特性都會被自動解析為 props。
在 2.5.0 及以上版本中,如果你使用了[單文件組件](https://cn.vuejs.org/v2/guide/single-file-components.html),那么基于模板的函數式組件可以這樣聲明:
|
template functional>
template>
|
組件需要的一切都是通過上下文傳遞,包括:
* `props`:提供所有 prop 的對象
* `children`: VNode 子節點的數組
* `slots`: 返回所有插槽的對象的函數
* `data`:傳遞給組件的[數據對象](https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5-data-%E5%AF%B9%E8%B1%A1),作為?`createElement`?的第二個參數傳入組件
* `parent`:對父組件的引用
* `listeners`: (2.3.0+) 一個包含了所有在父組件上注冊的事件偵聽器的對象。這只是一個指向?`data.on`?的別名。
* `injections`: (2.3.0+) 如果使用了?[`inject`](https://cn.vuejs.org/v2/api/#provide-inject)?選項,則該對象包含了應當被注入的屬性。
在添加?`functional: true`?之后,錨點標題組件的 render 函數之間簡單更新增加?`context`?參數,`this.$slots.default`?更新為?`context.children`,之后`this.level`?更新為?`context.props.level`。
因為函數式組件只是一個函數,所以渲染開銷也低很多。然而,對持久化實例的缺乏也意味著函數式組件不會出現在?[Vue devtools](https://github.com/vuejs/vue-devtools)?的組件樹里。
在作為包裝組件時它們也同樣非常有用,比如,當你需要做這些時:
* 程序化地在多個組件中選擇一個
* 在將 children, props, data 傳遞給子組件之前操作它們。
下面是一個依賴傳入 props 的值的?`smart-list`?組件例子,它能代表更多具體的組件:
|
var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }
Vue.component('smart-list', {
functional: true,
props: {
items: {
type: Array,
required: true
},
isOrdered: Boolean
},
render: function (createElement, context) {
function appropriateListComponent () {
var items = context.props.items
if (items.length === 0) return EmptyList
if (typeof items[0] === 'object') return TableList
if (context.props.isOrdered) return OrderedList
return UnorderedList
}
return createElement(
appropriateListComponent(),
context.data,
context.children
)
}
})
|
### [向子元素或子組件傳遞特性和事件](https://cn.vuejs.org/v2/guide/render-function.html#%E5%90%91%E5%AD%90%E5%85%83%E7%B4%A0%E6%88%96%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BC%A0%E9%80%92%E7%89%B9%E6%80%A7%E5%92%8C%E4%BA%8B%E4%BB%B6 "向子元素或子組件傳遞特性和事件")
在普通組件中,沒有被定義為 prop 的特性會自動添加到組件的根元素上,將現有的同名特性替換或與其[智能合并](https://cn.vuejs.org/v2/guide/class-and-style.html)。
然而函數式組件要求你顯式定義該行為:
|
Vue.component('my-functional-button', {
functional: true,
render: function (createElement, context) {
// 完全透明的傳入任何特性、事件監聽器、子結點等。
return createElement('button', context.data, context.children)
}
})
|
向?`createElement`?通過傳入?`context.data`?作為第二個參數,我們就把?`my-functional-button`?上面所有的特性和事件監聽器都傳遞下去了。事實上這是非常透明的,那些事件甚至并不要求?`.native`?修飾符。
如果你使用基于模板的函數式組件,那么你還需要手動添加特性和監聽器。因為我們可以訪問到其獨立的上下文內容,所以我們可以使用?`data.attrs`?傳遞任何 HTML 特性,也可以使用?`listeners`?*(即?`data.on`?的別名)*?傳遞任何事件監聽器。
|
template functional>
button
class="btn btn-primary"
v-bind="data.attrs"
v-on="listeners"
>
slot/>
button>
template>
|
### [`slots()`?和?`children`?對比](https://cn.vuejs.org/v2/guide/render-function.html#slots-%E5%92%8C-children-%E5%AF%B9%E6%AF%94 "slots() 和 children 對比")
你可能想知道為什么同時需要?`slots()`?和?`children`。`slots().default`?不是和?`children`?類似的嗎?在一些場景中,是這樣,但是如果是函數式組件和下面這樣的 children 呢?
|
my-functional-component>
p slot="foo">
first
p>
p>secondp>
my-functional-component>
|
對于這個組件,`children`?會給你兩個段落標簽,而?`slots().default`?只會傳遞第二個匿名段落標簽,`slots().foo`?會傳遞第一個具名段落標簽。同時擁有?`children`和?`slots()`?,因此你可以選擇讓組件通過?`slot()`?系統分發或者簡單的通過?`children`?接收,讓其他組件去處理。
## [模板編譯](https://cn.vuejs.org/v2/guide/render-function.html#%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91 "模板編譯")
你可能有興趣知道,Vue 的模板實際是編譯成了 render 函數。這是一個實現細節,通常不需要關心,但如果你想看看模板的功能是怎樣被編譯的,你會發現會非常有趣。下面是一個使用?`Vue.compile`?來實時編譯模板字符串的簡單 demo:
render:
~~~
function anonymous(
) {
with(this){return _c('div',[_m(0),(message)?_c('p',[_v(_s(message))]):_c('p',[_v("No message.")])])}
}
~~~
staticRenderFns:
~~~
_m(0): function anonymous(
) {
with(this){return _c('header',[_c('h1',[_v("I'm a template!")])])}
}
~~~
# 插件
插件通常會為 Vue 添加全局功能。插件的范圍沒有限制——一般有下面幾種:
1. 添加全局方法或者屬性,如:?[vue-custom-element](https://github.com/karol-f/vue-custom-element)
2. 添加全局資源:指令/過濾器/過渡等,如?[vue-touch](https://github.com/vuejs/vue-touch)
3. 通過全局 mixin 方法添加一些組件選項,如:?[vue-router](https://github.com/vuejs/vue-router)
4. 添加 Vue 實例方法,通過把它們添加到 Vue.prototype 上實現。
5. 一個庫,提供自己的 API,同時提供上面提到的一個或多個功能,如?[vue-router](https://github.com/vuejs/vue-router)
## [使用插件](https://cn.vuejs.org/v2/guide/plugins.html#%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6 "使用插件")
通過全局方法?`Vue.use()`?使用插件。它需要在你調用?`new Vue()`?啟動應用之前完成:
|
// 調用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
new Vue({
//... options
})
|
也可以傳入一個選項對象:
|
Vue.use(MyPlugin, { someOption: true })
|
`Vue.use`?會自動阻止多次注冊相同插件,屆時只會注冊一次該插件。
Vue.js 官方提供的一些插件 (例如?`vue-router`) 在檢測到?`Vue`?是可訪問的全局變量時會自動調用?`Vue.use()`。然而在例如 CommonJS 的模塊環境中,你應該始終顯式地調用?`Vue.use()`:
|
// 用 Browserify 或 webpack 提供的 CommonJS 模塊環境時
var Vue = require('vue')
var VueRouter = require('vue-router')
// 不要忘了調用此方法
Vue.use(VueRouter)
|
[awesome-vue](https://github.com/vuejs/awesome-vue#components--libraries)?集合了來自社區貢獻的數以千計的插件和庫。
## [開發插件](https://cn.vuejs.org/v2/guide/plugins.html#%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6 "開發插件")
Vue.js 的插件應該有一個公開方法?`install`。這個方法的第一個參數是?`Vue`?構造器,第二個參數是一個可選的選項對象:
|
MyPlugin.install = function (Vue, options) {
// 1\. 添加全局方法或屬性
Vue.myGlobalMethod = function () {
// 邏輯...
}
// 2\. 添加全局資源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 邏輯...
}
...
})
// 3\. 注入組件
Vue.mixin({
created: function () {
// 邏輯...
}
...
})
// 4\. 添加實例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 邏輯...
}
}
|
# 過濾器
Vue.js 允許你自定義過濾器,可被用于一些常見的文本格式化。過濾器可以用在兩個地方:雙花括號插值和?`v-bind`?表達式?(后者從 2.1.0+ 開始支持)。過濾器應該被添加在 JavaScript 表達式的尾部,由“管道”符號指示:
|
{{ message | capitalize }}
div v-bind:id="rawId | formatId">div>
|
你可以在一個組件的選項中定義本地的過濾器:
|
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
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({
// ...
})
|
下面這個例子用到了?`capitalize`?過濾器:
John
過濾器函數總接收表達式的值 (之前的操作鏈的結果) 作為第一個參數。在上述例子中,`capitalize`?過濾器函數將會收到?`message`?的值作為第一個參數。
過濾器可以串聯:
|
{{ message | filterA | filterB }}
|
在這個例子中,`filterA`?被定義為接收單個參數的過濾器函數,表達式?`message`?的值將作為參數傳入到函數中。然后繼續調用同樣被定義為接收單個參數的過濾器函數?`filterB`,將?`filterA`?的結果傳遞到?`filterB`?中。
過濾器是 JavaScript 函數,因此可以接收參數:
|
{{ message | filterA('arg1', arg2) }}
|
這里,`filterA`?被定義為接收三個參數的過濾器函數。其中?`message`?的值作為第一個參數,普通字符串?`'arg1'`?作為第二個參數,表達式?`arg2`?的值作為第三個參數。
# 生產環境部署
## [開啟生產環境模式](https://cn.vuejs.org/v2/guide/deployment.html#%E5%BC%80%E5%90%AF%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E6%A8%A1%E5%BC%8F "開啟生產環境模式")
開發環境下,Vue 會提供很多警告來幫你對付常見的錯誤與陷阱。而在生產環境下,這些警告語句卻沒有用,反而會增加應用的體積。此外,有些警告檢查還有一些小的運行時開銷,這在生產環境模式下是可以避免的。
### [不使用構建工具](https://cn.vuejs.org/v2/guide/deployment.html#%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7 "不使用構建工具")
如果用 Vue 完整獨立版本,即直接用?`<script>`?元素引入 Vue 而不提前進行構建,請記得在生產環境下使用壓縮后的版本 (`vue.min.js`)。兩種版本都可以在[安裝指導](https://cn.vuejs.org/v2/guide/installation.html#%E7%9B%B4%E6%8E%A5%E7%94%A8-lt-script-gt-%E5%BC%95%E5%85%A5)中找到。
### [使用構建工具](https://cn.vuejs.org/v2/guide/deployment.html#%E4%BD%BF%E7%94%A8%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7 "使用構建工具")
當使用 webpack 或 Browserify 類似的構建工具時,Vue 源碼會根據?`process.env.NODE_ENV`?決定是否啟用生產環境模式,默認情況為開發環境模式。在 webpack 與 Browserify 中都有方法來覆蓋此變量,以啟用 Vue 的生產環境模式,同時在構建過程中警告語句也會被壓縮工具去除。這些所有?`vue-cli`?模板中都預先配置好了,但了解一下怎樣配置會更好。
#### [](https://cn.vuejs.org/v2/guide/deployment.html#webpack "webpack")webpack
在 webpack 4+ 中,你可以使用?`mode`?選項:
|
module.exports = {
mode: 'production'
}
|
但是在 webpack 3 及其更低版本中,你需要使用?[DefinePlugin](https://webpack.js.org/plugins/define-plugin/):
|
var webpack = require('webpack')
module.exports = {
// ...
plugins: [
// ...
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
}
|
#### [](https://cn.vuejs.org/v2/guide/deployment.html#Browserify "Browserify")Browserify
* 在運行打包命令時將?`NODE_ENV`?設置為?`"production"`。這等于告訴?`vueify`?避免引入熱重載和開發相關的代碼。
* 對打包后的文件進行一次全局的?[envify](https://github.com/hughsk/envify)?轉換。這使得壓縮工具能清除掉 Vue 源碼中所有用環境變量條件包裹起來的警告語句。例如:
|
NODE_ENV=production browserify -g envify -e main.js | uglifyjs -c -m > build.js
|
* 或者在 Gulp 中使用?[envify](https://github.com/hughsk/envify):
|
// 使用 envify 的自定義模塊來定制環境變量
var envify = require('envify/custom')
browserify(browserifyOptions)
.transform(vueify)
.transform(
// 必填項,以處理 node_modules 里的文件
{ global: true },
envify({ NODE_ENV: 'production' })
)
.bundle()
|
* 或者配合 Grunt 和?[grunt-browserify](https://github.com/jmreidy/grunt-browserify)?使用?[envify](https://github.com/hughsk/envify):
|
// 使用 envify 自定義模塊指定環境變量
var envify = require('envify/custom')
browserify: {
dist: {
options: {
// 該函數用來調整 grunt-browserify 的默認指令
configure: b => b
.transform('vueify')
.transform(
// 用來處理 `node_modules` 文件
{ global: true },
envify({ NODE_ENV: 'production' })
)
.bundle()
}
}
}
|
#### [](https://cn.vuejs.org/v2/guide/deployment.html#Rollup "Rollup")Rollup
使用?[rollup-plugin-replace](https://github.com/rollup/rollup-plugin-replace):
|
const replace = require('rollup-plugin-replace')
rollup({
// ...
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify( 'production' )
})
]
}).then(...)
|
## [模板預編譯](https://cn.vuejs.org/v2/guide/deployment.html#%E6%A8%A1%E6%9D%BF%E9%A2%84%E7%BC%96%E8%AF%91 "模板預編譯")
當使用 DOM 內模板或 JavaScript 內的字符串模板時,模板會在運行時被編譯為渲染函數。通常情況下這個過程已經足夠快了,但對性能敏感的應用還是最好避免這種用法。
預編譯模板最簡單的方式就是使用[單文件組件](https://cn.vuejs.org/v2/guide/single-file-components.html)——相關的構建設置會自動把預編譯處理好,所以構建好的代碼已經包含了編譯出來的渲染函數而不是原始的模板字符串。
如果你使用 webpack,并且喜歡分離 JavaScript 和模板文件,你可以使用?[vue-template-loader](https://github.com/ktsn/vue-template-loader),它也可以在構建過程中把模板文件轉換成為 JavaScript 渲染函數。
## [提取組件的 CSS](https://cn.vuejs.org/v2/guide/deployment.html#%E6%8F%90%E5%8F%96%E7%BB%84%E4%BB%B6%E7%9A%84-CSS "提取組件的 CSS")
當使用單文件組件時,組件內的 CSS 會以?`<style>`?標簽的方式通過 JavaScript 動態注入。這有一些小小的運行時開銷,如果你使用服務端渲染,這會導致一段“無樣式內容閃爍 (fouc)”。將所有組件的 CSS 提取到同一個文件可以避免這個問題,也會讓 CSS 更好地進行壓縮和緩存。
查閱這個構建工具各自的文檔來了解更多:
* [webpack + vue-loader](https://vue-loader.vuejs.org/zh-cn/configurations/extract-css.html)?(`vue-cli`?的 webpack 模板已經預先配置好)
* [Browserify + vueify](https://github.com/vuejs/vueify#css-extraction)
* [Rollup + rollup-plugin-vue](https://vuejs.github.io/rollup-plugin-vue/#/en/2.3/?id=custom-handler)
## [跟蹤運行時錯誤](https://cn.vuejs.org/v2/guide/deployment.html#%E8%B7%9F%E8%B8%AA%E8%BF%90%E8%A1%8C%E6%97%B6%E9%94%99%E8%AF%AF "跟蹤運行時錯誤")
如果在組件渲染時出現運行錯誤,錯誤將會被傳遞至全局?`Vue.config.errorHandler`配置函數 (如果已設置)。利用這個鉤子函數來配合錯誤跟蹤服務是個不錯的主意。比如?[Sentry](https://sentry.io/),它為 Vue 提供了[官方集成](https://sentry.io/for/vue/)。
# 單文件組件
## [介紹](https://cn.vuejs.org/v2/guide/single-file-components.html#%E4%BB%8B%E7%BB%8D "介紹")
在很多 Vue 項目中,我們使用?`Vue.component`?來定義全局組件,緊接著用?`new Vue({ el: '#container '})`?在每個頁面內指定一個容器元素。
這種方式在很多中小規模的項目中運作的很好,在這些項目里 JavaScript 只被用來加強特定的視圖。但當在更復雜的項目中,或者你的前端完全由 JavaScript 驅動的時候,下面這些缺點將變得非常明顯:
* 全局定義 (Global definitions)?強制要求每個 component 中的命名不得重復
* 字符串模板 (String templates)?缺乏語法高亮,在 HTML 有多行的時候,需要用到丑陋的?`\`
* 不支持 CSS (No CSS support)?意味著當 HTML 和 JavaScript 組件化時,CSS 明顯被遺漏
* 沒有構建步驟 (No build step)?限制只能使用 HTML 和 ES5 JavaScript, 而不能使用預處理器,如 Pug (formerly Jade) 和 Babel
文件擴展名為?`.vue`?的?single-file components(單文件組件)?為以上所有問題提供了解決方法,并且還可以使用 webpack 或 Browserify 等構建工具。
這是一個文件名為?`Hello.vue`?的簡單實例:
[](https://gist.github.com/chrisvfritz/e2b6a6110e0829d78fa4aedf7cf6b235)
現在我們獲得:
* [完整語法高亮](https://github.com/vuejs/awesome-vue#source-code-editing)
* [CommonJS 模塊](https://webpack.js.org/concepts/modules/#what-is-a-webpack-module)
* [組件作用域的 CSS](https://vue-loader.vuejs.org/zh-cn/features/scoped-css.html)
正如我們說過的,我們可以使用預處理器來構建簡潔和功能更豐富的組件,比如 Pug,Babel (with ES2015 modules),和 Stylus。
[](https://gist.github.com/chrisvfritz/1c9f2daea9bc078dcb47e9a82e5f7587)
這些特定的語言只是例子,你可以只是簡單地使用 Babel,TypeScript,SCSS,PostCSS - 或者其他任何能夠幫助你提高生產力的預處理器。如果搭配?`vue-loader`?使用 webpack,它也是把 CSS Modules 當作第一公民來對待的。
### [怎么看待關注點分離?](https://cn.vuejs.org/v2/guide/single-file-components.html#%E6%80%8E%E4%B9%88%E7%9C%8B%E5%BE%85%E5%85%B3%E6%B3%A8%E7%82%B9%E5%88%86%E7%A6%BB%EF%BC%9F "怎么看待關注點分離?")
一個重要的事情值得注意,關注點分離不等于文件類型分離。在現代 UI 開發中,我們已經發現相比于把代碼庫分離成三個大的層次并將其相互交織起來,把它們劃分為松散耦合的組件再將其組合起來更合理一些。在一個組件里,其模板、邏輯和樣式是內部耦合的,并且把他們搭配在一起實際上使得組件更加內聚且更可維護。
即便你不喜歡單文件組件,你仍然可以把 JavaScript、CSS 分離成獨立的文件然后做到熱重載和預編譯。
|
template>
div>This will be pre-compileddiv>
template>
script src="./my-component.js">script>
style src="./my-component.css">style>
|
## [起步](https://cn.vuejs.org/v2/guide/single-file-components.html#%E8%B5%B7%E6%AD%A5 "起步")
### [例子沙箱](https://cn.vuejs.org/v2/guide/single-file-components.html#%E4%BE%8B%E5%AD%90%E6%B2%99%E7%AE%B1 "例子沙箱")
如果你希望深入了解并開始使用單文件組件,請來 CodeSandbox?[看看這個簡單的 todo 應用](https://codesandbox.io/s/o29j95wx9)。
### [針對剛接觸 JavaScript 模塊開發系統的用戶](https://cn.vuejs.org/v2/guide/single-file-components.html#%E9%92%88%E5%AF%B9%E5%88%9A%E6%8E%A5%E8%A7%A6-JavaScript-%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91%E7%B3%BB%E7%BB%9F%E7%9A%84%E7%94%A8%E6%88%B7 "針對剛接觸 JavaScript 模塊開發系統的用戶")
有了?`.vue`?組件,我們就進入了高級 JavaScript 應用領域。如果你沒有準備好的話,意味著還需要學會使用一些附加的工具:
* Node Package Manager (NPM):閱讀?[Getting Started guide](https://docs.npmjs.com/getting-started/what-is-npm)?直到?*10: Uninstalling global packages*章節。
* Modern JavaScript with ES2015/16:閱讀 Babel 的?[Learn ES2015 guide](https://babeljs.io/docs/learn-es2015/)。你不需要立刻記住每一個方法,但是你可以保留這個頁面以便后期參考。
在你花一天時間了解這些資源之后,我們建議你參考?[webpack](https://github.com/vuejs-templates/webpack)?模板。只要遵循指示,你就能很快地運行一個用到?`.vue`?組件,ES2015 和熱重載 (hot-reloading) 的 Vue 項目!
想學習更多 webpack 的知識,請移步[它們的官方文檔](https://webpack.js.org/configuration/)以及?[webpack learning academy](https://webpack.academy/p/the-core-concepts)。在 webpack 中,每個模塊被打包到 bundle 之前都由一個相應的“loader”來轉換,Vue 也提供?[vue-loader](https://github.com/vuejs/vue-loader)?插件來執行?`.vue`?單文件組件 的轉換。
### [針對高級用戶](https://cn.vuejs.org/v2/guide/single-file-components.html#%E9%92%88%E5%AF%B9%E9%AB%98%E7%BA%A7%E7%94%A8%E6%88%B7 "針對高級用戶")
無論你更鐘情 webpack 或是 Browserify,我們為簡單的和更復雜的項目都提供了一些文檔模板。我們建議瀏覽?[github.com/vuejs-templates](https://github.com/vuejs-templates),找到你需要的部分,然后參考 README 中的說明,使用?[vue-cli](https://github.com/vuejs/vue-cli)?工具生成新的項目。
模板中使用?[webpack](https://webpack.js.org/),一個模塊加載器加載多個模塊然后構建成最終應用。為了進一步了解 webpack,可以看?[官方介紹視頻](https://www.youtube.com/watch?v=WQue1AN93YU)。如果你有基礎,可以看?[在 Egghead.io 上的 webpack 進階教程](https://egghead.io/courses/using-webpack-for-production-javascript-applications)。# 單元測試
## [配置和工具](https://cn.vuejs.org/v2/guide/unit-testing.html#%E9%85%8D%E7%BD%AE%E5%92%8C%E5%B7%A5%E5%85%B7 "配置和工具")
任何兼容基于模塊的構建系統都可以正常使用,但如果你需要一個具體的建議,可以使用?[Karma](https://karma-runner.github.io/)?進行自動化測試。它有很多社區版的插件,包括對?[Webpack](https://github.com/webpack/karma-webpack)?和?[Browserify](https://github.com/Nikku/karma-browserify)?的支持。更多詳細的安裝步驟,請參考各項目的安裝文檔,通過這些 Karma 配置的例子可以快速幫助你上手 ([Webpack](https://github.com/vuejs-templates/webpack/blob/master/template/test/unit/karma.conf.js)?配置,[Browserify](https://github.com/vuejs-templates/browserify/blob/master/template/karma.conf.js)?配置)。
## [簡單的斷言](https://cn.vuejs.org/v2/guide/unit-testing.html#%E7%AE%80%E5%8D%95%E7%9A%84%E6%96%AD%E8%A8%80 "簡單的斷言")
你不必為了可測性在組件中做任何特殊的操作,導出原始設置就可以了:
|
template>
span>{{ message }}span>
template>
script>
export default {
data () {
return {
message: 'hello!'
}
},
created () {
this.message = 'bye!'
}
}
script>
|
然后隨著 Vue 導入組件的選項,你可以使用許多常見的斷言:
|
// 導入 Vue.js 和組件,進行測試
import Vue from 'vue'
import MyComponent from 'path/to/MyComponent.vue'
// 這里是一些 Jasmine 2.0 的測試,你也可以使用你喜歡的任何斷言庫或測試工具。
describe('MyComponent', () => {
// 檢查原始組件選項
it('has a created hook', () => {
expect(typeof MyComponent.created).toBe('function')
})
// 評估原始組件選項中的函數的結果
it('sets the correct default data', () => {
expect(typeof MyComponent.data).toBe('function')
const defaultData = MyComponent.data()
expect(defaultData.message).toBe('hello!')
})
// 檢查 mount 中的組件實例
it('correctly sets the message when created', () => {
const vm = new Vue(MyComponent).$mount()
expect(vm.message).toBe('bye!')
})
// 創建一個實例并檢查渲染輸出
it('renders the correct message', () => {
const Constructor = Vue.extend(MyComponent)
const vm = new Constructor().$mount()
expect(vm.$el.textContent).toBe('bye!')
})
})
|
## [編寫可被測試的組件](https://cn.vuejs.org/v2/guide/unit-testing.html#%E7%BC%96%E5%86%99%E5%8F%AF%E8%A2%AB%E6%B5%8B%E8%AF%95%E7%9A%84%E7%BB%84%E4%BB%B6 "編寫可被測試的組件")
很多組件的渲染輸出由它的 props 決定。事實上,如果一個組件的渲染輸出完全取決于它的 props,那么它會讓測試變得簡單,就好像斷言不同參數的純函數的返回值。看下面這個例子:
|
template>
p>{{ msg }}p>
template>
script>
export default {
props: ['msg']
}
script>
|
你可以在不同的 props 中,通過?`propsData`?選項斷言它的渲染輸出:
|
import Vue from 'vue'
import MyComponent from './MyComponent.vue'
// 掛載元素并返回已渲染的文本的工具函數
function getRenderedText (Component, propsData) {
const Constructor = Vue.extend(Component)
const vm = new Constructor({ propsData: propsData }).$mount()
return vm.$el.textContent
}
describe('MyComponent', () => {
it('renders correctly with different props', () => {
expect(getRenderedText(MyComponent, {
msg: 'Hello'
})).toBe('Hello')
expect(getRenderedText(MyComponent, {
msg: 'Bye'
})).toBe('Bye')
})
})
|
## [斷言異步更新](https://cn.vuejs.org/v2/guide/unit-testing.html#%E6%96%AD%E8%A8%80%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0 "斷言異步更新")
由于 Vue 進行?[異步更新 DOM](https://cn.vuejs.org/v2/guide/reactivity.html#%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0%E9%98%9F%E5%88%97)?的情況,一些依賴 DOM 更新結果的斷言必須在?`Vue.nextTick`?回調中進行:
|
// 在狀態更新后檢查生成的 HTML
it('updates the rendered message when vm.message updates', done => {
const vm = new Vue(MyComponent).$mount()
vm.message = 'foo'
// 在狀態改變后和斷言 DOM 更新前等待一刻
Vue.nextTick(() => {
expect(vm.$el.textContent).toBe('foo')
done()
})
})
|
我們計劃做一個通用的測試工具集,讓不同策略的渲染輸出 (例如忽略子組件的基本渲染) 和斷言變得更簡單。
關于更深入的 Vue 單元測試的內容,請移步?[vue-test-utils](https://vue-test-utils.vuejs.org/zh/)?以及我們關于?[Vue 組件的單元測試](https://cn.vuejs.org/v2/cookbook/unit-testing-vue-components.html)的 cookbook 文章。
# TypeScript 支持
在 Vue 2.5.0 中,我們大大改進了類型聲明以更好地使用默認的基于對象的 API。同時此版本也引入了一些其它變化,需要開發者作出相應的升級。閱讀[博客文章](https://medium.com/the-vue-point/upcoming-typescript-changes-in-vue-2-5-e9bd7e2ecf08)了解更多詳情。
## [發布為 NPM 包的官方聲明文件](https://cn.vuejs.org/v2/guide/typescript.html#%E5%8F%91%E5%B8%83%E4%B8%BA-NPM-%E5%8C%85%E7%9A%84%E5%AE%98%E6%96%B9%E5%A3%B0%E6%98%8E%E6%96%87%E4%BB%B6 "發布為 NPM 包的官方聲明文件")
靜態類型系統能幫助你有效防止許多潛在的運行時錯誤,而且隨著你的應用日漸豐滿會更加顯著。這就是為什么 Vue 不僅僅為 Vue core 提供了針對?[TypeScript](https://www.typescriptlang.org/)?的[官方類型聲明](https://github.com/vuejs/vue/tree/dev/types),還為?[Vue Router](https://github.com/vuejs/vue-router/tree/dev/types)?和?[Vuex](https://github.com/vuejs/vuex/tree/dev/types)?也提供了相應的聲明文件。
而且,我們已經把它們[發布到了 NPM](https://cdn.jsdelivr.net/npm/vue/types/),最新版本的 TypeScript 也知道該如何自己從 NPM 包里解析類型聲明。這意味著只要你成功地通過 NPM 安裝了,就不再需要任何額外的工具輔助,即可在 Vue 中使用 TypeScript 了。
## [推薦配置](https://cn.vuejs.org/v2/guide/typescript.html#%E6%8E%A8%E8%8D%90%E9%85%8D%E7%BD%AE "推薦配置")
|
// tsconfig.json
{
"compilerOptions": {
// 與 Vue 的瀏覽器支持保持一致
"target": "es5",
// 這可以對 `this` 上的數據屬性進行更嚴格的推斷
"strict": true,
// 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:
"module": "es2015",
"moduleResolution": "node"
}
}
|
注意你需要引入?`strict: true`?(或者至少?`noImplicitThis: true`,這是?`strict`模式的一部分) 以利用組件方法中?`this`?的類型檢查,否則它會始終被看作?`any`?類型。
參閱?[TypeScript 編譯器選項文檔 (英)](https://www.typescriptlang.org/docs/handbook/compiler-options.html)?了解更多。
## [開發工具鏈](https://cn.vuejs.org/v2/guide/typescript.html#%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7%E9%93%BE "開發工具鏈")
### [工程創建](https://cn.vuejs.org/v2/guide/typescript.html#%E5%B7%A5%E7%A8%8B%E5%88%9B%E5%BB%BA "工程創建")
[Vue CLI 3](https://github.com/vuejs/vue-cli)?可以使用 TypeScript 生成新工程。創建方式:
|
# 1\. 如果沒有安裝 Vue CLI 就先安裝
npm install --global @vue/cli
# 2\. 創建一個新工程,并選擇 "Manually select features (手動選擇特性)" 選項
vue create my-project-name
|
### [編輯器支持](https://cn.vuejs.org/v2/guide/typescript.html#%E7%BC%96%E8%BE%91%E5%99%A8%E6%94%AF%E6%8C%81 "編輯器支持")
要使用 TypeScript 開發 Vue 應用程序,我們強烈建議您使用?[Visual Studio Code](https://code.visualstudio.com/),它為 TypeScript 提供了極好的“開箱即用”支持。如果你正在使用[單文件組件](https://cn.vuejs.org/v2/guide/single-file-components.html)?(SFC), 可以安裝提供 SFC 支持以及其他更多實用功能的?[Vetur 插件](https://github.com/vuejs/vetur)。
[WebStorm](https://www.jetbrains.com/webstorm/)?同樣為 TypeScript 和 Vue 提供了“開箱即用”的支持。
## [基本用法](https://cn.vuejs.org/v2/guide/typescript.html#%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95 "基本用法")
要讓 TypeScript 正確推斷 Vue 組件選項中的類型,您需要使用?`Vue.component`?或?`Vue.extend`?定義組件:
|
import Vue from 'vue'
const Component = Vue.extend({
// 類型推斷已啟用
})
const Component = {
// 這里不會有類型推斷,
// 因為TypeScript不能確認這是Vue組件的選項
}
|
## [基于類的 Vue 組件](https://cn.vuejs.org/v2/guide/typescript.html#%E5%9F%BA%E4%BA%8E%E7%B1%BB%E7%9A%84-Vue-%E7%BB%84%E4%BB%B6 "基于類的 Vue 組件")
如果您在聲明組件時更喜歡基于類的 API,則可以使用官方維護的?[vue-class-component](https://github.com/vuejs/vue-class-component)?裝飾器:
|
import Vue from 'vue'
import Component from 'vue-class-component'
// @Component 修飾符注明了此類為一個 Vue 組件
@Component({
// 所有的組件選項都可以放在這里
template: 'Click!'
})
export default class MyComponent extends Vue {
// 初始數據可以直接聲明為實例的屬性
message: string = 'Hello!'
// 組件方法也可以直接聲明為實例的方法
onClick (): void {
window.alert(this.message)
}
}
|
## [增強類型以配合插件使用](https://cn.vuejs.org/v2/guide/typescript.html#%E5%A2%9E%E5%BC%BA%E7%B1%BB%E5%9E%8B%E4%BB%A5%E9%85%8D%E5%90%88%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8 "增強類型以配合插件使用")
插件可以增加 Vue 的全局/實例屬性和組件選項。在這些情況下,在 TypeScript 中制作插件需要類型聲明。慶幸的是,TypeScript 有一個特性來補充現有的類型,叫做[模塊補充 (module augmentation)](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)。
例如,聲明一個?`string`?類型的實例屬性?`$myProperty`:
|
// 1\. 確保在聲明補充的類型之前導入 'vue'
import Vue from 'vue'
// 2\. 定制一個文件,設置你想要補充的類型
// 在 types/vue.d.ts 里 Vue 有構造函數類型
declare module 'vue/types/vue' {
// 3\. 聲明為 Vue 補充的東西
interface Vue {
$myProperty: string
}
}
|
在你的項目中包含了上述作為聲明文件的代碼之后 (像?`my-property.d.ts`),你就可以在 Vue 實例上使用?`$myProperty`?了。
|
var vm = new Vue()
console.log(vm.$myProperty) // 將會順利編譯通過
|
你也可以聲明額外的屬性和組件選項:
|
import Vue from 'vue'
declare module 'vue/types/vue' {
// 可以使用 `VueConstructor` 接口
// 來聲明全局屬性
interface VueConstructor {
$myGlobal: string
}
}
// ComponentOptions 聲明于 types/options.d.ts 之中
declare module 'vue/types/options' {
interface ComponentOptionsextends Vue> {
myOption?: string
}
}
|
上述的聲明允許下面的代碼順利編譯通過:
|
// 全局屬性
console.log(Vue.$myGlobal)
// 額外的組件選項
var vm = new Vue({
myOption: 'Hello'
})
|
## [標注返回值](https://cn.vuejs.org/v2/guide/typescript.html#%E6%A0%87%E6%B3%A8%E8%BF%94%E5%9B%9E%E5%80%BC "標注返回值")
因為 Vue 的聲明文件天生就具有循環性,TypeScript 可能在推斷某個方法的類型的時候存在困難。因此,你可能需要在?`render`?或?`computed`?里的方法上標注返回值。
|
import Vue, { VNode } from 'vue'
const Component = Vue.extend({
data () {
return {
msg: 'Hello'
}
},
methods: {
// 需要標注有 `this` 參與運算的返回值類型
greet (): string {
return this.msg + ' world'
}
},
computed: {
// 需要標注
greeting(): string {
return this.greet() + '!'
}
},
// `createElement` 是可推導的,但是 `render` 需要返回值類型
render (createElement): VNode {
return createElement('div', this.greeting)
}
})
|
如果你發現類型推導或成員補齊不工作了,標注某個方法也許可以幫助你解決這個問題。使用?`--noImplicitAny`?選項將會幫助你找到這些未標注的方法。
# 路由
## [官方路由](https://cn.vuejs.org/v2/guide/routing.html#%E5%AE%98%E6%96%B9%E8%B7%AF%E7%94%B1 "官方路由")
對于大多數單頁面應用,都推薦使用官方支持的?[vue-router 庫](https://github.com/vuejs/vue-router)。更多細節可以看?[vue-router 文檔](https://router.vuejs.org/)。
## [從零開始簡單的路由](https://cn.vuejs.org/v2/guide/routing.html#%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%AE%80%E5%8D%95%E7%9A%84%E8%B7%AF%E7%94%B1 "從零開始簡單的路由")
如果只需要非常簡單的路由而不需要引入整個路由庫,可以動態渲染一個頁面級的組件像這樣:
|
const NotFound = { template: 'Page not found' }
const Home = { template: 'home page' }
const About = { template: 'about page' }
const routes = {
'/': Home,
'/about': About
}
new Vue({
el: '#app',
data: {
currentRoute: window.location.pathname
},
computed: {
ViewComponent () {
return routes[this.currentRoute] || NotFound
}
},
render (h) { return h(this.ViewComponent) }
})
|
結合 HTML5 History API,你可以建立一個非常基本但功能齊全的客戶端路由器。可以直接看[實例應用](https://github.com/chrisvfritz/vue-2.0-simple-routing-example)
## [整合第三方路由](https://cn.vuejs.org/v2/guide/routing.html#%E6%95%B4%E5%90%88%E7%AC%AC%E4%B8%89%E6%96%B9%E8%B7%AF%E7%94%B1 "整合第三方路由")
如果有非常喜歡的第三方路由,如?[Page.js](https://github.com/visionmedia/page.js)?或者?[Director](https://github.com/flatiron/director),整合[很簡單](https://github.com/chrisvfritz/vue-2.0-simple-routing-example/compare/master...pagejs)。這有個用了 Page.js 的[復雜示例](https://github.com/chrisvfritz/vue-2.0-simple-routing-example/tree/pagejs)。
# 狀態管理
## [類 Flux 狀態管理的官方實現](https://cn.vuejs.org/v2/guide/state-management.html#%E7%B1%BB-Flux-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E7%9A%84%E5%AE%98%E6%96%B9%E5%AE%9E%E7%8E%B0 "類 Flux 狀態管理的官方實現")
由于狀態零散地分布在許多組件和組件之間的交互中,大型應用復雜度也經常逐漸增長。為了解決這個問題,Vue 提供?[vuex](https://github.com/vuejs/vuex):我們有受到 Elm 啟發的狀態管理庫。vuex 甚至集成到?[vue-devtools](https://github.com/vuejs/vue-devtools),無需配置即可進行[時光旅行調試](https://raw.githubusercontent.com/vuejs/vue-devtools/master/media/demo.gif)。
### [React 的開發者請參考以下信息](https://cn.vuejs.org/v2/guide/state-management.html#React-%E7%9A%84%E5%BC%80%E5%8F%91%E8%80%85%E8%AF%B7%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BF%A1%E6%81%AF "React 的開發者請參考以下信息")
如果你是來自 React 的開發者,你可能會對 Vuex 和?[Redux](https://github.com/reactjs/redux)?間的差異表示關注,Redux 是 React 生態環境中最流行的 Flux 實現。Redux 事實上無法感知視圖層,所以它能夠輕松的通過一些[簡單綁定](https://yarnpkg.com/en/packages?q=redux%20vue&p=1)和 Vue 一起使用。Vuex 區別在于它是一個專門為 Vue 應用所設計。這使得它能夠更好地和 Vue 進行整合,同時提供簡潔的 API 和改善過的開發體驗。
## [簡單狀態管理起步使用](https://cn.vuejs.org/v2/guide/state-management.html#%E7%AE%80%E5%8D%95%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E8%B5%B7%E6%AD%A5%E4%BD%BF%E7%94%A8 "簡單狀態管理起步使用")
經常被忽略的是,Vue 應用中原始`數據`對象的實際來源 - 當訪問數據對象時,一個 Vue 實例只是簡單的代理訪問。所以,如果你有一處需要被多個實例間共享的狀態,可以簡單地通過維護一份數據來實現共享:
|
const sourceOfTruth = {}
const vmA = new Vue({
data: sourceOfTruth
})
const vmB = new Vue({
data: sourceOfTruth
})
|
現在當?`sourceOfTruth`?發生變化,`vmA`?和?`vmB`?都將自動的更新引用它們的視圖。子組件們的每個實例也會通過?`this.$root.$data`?去訪問。現在我們有了唯一的數據來源,但是,調試將會變為噩夢。任何時間,我們應用中的任何部分,在任何數據改變后,都不會留下變更過的記錄。
為了解決這個問題,我們采用一個簡單的?store 模式:
|
var store = {
debug: true,
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
if (this.debug) console.log('setMessageAction triggered with', newValue)
this.state.message = newValue
},
clearMessageAction () {
if (this.debug) console.log('clearMessageAction triggered')
this.state.message = ''
}
}
|
需要注意,所有 store 中 state 的改變,都放置在 store 自身的 action 中去管理。這種集中式狀態管理能夠被更容易地理解哪種類型的 mutation 將會發生,以及它們是如何被觸發。當錯誤出現時,我們現在也會有一個 log 記錄 bug 之前發生了什么。
此外,每個實例/組件仍然可以擁有和管理自己的私有狀態:
|
var vmA = new Vue({
data: {
privateState: {},
sharedState: store.state
}
})
var vmB = new Vue({
data: {
privateState: {},
sharedState: store.state
}
})
|

重要的是,注意你不應該在 action 中 替換原始的狀態對象 - 組件和 store 需要引用同一個共享對象,mutation 才能夠被觀察
接著我們繼續延伸約定,組件不允許直接修改屬于 store 實例的 state,而應執行 action 來分發 (dispatch) 事件通知 store 去改變,我們最終達成了?[Flux](https://facebook.github.io/flux/)?架構。這樣約定的好處是,我們能夠記錄所有 store 中發生的 state 改變,同時實現能做到記錄變更 (mutation)、保存狀態快照、歷史回滾/時光旅行的先進的調試工具。
說了一圈其實又回到了[vuex](https://github.com/vuejs/vuex),如果你已經讀到這兒,或許可以去嘗試一下!
# 服務端渲染
## [SSR 完全指南](https://cn.vuejs.org/v2/guide/ssr.html#SSR-%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97 "SSR 完全指南")
在 2.3 發布后我們發布了一份完整的構建 Vue 服務端渲染應用的指南。這份指南非常深入,適合已經熟悉 Vue, webpack 和 Node.js 開發的開發者閱讀。請移步?[ssr.vuejs.org](https://ssr.vuejs.org/zh/)。
## [Nuxt.js](https://cn.vuejs.org/v2/guide/ssr.html#Nuxt-js "Nuxt.js")
從頭搭建一個服務端渲染的應用是相當復雜的。幸運的是,我們有一個優秀的社區項目?[Nuxt.js](https://nuxtjs.org/)?讓這一切變得非常簡單。Nuxt 是一個基于 Vue 生態的更高層的框架,為開發服務端渲染的 Vue 應用提供了極其便利的開發體驗。更酷的是,你甚至可以用它來做為靜態站生成器。推薦嘗試。