在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>
复制代码