【 所有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)。 只有当实例被创建时就已经存在于 data 中的 property 才是响应式的。如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么你仅需要设置一些初始值】
插值: {{ }} / v-bind:title = "title" / v-html
- 请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。
- 对于布尔 属性 (它们只要存在就意味着值为 true),如果 isButtonDisabled 的值是 null、undefined 或 false,则 disabled属性不会被包含在渲染出来的 元素中。
<button v-bind:disabled="isButtonDisabled">Button</button>
指令
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
计算属性和侦听器 coumputed watch
1. 计算属性
// 计算属性若没有放到视图中是不会执行的
// 计算属性放到视图中第一次执行的时机是created之后,mounted之前。
// 除了第一次执行的时机外后面每次执行频率是reversedMessage方法里面的某个data值发生了变化或者是引用类型的地址发生变化就会执行。
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
<p>computedAge:{{computedAge}}</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello',
info:{
age:18
}
},
computed: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
},
// info地址发生改变或者age的值发生改变都会执行次计算方法
computedAge () {
console.log('computedAge---start')
return this.info.age;
}
}
})
2. 计算属性缓存 vs 方法
// 虽然计算属性缓存和方法都能达到相同效果,第一次执行时机都是mounted之前。
// 但是后面每次执行时机不一样:计算属性缓存的执行是方法里面的某个值发生了变化或者是引用类型的地址发生变化就会执行;而方法的执行是模板重新渲染时就会执行。
// 方法是没有缓存的,当一个组件多次使用同一个方法{{ reversedMessage() }}时,方法会执行多次;而计算属性是具有缓存机制,多个地方使用同一个计算属性,计算属性只会执行一次
// 总结:方法的执行频率 >= 计算属性缓存
3. 侦听器 watch
//执行时机:被检测的数据发生变化时才会执行
//使用场景:监听一个数据变化,在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
watch: {
//监听基本数据类型
age(newVal) {
console.log('age', newVal)
},
//监听引用类型
userInfo: {
handler(newVal) {
console.log('userInfo', newVal)
},
//deep不配置或者值为false,都表示只有userInfo这个对象地址值发生变化时才会执行handler回调;
//deep:true,表示userInfo这个对象地址值发生变化时或者该对象里面任何一个属性值发生变化时会执行
deep: true,
//immediate不配置或者值为false,都表示初始值时不会执行handler的函数;只有配置为true,才在beforeCreate之后created之前执行
immediate: true
},
// 监听某个对象里面的某个属性
'userInfo.name'(newVal){
console.log('userInfo.name', newVal)
}
},
Class 与 Style 绑定
<div :class="[ { active: isActive }, 'errorClass' ]">456</div>
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
},
isActive : true
}
条件渲染v-if v-show
<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>
1. 用 key 管理可复用的元素
// 由于没有提供key, 在loginType切换时,label标签复用、input标签复用导致value复用
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
// 虚拟dom作diff比较时,给input提供key,key值不一样,input元素本身以及它的子标签元素直接生成新的虚拟dom,不会复用旧的
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="Username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="Email">
</template>
2.v-if vs v-show
// v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display。v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
// 如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
列表渲染 v-for
// 遍历数组
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
// 遍历对象
<div v-for="(value, name) in object">
{{ name }}: {{ value }}
</div>
1.v-for 中的key
// key的唯一性能提高渲染的速度
// key的工作原理:每次渲染真实dom之前,会进行新的虚拟Dom和上一次虚拟Dom比较。
// 若key值不同,则它本身以及子元素会直接创建新的真实Dom, 从而渲染到页面;
// 若key值相同,则会递归元素本身显示属性和其子节点,属性键名键值都相同部分复用,子节点完全一样会复用,复用下就不会创建新的真实Dom,只有不一样的才会创建。从而提高性能。
事件处理
// 访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
1.修饰符
<!-- 阻止单击事件继续传播 -->
<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>
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
// exact
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
表单输入绑定
// 自动将用户的输入值转为数值类型
<input v-model.number="age" type="number">
// 自动过滤用户输入的首尾空白字符
<input v-model.trim="msg">
// v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
组件基础
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
// 你每用一次组件,就会有一个它的新实例被创建。如下data函数、created钩子会执行3次
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
1.一个组件的 data 选项为什么必须是一个函数
// 若data是一个对象
const MyComponent = function () { };
MyComponent.prototype.data = {
b: 2
}
// 多个实例共享同一份data对象,多个实例取的是同一个地址的引用
const component1 = new MyComponent();
const component2 = new MyComponent();
// 引用类型相互影响
component1.data.b = 5;
console.log(component2.data.b);//5
// data若是一个函数
const MyComponent = function () {
this.data = this.data();
};
MyComponent.prototype.data = function () {
return {
b: 2,
userInfo: {
name: 'bwf'
}
}
};
const component1 = new MyComponent();
const component2 = new MyComponent();
component1.data.b = 5;
component1.data.userInfo.name = 'wmywmy';
console.log(component2.data.b); //2
console.log(component2.data.userInfo.name); //bwf
2.函数构成自己的作用域
const myfun = function () {
return { name: 'xxx'}
}
const my1 = myfun()
const my2 = myfun()
my1.name = 'bbbb'
console.log(my1) // {name:'bbbb'}
console.log(my2) // {name:'xxx'}
//注意写法的区别
const obj = {
name: 'xxx'
}
const myfun = function () {
return obj
}
const my1 = myfun()
const my2 = myfun()
my1.name = 'bbbb'
console.log(my1) // {name:'bbbb'}
console.log(my2) // {name:'bbbb'}
3. 父向子组件传值
// 父组件attribute 传入数据,子组件props 接收
// 父组件
<blog-post title="My journey with Vue"></blog-post>
// 子组件
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
4. 子向父传值
// 父组件 @方法名='methods中的方法' 子组件$emit('方法名',【参数列表】)
// 父组件
<blog-post @enlarge-text="enlarge"></blog-post>
methods: {
enlarge: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
// 子组件
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
5. 通过插槽分发内容
// 在子组件中 <slot></slot>占位
// 父组件
<alert-box>
Something bad happened.
</alert-box>
// 子组件
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
prop单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
1.基本数据类型传递(在子组件中若想改变传递过来的数据,最好定义一个新的变量来接收)
// 父组件传递给子组件的若是基本数据类型,那么由于是值传递,所以在子组件容器里改变传递传递过来的数据,是对父组件没有任何影响的。但是在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值
// 这种情况下,由于是值传递,所以父容器改变值,子组件读取到的依旧是旧值;若想子组件实时读取到父容器更新后的值,则需要运用计算属性。
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter //子组件容器中定义一个新的变量counter来接收父组件传递过来的initialCounter
}
}
2.引用数据类型传递
在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态,同样的父组件更新值,子组件也能动态响应更新值。
// 2.1 如果你只是子组件改变而不影响父组件,可以用工具类等进行深拷贝
// 2.2 如果你想子组件改变后父组件也跟着改变,为了保证单向流,最好通过$emit等机制改变源数据。
prop的各种验证形式
// prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
// type: String Number Boolean Array Object Date Function Symbol
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
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
}
}
}
})
.sync 修饰符(传递prop和改变prop事件的语法糖)
<text-document
:count="count"
@update:count="count = $event"
></text-document>
// 在父组件中,以上简写为
<text-document :count.sync="count"></text-document>
// 子组件
props: ['count'], // 读取prop
this.$emit('update:count', 5) //设置prop, 这时父组件count会改变,单向流入子组件
插槽
1.插槽内容
// 插槽内可以包含任何模板代码,包括 HTML,甚至其它的组件.
2.后备内容
// 提供默认值。
<button type="submit">
<slot>Submit</slot>
</button>
3.自 2.6.0具名插槽
// 父容器 v-slot:xxx 指明内容放置到子容器<slot name="xxx"></slot>对应的区域
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
// 子容器
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
动态组件 & 异步组件
1.在动态组件上使用 keep-alive
// 通常情况下,切换路由等,组件会跟随着重新创建,但某些时候某个组件自始至终都是固定的内容,这时为了提高性能,可以使用keep-alive,在它们第一次被创建的时候缓存下来,被包裹的组件缓存后就不会再创建了(除非有页面刷新等级制),当然使用keep-alive后,钩子函数等也不再执行。
2.异步组件
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
访问元素 & 组件
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
CSS 动画
// v-enter:在元素被插入之前的类
// v-leave-to:元素移除之后。
<div id="example-2">
<button @click="show = !show">Toggle show</button>
<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>
.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);
}
}
// 当有相同标签名的元素切换时,需要通过 key attribute 设置唯一的值, 否则diff算法时,标签名相同,采取复用方式,标签是复用旧的,导致过度动画失效。
//下面实例不会有动画效果,给每个button加上唯一的key时才有效果
<button @click="handleClick">Toggle show</button>
<transition name="bounce">
<button v-if="docState === 'saved'">
saved
</button>
<button v-if="docState === 'edited'">
edited
</button>
<button v-if="docState === 'editing'">
editing
</button>
</transition>
<style scoped>
.bounce-enter-active {
animation: bounce-in 0.5s;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
</style>
####混入
1.一个混入对象可以包含任意组件选项。
2.选项合并
//数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先
//同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
//值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
开发插件
1.Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
2.通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
过滤器
1.过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。
2.可以在一个组件的选项中定义本地的过滤器,也可以全局定义过滤器
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
//全局定义过滤器
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
解决对象、数组变化不能引起页面渲染方法
1.对象
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的
//解决方法1
this.$set(this.someObject,'b',2)
//解决方法2 添加的属性比较多
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
// 解决方法3
在初始化实例前声明所有根级响应式 property,哪怕只是一个空值:
2.数组
Vue 不能检测以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
// 方法1
Vue.set(vm.items, indexOfItem, newValue)
// 方法2
vm.items.splice(indexOfItem, 1, newValue)
####解决更新是异步的问题
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
methods: {
updateMessage: async function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}
几个主要生命周期
<template>
<div>
<h3>Home Page</h3>
<p>count:{{count}}</p>
<button @click="age++">change age</button>
<p>countBigger:{{countBigger}}</p>
</div>
</template>
<script>
export default {
data () {
return {
age: 9
}
},
//1.实例生成后,数据(data)和事件(methods)之前调用
beforeCreate () {
console.log('Home---beforeCreate', this)
},
props: {
count: {
type: Number,
//2.数据(data)和事件(methods)之前调用
validator (value) {
console.log('props---validator---start')
return value < 10
}
}
},
//3.数据(data)和事件(methods)已经被挂载到this,顺序有可能会变动,但是是在mounted之前调用
computed: {
countBigger () {
console.log('computed---countBigger---start')
return this.count * 10
}
},
watch: {
age: {
handler (newValue) {
console.log('watch---countBigger---start', newValue)
},
//4.数据(data)和事件(methods)已经被挂载到this, immediate: true, 在beforeCreate和created之间调用
immediate: true
}
},
//5.数据侦听 方法 等配置完毕
created () {
console.log('Home---created', this)
},
//6.挂载之前开始被调用
beforeMount () {
console.log('Home---beforeMount', this)
},
//7.挂载完成后被调用,不会保证所有子组件也都挂载完成
mounted () {
console.log('Home---mounted', this)
this.$nextTick(() => {
//在整个视图都被渲染完成之后才会运行的代码
})
},
beforeDestroy () {
console.log('Home---beforeDestroy', this)
},
destroyed () {
console.log('Home---destroyed', this)
},
//DOM发生更新之前被调用
beforeUpdate () {
console.log('Home---beforeUpdate', this)
},
//DOM发生更新之后被调用
updated () {
console.log('Home---updated', this)
},
methods: {
say () {
console.log('say')
}
},
};
</script>
<style scoped>
</style>
开发工具vue-devtools
1.npm install vue-devtools
2.node_modules会发现到vue-devtools 。打开这个文件夹,找到 render文件夹,找到manifest.json,修改里面的配置"persistent": true
3.将 render文件夹拖拽到谷歌浏览器的开发工具里就可以了