在2.x版本中Vue
會經過Object.defineProperty
對數據進行劫持, 以實現雙向數據綁定. 但在一些特定的業務場景, 組件只須要進行純數據展現, 不會有任何變化, 此時咱們可能不須要Vue
對來數據進行劫持. 在大量數據須要進行呈現時, 若是禁止Vue
對數據進行劫持, 會明顯減小組件初始化的時間.javascript
::: tip 經過Object.freeze
方法凍結對象, 對象一旦被凍結就不能再被修改了. :::css
export default {
data: () => ({
userList: []
}),
async created() {
const userList = await this.$service.get("/getuserList");
this.userList = Object.freeze(userList);
}
};
複製代碼
基於上面的案例(長列表性能優化
), 能夠經過Object.freeze
來實現純呈現的列表性能優化, 那如何來確認呢?html
咱們能夠經過Chrome Devtools來檢測. 但爲了得到準確的性能分析數據, 咱們須要開啓Vue應用的性能模式.vue
在工程中的main.js
中(Vue根實例初始化以前), 添加如下代碼:java
Vue.config.performance = true;
複製代碼
固然, 你也能夠根據須要對當前環境進行判斷, 來決定是否開啓性能模式.webpack
const isDev = process.env.NODE_ENV !== "production";
Vue.config.performance = isDev;
複製代碼
這樣, 將會激活Vue在內部用於標記組件性能的 Timing API. 以下圖所示: git
假設, 此時咱們建立好了一個demo工程, 並有一個Hello.vue
的組件, 用於驗證長列表渲染性能問題. 運行本地工程後, 打開瀏覽器到指定路由(確認有加載Hello.vue
組件). 打開控制檯, 並點擊"reload"按鈕, 以下圖所示: github
此時, 將會記錄頁面性能. 由於已經在main.js上添加了Vue.config.performance設置,此時你將可以在分析中看到時序部分. 以下圖所示. web
此時, 你會發現這裏有3個指標:chrome
在此例中, http://localhost:8080/#/hello 路由下, 只有兩個組件:
App.vue
Hello.vue
複製代碼
App.vue
是視圖組件, 只有一個<router-view/>
Hello.vue
只作一個簡單的長列表(100000條件數據)展現, 代碼以下:
<template>
<div>
<span v-for="(item, idx) in users" :key="idx">
{{item.name}}
</span>
</div>
</template>
<script>
export default {
data () {
return {
users: []
}
},
components: {
},
created () {
let users = Array.from({ length: 100000 }, (item, index) => ({ name: index }))
this.users = users
}
}
</script>
複製代碼
此時, Hello.vue
組件render
&patch
的時間爲:
修改Hello.vue
的created
鉤子函數中的代碼以下:
created () {
let users = Array.from({ length: 100000 }, (item, index) => ({ name: index }))
this.users = Object.freeze(users)
}
複製代碼
再次點擊"reload"按鈕, 從新測試性能.
此時, Hello.vue
組件render
&patch
的時間爲:
這裏僅測試了一次, 但從結果來看, 增長Object.freeze
凍結後, 總體性能會有明顯提高.
2.6.0 新增
返回的對象能夠直接用於渲染函數和計算屬性內,而且會在發生改變時觸發相應的更新。也能夠做爲最小化的跨組件狀態存儲器,用於簡單的場景:
const state = Vue.observable({ count: 0 })
const Demo = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `count is: ${state.count}`)
}
}
複製代碼
咱們能夠利用這個API來應對一些簡單的跨組件數據狀態共享的狀況.
// miniStore.js
import Vue from "vue";
export const miniStore = Vue.observable({ count: 0 });
export const actions = {
setCount(count) {
miniStore.count = count;
}
}
export const getters = {
count: () => miniStore.count
}
複製代碼
// Demo.vue
<template>
<div>
<p>count:{{count}}</p>
<button @click="add"> +1 </button>
<button @click="sub"> -1 </button>
</div>
</template>
<script> import { actions, getters } from "./store"; export default { name: "App", computed: { count() { return getters.count; } }, methods: { add: actions.setCount(this.count+1), sub: actions.setCount(this.count-1) } }; </script>
複製代碼
在寫Vue
組件時, 常常會遇到:
props
或listerers
props
或listerers
有沒有什麼辦法能夠解決以上兩種場景的問題呢?
::: tip v-bind
和v-on
, 能夠實現解決上述問題 :::
代碼示例以下:
<template>
<Child v-bind="$props" v-on="$listeners"> </Child>
</template>
<script> import Child from "./Child"; export default { props: { title: { required: true, type: String } } components: { Child } }; </script>
複製代碼
有時, 須要在父組件監聽子組件掛載後mounted
, 作一些邏輯處理. 例如: 加載遠端組件時, 想抓取組件從遠端加載到掛載的耗時.
此時, 就不能用常規的寫法, 在每一個子組件中去this.$emit
事件了. 有沒有辦法, 只須要在父組件中監聽各子組件的生命週期鉤子函數呢?
::: tip @hook
能夠監聽到子組件的生命週期鉤子函數(created
, updated
等等). 例如: @hook:mounted="doSomething"
:::
// Parent.vue
<template>
<Child v-bind="$props" v-on="$listeners" @hook:mounted="doSomething"> </Child>
</template>
<script>
import Child from "./Child";
export default {
props: {
title: {
required: true,
type: String
}
}
components: {
Child
},
methods: {
doSomething(){
console.log("child component has mounted!");
}
}
};
</script>
複製代碼
::: tip 函數式組件, 無狀態,沒法實例化,內部沒有任何生命週期處理方法,很是輕量,於是渲染性能高,特別適合用來只依賴外部數據傳遞而變化的組件。 :::
寫法以下:
functional
<!-- App.vue -->
<template>
<div>
<UserList :users="users" :click-handler="clickHandler.bind(this)"></UserList>
</div>
</template>
<script> import UserList from "./UserList"; export default { name: "App", data: () => { users: ['james', 'ian'] } components: { UserList }, methods: { clickHandler(name){ console.log(`clicked: ${name}`); } } }; </script>
複製代碼
// UserList.vue
<template functional>
<div>
<p v-for="(name, idx) in props.users" @click="props.clickHandler(name)" :key="idx">
{{ name }}
</p>
</div>
</template>
複製代碼
在 2.6.0 中,Vue爲具名插槽和做用域插槽引入了一個新的統一的語法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 這兩個目前已被廢棄但未被移除且仍在文檔中的特性。新語法的由來可查閱這份 RFC。
如何使用做用域插槽呢? 請先看以下示例:
<template>
<List :items="items">
<template slot-scope="{ filteredItems }">
<p v-for="item in filteredItems" :key="item">{{ item }}</p>
</template>
</List>
</template>
複製代碼
使用v-slot
, 能夠直接在組件標籤上寫入該插槽的scope
.
<template>
<List v-slot="{ filteredItems }" :items="items">
<p v-for="item in filteredItems" :key="item">{{ item }}</p>
</List>
</template>
複製代碼
::: tip v-slot
只能在組件或template
標籤上使用, 不能使用在普通原生的HTML標籤上. :::
這樣使得代碼可讀性加強, 特別是在一些很難說明模板變量來源的場景中.
v-slot
指令還引入了一種方法來組合使用slot
&scoped-slot
, 但須要用":"來分隔.
<template>
<Promised :promise="usersPromise">
<p slot="pending">Loading...</p>
<ul slot-scope="users">
<li v-for="user in users">{{ user.name }}</li>
</ul>
<p slot="rejected" slot-scope="error">Error: {{ error.message }}</p>
</Promised>
</template>
複製代碼
使用v-slot
重寫:
<template>
<Promised :promise="usersPromise">
<template v-slot:pending>
<p>Loading...</p>
</template>
<template v-slot="users">
<ul>
<li v-for="user in users">{{ user.name }}</li>
</ul>
</template>
<template v-slot:rejected="error">
<p>Error: {{ error.message }}</p>
</template>
</Promised>
</template>
複製代碼
v-slot
還能夠簡寫爲 #
, 重寫上面的例子:
<template>
<Promised :promise="usersPromise">
<template #pending>
<p>Loading...</p>
</template>
<template #default="users">
<ul>
<li v-for="user in users">{{ user.name }}</li>
</ul>
</template>
<template #rejected="error">
<p>Error: {{ error.message }}</p>
</template>
</Promised>
</template>
複製代碼
::: tip 注意, v-slot
的簡寫是 #default
:::
雖然Vue.js
爲咱們提供了有用的computed
, 但在某些場景下, 仍然仍是須要使用到watch
.
::: tip 默認狀況下, watch
只在被監聽的屬性值發生變化時執行. :::
例如:
export default {
data: () => ({
dog: ""
}),
watch: {
dog(newVal, oldVal) {
console.log(`Dog changed: ${newVal}`);
}
}
};
複製代碼
如上代碼所示, 只有當dog
的值有發生改變時, watch
中的dog
函數纔會執行.
可是, 在某些狀況下, 你可能須要在建立組件後當即運行監聽程序. 固然, 你能夠將邏輯遷移至methods
中, 而後從watch
和created
鉤子函數中分別調用它, 但有沒有更簡單一點的辦法呢?
你能夠在使用watch
時, 使用immediate: true
選項, 這樣它就會在組件建立時當即執行.
export default {
data: () => ({
dog: ""
}),
watch: {
dog: {
handler(newVal, oldVal) {
console.log(`Dog changed: ${newVal}`);
},
immediate: true
}
}
};
複製代碼
v-lazy-image圖片懶加載組件.
安裝: npm install v-lazy-image
使用:
// main.js
import Vue from "vue";
import { VLazyImagePlugin } from "v-lazy-image";
Vue.use(VLazyImagePlugin);
複製代碼
<template>
<v-lazy-image src="http://lorempixel.com/400/200/" />
</template>
複製代碼
你也可使用漸進式圖像加載方式來加載圖片, 經過設置src-placeholder
先加載縮略圖, 同時使用CSS應用本身的過濾效果.
<template>
<v-lazy-image src="http://demo.com/demo.jpeg" src-placeholder="http://demo.com/min-demo.jpeg" />
</template>
<style scoped> .v-lazy-image { filter: blur(10px); transition: filter 0.7s; } .v-lazy-image-loaded { filter: blur(0); } </style>
複製代碼
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>
複製代碼
::: danger 帶有 .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 }」
,是沒法正常工做的.
由於在解析一個像這樣的複雜表達式的時候,有不少邊緣狀況須要考慮。
2.2.0 新增
類型:
注意: provide 和 inject 主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中。
這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。若是你熟悉 React,這與 React 的上下文特性很類似。
provide 選項應該是一個對象或返回一個對象的函數。該對象包含可注入其子孫的屬性。在該對象中你可使用 ES2015 Symbols 做爲 key,可是隻在原生支持 Symbol 和 Reflect.ownKeys 的環境下可工做。
inject 選項應該是:
提示: provide 和 inject 綁定並非可響應的。這是刻意爲之的。然而,若是你傳入了一個可監聽的對象,那麼其對象的屬性仍是可響應的。
示例:
// 父級組件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子組件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
複製代碼
利用 ES2015 Symbols、函數 provide 和對象 inject:
const s = Symbol()
const Provider = {
provide () {
return {
[s]: 'foo'
}
}
}
const Child = {
inject: { s },
// ...
}
複製代碼
在 2.5.0+ 的注入能夠經過設置默認值使其變成可選項:
const Child = {
inject: {
foo: { default: 'foo' }
}
}
複製代碼
若是它須要從一個不一樣名字的屬性注入,則使用 from 來表示其源屬性:
const Child = {
inject: {
foo: {
from: 'bar',
default: 'foo'
}
}
}
複製代碼
與 prop 的默認值相似,你須要對非原始值使用一個工廠方法:
const Child = {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
}
}
複製代碼
在Vue開發過程當中, 常常會遇到template模板渲染時JavaScript變量出錯的問題, 此時也許你會經過console.log
來進行調試. 例如:
<template>
<h1>
{{ log(message) }}
</h1>
</template>
<script> methods: { log(message) { console.log(message); } } </script>
複製代碼
每次調試模板渲染時, 都相似重複這樣寫, 可能會很無聊, 有沒有更好的辦法呢?
在Vue.prototype
原型鏈上添加一個自定義的方法.
// main.js
Vue.prototype.$log = window.console.log;
複製代碼
至止, 咱們能夠在每一個組件的模板中使用$log
, 若是咱們不想影響模板的渲染, 也能夠:
<h1>
{{ log(message) || message }}
</h1>
複製代碼
這樣是否是很方便的調試模板了?
那延展一下, 有沒有辦法增長一個斷點, 以調試模板渲染時, 查看相關聯的變量? 咱們在使用模板時放入一個debugger
.
<h1>
{{ debugger }}
</h1>
複製代碼
你會發現, 組件根本就沒有編譯模板. 有沒有辦法呢?
咱們能夠嘗試在模板中添加一個自執行的函數, 例如:
<h1>
{{ (function(){degugger;}) || message }}
</h1>
複製代碼
此時, 咱們將能夠看到斷點定位到了模板的渲染函數中了.
此時的_vm
, 就是咱們組件的實例對象.
檢查編譯的模板雖然頗有意思, 但因爲某些緣由, 變量在被咱們放在debugger
後, 在chrome devtools的函數範圍內變得不可用.
修改下寫法:
<h1>
{{ (function(){degugger; message}) || message }}
</h1>
複製代碼
此時, 你就能夠爲所欲爲了.
Vue中style標籤的scoped屬性表示它的樣式只做用於當前模塊,是樣式私有化, 設計的初衷就是讓樣式變得不可修改.
渲染的規則/原理:
例如, 以下代碼所示:
<template>
<div class="demo">
<span class="content">
Vue.js scoped
</span>
</div>
</template>
<style lang="less" scoped> .demo{ font-size: 14px; .content{ color: red; } } </style>
複製代碼
瀏覽器渲染後的代碼:
<div data-v-fed36922>
Vue.js scoped
</div>
<style type="text/css"> .demo[data-v-039c5b43] { font-size: 14px; } .demo .content[data-v-039c5b43] { color: red; } </style>
複製代碼
::: tip 注意 添加scoped屬性後, 父組件沒法修改子組件的樣式. :::
如上例中, 若想在父組件中修改子組件的樣式, 怎麼辦呢?
這裏咱們主要講解使用deep
修改子組件的樣式. 將上例的代碼修改成:
<template>
<div class="demo">
<span class="content">
Vue.js scoped
</span>
</div>
</template>
<style lang="less" scoped> .demo{ font-size: 14px; } .demo /deep/ .content{ color: blue; } </style>
複製代碼
最終style編譯後的輸出爲:
<style type="text/css"> .demo[data-v-039c5b43] { font-size: 14px; } .demo[data-v-039c5b43] .content { color: blue; } </style>
複製代碼
從編譯能夠看出, 就是.content
後有無添加CSS屬性data-v-xxx
的區別, 屬性CSS選擇器權重問題的同窗, 對此應該當即明白了吧!
CSS Modules 是一個流行的,用於模塊化和組合 CSS 的系統。vue-loader
提供了與 CSS Modules 的一流集成,能夠做爲模擬 scoped CSS
的替代方案。
首先,CSS Modules 必須經過向 css-loader
傳入 modules: true
來開啓:
// webpack.config.js
{
module: {
rules: [
// ... 其它規則省略
{
test: /\.css$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
// 開啓 CSS Modules
modules: true,
// 自定義生成的類名
localIdentName: '[local]_[hash:base64:8]'
}
}
]
}
]
}
}
複製代碼
而後在你的 <style>
上添加 module 特性:
<style module> .red { color: red; } .bold { font-weight: bold; } </style>
複製代碼
這個 module 特性指引 Vue Loader 做爲名爲 $style 的計算屬性,向組件注入 CSS Modules 局部對象。而後你就能夠在模板中經過一個動態類綁定來使用它了:
<template>
<p :class="$style.red">
This should be red
</p>
</template>
複製代碼
由於這是一個計算屬性,因此它也支持 :class
的對象/數組語法:
<template>
<div>
<p :class="{ [$style.red]: isRed }">
Am I red?
</p>
<p :class="[$style.red, $style.bold]">
Red and bold
</p>
</div>
</template>
複製代碼
你也能夠經過 JavaScript 訪問到它:
<script> export default { created () { console.log(this.$style.red) // -> "red_1VyoJ-uZ" // 一個基於文件名和類名生成的標識符 } } </script>
複製代碼
你能夠查閱 CSS Modules 規範瞭解更多細節,諸如 global exceptions
和 composition
等。
若是你只想在某些 Vue 組件中使用 CSS Modules,你可使用 oneOf 規則並在 resourceQuery 字符串中檢查 module 字符串:
// webpack.config.js -> module.rules
{
test: /\.css$/,
oneOf: [
// 這裏匹配 `<style module>`
{
resourceQuery: /module/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[local]_[hash:base64:5]'
}
}
]
},
// 這裏匹配普通的 `<style>` 或 `<style scoped>`
{
use: [
'vue-style-loader',
'css-loader'
]
}
]
}
複製代碼
CSS Modules 能夠與其它預處理器一塊兒使用:
// webpack.config.js -> module.rules
{
test: /\.scss$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: { modules: true }
},
'sass-loader'
]
}
複製代碼
在 .vue 中你能夠定義不止一個 <style>
,爲了不被覆蓋,你能夠經過設置 module 屬性來爲它們定義注入後計算屬性的名稱。
<style module="a"> /* 注入標識符 a */ </style>
<style module="b"> /* 注入標識符 b */ </style>
複製代碼