1.计算属性的作用
计算属性出现的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题.
计算属性是根据data中已有的属性,计算得到一个新的属性.
使用计算属性还有一个好处:
计算属性是有缓存性:如果值没有发生变化,则页面不会重新渲染
也就是说:只要相关依赖没有改变,对此访问计算属性得到的是之前缓 存的结果,不会多次执行。 所以说在进行大量耗时计算的时候,通过计算属性可以提升性能。2.
2. 侦听器
侦听器就是侦听data
中的数据变化,如果数据一旦发生变化就通知侦听器所绑定方法,来执行相应的操作。从这一点上,与计算属性是非常类似的。
但是,侦听器也有自己独有的应用场景。
执行异步或开销较大的操作。
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
}
加上了immediate
属性,并且该属性的值为true
,表示的就是在初始化绑定的时候,也会去执行侦听器
看一个案例
案例:就是通过watch
来监听uname
的值是否发生变化,如果发生了变化,就通过发送异步请求来检查uname
中的值,是否已经被占用。
通过以上的案例:我们可以看到watch
是允许异步操作的,并且在我们得到最终的结果前,可以设置中间状态,这些都是计算属性无法做到的。
3.计算属性与侦听器总结
第一点:语境上的差异:
watch
适合一个值发生了变化,对应的要做一些其它的事情,适合一个值影响多个值的情形。
而计算属性computed
:一个值由其它的值得来,其它值发生了变化,对应的值也会变化,适合做多个值影响一个值的情形。
第二点:计算属性有缓存性。
由于这个特点,我们在实际的应用中,能用计算属性的,会首先考虑先使用计算属性。
第三点:侦听器选项提供了更加通用的方法,适合执行异步操作或者较大开销操作。
4. 生命周期
(1)生命周期是什么?
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
(2)各个生命周期的作用
其实Vue
实例的生命周期,主要分为三个阶段,分别为
-
挂载(初始化相关属性,例如
watch
属性,method
属性)-
beforeCreate
-
created
-
beforeMount
-
mounted
-
-
更新(元素或组件的变更操作)
-
beforeUpdate
-
updated
-
-
销毁(销毁相关属性)
-
beforeDestroy
-
destroyed
-
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例被创建之初,组件的属性生效之前 |
created | 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用 |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用 |
mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 |
beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
update | 组件数据更新之后 |
activited | keep-alive 专属,组件被激活时调用 |
deactivated | keep-alive 专属,组件被销毁时调用 |
beforeDestory | 组件销毁前调用 |
destoryed | 组件销毁后调用 |
(3)生命周期示意图
5.组件化应用
-
问题是,如何确定页面中哪些内容划分到一个组件中呢?
你可以将组件当作一种函数或者是对象来考虑(函数的功能是单一的),根据[单一功能原则]来判定组件的范围。也就是说,一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件。
6.组件通信
父组件向子组件传值
第一:子组件内部通过props
接收传递过来的值。
Vue.component('menu-item',{
props:['title'] // props后面跟一个数组,数组中的内容为字符串,这个字符串可以当做属性类使用。
template:'<div>{{title}}</div>'
})
第二: 父组件通过属性将值传递给子组件
<menu-item title="向子组件传递数据"> </menu-item>
<menu-item :title="title"></menu-item> <!--可以使用动态绑定的方式来传值-->
子组件向父组件传值
第一:子组件通过自定义事件向父组件传递信息。
<div>我的儿子叫{{mySonName}}<son @tellMyFatherMyName="getMySonName"></son></div>
第二:父组件监听子组件的事件
template: '<button @click="emitMyName">我叫{{myName}}</button>',
methods: {
emitMyName() {
// 子组件传值给父组件需要用到$emit()方法,这个方法可以传递两个参数,一个是事件名称,一个是需要传递的数据
this.$emit('tellMyFatherMyName', this.myName)
}
}
兄弟组件之间数据传递
兄弟组件传值,通过事件总线完成。
创建事件总线:通过事件总线发射一个事件名称和需要传递的数据 。
// 创建一个空的vue实例,作为事件总线
var eventbus = new Vue()
methods: {
emitMyName() {
// 通过事件总线发射一个事件名称和需要传递的数据
eventbus.$emit('tellBroMyName', this.myName)
}
}
通过eventbus的$on()方法去监听兄弟节点发射过来的事件
mounted() {
// 通过eventbus的$on()方法去监听兄弟节点发射过来的事件
// $on有两个参数,一个是事件名称,一个是函数,该函数的默认值就是传递过来的数据
eventbus.$on('tellBroMyName', data => {
this.mySisterName = data
})
}
},
7.组件插槽应用
组件中的插槽,让使用者可以决定组件内部的一些内容到底展示什么,也就是,插槽可以实现父组件向子组件传递模板内容。具有插槽的组件将会有更加强大的拓展性,
作用域插槽
父组件对子组件的内容进行加工处理。
即将组件的数据暴露出去。而这么做,给了组件的使用者根据数据定制模板的机会,组件不再是写死成一种特定的结构。
<user-list :list="userList">
<template slot-scope="slotProps">
<strong v-if="slotProps.info.id===2"
>{{slotProps.info.userName}}</strong
>
<span v-else>{{slotProps.info.userName}}</span>
</template>
</user-list>
Vue.component("user-list", {
props: ["list"],
template: `<div>
<ul>
<li :key="item.id" v-for='item in list'>
<slot :info="item">
{{item.userName}}
</slot>
</li>
</ul>
</div>`,
});
作用域插槽案例
我们首先在子组件my-list
中,添加了作用域的插槽。
<div class="list">
<div class="list-title">
{{title}}
</div>
<div class="list-content">
<ul class="list-content">
<li v-for="item in content" :key="item.id">
<!--这里将content中的每一项数据绑定到slot的itemb变量上,在父组件中就可以获取到item变量-->
<slot :item="item">{{item.userName}}</slot>
</li>
</ul>
</div>
props: ["title", "content"],
同时在父组件中,使用对应的插槽
<div id="app">
<!-- 如果没有传递模板,那么子组件的插槽中只会展示用户名 -->
<my-list title="用户列表" :content="listData"></my-list>
<!-- 传递模板 -->
<my-list title="用户列表2" :content="listData">
<template slot-scope="scope">
<img src="./one.png" width="20"/>
<span>{{scope.item.userName}}</span>
</template>
</my-list>
</div>
插槽应用总结
为什么要使用插槽
插槽是组件最大化利用的一种手段,而不是替代组件的策略,当然也不能替代组件。如果能在组件中实现的模块,或者只需要使用一次v-else
, 或一次v-else-if
,v-else
就能解决的问题,都建议直接在组件中实现。
8. Vue
组件化的理解
定义:组件是可复用的Vue
实例,准确讲它是VueComponent
的实例,继承自Vue
优点:组件化可以增加代码的复用性,可维护性和可测试性。
使用场景:什么时候使用组件?以下分类可以作为参数
第一:通用组件:实现最基本的功能,具有通用性,复用性。例如按钮组件,输入框组件,布局组件等。(Element UI
组件库就是属于这种通用的组件)
第二:业务组件,用于完成具体的业务,具有一定的复用性。例如登录组件,轮播图组件。
第三:页面组件,组织应用各部分独立内容,需要时在不同页面组件间切换,例如:商品列表页,详情页组件。
如何使用组件
-
定义:
Vue.component()
,components
选项 -
分类:有状态组件(有data属性),
functional
-
通信:
props
,$emit()/$on()
,provide/inject
-
内容分发:
<slot>
,<template>
,v-slot
-
使用及优化:
is
,keep-alive
,异步组件
(这些内容在后面的课程中会详细的讲解)
组件的本质
vue
中的组件经历如下过程 组件配置 =>VueComponent
实例 => render()
=> Virtual DOM
=> DOM
所以组件的本质是产生虚拟DOM
9. 常用API
说明
1 Vue.set
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且会触发视图更新。
使用方法:Vue.set(target,propertyName,value)
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
methods: {
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
// Vue.set(c, "height", this.height);
this.$set(c, "height", this.height);
});
},
修改的代码含义就是通过Vue.set
方法,给users
数组中每个对象,设置一个height
属性,这时该属性就变成了响应式的,同时把 data
中的height
属性的值赋值给height
.
2 Vue.delete
删除对象的属性,如果对象是响应式的,确保删除能触发更新视图。
使用方式:Vue.delete(target,propertyName)
如果使用delete obj['property']
是不能更新页面的。
以上两个方法Vue.set()
和Vue.delete()
等同于以下两个实例方法。
vm.$set()
vm.$delete()
3 自定义事件实现双向数据绑定,v-model原理
以下的写法
<user-add @add-user="addUser" v-model="userInfo"></user-add>
等价以下的写法
<user-add
v-bind:value="userInfo"
v-on:input="userInfo = $event"
></user-add>
也就是说v-model
就是v-bind
与v-on
的语法糖。
在这里我们将userInfo
的值给了value
属性,而value
属性传递到了user-add
组件中,所以在user-add
组件中要通过props
来接收value
属性的值。
在user-add
组件的文本中,输入内容后触发@input
事件,对应的会调用onInput
方法,在该方法中,执行了
this.$emit("input", e.target.value);
发送了input
事件,并且传递了用户在文本框中输入的值。
那很明显,这时会触发下面代码中的input
事件,将传递过来的值给userInfo
属性。
<user-add
v-bind:value="userInfo"
v-on:input="userInfo = $event"
></user-add>
4. $emit $on
我们知道vm.$on
与vm.$emit
的典型应用就是事件总线。
也就是通过在Vue
原型上添加一个Vue
实例作为事件总线,实现组件间相互通信,而且不受组件间关系的影响
Vue.prototype.$bus=new Vue()
在所有组件最上面创建事件总线,
这样做的好处就是在任意组件中使用this.$bus
访问到该Vue
实例。
<!-- 清空提示栏 -->
<div class="toolbar">
<button @click="$bus.$emit('message-close')">
清空提示栏
</button>
</div>
mounted() {
//给总线绑定`message-close`事件
//也就是监听是否有`message-close`事件被触发。
this.$bus.$on("message-close", () => {
this.$emit("close", false);
});
},
5.ref
和vm.$refs
ref
是作为渲染结果被创建的,在初始渲染时不能访问它们。也就是必须在mounted
构造函数中。$refs
不是响应式的,不要试图用它在模板中做数据绑定。
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的$refs
对象上,如果在普通的DOM
元素上使用,引用指向的就是DOM
元素;如果用在子组件上,引用就指向组件的实例。
如下代码示例,是用来设置输入框的焦点
<input type="text" ref="inp" />
mounted(){
//mounted之后才能访问到inp
this.$refs.inp.focus()
}
6.vm.$once
与vm.$off
关于这两个方法,大家只需要了解一下就可以了。
vm.$once
监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。
vm.$on('test', function (msg) { console.log(msg) })
vm.$off
移除自定义事件监听器。
-
如果没有提供参数,则移除所有的事件监听器;
-
如果只提供了事件,则移除该事件所有的监听器;
-
如果同时提供了事件与回调,则只移除这个回调的监听器
vm.$off() // 移除所有的事件监听器
vm.$off('test') // 移除该事件所有的监听器
vm.$off('test', callback) // 只移除这个回调的监听器
10.过滤器
过滤器基本使用
在Vue
中,过滤器的作用就是格式化数据,也就是对数据的过滤处理,比如将字符串格式化为首字母大写
或者将日期格式化为指定的格式等。
下面先看一下自定义过滤器的语法
Vue.filter('过滤器名称',function(value){
//value参数表示要处理的数据
//过滤器业务逻辑,最终将处理后的数据进行返回
})
定义好以后可以使用。使用的方式如下:
<div>{{msg|upper}}</div>
<div>{{msg|upper|lower}}</div>
//定义过滤器,让输入的单词首字母变成大写.
Vue.filter("upper", function (value) {
//获取首字母让其转换成大写,然后拼接后面的内容。
return value.charAt(0).toUpperCase() + value.slice(0);
});
Vue.filter("lower", function (value) {
return value.charAt(0).toLowerCase() + value.slice(0);
});
上面定义的顾虑器是全局的过滤器,当然也可以定义局部过滤器。
局部过滤器只能在其所定义的组件内使用。
data: {
msg: "",
},
//局部过滤器
filters: {
upper: function (value) {
return value.charAt(0).toUpperCase() + value.slice(0);
},
},
带参数的过滤器
带参数的过滤器定义如下:
Vue.filter('format',function(value,arg1){
//value表示要过滤的数据。
//arg1,表示传递过来的参数
})
使用的方式如下
<div>
{{data|format(`yyyy-MM-dd`)}}
</div>
要处理的数据data
交给了过滤器中回调函数的value
参数,yyyy-MM-dd
交给了arg1
.
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>过滤器参数</title>
</head>
<body>
<div id="app">
<div>
{{date|format('yyyy-MM-dd')}}
</div>
</div>
<script src="vue.js"></script>
<script>
Vue.filter("format", function (value, arg, arg1) {
let result = "";
result +=
value.getFullYear() +
"-" +
(value.getMonth() + 1) +
"-" +
value.getDate();
return result;
});
const vm = new Vue({
el: "#app",
data: {
date: new Date(),
},
});
</script>
</body>
</html>
11.自定义指令
我们通过directive
方法创建了一个自定义指令。
在使用该指令的时候,一定要加上v-
的形式。
inserted
表示的是指令的钩子函数,含义是:被绑定元素插入父节点时调用。
出去inserted也可以用bind
这个钩子函数:只调用一次,第一次绑定指令到元素时调用,我们可以在此绑定只执行一次的初始化动作。
1.基本使用
Vue.directive('focus',{
inserted:function(el){
//获取元素焦点
el.focus();
}
})
自定义指令用法
<input type="text" v-focus>
2.自定义指令-带参数
带参数的自定义指令创建的语法(改变元素背景色)
Vue.directive('color',{
inserted:function(el,binding){
//binding表示传递过来的参数
el.style.backgroundColor=binding.value.color;
}
})
JsCopy
指令的用法
<input type="text" v-color='{color:"orange"}' />
3.自定义局部指令
局部指令的基本语法:在Vue
实例中添加directives
directives:{
focus:{
//指令的定义
inserted:function(el){
el.focus()
}
}
}
12.渲染函数
Vue
推荐在绝大数情况下使用模板来创建你的HTML
。然后在一些场景中,你真的需要JavaScript
的完全编程的能力,也就是使用javaScript来创建HTML
,这时你可以用渲染函数,它比模板更接近编译器。
这里我们先来做一个基本的了解,为后期的深入学习打好一个基础。
下面先看一下render
函数的基本结构。
render:function(createElement){
//createElement函数返回的结果为VNode. VNode就是虚拟dom,用js对象来模拟真实的DOM.
retrun createElement(
tag, //标签名称
data,// 传递数据
children //子节点数组
)
}
// heading组件
//<heading :level="1">{{title}}</heading> //这时要创建的组件
// <h2 title=""></h2> //这时上面的组件最终渲染的结果
Vue.component("heading", {
props: {
level: {
type: String,
required: true,
},
},
render(h) { //h 就是createElement函数
return h(
"h" + this.level, //参数1,表示要创建的元素
this.$slots.default //参数3,子节点VNode数组。(这里没有使用参数2,{{tile}}就是一个子元素)
);
},
});
13.混入
混入(mixin
)提供了一种非常灵活的方式,来分发Vue
组件中的可复用功能,一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin={
created:function(){
this.hello()
},
methods:{
hello:function(){
console.log('hello world')
}
}
}
Vue.component('comp',{
mixins:[myMixin]
})
“混入”可以提高组件的复用功能,例如:上面所写的hello
这个方法,不仅在一个组件中使用,还会
在其它组件中使用.那么,我们的处理方式就是,可以将hello
这个方法单独定义在一个地方,如果某个组件想要使用,可以直接将该方法注入到组件中。
14、插件
前面我们讲解的混入,组件封装等都可以提高组件的复用功能。
但是这种方式不适合分发,也就是不适合将这些内容上传到github
上,npm
上。而这种情况最适合通过插件
来实现。
插件通常用来为Vue
添加全局功能。插件的功能范围一般有下面几种:
- 添加全局方法或者属性。例如:'element'
- 添加全局资源
- 通过全局混入来添加一些组件选项。例如
vue-router
- 添加
vue实例
方法,通过把它们添加到Vue.prototype
上实现 - 一个库,提供自己的
API
,同时提供上面提到的一个或多个功能,例如vue-router
插件声明
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) {
// 逻辑...
}
}