- vue是一套用于构建用户界面的渐进式框架,采用自底向上的增量设计。可以先用自己想要的部分,然后慢慢加第三方库。而不像react之类的要求全套引入才能使用其一。
- 注意,实例的数据和实例data的数据是同步的,除非data对象被Object.freeze(obj)冻结则不再更新视图。但只有创建实例的时候绑定的data数据才是响应式的,后续新增的数据vue检测不到不会发生视图变化。所以可能用到的数据要在data里面初始化为空。vue中有些默认属性以$开头,如
vm.$data === data
。 - 生命周期:beforeCreated之前没data;created之后拿到vue实例和this,不过还没拿到dom;mounted之后可以获取dom节点。
-
<span v-once>{{msg}}</span>
可以绑定一次值,之后数据改变显示不会更改,注意会影响该节点下所有数据。使用<span v-html="msg"></span>
可以解析html。为了避免xss攻击,绝对不要对用户输入的内容用v-html。在布尔特性如:disabled
的值下,绑定值会被转为布尔。正常没有冒号则会转为字符串。 - 插值可以为表达式,不过只能为一个(三元和计算都可以,如果复杂计算尽量用computed属性)。该表达式是在沙盒环境运行的,只能访问Date、Math等白名单,不能访问用户自定义的全局变量。
- 计算属性有缓存,只有相关响应式依赖(绑定的数据,Date.now()不算)发生改变时才会计算,否则返回原值,大量计算的时候效率高。而函数则每次都执行。而监听属性常用于做一些异步操作,对于赋值计算最好用computed好过watch。计算属性还可以设置setter和getter方法。
- 绑定类:
<div v-bind:class="{'active': bool}"></div>
或者对象以及计算属性<div v-bind:class="classObject"></div>
或者数组<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
也可以这样写<div v-bind:class="[{ active: isActive }, errorClass]"></div>
。注意用-分隔要加引号,否则需改成驼峰式。 - 绑定样式:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
或者<div v-bind:style="styleObject"></div>
或者数组<div v-bind:style="[baseStyles, {color: 'red'}]"></div>
。如果需要,vue可以自动检测为css添加浏览器引擎前缀,也可以手动指定<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
,如果不需要前缀会选择flex。 - 条件渲染:v-if、v-else-if、v-else(后两个必须跟在v-if或v-else-if后面)。如果隐藏多个组件可以用<template>包裹分组,相比div其不会渲染。但切换的时候对于相同标签会进行复用,只改变内部值,比如下面label只改变值,若不想复用可以加个不同的key。比如例子如果input没加则只改变placeholder。
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
- v-show只是改变display,注意他不接受v-else和template。v-if只有等到真才渲染,且在切换的过程中各种监听器和子组件会被销毁重建,切换开销大,适合在运行条件很少改变的时候用。而v-show第一次就渲染,只是显示与否,初始渲染开销大,适合频繁切换的时候用。
- v-for:
<div v-for="(value, key, index) in object"></div>
其是按Object.keys()的结果来遍历的,不同js引擎可能不一样。也可以用of来替换in。由于渲染也采用复用的形式,所以最好指定key,自定义组件一定要key。后面的object可以是个数字。v-for的优先级高于v-if,若要先判断则可在v-for外层套一个再v-if。v-for也可以与template结合使用。 - 数组更新检测:变异方法(即会改变原数组)
push、pop、shift、unshift、splice、reverse、sort
会检测到改变;非变异方法如filter、slice、concat
会返回新数组,即使如此由于复用原则的存在渲染仍旧是高效的。但对于vm.items[indexOfItem] = newValue
和vm.items.length = newLength
确实监听不到变化的,则无法触发渲染变更。可以改为Vue.set(vm.items, indexOfItem, newValue)、vm.$set(vm.items, indexOfItem, newValue)、vm.items.splice(indexOfItem, 1, newValue)
和vm.items.splice(newLength)
。 - 对象更新检测:对象的属性添加和删除无法被监听,对象的修改可以。所以
Vue.set(vm.userProfile, 'age', 27)
或者别名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'
})
- v-on=""后可跟表达式,也可以是个方法,一般要求是写括号的,不加会默认加上($event)。
- 事件修饰符:
.stop
停止冒泡、.prevent
阻止默认、.self
只自身触发有用、.capture
捕获先自身再内部、.once
只触发一次也可用于自定义事件、.passive
默认行为立即触发不先执行函数且.prevent
会被忽略。修饰符顺序很重要,用v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击。 - 按键修饰符:可以直接用keyCode做修饰符,如
@keyup.13="enter"
,为了方便系统内置了一些别名.enter、.delete、.space、.esc、.tab、.up、.down、.left、.right
,可用Vue.config.keyCodes.f1 = 112
进行自定义。对于key暴露的也可以作为修饰符,<input @keyup.page-down="onPageDown">
只在$event.key === 'PageDown'
时被调用。 - 系统修饰符:
.ctrl、.alt、.shift、.meta
,meta不同系统不同键,window下是⊞。在和keyUp等一起使用时,必须是按下的时候释放其他键才触发(可以理解为同时按下),若只要监听按下ctrl可以用keyup.17
。.extra
精准控制系统修饰符。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
- 鼠标按钮修饰符:
.left、.middle、.right
。 - v-on的好处:无须在 JavaScript 里手动绑定事件, ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除,无须担心如何清理它们。
- 表单元素
<input>、<textarea> 及 <select>
可以用v-model来进行双向绑定,但对于输入法输入的不会及时变更,可用input监听,且value、checked、selected
初始值会被忽略,会默认读实例的值所以应该在data里设置。
// 多选,checkedNames: [ "Mike", "John", "Jack" ]
<div id='example-3'>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
</div>
// 单选,picked: "One"
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
// 下拉单选,selected: "B"
<div id="example-5">
<select v-model="selected">
<option disabled value="">请选择</option><!--若v-model设置的值没有匹配项会默认未选中导致ios第一项不可选,所以第一项可以设置为这个-->
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
// 下拉多选,selected: [ "A", "B", "C" ]
<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
- 表单修饰符:v-model可以跟修饰符,
.lazy
将input延迟到change、.number
将输入默认的字符串转为数字、.trim
去除首尾空白。 - 由于组件是可复用的,所以data必须是一个函数返回新对象,否则是一个对象所有实例的data都会互相影响。
- 可以用prop为子组件传递属性,当属性较多的时候,建议将多个属性放在一个对象传递过去。子组件给父组件传值通过$emit事件来实现,子组件
this.$emit('change-value', value)
,父组件通过监听@change-value="value = $event"
或者@change-value="changeValue"
,通过$event能获得传的值。
<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>
- 对于
<ul>、<ol>、<table> 和 <select>
,哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如<li>、<tr> 和 <option>
,只能出现在其它某些特定的元素内部。可通过is来解决。
// 下面写法会报错
<table>
<blog-post-row></blog-post-row>
</table>
// 正确写法
<table>
<tr is="blog-post-row"></tr>
</table>
组件注册
- 全局注册,
Vue.component('my-component-name', { /* ... */ })
名字可以为驼峰式或分隔式,但使用必须为分隔。全局注册的注册所有vue实例默认可用不需引入,但这样对用户每次都引入没必要的组件造成没必要的开销。 - 局部注册,用普通对象定义
var ComponentA = { /* ... */ }
,使用new Vue({ el: '#app',components: {'component-a': ComponentA}})
。如果用webpack模块化的写法,还可以import ComponentA from './ComponentA.vue';export default {components: {ComponentA}}
。注意局部注册只有当前的组件可以用,其子组件若要使用要单独再注册。 - 经常使用的基础组件,也可以自定义注册为全局组件,但全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。
prop
- prop除了字符串可以直接传,其他类型(数字、布尔等)都要在属性前加v-bind或冒号告诉vue这是个表达式,否则都会当做字符串传递过去。传递数组和对象传递的是引用,子组件修改数据会同步改变,但不建议这么做。若prop需要做子组件初始值,则子组件写个data等于prop即可;若是要做为初始计算值但需要转换,则子组件写个computed属性。
- prop可以是
props: ['initialCounter']
的写法,也可以用对象加验证的写法:
props: {
// 基础的类型检查 (`null` 匹配任何类型)
propA: Number,
// 多个可能的类型支持以下8种,甚至自定义的类型,内部通过instanceof判断
propB: [String, Number, Boolean, Object, Array, Date, Function, Symbol],
// 必填的字符串
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
}
}
}
- 写在自定义组件上的属性会继承到自定义组件的根组件上,class、style属性会进行合并,其他属性直接替换。但有时这并不是我们想要的,可以在自定义组件对象里增加
inheritAttrs: false
禁用继承。可搭配$attr使用???
自定义事件
-
自定义组件上可以用v-model属性,用法如下(将v-model的值绑定给checked属性,然后子组件监听修改这个checked属性):
自定义组件使用v-model - 自定义事件:自定义组件里可以通过
$listeners
属性获得父组件定义在组件上事件对象。
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>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
- prop的双向绑定:可以加个
.sync
修饰符。
// 子组件要修改父组件传递的prop时要通过$emit
this.$emit('update:title', newTitle)
// 父组件里通过下面的写法实时更新prop
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
// 上面的写法可以简写成如下
<text-document v-bind:title.sync="doc.title"></text-document>
// 多个属性绑定在同个对象上,还可以写成如下,直接省略属性名,注意后面必须是个变量而不是字面量表达式,v-bind.sync=”{ title: doc.title }”错误
<text-document v-bind.sync="doc"></text-document>
插槽
- 父组件中使用自定义组件包裹的内容,会被插入到组件内部的
<slot></slot>
,如果组件内部没有slot,则内容会被忽略。自定义组件包裹的内容可以通过在标签(也可用template)增加slot属性slot="slotName"
来指定插入到组件内部什么位置,如上所写会插入到组件内部<slot name="slotName"></slot>
的位置,若没指定slot则默认插入到组件内部<slot></slot>
处。<slot></slot>
可以有默认内容,如果父组件没指定则显示默认,否则替换。注意,父组件的包裹内容只能使用当前作用域即父级作用域下的变量,绑定在自定义组件上的数据无法使用。 - 作用域插槽,可以根据子组件的数据来定制插入内容。
// 父组件
<todo-list v-bind:todos="todos">
<!-- 将 `slotProps` 定义为插槽作用域的名字 -->
<template slot-scope="slotProps">
<!-- 为待办项自定义一个模板,-->
<!-- 通过 `slotProps` 定制每个待办项。-->
<span v-if="slotProps.todo.isComplete">✓</span>
{{ slotProps.todo.text }}
</template>
</todo-list>
// 子组件
<ul>
<li v-for="todo in todos" v-bind:key="todo.id" >
<!-- 我们为每个 todo 准备了一个插槽,-->
<!-- 将 `todo` 对象作为一个插槽的 prop 传入。-->
<slot v-bind:todo="todo">
<!-- 回退的内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
// 如果用解构的语法还可以简写成如下
<todo-list v-bind:todos="todos">
<template slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
动态组件
-
<component v-bind:is="currentTabComponent"></component>
通过is后面的组件名变量来指定当前组件渲染啥。但每次都会重新创建销毁,若想保留缓存,则在外面套一层<keep-alive></keep-alive>
。 - 异步组件:将对象返回一个promise对象,需要的时候再加载。
特殊例子
-
this.$root
访问根实例(比如默认的#app),通过这个实例可以访问属性和方法。this.$parent
访问父实例。通过this.$refs.name
访问<comp ref="name"></comp>
组件,如果和v-for一起用,得到的是数组;注意只在组件渲染完才可用,所以避免在计算属性或模板里使用。 - 依赖注入,父组件通过
provide() { return {getMap: this.getMap}}
提供子组件公用方法,子组件通过inject: ['getMap']
获取。类似prop,但是此法耦合度太高不建议使用。 -
$on(eventName, eventHandler)、$once(eventName, eventHandler)、$off(eventName, eventHandler)
可以用来监听事件。
// 优化前每次清理
mounted: function () {
// Pikaday 是一个第三方日期选择器的库
this.picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
},
// 在组件被销毁之前,
// 也销毁这个日期选择器。
beforeDestroy: function () {
this.picker.destroy()
}
// 优化后程序化清理更易维护
mounted: function () {
var picker = new Pikaday({
field: this.$refs.input,
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
- 组件可以通过name来调用自身,
name: 'stack-overflow', template: '<div><stack-overflow></stack-overflow></div>'
,但要注意,必须有一个明确会得到false的v-if来结束无限递归。 - 循环引用:指的是父组件A依赖组件B,B里面又依赖与A(上面的递归调用是依赖自身)。通过
Vue.component
全局注册可以自动解依赖。若是局部注册,则需要给系统一个点,在那里“A 反正是需要 B 的,但是我们不需要先解析 B。”
// 可以在beforeCreate钩子处引入
beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}
// 或者,在本地注册组件的时候,你可以使用 webpack 的异步 import:
components: {
TreeFolderContents: () => import('./tree-folder-contents.vue')
}
-
$forceUpdate
可以强制刷新,v-once
可以缓存组件只渲染一次,inline-template
可以自定义组件里面插内容而不插入slot。但以上都不推荐使用。