自定义指令:
-
bind 和 inserted 的区别
-
bind在inserted之前
-
bind 时父节点el.parentNode为 null
-
inserted 时父节点el.parentNode存在。
-
bind是在vue编译解析dom之前调用,inserted在vue编译解析完dom后调用
-
-
以后都优先用inserted,因为inserted里获取到的信息比bind中多一些
-
在钩子函数内部可以通过第二个参数binding的value属性,获取到指令绑定的参数
-
指令的修饰符可以用来做一些特定的逻辑判断处理
-
注意:在钩子函数内部的this不是vm实例,可以通过第三个参数vnode.context获取到vm实例
-
特别要注意:指令绑定的属性值,是动态属性绑定,会经过Vue的编译解析
-
如果自定义指令名称有多个单词,在定义时用小驼峰,如:
Vue.directive('sayHello', { ... })
,在html元素上使用时用短横线,如:<div v-say-hello="xxx"></div>
全局指令和局部指令:
- 通过
Vue.directive
定义的指令称为全局指令 - 通过
new Vue({ ..., directives: { ... } })
定义的指令称为局部指令 - 补充说明:一个Vue实例就是一个组件,局部指令只能在本组件内可用,全局指令在所有组件中都可用
计算属性
-
有什么用?
-
我们Vue的HTML模板中是可以写一些简单的表达式的,但是当逻辑比较复杂时,如果还直接写在HTML中,就显示不太合适了 比较臃肿 可读性较差,所以,这个时候就要考虑使用计算属性来重构了
-
如果需要根据某些数据计算出一个新的结果,我们用计算属性就比较合适
-
-
使用注意点:
- 都定义在computed选项中
- 每个计算属性就是一个函数,函数的返回值就是该计算属性的值
- 在模板中使用计算属性时,不需要加小括号调用(加了会GG),直接把函数名当做data使用就阔以鸟
- 计算属性时依赖数据动态计算值的,也就是说,以来的数据变了,计算属性的值就会跟着变
-
计算属性和方法的区别
- 计算属性有缓存(基于所依赖的数据,当数据没有发生变化时,多次使用,用的是已缓存的数据,只有当所依赖的数据发生变化后,才会重新计算)
- 方法木有缓存,多次使用都会调用执行内部的代码
侦听器:
-
用来监听数据的变化从而响应某种操作,适合处理异步操作
-
注意:侦听器的名字必须和数据名一致
-
侦听器和计算属性的区别:
- 计算属性的值是需要同步的返回,而侦听器是不需要返回值的
- 计算属性里面不适合做异步操作,而侦听器就适合
- 使用场景:
- 如果没有异步操作,又需要根据数据的变化计算出一个结果,就用计算属性
- 如果有异步操作,或仅需要根据数据的变化响应某种操作,就用侦听器
-
侦听器和blur事件的区别:侦听器只有当数据真正发生变化时才会触发函数,而blur事件不管数据有没有发生变化都会触发事件处理函数
过滤器:
-
可以对数据进行过滤(格式化),通过
Vue.filter
定义全局过滤器,通过new Vue({ fitlers: {} })
定义局部过滤器,参考全局/局部指令 -
语法:
Vue.filter('uppercase', function (originVal) { return newVal })
- 第一个参数是过滤器名称
- 第二个参数是处理函数
- 处理函数的第一个参数是被过滤的源数据
- 处理函数的返回值,就是最终过滤后的值
-
使用:
<div>{{ msg | uppercase }}</div>
-
带参数的过滤器:
- 定义:
Vue.filter('dateformat', (originVal, pattern) => dateformat(originVal, pattern))
- 使用:
<div>{{ user.regAt | dateformat('yyyy-MM-dd hh:mm:ss') }}</div>
- 定义:
Vue生命周期钩子:
- 说白了,就是Vue实例从出生到死亡的过程,Vue在一些关键的时间节点都给开发者提供了回调函数
- 分为四个阶段:
- 创建(只会触发一次):beforeCreate、created
- 挂载(只会触发一次):beforeMount、mounted
- 更新(会触发N次,只要数据有更新就会触发):beforeUpdate、updated
- 销毁(只会触发一次):beforeDestroy、destroyed
Vue数组类型数据操作:
- 变异方法Vue直接支持响应式(数据驱动视图)
- 非变异方法是不支持响应式的,但是可以通过重新赋值达到触发视图更新的目的
- 补充说明:数组的变异方法就是调用完该方法后会影响原数组的方法,反之,不会影响原数组的方法就是非变异方法
Vue自带的修改数据直接支持响应式方法:
- Vue.set(data中的对象或数组, 键名或索引, 值)
- vm.$set(data中的对象或数组, 键名或索引, 值) (推荐使用这种)
- 补充说明:
- 如果能拿到实例对象就用vm.$set,否则就用Vue.set
- vm实例在创建时(created阶段)data中已经存在的对象属性,是支持响应式的,而在后续添加的是不支持响应式的
组件化开发:就是把一类功能的元素组合到一起,将css(样式)/js(功能)/html(结构)包装到一起,供后续复用
Vue组件注册语法:
// 全局组件
Vue.component('HelloWorld', {
// 这里的箭头函数是直接返回一个对象,如果不加小括号,那么会当做函数体的左右花括号,有语法错误
data: () => ({ msg: 'xxx' }),
template: '<div>{{ msg }}</div>',
})
// HTML中使用时,如下:
// <hello-world></hello-world>
组件注册注意事项:
- data必须是函数,并且返回一个对象
- 因为我们的组件定义出来后,是可能被多次使用的,如果是一个对象,我们知道,对象是属于复杂数据类型,在内存中是一小块内存(栈)指向一大块内存(堆),那么在复用时,多个组件实例之间的data是共享的,也就是说会互相影响,为了避免这个问题,必须使用函数,每次返回一个新的对象,就能保证每个组件实例有自己独立的data数据,互不影响
- template必须只有一个根元素包裹,我们惯用的做法就是用一个div包裹所有元素
- template优先用模板字符串(后面我们学了单文件组件之后,就不需要关心了)
- 组件在命名时使用大驼峰命名法(和我们之后学习的单文件组件配套,如:HelloWorld.vue),但是在html中需要使用短横线的方式,如:
- 默认情况下,组件内不可以使用父组件中的data数据(父子组件之间的作用域不同)
注册局部组件:
new Vue({
el: '#app',
components: {
HelloWorld: {
data: () => ({ }),
template: ``
}
}
})
- 注意:局部组件只能在注册它的父组件的HTML模板中使用,其他任何地方都不能用
- 对于全局组件来说:组件的父子关系,在注册组件时是不能确定的,在写HTML结构时才能确定(看的就是HTML结构),比如:A组件写在B组件的HTML模板中,那么A组件就是B组件的子组件
Vue-devtools:
- 谷歌浏览器的搞法,步骤稍微有点麻烦(因为谷歌的应用商店仓库地址在国外,需要科学上网,否则网速极差),我这里推荐大家安装火狐浏览器(一线产品,跨平台支持的很好:Windows、Mac、Mobile),安装插件so easy.
- 注意:只有使用了Vue.js框架的页面,才能看到Vue-devtools选项卡
组件之间的数据交互:
- 父向子传值:
<div id="app">
<p>heheda</p>
<!-- 父组件在使用子组件标签时,通过属性绑定的方式传值数据给子组件 -->
<menu-item menu-title="传统属性-爸爸的title"></menu-item>
<!-- 普通HTML中用短横线引用子组件props中的属性(因为HTML忽略大小写) -->
<menu-item :menu-title="ptitle"></menu-item>
</div>
<script>
// 定义menu-item组件
Vue.component('MenuItem', {
// 子组件通过props属性接收父组件传递过来的数据,可以理解成函数的形参
props: [ 'menuTitle' ],
data: () => ({ selfTitle: '自己的title' }),
template: `
<div>
<!-- 模板语法中用小驼峰引用props中的属性 -->
{{ selfTitle }} --- {{ menuTitle }}
</div>
`,
methods: {
onClick() {
// JS中用小驼峰引用props中的属性
console.log(this.menuTitle)
},
},
})
new Vue({
el: '#app',
data: {
ptitle: 'Vue动态属性绑定-爸爸的title',
},
})
</script>
补充说明:
- props在定义时使用小驼峰命名法,和组件的命名保持一致
- props在使用时:
- JS中或Vue的模板语法中用小驼峰
- 普通HTML中用短横线
- props在定义时,可以指定固定的类型
Vue.component('User', {
props: {
name: String,
age: {
type: Number,
default: 18, // 默认值
required: true, // 是否必传
},
isMarry: Boolean, // 这个布尔值有点特殊,只要你在HTML中加了这个属性,就是true,反之false
},
})
// 在HTML中使用时
// <user :name="'张三'" :age="18" :is-marry="false"></user>
- 子向父传值:
- 单向数据流:不能直接在组件中修改父组件传递过来的数据(这是一个开发规范,并不是语法要求,这个规范是我们的老猿惨痛的教训总结出来的),这是为了统一管理我们的数据变更,不然,当有多个子组件并且业务复杂度到达一定程度时,就不会知道到底是那个儿子改了我的数据?我的数据时通过怎样的逻辑修改的?当项目业务功能逐渐复杂时,将难以维护,Vue官方详解
- 鉴于存在单向数据流的规范,我们子组件向父组件进行数据交互时,就不能直接操作,需要借助事件机制来通知父组件,让父组件自己操作自己的数据:
- 子组件触发事件:vm. e m i t ( ′ 自 定 义 事 件 名 称 ′ ) , 调 用 v m . emit('自定义事件名称'),调用vm. emit(′自定义事件名称′),调用vm.emit方法可以触发自定义事件
- 父组件监听事件:<menu-item @自定义事件名称=“事件处理函数()”>,跟我们监听@click="onClick()"是一个意思
- 范例(带事件参数):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<user-name-input @name-send="onNameSend($event)"></user-name-input>
<br>
{{ msg }}
</div>
<script src="./js/vue.js"></script>
<script>
Vue.component('UserNameInput', {
data: () => ({ name: '' }),
methods: {
send() {
// 触发自定义事件,用来通知父组件
this.$emit('name-send', this.name)
// 如果需要传递多个数据,可以使用数据或对象的形式
// this.$emit('name-send', [this.name, 'hehe', 123, 456])
}
},
template: `
<div>
<input type="text" v-model="name">
<button type="button" @click="send()">发送</button>
</div>
`,
})
new Vue({
el: '#app',
data: {
msg: '',
},
methods: {
onNameSend(eventData) {
console.log('<user-name-input> 子组件 触发了 name-send 事件', eventData);
this.msg = `hello, ${eventData} ~`
}
}
})
</script>
</body>
</html>
- 补充说明:
- $event在标准的DOM事件中是标准的事件对象,在通过$emit触发的自定义事件中是事件数据
- $emit('自定义事件类型', 事件数据)只能携带一个事件数据,如果想要传递多个,怎么办?用数据或对象即可
- 兄弟或(祖孙)组件传值:
- 需要借助事件中心:const eventHub = new Vue(),说白了,就是一个Vue实例,常用于但不限于兄弟组件之间的数据交互
- 其实这个事件中心的概念类似于中介/代理人,举个栗子:房屋出租,A想要租房,B想要出租,他们都去找房产中介,A告诉中介:我要租房子,如果有人出租时,请通知我,这个动作就好比在监听事件(eventHub. o n ) , B 告 诉 中 介 : 我 要 出 租 房 子 , 如 果 有 人 想 要 租 房 , 请 通 知 我 , 这 个 动 作 也 是 在 监 听 事 件 ( e v e n t H u b . on),B告诉中介:我要出租房子,如果有人想要租房,请通知我,这个动作也是在监听事件(eventHub. on),B告诉中介:我要出租房子,如果有人想要租房,请通知我,这个动作也是在监听事件(eventHub.on),当中介收到B想要出租房子的请求时,就会通知A,告知有房源了,这个动作就好比触发事件(eventHub. e m i t ) , 当 中 介 收 到 A 想 要 租 房 子 的 请 求 时 , 也 会 通 知 B , 告 知 有 人 想 要 租 你 的 房 子 了 , 这 个 动 作 也 是 在 触 发 事 件 ( e v e n t H u b . emit),当中介收到A想要租房子的请求时,也会通知B,告知有人想要租你的房子了,这个动作也是在触发事件(eventHub. emit),当中介收到A想要租房子的请求时,也会通知B,告知有人想要租你的房子了,这个动作也是在触发事件(eventHub.emit),如果交易达成,房产中介有可能会下架A和B的请求,这个就好比销毁事件(eventHub.$off)
插槽
- 注意:Vue在最终渲染时,标签只会渲染内部的HTML结构,不会包含tempalte元素
- 总结一点:无脑用标签,无脑写default默认插槽的名字,无脑指定插槽具体的名字
- 定义组件时,在模板里面写标签挖坑
Vue.component('HelloWorld', {
data: () => ({
user: { firstName: 'andre', lastName: 'mao' }
}),
template: `
<div>
hello, <slot name="default">world</slot> ~
<br>
hello, <slot name="username" :user="user">{{ user.firstName }}</slot> ~
</div>
`
})
- 使用组件时,用v-slot指令填坑
<hello-world>
<template v-slot:default>
heheda
</template>
<template v-slot:username="{ lastName }">
{{ lastName }}
</template>
</hello-world>
- 补充说明:v-slot:可以缩写成#,如:#default、#username
=和的区别:
- ===:严格等于,会先比较两边的类型,只有类型相同,才会比较数据
- ==:普通等于,如果两边的类型不同,会先进行隐式转换,再比较数据
- =比 性能高一丢丢,占用的资源少一丢丢,因为不存在隐式转换,效率会高一些,所以推荐使用严格等于:===
注意:
- 单向数据流,在局部组件中,可以忽略,因为局部组件只能在注册它的组件中使用,所以不会暴露出去,也是属于父组件的控制范围(个人经验,官网文档无说明)
- 写代码时,不要一直写到底再来验证,有的人写了100行代码,再回头验证,这时出错了,就一脸懵逼,不知道到底是哪一行出的错,要养成阶段性确认的编码习惯,这样可以大大缩小BUG定位