当在子组件上使用 key 属性时,Vue 会知道该组件的身份,并且在对列表进行重新排序时,将移动节点而不是对其进行修补。这能够确保手动编辑的输入框以及整个组件移动到新位置。
slot
可以将父组件的内容混入到子组件的模板当中,此时可以在子组件中使用<slot>
作为父组件内容的插槽。
父组件模板的内容在父组件作用域内编译,子组件模板的内容在子组件作用域内编译。
在有条件地渲染组件或元素时,还可以用 key 属性来向 Vue 发出有关元素唯一性的信号,并确保元素不会被新数据重新修补。
渲染函数render()
用来创建VNode,该函数接收createElement()
方法作为第1个参数,该方法调用后会返回一个虚拟DOM(即VNode)。
直接使用表达式,或者在render()
函数内通过createElement()
进行手动渲染,Vue都会自动保持blogTitle
属性的响应式更新。
<h1>{{ blogTitle }}</h1>
<script>
render: function (createElement) {
return createElement("h1", this.blogTitle)
}
</script>
如果组件是一个函数组件,render()还会接收一个context参数,以便为没有实例的函数组件提供上下文信息。
通过render()函数实现虚拟DOM比较麻烦,因此可以使用Babel插件babel-plugin-transform-vue-jsx
在render()函数中应用JSX语法。
import AnchoredHeading from "./AnchoredHeading.vue"
new Vue({
el: "#demo",
render (h) {
return (
<AnchoredHeading level={1}>
Hello world!
</AnchoredHeading>
)
}
})
- 在 Vue 实例中编写生命周期 hook 或其他 option/propertie 时,为什么不使用箭头函数?
命名方式转换
因为HTML并不区分大小写,所以kebab-case(驼峰)风格命名的props,在组件中会以camelCased(短横线隔开)风格被接收。
<!-- camelCase in JavaScript -->
<script>
Vue.component("child", {
props: ["myMessage"],
template: "{{ myMessage }}"
})
<script>
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>
当由于数据属性或其他某种响应状态而动态切换组件时,每次将它们切换到渲染状态时,都会被重新渲染。尽管你可能需要这种行为,但在某些情况下重新渲染可能是不合适的。例如在创建时从 API 调用中引入数据的组件。你可能不希望每次动态切换这个组件进行渲染时都调用此 API。这时你可以将组件包含在 keep-alive 元素中。keep-alive 元素缓存该组件并从那里获取它,而不是每次都重新渲染它。
Vue是一款高度封装的、开箱即用的、一栈式的前端框架,既可以结合webpack进行编译式前端开发,也适用基于gulp、grunt等自动化工具直接挂载至全局window
使用。本文成文于Vue2.4.x版本发布之初,笔者生产环境当前使用的最新版本为2.5.2。在经历多个前端重度交互项目的开发实践之后,笔者结合官方文档对Vue技术栈进行了全面的梳理、归纳和注解,因此本文可以作为Vue2官方tutorial的补充性读物。建议暂不具备Vue2开发经验的同学,完成官方tutorial的学习之后再行阅读本文。
在 vue.config.js 文件中:
Vue与Angular的比较
当提供唯一的键值 IS 时,将根据对键的更改对元素进行重新排序(并且不使用新数据对它们进行修补),如果删除了 key(例如,删除列表中的项目时),则对应的元素节点也被销毁或删除。
函数化组件
即无状态(没有data)无实例(没有this上下文)的组件,渲染开销较小,且不会出现在Vue devtools
当中。
Vue.component("my-component", {
functional: true,
// 通过提供context参数为没有实例的函数组件提供上下文信息
render: function (createElement, context) {},
// Props可选
props: {}
})
2.你将怎样在模板中渲染原始 HTML?
验证props
可以为组件的props指定验证规则,如果传入数据不符合要求,Vue会发出相应警告,这样可以有效提高组件的健壮性。
Vue.component("example", {
props: {
// 基础类型检测
propA: Number,
// 多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数字,有默认值
propD: {
type: Number,
default: 100
},
// 数组或对象的默认值由1个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: "hello" }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
});
props
会在组件实例创建之前进行校验。
这里是用 HTML 作为模板的 Vue 程序
动态props
可以通过v-bind
指令,响应式的绑定父组件数据到子组件的props。当父组件数据变化时,该变化也会传导至子组件。
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
使用v-bind
可以让其参数值能够以JavaScript表达式的方式被解析,否则所有传入的props都会被子组件认为是字符串类型。
<!-- 传递的是字符串"1" -->
<comp some-prop="1"></comp>
<!-- 传递实际的 number -->
<comp v-bind:some-prop="1"></comp>
- 什么是 mixin?
Vue.mixin(mixin)
使用全局mixins将会影响到所有之后创建的Vue实例。
// 为自定义选项myOption注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: "hello!"
})
// => "hello!"
模板
Vue2的API结构相比Angular2更加简洁,可以自由的结合TypeScript或是ECMAScript6使用,并不特定于具体的预处理语言去获得最佳使用体验,框架本身的特性也并不强制依赖于各类炫酷的语法糖。Vue2总体是一款非常轻量的技术栈,设计实现上紧随W3C技术规范,着力于处理HTML模板组件化、事件和数据的作用域分离、多层级组件通信三个单页面前端开发当中的重点问题。本文在行文过程中,穿插描述了Angular、React等前端框架的异同与比较,供徘徊于各类前端技术选型的开发人员参考。
一旦计算出,就将其应用于实际的 DOM 树,这就提高了性能,这就是为什么基于虚拟 DOM 的框架(例如 Vue 和 React)如此突出的原因。
动态组件
使用<component>
元素并动态绑定其is
属性,可以让多个组件使用相同的Vue对象挂载点,并实现动态切换。
<script>
var vm = new Vue({
el: "#example",
data: {
currentView: "home"
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
</script>
<component v-bind:is="currentView">
<!-- 组件在vm.currentview变化时改变! -->
</component>
如果需要将切换的组件保持在内存,保留其状态并且避免重新渲染,可以使用Vue内置的keep-alive
指令。
<keep-alive>
<component :is="currentView">
<!-- 非活动组件将被缓存! -->
</component>
</keep-alive>
new Vue({ components: { ‘tweet-box’: () = import(‘./components/async/TweetBox’) }});
组件的非props属性
组件可以接收任意传入的属性,这些属性都会被添加到组件HTML模板的根元素上(无论有没有在props中定义)。
<!-- 带有属性的自定义组件 -->
<bs-date-input
data-3d-date-picker="true"
class="date-picker-theme-dark">
</bs-date-input>
<!-- 渲染出来的组件,class属性被合并 -->
<input type="date" data-3d-date-picker="true" class="form-control date-picker-theme-dark">
父组件传递给子组件的属性可能会覆盖子组件本身的属性,因而会对子组件造成破坏和污染。
当不使用 key 属性时:例如如果列表已重新排序,则 Vue 会使用重新排序的数据简单地修补已经存在的三个节点,而不用移动这些节点。只要用户没有输入或更改这些子组件中一个或多个子组件的本地状态,此方法就可以正常工作。因此假设用户输入了组件编号为 3的输入框,重新排序列表后,组件编号为 3 的 span 标签内容将呗更改,但是输入框将与用户键入的内容击破状态数据一起保留在这里。这是因为 Vue 无法识别组件编号 3,它只是重新修补它所看到的更新数据,即 span 标签的内容。
混合属性mixins
用来将指定的mixin对象复用到Vue组件当中。
// mixin对象
var mixin = {
created: function () {
console.log("混合对象的钩子被调用")
},
methods: {
foo: function () {
console.log("foo")
},
conflicting: function () {
console.log("from mixin")
}
}
}
// vue属性
var vm = new Vue({
mixins: [mixin],
created: function () {
console.log("组件钩子被调用")
},
methods: {
bar: function () {
console.log("bar")
},
conflicting: function () {
console.log("from self")
}
}
})
// => "混合对象的钩子被调用"
// => "组件钩子被调用"
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
同名组件option对象的属性会被合并为数组依次进行调用,其中mixin对象里的属性会被首先调用。如果组件option对象的属性值是一个对象,则mixin中的属性会被忽略掉。
通过仅加载基本组件并把异步组件的加载推迟到未来的调用时间,可以节省带宽和程序加载时间。
内置指令
带有v-
前缀,当表达式值发生变化时,会响应式的将影响作用于DOM。指令可以接收后面以:
表示的参数(被指令内部的arg属性接收),或者以.
开头的修饰符(指定该指令以特殊方式绑定)。
<p v-if="seen">Hello world!</p>
<!-- 绑定事件 -->
<a v-bind:href="url"></a>
<!-- 绑定属性 -->
<a v-on:click="doSomething">
<!-- .prevent修饰符会告诉v-on指令对于触发的事件调用event.preventDefault() -->
<form v-on:submit.prevent="onSubmit"></form>
Vue为v-bind
和v-on
这两个常用的指令提供了简写形式:
和@
。
<!-- v-bind -->
<a v-bind:href="url"></a>
<a :href="url"></a>
<!-- v-on -->
<a v-on:click="doSomething"></a>
<a @click="doSomething"></a>
目前,Vue在2.4.2版本当中提供了如下的内置指令:
<html
v-text = "更新元素的textContent"
v-html = "更新元素的innerHTML"
v-show = "根据表达式的true/false,切换HTML元素的display属性"
v-for = "遍历内部的HTML元素"
v-pre = "跳过表达式渲染过程,可以显示原始的Mustache标签"
v-cloak = "保持在HTML元素上直到关联实例结束编译,可以隐藏未编译的Mustache"
v-once = "只渲染元素和组件一次"
></html>
<!-- 根据表达式的true和false来决定是否渲染元素 -->
<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>
<!-- 动态地绑定属性或prop到表达式 -->
<p v-bind:attrOrProp
.prop = "被用于绑定DOM属性"
.camel = "将kebab-case特性名转换为camelCase"
.sync = "语法糖,会扩展成一个更新父组件绑定值的v-on监听器"
></p>
<!-- 绑定事件监听器 -->
<button
v-on:eventName
.stop = "调用event.stopPropagation()"
.prevent = "调用event.preventDefault()"
.capture = "添加事件监听器时使用capture模式"
.self = "当事件是从监听器绑定的元素本身触发时才触发回调"
.native = "监听组件根元素的原生事件"-
.once = "只触发一次回调"
.left = "点击鼠标左键触发"
.right = "点击鼠标右键触发"
.middle = "点击鼠标中键触发"
.passive = "以{passive: true}模式添加监听器"
.{keyCode | keyAlias} = "触发特定键触事件"
>
</button>
<!-- 表单控件的响应式绑定 -->
<input
v-model
.lazy = "取代input监听change事件"
.number = "输入字符串转为数字"
.trim = "过滤输入的首尾空格" />
- 什么是vue-loader?
Vue.filter(id, [definition])
Vue可以通过定义过滤器,进行一些常见的文本格式化,可以用于mustache插值和v-bind表达式当中,使用时通过管道符|
添加在表达式尾部。
<!-- in mustaches -->
{{ message | capitalize }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>
<!-- capitalize filter -->
<script>
new Vue({
filters: {
capitalize: function (value) {
if (!value) return ""
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
</script>
过滤器可以串联使用,也可以传入参数。
{{ message | filterA | filterB }}
{{ message | filterA("arg1", arg2) }}
对于标准 HTML 模板的高级方案非常有用。
组件化
Angular的设计思想照搬了Java
Web开发当中MVC分层的概念,通过Controller
切割并控制页面作用域,然后通过Service
来实现复用,是一种对页面进行纵向分层的解耦思想。而Vue允许开发人员将页面抽象为若干独立的组件,即将页面DOM结构进行横向切割,通过组件的拼装来完成功能的复用、作用域控制。每个组件只提供props
作为单一接口,并采用Vuex进行state tree
的管理,从而便捷的实现组件间状态的通信与同步。
components.png
Angular在1.6.x版本开始提供component()
方法和Component Router
来提供组件化开发的体验,但是依然需要依赖于controller
和service
的划分,实质上依然没有摆脱MVC纵向分层思想的桎梏。
通过实现 prop 验证选项,可以为单个 prop 指定类型要求。这对生产没有影响,但是会在开发阶段发出警告,从而帮助开发人员识别传入数据和 prop 的特定类型要求的潜在问题。
组件模板
- 可以在Vue组件上使用
inline-template
属性,组件会将内嵌的HTML内容作为组件本身的模板进行渲染,而非将其作为slot
分发的内容。
<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>
- 也可以通过在
<script>
标签内使用type="text/x-template"
和id
属性来定义一个内嵌模板。
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
<script>
Vue.component("hello-world", {
template: "#hello-world-template"
})
</script>
- 在开发过程中,如果你的 Vue 程序和后端 API 服务器未在同一主机上运行,该如何代理 API 请求。假设使用 Vue-CLI 3 进行设置?
组件的循环引用
循环引用,即两个组件互相引用对方,例如下面代码中tree-folder
、tree-folder-contents
两个组件同时成为了对方的父或子节点,如果使用Webpack模块化管理工具requiring
/importing
组件的时候,会报出Failed to mount component: template or render function not defined.
错误。
<template>
<p>
{{ folder.name }}
<tree-folder-contents :children="folder.children"/>
</p>
</template>
<template>
<ul>
<li v-for="child in children">
<tree-folder v-if="child.children" :folder="child"/>
{{ child.name }}
</li>
</ul>
</template>
因为tree-folder
、tree-folder-contents
相互引用对方之后,无法确定组件加载的先后顺序陷入死循环,所以需要事先指明webpack组件加载的优先级。解决上面例子中Vue组件循环引用的问题,可以在tree-folder
组件的beforeCreate()
生命周期函数内注册引发问题的tree-folder-contents
组件。
beforeCreate: function () {
this.$options.components.TreeFolderContents = require("./tree-folder-contents.vue").default
}
这里有一个父组件渲染一个子组件列表。我们看到三个列表项被渲染为三个子组件节点。这些子组件都包含一个 span 标记和一个输入框,可能还包含一个本地状态对象(可选)。现在让我们检查两种情况:
$refs属性
子组件指定ref
属性之后,可以通过父组件的$refs
实例属性对其进行访问
。
<div id="parent">
<user-profile ref="profile"></user-profile>
</div>
<script>
var parent = new Vue({ el: "#parent" })
var child = parent.$refs.profile // 访问子组件
</script>
$refs会在组件渲染完毕后填充,是非响应式的,仅作为需要直接访问子组件的应急方案,因此要避免在模板或计算属性中使用$refs。
当以这种方式使用时,Webpack 的代码拆分将用于提供此功能。
作用域插槽
子组件通过props
传递数据给<slot>
插槽,父组件使用带有scope
属性的<template>
来表示表示当前作用域插槽的模板,scope
值对应的变量会接收子组件传递来的props对象。
<!-- 子组件通过props传递数据给插槽 -->
<div class="child">
<slot text="hello from child"></slot>
</div>
<!-- 父组件使用带有scope属性的<template> -->
<div class="parent">
<child>
<template scope="props">
hello from parent
{{ props.text }}
</template>
</child>
</div>
<!-- 渲染结果 -->
<div class="parent">
<div class="child">
hello from parent
hello from child
</div>
</div>
时间: 2019-11-27阅读: 146标签: 面试1. 渲染项目列表时,“key” 属性的作用和重要性是什么?
Mixins 使我们能够为 Vue 组件编写可插拔和可重用的功能。如果你希望在多个组件之间重用一组组件选项,例如生命周期 hook、方法等,则可以将其编写为 mixin,并在组件中简单地引用它。然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优先于组件自己的 hook 。
logo.png
new Vue({ el: '#app', data: { fruits: ['Apples', 'Oranges', 'Kiwi'] }, render: function(createElement) { return createElement('div', [ createElement('h1', 'Fruit Basket'), createElement('ol', this.fruits.map(function(fruit) { return createElement('li', fruit); })) ]); }});
Vue2.2.x之后的版本,Vue框架及其技术栈功能日趋完善,相比React+Reflux/Redux/MobX的组合,Vue更加贴近W3C技术规范(例如实现仍处于W3C草案阶段的<template>
、<slot>
、is
等新特性,提供了良好易用的模板书写环境),并且技术栈和开源生态更加完整和易于配置,将React中大量需要手动编码处理的位置,整合成最佳实践并抽象为简单的语法糖(比如Vuex中提供的store
的模块化特性),让开发人员始终将精力聚焦于业务逻辑本身。
- 什么是异步组件?
虚拟DOM
Vritual DOM这个概念最先由React引入,是一种DOM对象差异化比较方案,即将DOM对象抽象成为Vritual DOM对象(即render()函数渲染的结果),然后通过差异算法对Vritual DOM进行对比并返回差异,最后通过一个补丁算法将返回的差异对象应用在真实DOM结点。
10bet,Vue当中的Virtual
DOM对象被称为VNode(template
当中的内容会被编译为render()函数,而render()函数接收一个createElement()函数,并最终返回一个VNode对象),补丁算法来自于另外一个开源项目snabbdom,即将真实的DOM操作映射成对虚拟DOM的操作,通过减少对真实DOM的操作次数来提升性能。
➜ vdom git:(dev) tree
├── create-component.js
├── create-element.js
├── create-functional-component.js
├── helpers
│ ├── extract-props.js
│ ├── get-first-component-child.js
│ ├── index.js
│ ├── is-async-placeholder.js
│ ├── merge-hook.js
│ ├── normalize-children.js
│ ├── resolve-async-component.js
│ └── update-listeners.js
├── modules
│ ├── directives.js
│ ├── index.js
│ └── ref.js
├── patch.js
└── vnode.js
VNode的设计出发点与Angular的$digest
循环类似,都是通过减少对真实DOM的操作次数来提升性能,但是Vue的实现更加轻量化,摒弃了Angular为了实现双向绑定而提供的$apply()
、$eval()
封装函数,有选择性的实现Angular中$compile()
、$watch()
类似的功能。
在上面的例子中,我们用了一个函数,它返回一系列createElement()调用,每个调用负责生成一个元素。尽管 v-for 指令在基于 HTML 的模板中起作用,但是当使用渲染函数时,可以简单地用标准.map()函数遍历 fruits 数据数组。
实例属性和方法
Vue实例暴露了一系列带有前缀$的实例属性与方法。
let vm = new Vue();
vm = {
// Vue实例属性的代理
$data: "被watch的data对象",
$props: "当前组件收到的props",
$el: "Vue实例使用的根DOM元素",
$options: "当前Vue实例的初始化选项",
$parent: "父组件Vue对象的实例",
$root: "根组件Vue对象的实例",
$children: "当前实例的直接子组件",
$slots: "访问被slot分发的内容",
$scopedSlots: "访问scoped slots",
$refs: "包含所有拥有ref注册的子组件",
$isServer: "判断Vue实例是否运行于服务器",
$attrs: "包含父作用域中非props的属性绑定",
$listeners: "包含了父作用域中的v-on事件监听器",
// 数据
$watch: "观察Vue实例变化的表达式、计算属性函数",
$set: "全局Vue.set的别名",
$delete: "全局Vue.delete的别名",
// 事件
$on: "监听当前实例上的自定义事件,事件可以由vm.$emit触发",
$once: "监听一个自定义事件,触发一次之后就移除监听器",
$off: "移除自定义事件监听器",
$emit: "触发当前实例上的事件",
// 生命周期
$mount: "手动地挂载一个没有挂载的Vue实例",
$forceUpdate: "强制Vue实例重新渲染,仅影响实例本身和插入插槽内容的子组件",
$nextTick: "将回调延迟到下次DOM更新循环之后执行",
$destroy: "完全销毁一个实例",
}
ApplesOrangesKiwi
属性计算computed
在HTML模板表达式中放置太多业务逻辑,会让模板过重且难以维护。因此,可以考虑将模板中比较复杂的表达式拆分到computed属性当中进行计算。
<!-- 不使用计算属性 -->
<div id="example">
{{ message.split("").reverse().join("") }}
</div>
<!-- 将表达式抽象到计算属性 -->
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
var vm = new Vue({
el: "#example",
data: {
message: "Hello"
},
computed: {
reversedMessage: function () {
return this.message.split("").reverse().join("")
}
}
})
</script>
计算属性只在相关依赖发生改变时才会重新求值,这意味只要上面例子中的message没有发生改变,多次访问reversedMessage计算属性总会返回之前的计算结果,而不必再次执行函数,这是computed和method的一个重要区别。
计算属性默认只拥有getter方法,但是可以自定义一个setter方法。
<script>
... ... ...
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]
}
}
}
... ... ...
// 下面语句触发setter方法,firstName和lastName也会被相应更新
vm.fullName = "John Doe"
</script>
Vue.js
Vue.directive(id, [definition])
Vue允许注册自定义指令,用于对底层DOM进行操作。
Vue.directive("focus", {
bind: function() {
// 指令第一次绑定到元素时调用,只会调用一次,可以用来执行一些初始化操作。
},
inserted: function (el) {
// 被绑定元素插入父节点时调用。
},
update: function() {
// 所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前。
},
componentUpdated: function() {
// 所在组件VNode及其子VNode全部更新时调用。
},
unbind: function() {
// 指令与元素解绑时调用,只会被调用一次。
}
})
钩子之间共享数据可以通过
HTMLElement
的dataset
属性来进行(即HTML标签上通过data-
格式定义的属性)。
上面的钩子函数拥有如下参数:
- el: 指令绑定的HTML元素,可以用来直接操作DOM。
- vnode: Vue编译生成的虚拟节点。
- oldVnode:
之前的虚拟节点,仅在
update
、componentUpdated
钩子中可用。 - binding: 一个对象,包含以下属性:
- name: 指令名称,不包括
v-
前缀。 - value:
指令的绑定值,例如
v-my-directive="1 + 1"
中value
的值是2
。 - oldValue:
指令绑定的之前一个值,仅在
update
、componentUpdated
钩子中可用。 - expression:
绑定值的字符串形式,例如
v-my-directive="1 + 1"
当中expression
的值为"1 + 1"
。 - arg:
传给指令的参数,例如
v-my-directive:foo
中arg
的值是"foo"
。 - modifiers:
包含修饰符的对象,例如
v-my-directive.foo.bar
的modifiers
的值是{foo: true, bar: true}
。
- name: 指令名称,不包括
上面参数除
el
之外,其它参数都应该是只读的,尽量不要对其进行修改操作。
- 什么时候使用keep-alive元素?
使用JavaScript表达式
Vue对于所有数据绑定都提供了JavaScript表达式支持,但是每个绑定只能使用1个表达式。
{{ number + 1 }}
<button>{{ ok ? "YES" : "NO" }}</button>
<p>{{ message.split("").reverse().join("") }}</p>
<div v-bind:id=""list-" + id"></div>
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- if流程控制属于多个表达式,因此不会生效,但可以使用三元表达式 -->
{{ if (ok) { return message } }}
- 哪个生命周期 hook 最适合从 API 调用中获取数据?
v-bind:style
绑定HTML的style
属性。
<script>
... ...
data: {
styleObject: {
color: "red",
fontSize: "13px"
},
styleHeight: {
height: 10rem;
}
styleWidth: {
width: 20rem;
}
}
... ...
</script>
<div v-bind:style="styleObject"></div>
<!-- 使用数组可以将多个样式合并到一个HTML元素上面 -->
<div v-bind:style="[styleHeight, styleWidth]"></div>
使用v-bind:style
时Vue会自动添加prefix前缀,常见的prefix前缀如下:
-webkit-
Chrome、Safari、新版Opera、所有iOS浏览器(包括iOS版Firefox),几乎所有WebKit内核浏览器。-moz-
针对Firefox浏览器。-o-
未使用WebKit内核的老版本Opera。-ms-
微软的IE以及Edge浏览器。
这里是用渲染函数开发的同一个程序:
v-bind:class
绑定HTML的class
属性。
<!-- Vue对象中的data -->
<script>
... ...
data: {
isActive: true,
hasError: false,
classObject: {
active: true,
"text-danger": false
}
}
... ...
</script>
<!-- 直接绑定class到一个对象 -->
<div v-bind:class="classObject"></div>
<!-- 直接绑定class到对象的属性 -->
<div class="static" v-bind:class="{ active: isActive, "text-danger": hasError }"></div>
<!-- 渲染结果 -->
<div class="static active"></div>
可以传递一个数组给v-bind:class
从而同时设置多个class属性。
<!-- Vue对象中的data -->
<script>
... ...
data: {
activeClass: "active",
errorClass: "text-danger"
}
... ...
</script>
<!-- 绑定class到计算属性 -->
<div v-bind:class="[activeClass, errorClass]"></div>
<!-- 渲染结果 -->
<div class="active text-danger"></div>
<!-- 使用三目运算符,始终添加errorClass,只在isActive为true时添加activeClass -->
<div v-bind:class="[isActive ? activeClass : "", errorClass]"></div>
<!-- 在数组中使用对象可以避免三目运算符的繁琐 -->
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
当在自定义组件上使用class
属性时,这些属性将会被添加到该组件的根元素上面,这一特性同样适用于v-bind:class
。
<!-- 声明一个组件 -->
<script>
Vue.component("my-component", {
template: "<p class="foo bar">Hi</p>",
data: {
isActive: true
},
})
</script>
<!-- 添加2个class属性 -->
<my-component class="baz boo"></my-component>
<!-- 渲染结果 -->
<p class="foo bar baz boo">Hi</p>
<!-- 使用v-bind:class -->
<my-component v-bind:class="{ active: isActive }"></my-component>
<!-- 渲染结果 -->
<p class="foo bar active">Hi</p>
如果没有使用 key 属性,并且列表的内容发生了改变(例如对列表进行排序),则虚拟 DOM 宁愿使用更新的数据来修补节点,来反映更改,而不是上下移动元素。这是默认模式,非常有效。
事件
子组件可以通过Vue的自定义事件与父组件进行通信。
每个Vue实例都实现了如下API,但是并不能直接通过$on监听子组件冒泡的事件,而必须使用v-on指令。
$on(eventName)
监听事件$emit(eventName)
触发事件
$on
和$emit
并不是addEventListener
和dispatchEvent
的别名。
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
<script>
Vue.component("button-counter", {
template: "<button v-on:click="incrementCounter">{{ counter }}</button>",
data: function () {
return {
counter: 0
}
},
methods: {
// 子组件事件
incrementCounter: function () {
this.counter += 1
this.$emit("increment") //向父组件冒泡事件
}
},
})
new Vue({
el: "#counter-event-example",
data: {
total: 0
},
methods: {
// 父组件事件
incrementTotal: function () {
this.total += 1
}
}
})
</script>
.native
修饰符
开发人员也可以在组件的根元素上监听原生事件,这个时候需要借助到.native
修饰符。
<my-component v-on:click.native="doTheThing"></my-component>
.sync
修饰符
Vue中的props
本质是不能进行响应式绑定的,以防止破坏单向数据流,造成多个子组件对父组件状态形成污染。但是生产环境下,props
响应式绑定的需求是切实存在的。因此,Vue将.sync
修饰符封装为糖衣语法,父组件在子组件的props使用该修饰符后,父组件会为props自动绑定v-on
事件,子组件则在监听到props变化时向父组件$emit
更新事件,从而让父组件的props
能够与子组件进行同步。
<!-- 使用.sync修饰符 -->
<comp :foo.sync="bar"></comp>
<!-- 被自动扩展为如下形式,该组件的子组件会通过this.$emit("update:foo", newValue)显式触发更新事件 -->
<comp :foo="bar" @update:foo="val => bar = val"></comp>
- 平行组件通信
非父子关系的组件进行通信时,可以使用一个空的Vue实例作为中央事件总线。
var bus = new Vue()
// 触发组件A中的事件
bus.$emit("id-selected", 1)
// 在组件B监听事件
bus.$on("id-selected", function (id) {
... ... ...
})
更好的方式是借助VueX或者Redux之类的flux状态管理库。
- 什么时候调用 “updated” 生命周期 hook ?
组件命名约定
JavaScript中命名组件组件时可以使用kebab-case
、camelCase
、PascalCase
,但HTML模板中只能使用kebab-case
格式。
<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<pascal-cased-component></pascal-cased-component>
<!-- 也可以通过自关闭方式使用组件 -->
<kebab-cased-component />
<script>
components: {
"kebab-cased-component": {},
"camelCasedComponent": {},
"PascalCasedComponent": {}
}
</script>
推荐JavaScript中通过
PascalCase
方式声明组件, HTML中则通过kebab-case
方式使用组件。
输出
匿名插槽
当子组件只有一个没有属性的<slot>
时,父组件全部内容片段将插入到插槽所在的DOM位置,并替换插槽标签本身。
<!-- 子组件my-component的模板 -->
<div>
<h2>Child</h2>
<slot>
父组件没有需要插入的内容时显示
</slot>
</div>
<!-- 父组件模板中使用my-component -->
<div>
<h1>Parent</h1>
<child>
<p>Content 1</p>
<p>Content 2</p>
</child>
</div>
<!-- 渲染结果 -->
<div>
<h1>Parent</h1>
<div>
<h2>Child</h2>
<p>Content 1</p>
<p>Content 2</p>
</div>
</div>
<slot>
标签中的内容会在子组件作用域内编译,并在父组件没有需要插入的内容时才会显示。
文档对象模型或 DOM 定义了一个接口,该接口允许 JavaScript 之类的语言访问和操作 HTML 文档。元素由树中的节点表示,并且接口允许我们操纵它们。但是此接口需要付出代价,大量非常频繁的 DOM 操作会使页面速度变慢。
组件
组件可以扩展HTML元素功能,并且封装可重用代码。可以通过Vue.component( id, [definition] )
注册或者获取全局组件。
// 注册组件,传入一个扩展过的构造器
Vue.component("my-component", Vue.extend({ ... }))
// 注册组件,传入一个option对象(会自动调用Vue.extend)
Vue.component("my-component", { ... })
// 获取注册的组件(始终返回构造器)
var MyComponent = Vue.component("my-component")
下面代码创建了一个Vue实例,并将自定义组件my-component
挂载至HTML当中。
<script>
// 注册自定义组件
Vue.component("my-component", {
template: "<div>A custom component!</div>"
})
// 创建Vue根实例
new Vue({
el: "#example"
})
</script>
<!-- 原始模板 -->
<div id="example">
<my-component></my-component>
</div>
<!-- 渲染结果 -->
<div id="example">
<div>A custom component!</div>
</div>
- is属性
浏览器解析完HTML之后才会渲染Vue表达式,但是诸如<ul> <ol> <table> <select>
限制了可以被包裹的HTML元素,而<option>
只能出现在某些HTML元素内部,造成Vue表达式可能不会被正确的渲染。因此,Vue提供is
作为属性别名来解决该问题。
<!-- 不正确的方式 -->
<table>
<my-row>...</my-row>
</table>
<!-- 使用is的正确方式 -->
<table>
<tr is="my-row"></tr>
</table>
- data必须是函数
Vue.component()
传入的data属性不能是对象,而必须是函数。这样做的目的是避免组件在相同模板的多个位置被复用时,仅仅返回对象会造成组件间的数据被相互污染,而通过函数每次都返回全新的data对象能完美的规避这个问题。
Vue.component("simple-counter", {
template: "<button v-on:click="counter += 1">{{ counter }}</button>",
data: function () {
return {
a: "",
b: ""
}
}
});
- 父子组件之间的通信
父组件通过props
向下传递数据给子组件,子组件通过events
给父组件发送消息,即props
down, events up。
props-events.png
Vue 通过在内存中实现文档结构的虚拟表示来解决此问题,其中虚拟节点(VNode)表示 DOM 树中的节点。当需要操纵时,可以在虚拟 DOM的 内存中执行计算和操作,而不是在真实 DOM 上进行操纵。这自然会更快,并且允许虚拟 DOM 算法计算出最优化的方式来更新实际 DOM 结构。
具名插槽
可以通过<slot>
元素的name
属性来配置如何分发内容。
<!-- 子组件 -->
<div id="app">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<!-- 父组件 -->
<app>
<div slot="header">Header</div>
<p>Content 1</p>
<p>Content 2</p>
<div slot="footer">Footer</div>
</app>
<!-- 渲染结果 -->
<div id="app">
<header>
<div>Header</div>
</header>
<main>
<p>Content 1</p>
<p>Content 2</p>
</main>
<footer>
<p>Footer</p>
</footer>
</div>
匿名slot会作为没有匹配内容的父组件片段的插槽。
本文由10bet发布于Web前端,转载请注明出处:Vue2技术栈归纳与精粹(上篇)【10bet】
关键词: