Vue实例
MVVM模型
M:模型(Model):data中的数据
V:视图(View):也称模版代码(中的内容都是模版)
VM:vue实例对象(ViewModel),功能是:DOM监听、数据绑定
创建一个Vue实例
每个应用都是从Vue实例开始的,真实开发中一般只有一个vue实例
let vm = new Vue({
// 选项
})
挂载点el、数据data与事件响应方法methods
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app"><!-- 在这段HTML代码中,div元素中的id属性,是vue实例通过el指定的挂载点 -->
<!-- 使用如下方法会自动传一个event事件参数给回调函数showInfo3 -->
<button @click="showInfo($event,name)">{{name}}你再点我一下试试?</button>
</div>
</body>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false//可以阻止 vue 在启动时生成生产提示
let data_llz = {
name: "陆龙忠",
};
let vm = new Vue({
el: "#app",
data: data_llz,
methods: {
showInfo(event,myName){
console.log(myName);//输出:陆龙忠
console.log(event.target.innerText); //拿到发生该事件的标签体文本信息
console.log(this); //此处的this是vm
},
},
});
console.log(data_llz.name==vm.name)//true
</script>
</html>
不要直接在插值语法中使用methods中的方法
如果我不使用v-on
,而直接使用插值语法{{xxx}}
去调用methods
中的方法,可能会导致一些无限循环的bug,原因是methods
中的方法改变了真实dom,导致vue重新渲染,又再次调用插值语法{{xxx}}
。
如果有类似的需求,请在VUE生命周期函数(钩子函数如:mounted())中实现相应的需求。(后面的VUE生命周期会详细讲解这个例子)
以下是直接使用methods
中的方法导致的无限循环bug:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 会被修改透明度的dom元素 -->
<h2 :style="{opacity}">好好吃,颜色会变淡</h2>
<!-- 直接使用模版语法调用methods中的changeStyle()函数,导致无限次创建定时器并修改dom样式 -->
{{changeStyle()}}
</div>
</body>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false//可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
opacity:1,//设置透明度
},
methods: {
changeStyle(){
//创建一个定时器,每隔16秒执行一次(定时器指数级无限次创建,内存爆炸)
setInterval(()=>{
// 在定时器中修改dom样式,触发vue页面重新渲染
this.opacity-=0.01
if(this.opacity<=0) {
this.opacity =1
};
},16)
},
},
});
</script>
</html>
el与data的两种写法
与上面的例子相对比:
- 可以通过vm.$mount("#app")来挂载到对应的容器上
- 使用组件时,data必须使用函数式写法
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app"><!-- 在这段HTML代码中,div元素中的id属性,是vue实例通过el指定的挂载点 -->
<!-- 使用如下方法会自动传一个event事件参数给回调函数showInfo3 -->
<button @click="showInfo($event,name)">{{name}}你再点我一下试试?</button>
</div>
</body>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false//可以阻止 vue 在启动时生成生产提示
let data_llz = {
name: "陆龙忠",
};
let vm = new Vue({
//组件必须使用函数式写法
data: function(){
return data_llz;
},
methods: {
showInfo(event,myName){
console.log(myName);//输出:陆龙忠
console.log(event.target.innerText); //拿到发生该事件的标签体文本信息
console.log(this); //此处的this是vm
},
},
}).$mount("#app");
console.log(data_llz.name==vm.name)//true
</script>
</html>
模版语法
模版
HTML标签里的vue实例挂载的根容器就是模版,以及每一个组件vue文件中的<template>
标签中的内容就是模版。
比如:
<body>
<div id="app">
<button @click="showInfo1(18)">{{name}}你敢点我一下试试?</button>
</div>
</body>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false//可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",//挂载到根模版下
data: {//数据存放点
name: "陆龙忠",
},
methods: {//事件响应方法存放点
showInfo1(id) {
console.log(id);
},
},
});
</script>
上面的body不是模版,而div是模版,因为vue实例挂载到div上了
插值语法 {{xxx}}
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:
<div id="app">
<button @click="showInfo1(18)">{{name}}你敢点我一下试试?</button>
</div>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false//可以阻止 vue 在启动时生成生产提示
let people={
name: "陆龙忠",
}
let vm = new Vue({
el: "#app",//挂载到根模版下
data: people,
methods: {//事件响应方法存放点
showInfo1(id) {
if(people.name=="陆龙忠"){
people.name="hhc"
}else{
people.name="陆龙忠"
}
},
},
});
</script>
如果data中的name属性发生变化,则模版中的{{ name }}也会一起发生变化。
Vue实例对象中所有属性都可以用插值语法访问,如下是chrome控制台下输出的Vue实例对象及其部分属性:
指令语法
指令 (Directives) 是带有 v- 前缀的特殊 属性(attribute)。指令 属性(attribute) 的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
数据绑定
v-bind:
单向数据绑定
v-bind:
指令可以用于响应式地更新 HTML 属性(attribute):一般用于非交互数据(无输入),纯显示数据
可以简写为":
"冒号
v-bind:
指令后的HTML属性值是javascript表达式,也就是说,可以在HTML属性值内调用函数,数学计算
比如:动态更新按钮背景颜色
<div id="app">
<button @click="showInfo1" v-bind:style="backgroundColor">{{name}}你敢点我一下试试?</button>
<button @click="showInfo1()" :style="backgroundColor">{{name}}你再点我一下试试?</button>
</div>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false//可以阻止 vue 在启动时生成生产提示
let people={
name: "陆龙忠",
backgroundColor:"background-color:green;",
};
let vm = new Vue({
el: "#app",//挂载到根模版下
data: people,
methods: {//事件响应方法存放点
showInfo1() {
if(data_llz.backgroundColor=="background-color:green;"){
data_llz.backgroundColor="background-color:red;"
}else{
data_llz.backgroundColor="background-color:green;"
}
},
},
});
</script>
v-bind:
的属性绑定是单向的,就是说vue实例中的data发生变化时会更新HTML中对应绑定的属性值。但是当用户修改表单输入框(比如:<input type="text" v-bind:value="name">
)的属性值时,data是不会发生变化的。
v-model
双向数据绑定(表单数据获取)
如果要保证data和用户输入完全一致,用户输入的数据发生变化,data也一起跟着变化,就需要使用v-model
v-model
指令在表单 <input>
(输入框)、<textarea>
(多行可输入文本框)及 <select>
(下拉列表) 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
一般情况下绑定的是表单元素的value
值,一般书写语法为v-model:value="js表达式"
,可以简写为v-model="js表达式"
。
v-model
会忽略所有表单元素的 value
、checked
、selected
属性 的初始值,而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
比如:文本框
<div id="app">
<span>请输入第一句话:</span>
<input v-model:value="message1" placeholder="请输入" />
<span>请输入第二句话:</span>
<input v-model="message2" placeholder="请输入" />
<span>请输入第三段话:</span>
<textarea v-model="message3" placeholder="请添加内容"></textarea>
<p>你输入的第一句话内容: {{ message1 }}</p>
<p>你输入的第二句话内容: {{ message2 }}</p>
<p style="white-space:pre-line">你输入的第三段话内容:{{ message3 }}</p>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
var vm = new Vue({
el: "#app",
data: {
message1: "",
message2: "",
message3: "",
},
});
</script>
比如:复选框
<html>
<body>
<div id="app">
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">hhc</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">llz</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">xjh</label>
<span>选中者: {{ checkedNames }}</span>
</div>
</body>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
var vm = new Vue({
el: "#app",
data: {checkedNames: []},
});
</script>
</html>
比如:单选按钮
<div id="app">
<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 }}</span>
</div>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
var vm = new Vue({
el: "#app",
data: {picked: ''},
});
</script>
比如:下拉列表
<div id="app">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>你选择了: {{ selected }}</span>
</div>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
var vm = new Vue({
el: "#app",
data: {selected: ''},
});
</script>
当然还有一些使用细节没记录,以及v-model和v-bind结合使用,详情见:官方文档-表单输入绑定
事件处理v-on:
v-on:
指令,它用于监听 DOM 事件,其实上面我们已经用过。
可以简写为"@
"
v-on:
指令和v-bind:
指令一样,其后的HTML属性值是javascript表达式,可以在HTML属性值内调用函数,做数学计算。
比如:监听点击按钮点击事件,并调用函数,传递参数
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用如下方法将不会传事件参数给回调函数showInfo1,只会传一个值18 -->
<button v-on:click="showInfo1(18)" v-bind:style="backgroundColor">{{name}}你敢点我一下试试?</button>
<!-- 使用如下方法,不仅仅会把值sex传给回调函数showInfo2,还会传事件参数给回调函数showInfo2 -->
<button @click="showInfo2($event,sex)" :style="backgroundColor">{{name}}你再点我一下试试?</button>
<!-- 使用如下方法会自动传一个event事件参数给回调函数showInfo3 -->
<button @click="showInfo3">{{name}}你点不点啊?来啊!</button>
</div>
</body>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let data_llz = {
name: "陆龙忠",
backgroundColor:"background-color:green;",
sex:"男",
};
var vm = new Vue({
el: "#app",
data: data_llz,
methods: {
showInfo1(id) {
console.log(id);
if(data_llz.name=="陆龙忠"){
data_llz.name="hhc"
}else{
data_llz.name="陆龙忠"
}
},
showInfo2: function (event, sex) {
console.log(event.target); //拿到发生该事件的标签:<button style="background-color: red;">陆龙忠你再点我一下试试?</button>
console.log(sex);//输出:男
if(data_llz.backgroundColor=="background-color:green;"){
data_llz.backgroundColor="background-color:red;"
}else{
data_llz.backgroundColor="background-color:green;"
}
console.log(this); //此处的this是vm
},
showInfo3: (event) => {
console.log(event.target.innerText); //拿到发生该事件的标签体文本信息
console.log(this); //此处的this是window
},
},
});
</script>
</html>
事件修饰符.stop
阻止冒泡
事件修饰符.prevent
prevent是阻止默认行为,比如a标签,form表单
条件渲染
v-show
v-show
只接收布尔值
例如:
<div id="app">
<!-- 下面代码将只显示llz,不显示hhc -->
<h1 v-show="false">hhc</h1>
<h1 v-show="1 === 1">llz</h1>
</div>
v-if
、v-else-if
、v-else
<div id="app">
<span>请输入你的想查看的字母:</span>
<input v-model:value="type" placeholder="请输入" />
<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>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
type: "A",
},
});
</script>
v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。
类似于 v-else
,v-else-if
也必须紧跟在带 v-if
或者 v-else-if
的元素之后。
v-if
与v-show
对比
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
<template>
中可以使用v-if
不能使用v-show
列表渲染v-for
v-for遍历数组
<div id="app">
<ul>
<li v-for="(item,index) in items" :key="item.id">
{{ item.id }}-{{ item.message }}-{{ item.age }}-{{ index }}
</li>
</ul>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
items: [
{ id:"001",message: 'wjt' ,age:18},
{ id:"002",message: 'hhc' ,age:20},
{ id:"003",message: 'llz' ,age:17},
{ id:"004",message: 'xjh' ,age:22},
]
},
});
</script>
v-for遍历对象中的属性
<div id="app">
<ul>
<li v-for="(value,index) in object" :key="index">
{{ value }}-{{ index }}
</li>
</ul>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
object: {
title: '标题:买g7x3',
author: '作者名:好好吃',
publishedAt: '时间:2016-04-10'
},
},
});
</script>
数组与对象变化检测
由于 JavaScript 的限制,Vue 不能检测数组内容的变化(增删改)和 对象的属性 的增删。
比如:
<div id="app">
<ul>
<li v-for="(item,index) in items" :key="item.id">
{{ item.id }}-{{ item.name }}-{{ item.age }}-{{index}}
</li>
<li>
{{nb}}
</li>
<li>
{{object}}
</li>
<button v-on:click="updataItems">更改数据</button>
</ul>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
var vm = new Vue({
el: "#app",
data: {
items: [
{ id:"001",name: 'wjt' ,age:18},
{ id:"002",name: 'hhc' ,age:20},
{ id:"003",name: 'llz' ,age:17},
{ id:"004",name: 'xjh' ,age:22},
],
object: {
title: '标题:买g7x3',
author: '作者名:好好吃',
publishedAt: '时间:2016-04-10'
},
},
methods: {
updataItems(){
this.items[0]={ id:"005",name: 'wkx' ,age:20}//修改数组中某个元素
this.items[4]={ id:"009",name: 'wkx' ,age:20}//向数组中添加一个元素
this.nb={name:"hhc买g7x3"}//新增对象(或者删除原有对象)
this.object.place="澳门"//在对象中新增属性
delete this.object.title//删除对象中的原有属性
},
},
});
</script>
点击button按钮后可以观察到页面的内容并没有更新,但是在chrome后台可以观察到数据实际上是更新了的:
我们先看点之前后台的数据:
再看点之后后台的数据:
对比后我们可以发现 增添的新对象 和 修改后的数组元素 中没有set和get函数。这就是数据更新了但页面没更新的根本原因。
对象监测Vue.set()与Vue.delete()函数
如果我们要使之能监视到新加对象或者删除对象,我们需要使用Vue.set()
和Vue.delete()
和函数,这个函数也可以用Vue实例对象调用:$vm.set()
和$vm.delete()
。它们的使用方法将在数组监测知识点之后的例子中展示。注意:它们的目标对象不能是一个 Vue 实例或 Vue 实例的根数据对象(即不能是vm
或者vm._data
)。
数组监测
如果我们要使之能监视到数组中数据的变化,我们可以使用vue官方包裹好的数组方法,这些方法调用时也会触发视图更新。这些被包裹过的方法包括:
push()
添加pop()
删除shift()
把数组的第一个元素从其中删除,并返回第一个元素的值unshift()
向数组最前面添加一个元素splice()
修改sort()
排序reverse()
对上面的例子进行改进如下:
<div id="app">
<ul>
<li v-for="(item,index) in items" :key="item.id">
{{ item.id }}-{{ item.name }}-{{ item.age }}-{{index}}
</li>
<li>
{{object}}
</li>
<button v-on:click="updataItems">更改数据</button>
</ul>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
var vm = new Vue({
el: "#app",
data: {
items: [
{ id:"001",name: 'wjt' ,age:18},
{ id:"002",name: 'hhc' ,age:20},
{ id:"003",name: 'llz' ,age:17},
{ id:"004",name: 'xjh' ,age:22},
],
object: {
title: '标题:买g7x3',
author: '作者名:好好吃',
publishedAt: '时间:2016-04-10'
},
},
methods: {
updataItems(){
Vue.set(this.items,0,{ id:"005",name: 'wkx' ,age:20})//修改数组中某个元素
this.items.splice(1,1,{ id:"002",name: 'hhc' ,age:25})//修改数组中某个元素方法2
this.items.push({ id:"009",name: 'py' ,age:20})//向数组最后面添加一个元素
this.items.unshift({ id:"000",name: 'py' ,age:20})// 向数组最前面添加一个元素
// this.$set(this,"name","hhc买g7x3")//报错,不允许向data中添加新的响应式数据,想加必须在已有的数据中加,如下:
this.$set(this.object,"name","hhc买g7x3")
Vue.set(this.object,"place","澳门")//在对象中新增属性
Vue.delete(this.object,"title") //删除对象中的原有属性
},
},
});
</script>
一个列表排序例子:
<div id="app">
<ul>
<li v-for="(item,index) in items" :key="item.id">
{{ item.id }}-{{ item.name }}-{{ item.age }}-{{index}}
</li>
</ul>
<span>请输入你想查找的人:</span>
<input v-model="keyWord" placeholder="请输入" />
<br/>
<button v-on:click="sortItem">更改排序方式</button>
<span>按年龄排序</span>
<ul>
<li v-for="(value,index) in findItem" :key="index">
{{ value }}-{{ index }}
</li>
</ul>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
keyWord:"",
sortType:0,//0代表原顺序,1升序,2降序
items: [
{ id:"001",name: 'wjt' ,age:18},
{ id:"002",name: 'hhc' ,age:20},
{ id:"003",name: 'llz' ,age:17},
{ id:"004",name: 'xjh' ,age:22},
],
},
methods: {
sortItem(){
this.sortType++
if(this.sortType>2){
this.sortType=0
}
},
},
computed: {
findItem(){
const arr = this.items.filter((p)=>{
return p.name.indexOf(this.keyWord)!== -1
})
//判断是否需要排序
if(this.sortType){
arr.sort((p1,p2)=>{
return this.sortType===1? p2.age-p1.age : p1.age-p2.age;
})
}
return arr
},
},
});
</script>
常见内置指令
v-text
v-html
v-cloak
v-once
v-pre
自定义指令
计算属性computed
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。
所以,对于任何复杂逻辑,你都应当使用计算属性。
简单例子:姓氏变化,其全名就跟着变化
<div id="app">
<span>请输入你的姓氏:</span>
<input v-model:value="firstName" placeholder="请输入" />
<span>请输入你的名字:</span>
<input v-model="lastName" placeholder="请输入" />
<p>你的全名是: {{ fullName }}</p>
<p>把你的全名倒过来: {{ reverseName }}</p>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
firstName: "",
lastName: "",
},
computed: {
//计算属性的写法1
//fullName一开始是不存在的,是被计算出来的,计算出来后会被存到vue实例中
fullName: {
//初次读取fullName时,或者所依赖的数据(firstName和lastName)发生变化时,
//get就会被调用,且返回值就作为fullName的值
get() {
return this.firstName + this.lastName;
},
//当fullName被修改时,自动执行set
set(value) {
this.firstName = value[0];
const str_arr = value.split(value[0]);
this.lastName = str_arr[1];
},
},
// 计算属性的 getter 写法2:简写,因为一般只是读取计算属性,不会修改计算属性
reverseName: function () {
//get函数中写业务逻辑处理与计算( 函数里面的`this` 指向 vm 实例 )
let message = this.firstName + this.lastName;
return message.split("").reverse().join("");
},
},
});
</script>
计算属性computed
与methods
对比
与直接调用methods
相比,computed
更快(因为有缓存,直接读取不用重复调用)
数据代理
侦听属性watch
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。使用 watch
选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
<div id="app">
<span>请输入你的姓氏:</span>
<input v-model:value="firstName" placeholder="请输入" />
<span>请输入你的名字:</span>
<input v-model="lastName" placeholder="请输入" />
<p>你的全名:{{fullName}}</p>
<p>把你的全名倒过来: {{ reverseName }}</p>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
function sum(x, y) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let z = x + y;
resolve(z);
}, 1000);
});
}
let vm = new Vue({
el: "#app",
data: {
firstName: "",
lastName: "",
fullName:"",
},
computed: {
reverseName: function () {
let result = this.firstName + this.lastName ;
return result.split("").reverse().join("");
},
},
watch: {
// 如果 `firstName` 发生改变,这个函数就会运行,简写形式:
firstName: async function (newQuestion, oldQuestion) {
let result = await sum(this.firstName, this.lastName);
this.fullName = result;
},
lastName: {
immediate: true, //immediate默认为false,当immediate为true时,在 watch 创建时会自动调用handler函数
//handler函数是 watch 的必选选项,它是一个函数,用来处理数据变化时的逻辑。
handler: async function (newVal, oldVal) {
// console.log(newVal, oldVal);
let result = await sum(this.firstName, this.lastName);
this.fullName = result;
},
},
},
});
</script>
监视多级结构中某个属性的变化
<div id="app">
<span>请输入你的姓名:</span>
<input v-model:value="userInfo.name" placeholder="请输入" />
<span>请输入你的年龄:</span>
<input v-model="userInfo.age" placeholder="请输入" />
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
userInfo: {
name: '张三',
age: 18
}
},
watch: {
//监视userInfo中的name属性是否发生变化
'userInfo.name': function(newVal, oldVal) {
console.log('userInfo.name 发生了变化', newVal, oldVal);
},
'userInfo.age': function(newVal, oldVal) {
console.log('userInfo.age 发生了变化', newVal, oldVal);
},
},
});
</script>
这里需要说明一下,如果我把上面的代码写为如下形式,是监测不到userInfo
内部name
,age
等属性的变化的,因为userInfo
指向一个地址,只有那个地址发生变化时,才会被watch
捕获:
let vm = new Vue({
el: "#app",
data: {
userInfo: {
name: '张三',
age: 18
}
},
watch: {
//监视userInfo地址是否发生变化,但无法监视内部的name属性值是否发生变化
//即监视userInfo的赋值是否被调用,比如:如果执行了userInfo={},则下面的函数将会被执行
'userInfo': function(newVal, oldVal) {
console.log('userInfo.name changed', newVal, oldVal);
}
},
});
如果要监视对象中所有属性的变化,可以使用watch
提供的deep
选项,具体请参考下面深度监视知识点。
深度监视,deep
选项(监视多级结构中所有属性的变化)
watch
中的 deep
选项,功能:递归监听对象内部属性的变化。
比如:
<div id="app">
<span>请输入你的姓名:</span>
<input v-model:value="userInfo.name" placeholder="请输入" />
<span>请输入你的年龄:</span>
<input v-model="userInfo.age" placeholder="请输入" />
<button @click="changeAge">年龄增加一岁</button>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
userInfo: {
name: "张三",
age: 18,
},
},
methods: {
changeAge() {
this.userInfo.age++;
},
},
watch: {
userInfo: {
deep: true,
handler(newVal, oldVal) {
//但是使用深度监视时,你有可能无法在handler内部捕获userInfo变化前的值
if (!oldVal && !newVal) {
} else if (!oldVal) {
console.log(newVal.age);
} else {
console.log(newVal.age, oldVal.age);
}
},
},
},
});
</script>
计算属性computed
与侦听属性watch
对比
一个通俗易懂的解释:一个参数随着不同的值改变就用computed
,return出来是动态的。watch
就是监听某个某个值发生变化了。
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch
——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch
回调。除非开销特别大的情况下,请使用watch
异步的完成任务,否则请使用computed
即可。
过滤器filters
filters
可以用来定义一些简单的、可复用的文本格式化函数。filters
一般用于两个地方:双花括号插值和 v-bind
表达式 。
比如:
<div id="app">
<span>请输入你的姓名:</span>
<input v-model:value="fullName" placeholder="请输入" />
<p>显示你的姓名,但是只显示前三个字,并把其中字母转换为大写:{{ fullName | capitalize | upperCase }}</p>
<p>把你的全名倒过来输出,只显示名字后三个字: {{ reverseName | capitalize }}</p>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
fullName: "",
},
computed: {
reverseName: function () {
if (!this.fullName) return ""
return this.fullName.split("").reverse().join("");
},
},
filters: {
capitalize: function (value) {
if (!value) return "";
value = value.toString();
return value.slice(0, 3);
},
upperCase(value) {
return value.toUpperCase();
},
},
});
</script>
过滤器可以串联:
{{ message | filterA | filterB }}
在这个例子中,filterA
被定义为接收单个参数的过滤器函数,表达式 message
的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB
,将 filterA
的结果传递到 filterB
中。
过滤器是 JavaScript 函数,因此可以接收参数:
{{ message | filterA('arg1', arg2) }}
这里,filterA
被定义为接收三个参数的过滤器函数。其中 message
的值作为第一个参数,普通字符串 'arg1'
作为第二个参数,表达式 arg2
的值作为第三个参数。
过滤器可以全局定义
// 第一个参数“upperCase”是指过滤器的名称,第二个参数是过滤器的功能
Vue.filter('upperCase', function (value) {
if (!value) return ''
value = value.toString()
return value.toUpperCase()
})
过滤器与计算属性区别
过滤器没有缓存,计算属性有缓存
如果需要在组件中处理数据,并且想要缓存计算结果以供多次使用,就应该使用computed属性。而如果需要格式化数据,如将数据转换为大写或将数据转换为货币格式,可以使用filters。
Vue组件
使用组件的注意事项
组件中的data必须是函数
组件命名以及组件标签写法
在使用非单文件组件时,给组件命名时不能使用大写字母。
在使用单文件组件时,推荐的命名方法有两种:
驼峰命名法(camelCased)
注:VUE官方默认用大驼峰
比如:下面是App.vue文件(Vue根组件)
<template>
<div id="app">
<componentA/>
<ComponentB/>
</div>
</template>
<script>
//引入组件
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
export default {
name: 'App',
//在当前组件中注册组件
components: {
'componentA': ComponentA,//小驼峰命名
ComponentB,//大驼峰组件命名
},
}
</script>
短横线命名(kebab-case)
即:每个单词之间用“-”隔开,但单词大小写随意
比如:下面是main.js文件(js入口文件)
import Vue from 'vue'
import App from './App.vue'
import buttonCounter from './components/allComponent.vue'
Vue.config.productionTip = false
//全局注册组件,以后在任何<template>中都可以直接使用
Vue.component('button-counter', buttonCounter)
new Vue({
render: h => h(App),
}).$mount('#app')
非单文件组件
创建组件Vue.extend(options)
注意:该函数不能在没有模版编译器的运行版Vue.js中使用,想要使用它必须使用完全版Vue.js,或者把它交给render()
函数
data
选项是特例,需要注意 - 在Vue.extend()
中它必须是函数
// 创建组件方式一(简写方式)
var ComponentA = {
data() {
return {
count: 0
}
},
template: '<button @click="count++">我是组件A,你点了我 {{ count }} 次.</button>'
}
// 创建组件方式二
var B =Vue.extend({
//强制命名
name="component-b",
data() {
return {
count: 0
}
},
template: '<button @click="count++">我是组件B,你点了我 {{ count }} 次.</button>'
})
在vue实例上全局注册组件:
全局注册的组件在哪都可以直接使用。
注意:非单文件组件命名时不能使用大写字母。
<div id="app">
<!-- 把组件挂载到vm实例上,一次挂载三个 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
<!-- 再挂一个其他组件 -->
<component-b></component-b>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
//创建组件
var B = Vue.extend({
//强制命名,之后不能再自定义命名
name:"component-b",
data() {
return {
count: 0,
};
},
template:
'<button @click="count++">我是组件B,你点了我 {{ count }} 次.</button>',
});
//全局注册组件
Vue.component("component-b", B);//这里必须用强制命名才能注册成功
//全局注册组件
Vue.component("button-counter", {
data: function () {
return {
count: 0,
};
},
template: '<button @click="count++">你点了我 {{ count }} 次.</button>',
});
new Vue({}).$mount("#app");
</script>
注册组件方式二:components
局部注册
注意:非单文件组件命名时不能使用大写字母。
<div id="app">
<!-- 把组件挂载到vm实例上,挂载两个A,一个B -->
<component-a></component-a>
<component-a></component-a>
<component-b></component-b>
</div>
<script type="module">
// import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false; //可以阻止 vue 在启动时生成生产提示
//组件A
var ComponentA = {
data: function () {
return {
count: 0
}
},
template: '<button @click="count++">我是组件A,你点了我 {{ count }} 次.</button>'
}
//创建组件B
var ComponentB = Vue.extend({
data: function () {
return {
count: 0
}
},
template: '<button @click="count++">我是组件B,你点了我 {{ count }} 次.</button>'
})
new Vue({
//注册组件
components: {
'component-a': ComponentA,//组件命名
'component-b': ComponentB,//组件命名
},
}).$mount('#app');
</script>
组件构造函数VueComponent(options)
与Vue.extend(options)
Vue.extend(options)
函数返回的是一个VueComponent(options)
函数
当我们把Vue.extend(options)
创建的VueComponent(options)
函数注册到Vue实例时,Vue实例解析到VueComponent(options)
函数后,会根据里面的内容来帮我们渲染页面,也就是说Vue会执行它 :new VueComponent(options)
。
每次调用Vue.extend(options)
返回的都是一个全新的VueComponent(options)
Vue.extend(options)
中的options
对象中的this
都是指向VueComponent实例对象。
new Vue(options)
中的options
对象中的this
都是指向Vue实例对象。
VueComponent实例对象,以后简称为vc实例对象。(也可以称为:组件实例对象)
Vue实例对象,以后简称vm。
Vue原型链
图示
VueComponent.prototype.proto===Vue.prototype
这个关系让组件实例对象(vc)可以访问到Vue原型上的属性、方法
单文件组件
用vue-cli创建环境
略
render
渲染函数与vue运行版
render函数
首先中main.js入口文件中引入vue,并创建vue实例,并用render渲染页面
render()接收一个组件模版函数,然后递归的去渲染其中的内容
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render(createElement){
return createElement(App)
}
}).$mount('#app')
vue.js与vue.runtime.xxx.js的区别
vue.js是完整版的vue。包含:核心功能+模版解析器
vue.runtime.xxx.js是运行版的vue,只包含核心功能,没有模版解析器,所以不能使用template
配置项,需要使用render()函数接收到的createElement函数去渲染组件中的内容。
而vue文件中的内容是由webpack构建工具中的vue-template-compiler插件来解析的。有点像webpack中的加载器。
scoped
局部样式
从这里开始所有例子将会在vscode中同步编写!!!
首先我们需要知道每个vue单文件组件中的<style>
并不仅仅作用于当前vue组件中,而是作用于所有vue组件。因为最后所有vue组件都会被webpack编译成一个html文件,所以所有的vue单文件组件中的<style>
内容都会被整合为一个html文件中的<style>
标签。所以这可能会导致css样式冲突,后引入的样式会覆盖之前引入的样式,比如如下两个组件就是冲突的例子:
组件A:ComponentA.vue
<template>
<!-- 组件A和组件B定义的class名字相同,导致冲突 -->
<div class="demo">
<button @click="count++">我是组件A,你点了我 {{ count }} 次.</button>
</div>
</template>
<script>
export default {
data: function () {
return {
count: 0
}
},
}
</script>
<style>
.demo{
background-color: orange;/* 橘黄色 */
}
</style>
组件B:ComponentB.vue
<template>
<!-- 组件A和组件B定义的class名字相同,导致冲突 -->
<div class="demo">
<button @click="count++">我是组件B,你点了我 {{ count }} 次.</button>
</div>
</template>
<script>
export default {
data: function () {
return {
count: 0
}
},
}
</script>
<style>
.demo{
background-color: skyblue;/* 天蓝色 */
}
</style>
根组件:App.vue
<template>
<div id="app">
<ComponentA />
<ComponentB />
</div>
</template>
<script>
import ComponentB from './components/ComponentB.vue'//组件B是天蓝色,但是最后显示为橘黄色(被组件A覆盖)
import ComponentA from './components/ComponentA.vue'//组件A是橘黄色
//因为组件A在组件B之后引入,所以组件A的颜色覆盖了组件B的颜色,所以最终颜色为橘黄色。与组件的注册顺序、使用顺序无关。
export default {
name: 'App',
components: {
ComponentA,
ComponentB
}
}
</script>
<style></style>
如果在<style>
标签上加一个scoped
就可以解决这个冲突,比如:
组件A:ComponentA.vue
<template>
<!-- 组件A和组件B定义的class名字相同 -->
<div class="demo">
<button @click="count++">我是组件A,你点了我 {{ count }} 次.</button>
</div>
</template>
<script>
export default {
data: function () {
return {
count: 0
}
},
}
</script>
<style scoped>
/* 因为使用了scoped,所以组件A与组件B中的样式不会发生冲突 */
.demo{
background-color: orange;/* 橘黄色 */
}
</style>
组件B:ComponentB.vue
<template>
<!-- 组件A和组件B定义的class名字相同 -->
<div class="demo">
<button @click="count++">我是组件B,你点了我 {{ count }} 次.</button>
</div>
</template>
<script>
export default {
data: function () {
return {
count: 0
}
},
}
</script>
<style scoped>
/* 因为使用了scoped,所以组件A与组件B中的样式不会发生冲突 */
.demo{
background-color: skyblue;/* 天蓝色 */
}
</style>
根组件:App.vue
<template>
<div id="app">
<!-- 显示橘黄色 -->
<ComponentA />
<!-- 显示天蓝色 -->
<ComponentB />
</div>
</template>
<script>
import ComponentB from './components/ComponentB.vue'//组件B是天蓝色
import ComponentA from './components/ComponentA.vue'//组件A是橘黄色
export default {
name: 'App',
components: {
ComponentA,
ComponentB
}
}
</script>
<style></style>
其原理是:这个 scoped
会自动为组件中的class命名后添加一个唯一的 随机值 (比如 data-v-21e5b78
) 组成一个新的class名字:demo[data-v-21e5b78]
,这个随机值为组件内的 CSS 指定作用域,webpack编译vue文件的时候 会把 .demo
编译成类似 .demo[data-v-21e5b78]
的形式,从而实现在vue单文件组件中局部指定css样式。
在根组件使用全局样式
根据上面的分析,我们知道,在任意一个vue单文件组件中使用<style>
标签,但不使用scoped
时,该<style>
标签中的所有样式,在其他任何组件中都可以访问到,不论这些组件是它的父组件还是子组件。所以平时我们使用<style>
标签时,最好同时使用scoped
。但是也有例外,比如我们想全局定义一些样式时,我们约定在根组件中不使用scoped
只使用<style>
标签。
在根组件中定义的css样式,在任何其他子组件中都可以使用,不用重复定义。比如:
根组件:App.vue
<template>
<div id="app">
<ComponentA />
<ComponentB />
</div>
</template>
<script>
import ComponentB from './components/ComponentB.vue'//组件B是天蓝色
import ComponentA from './components/ComponentA.vue'//组件A是橘黄色,字为红色
export default {
name: 'App',
components: {
ComponentA,
ComponentB
}
}
</script>
<style >
/* 定义全局样式title */
/* 该处不能使用scoped,使用了之后该样式就不是全局的了,其他组件将不能使用该样式 */
.title{
color:red;
}
</style>
组件A:ComponentA.vue
<template>
<!-- 组件A和组件B定义的class名字相同 -->
<div class="demo">
<!-- 在这里使用根组件中定义的全局样式class="title",该按钮的字的颜色变为红色 -->
<button @click="count++" class="title">我是组件A,你点了我 {{ count }} 次.</button>
</div>
</template>
<script>
export default {
data: function () {
return {
count: 0
}
},
}
</script>
<style scoped>
/* 因为使用了scoped,所以组件A与组件B中的样式不会发生冲突 */
.demo{
background-color: orange;/* 橘黄色 */
}
</style>
组件B:ComponentB.vue
<template>
<!-- 组件A和组件B定义的class名字相同 -->
<div class="demo">
<button @click="count++">我是组件B,你点了我 {{ count }} 次.</button>
</div>
</template>
<script>
export default {
data: function () {
return {
count: 0
}
},
}
</script>
<style scoped>
/* 因为使用了scoped,所以组件A与组件B中的样式不会发生冲突 */
.demo{
background-color: skyblue;/* 天蓝色 */
}
</style>
ref
获取虚拟DOM
在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs
对象上。
可以认为是组件与元素的唯一标识,虚拟DOM唯一标识,用于获取虚拟DOM。
如果在普通的 DOM 元素上使用ref
,则$refs
引用指向的就是 DOM 元素;如果ref
用在子组件上,$refs
引用就指向组件实例。如下所示:
<template>
<div id="app">
<h1 ref="h1Id">{{info.title}}</h1>
<componentB ref="componentBId"/>
<button @click="showDom" ref="buttonId">点我显示该组件下所有ref注册的Dom信息</button>
</div>
</template>
<script>
//引入组件A
import ComponentB from './components/ComponentB.vue'
export default {
name: 'App',
data(){
return {
info:{
title:"哈哈哈这是h1标签",
}
}
},
methods:{
showDom(){
console.log(this.$refs.h1Id)//输出元素h1
console.log(this.$refs.buttonId)//输出元素button
console.log(this.$refs.componentBId)//输出:组件B的实例对象VC
},
},
components: { ComponentB,}//大驼峰命名
}
</script>
<style></style>
props
父组件传值给子组件
单向数据流
props
用于父组件传值给子组件,所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
传值例子:
父组件:App.vue文件
<template>
<div id="app">
<ul>
<li v-for="(item,index) in items" :key="item.id">
<!-- 用v-bind传给子组件js变量 -->
<ComponentB :id="item.id" :name="item.name" :age="item.age"/>
</li>
<li>
<!-- 用普通“=”传给子组件字符串,用v-bind传给子组件数字或者布尔值 -->
<ComponentB id="008" name="好好吃" :age="25"/>
</li>
</ul>
</div>
</template>
<script>
//引入组件A
import ComponentB from './components/ComponentB.vue'
export default {
name: 'App',
data(){
return {
items: [
{ id:"001",name: 'wjt' ,age:18},
{ id:"002",name: 'hhc' ,age:20},
{ id:"003",name: 'llz' ,age:17},
{ id:"004",name: 'xjh' ,age:22},
]
}
},
components: { ComponentB,}//大驼峰命名
}
</script>
<style></style>
子组件:ComponentB.vue文件
<template>
<div>
<span>
{{id}}-{{name}}-{{age+1}}
</span>
</div>
</template>
<style lang="less"></style>
<script>
export default {
props:["id","name","age"],//用props数组来接收父组件传递的参数
}
</script>
props
可以传递的数据类型
export default {
props: {
title: String,//字符串
likes: Number,//数字
isPublished: Boolean,//布尔值true,false
commentIds: Array,//数组
author: Object,//对象
callback: Function,//函数
contactsPromise: Promise // 或任何其他构造函数,如Promise
}
}
props
类型限制
子组件可以在接收数据时对数据类型进行检验与限制,如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你,但不会报错。
常用的有:限制类型(type
)、限制必要性(required
)、指定默认值(default
)
限制方法如下:
export default {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字,使用时,required必须为false
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
}
}
}
props
使用方法与注意事项
1、不允许在props
和data
中定义相同的属性,会报错:Duplicated key 'xxx'
2、props
是只读的,vue底层会监视你对props
的操作,如果你修改了props
中的属性值,那么会在控制台输出警告。
如果需要在子组件中修改从父组件传入的数据(props
中的数据),一般建议把props
中的数据换个名字赋值到data
中,然后修改data
中的数据。不建议直接修改父组件传入的数据(props
中的数据),可能会引发bug。
比如:
子组件:ComponentB.vue文件
<template>
<div>
<span>
{{myId}}-{{myName}}-{{myAge}}
</span>
<button @click="increaseByAge">{{myName}}年纪增长</button>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data(){
return{
myId:this.id,
myName:this.name,
myAge:this.age,
}
},
methods: {
increaseByAge(){
this.myAge++
},
},
props:["id","name","age"],//用props数组来接收父组件传递的参数
}
</script>
props
实现子组件传值给父组件
这是一种特殊的用法,通过传递父组件的函数让子组件调用,从而实现在父组件获取子组件信息,一般不推荐使用。而推荐使用this.$emit()
、Vue.emit()
或者Vuex。
<template>
<div id="app">
<h1>显示选中的子组件的姓名:{{msg}}</h1>
<!-- 将父组件函数传递给子组件调用 -->
<ComponentA :getComponentsName="getComponentsName"/>
<ComponentB :getComponentsName="getComponentsName"/>
</div>
</template>
<script>
import ComponentB from './components/ComponentB.vue'
import ComponentA from './components/ComponentA.vue'
export default {
name: 'App',
components: {
ComponentA,
ComponentB
},
data(){
return {
msg:"",
}
},
methods:{
getComponentsName(name){
console.log("父组件在后台输出子组件的name",name)
this.msg=name;//用子组件数据修改父组件数据。
}
}
}
</script>
<style ></style>
组件A:ComponentA.vue
<template>
<div>
<span>
组件A-{{myId}}-{{myName}}-{{myAge}}
</span>
<button @click="increaseByAge">{{myName}}年纪增长</button>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data(){
return{
myId:"001",
myName:"llz",
myAge:20,
}
},
methods: {
increaseByAge(){
this.myAge++
this.getComponentsName(this.myName)//子组件中调用父组件函数,把自己的数据通过函数参数传递给父组件
},
},
props:["getComponentsName"],
}
</script>
组件B: ComponentB.vue
<template>
<div>
<span>
组件B-{{myId}}-{{myName}}-{{myAge}}
</span>
<button @click="increaseByAge">{{myName}}年纪增长</button>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data(){
return{
myId:"002",
myName:"好好吃",
myAge:18,
}
},
methods: {
increaseByAge(){
this.myAge++
this.getComponentsName(this.myName)//子组件中调用父组件函数,把自己的数据通过函数参数传递给父组件
},
},
props:["getComponentsName"],//用props数组来接收父组件传递的参数
}
</script>
mixins混入
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
混入组件中的部分不会覆盖组件中原有的部分,只是把功能或数据添加进组件中,如果组件中已经有了名称相同的功能/数据,触发功能或使用数据时,只会 执行/使用 组件中的 功能/数据,不会执行 执行/使用 混入中的 功能/数据(mixins
中的 功能/数据 )。这样给人的感觉就是组件中的 功能/数据 会覆盖混入中的 功能/数据(mixins
中的 功能/数据 )。
局部混入
例如:在组件A和组件B中使用混入的 功能/数据
主页面:App.vue,使用组件A和组件B
<template>
<div id="app">
<ComponentA />
<ComponentB />
</div>
</template>
<script>
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
export default {
name: 'App',
components: {
ComponentA,
ComponentB
}
}
</script>
<style></style>
混入功能js文件:mixin.js(和App.vue在同一个文件夹)
export const hunhe = {
methods: {
showName() {
console.log(this.name,this.age,this.sex)
}
},
}
export const hunru = {
data(){
return {
name:"hhc",
age:18,
}
}
}
组件A:ComponentA.vue
<template>
<div>
<h1>我是组件A</h1>
<span>
{{ name }}-{{ age }}-{{ sex }}
</span>
<button @click="showName">显示姓名、年龄和性别到后台</button>
</div>
</template>
<style lang="less"></style>
<script>
import {hunhe,hunru} from "../mixin"
export default {
data: function () {
return {
sex: "男"
}
},
// 混入hunhe与hunru两个对象
mixins:[hunru,hunhe]
}
</script>
组件B:ComponentB.vue
<template>
<div>
<h1>我是组件B</h1>
<span>
{{ name }}-{{ age }}-{{ sex }}
</span>
<button @click="showName">显示姓名、年龄和性别到后台</button>
</div>
</template>
<style lang="less"></style>
<script>
import {hunhe,hunru} from "../mixin"
export default {
data: function () {
return {
name: "陆龙忠",
sex: "男"
}
},
mixins:[hunru,hunhe]
}
</script>
全局混入Vue.mixin(object)
例如:在所有组件中使用混入的 功能/数据
主页面:App.vue,使用组件A和组件B
<template>
<div id="app">
<ComponentA/>
<ComponentB/>
</div>
</template>
<script>
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
export default {
name: 'App',
components: {
ComponentA,
ComponentB
}
}
</script>
<style></style>
混入功能js文件:mixin.js(和App.vue在同一个文件夹)
export const hunhe = {
methods: {
showName() {
console.log(this.name,this.age,this.sex)
}
},
}
export const hunru = {
data(){
return {
name:"hhc",
age:18,
}
}
}
在main.js中引入混入功能js文件:
import Vue from 'vue'
import App from './App.vue'
import {hunhe,hunru} from "./mixin"
Vue.config.productionTip = false
//全局混入
Vue.mixin(hunru)
Vue.mixin(hunhe)
new Vue({
render: h => h(App),
}).$mount('#app')
其他组件中将不用引入混入功能js文件:
组件A:ComponentA.vue
<template>
<div>
<h1>我是组件A</h1>
<span>
{{ name }}-{{ age }}-{{ sex }}
</span>
<button @click="showName">显示姓名、年龄和性别到后台</button>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data: function () {
return {
sex: "男"
}
},
}
</script>
组件B:ComponentB.vue
<template>
<div>
<h1>我是组件B</h1>
<span>
{{ name }}-{{ age }}-{{ sex }}
</span>
<button @click="showName">显示姓名、年龄和性别到后台</button>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data: function () {
return {
name: "陆龙忠",
sex: "男"
}
},
methods: {
showName() {
console.log("名字、性别、年龄:",this.name,this.age,this.sex)
}
},
}
</script>
this.$emit(String)
自定义事件
this.$emit(String)
一般是在组件中使用,this指向的是组件实例:vc
使用this.$emit(eventName)
触发自定义事件,使用this.$on(eventName,function())
监听自定义事件(也可以使用指令语法v-on
即:@
)
例:子组件给父组件传值
在父组件(App.vue)中给子组件注册一个自定义事件监听(v-on):
<template>
<div id="app">
<h1>显示选中的子组件的姓名:{{msg}}</h1>
<!-- 用v-on给一个子组件实例对象ComponentA绑定一个名为“getName”的自定义事件 -->
<!-- 自定义事件的触发是由子组件实例对象来控制的 -->
<!-- 自定义事件触发后会调用getComponentsName()函数 -->
<ComponentA @getName="getComponentsName"/>
<!-- 用v-on给一个子组件实例对象ComponentB绑定一个名为“getName”的自定义事件的监听,该事件的监听只执行一次 -->
<ComponentB @getName.once="getComponentsName"/>
</div>
</template>
<script>
import ComponentB from './components/ComponentB.vue'
import ComponentA from './components/ComponentA.vue'
export default {
name: 'App',
components: {
ComponentA,
ComponentB
},
data(){
return {
msg:"",
}
},
methods:{
getComponentsName(name){
console.log("父组件在后台输出子组件的name",name)
this.msg=name;//用子组件数据修改父组件数据。
}
}
}
</script>
<style ></style>
在子组件中规定自定义事件何时触发。
比如下面两个组件的自定义事件的触发条件为点击子组件中的“年纪增长”按钮,然后触发自定义事件将子组件中的值传递给父组件。父组件的v-on监听到自定义事件的发生,开始调用回调函数“getComponentsName(name)”
组件A:ComponentA.vue
<template>
<div>
<span>
组件A-{{myId}}-{{myName}}-{{myAge}}
</span>
<button @click="increaseByAge">{{myName}}年纪增长</button>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data(){
return{
myId:"002",
myName:"好好吃",
myAge:18,
}
},
methods: {
increaseByAge(){
this.myAge++
// 通过this获取组件实例对象,然后通过实例对象this调用$emit("事件名"),触发事件
// 在父组件使用该子组件(ComponentA)时,在“<ComponentA>”标签中使用v-on绑定该事件的回调函数
// 则调用this.$emit("事件名")时,父组件中定义的回调函数就会触发。
this.$emit("getName",this.myName);//后面可以接无数的参数,传递给父组件,但一般只建议传一个参数
},
},
}
</script>
组件B:ComponentB.vue
<template>
<div>
<span>
组件B-{{myId}}-{{myName}}-{{myAge}}
</span>
<button @click="increaseByAge">{{myName}}年纪增长</button>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data(){
return{
myId:"001",
myName:"llz",
myAge:20,
}
},
methods: {
increaseByAge(){
this.myAge++
// 通过this获取组件实例对象,然后通过实例对象this调用$emit("事件名"),触发事件
// 在父组件使用该子组件(ComponentB)时,在“<ComponentB>”标签中使用v-on绑定该事件的回调函数
// 则调用this.$emit("事件名")时,父组件中定义的回调函数就会触发。
this.$emit("getName",this.myName);//后面可以接无数的参数,传递给父组件,但一般只建议传一个参数
},
},
}
</script>
例:自定义事件的灵活监听方法($on使用方法)
在父组件中可以使用mounted()
钩子函数结合$refs
获取子组件实例,然后通过子组件实例对象调用$on()
或者$once()
等方法来注册自定义事件的监听。
注:$on()
和$once()
只能用于组件的自定义事件监听
父组件:App.vue
<template>
<div id="app">
<h1>显示选中的子组件的姓名:{{msg}}</h1>
<ComponentA ref="A"/>
<ComponentB ref="B"/>
</div>
</template>
<script>
import ComponentB from './components/ComponentB.vue'
import ComponentA from './components/ComponentA.vue'
export default {
name: 'App',
components: {
ComponentA,
ComponentB
},
data(){
return {
msg:"",
}
},
methods:{
getComponentsName(name){
console.log("父组件在后台输出子组件的name",name)
this.msg=name;//用子组件数据修改父组件数据。
}
},
mounted(){
//使用钩子函数mounted,当DOM渲染完毕时给组件A绑定一个名为“getName”的自定义事件
this.$refs.A.$on("getName",this.getComponentsName)
setTimeout(()=>{
//当DOM渲染完毕,然后延迟3秒,给B组件绑定一个名为“getName”的自定义事件,且只生效一次
this.$refs.B.$once("getName",this.getComponentsName)
},3000)
}
}
</script>
<style ></style>
例:解绑自定义事件(this.$off([Str,Str]))
父组件:App.vue
组件A:ComponentA.vue
组件B:ComponentB.vue
全局事件总线(组件之间传值)
该方法常用于两个兄弟组件之间传值,当组件数量大于2个时,就不再适用(会导致项目代码可读性变差)。
大于两个组件之间传值一般使用Vuex实现
思路:
- 要使所有其他组件都能访问到该对象,所以要把事件的触发和监听对象注册到一个全局对象上(比如:Vue实例的原型对象
Vue.prototype
中,浏览器window
对象中也可以,因为是全局的,但是不建议用window
,可能有兼容性问题)(不能注册到VueComponent(options)
的原型对象上,因为:每次调用Vue.extend(options)
返回的都是一个全新的VueComponent(options)
,所以其原型对象也是全新的,并不能达到共享全局) - 什么对象拥有拥有触发和监听功能呢?答:组件实例对象
vc
或者Vue实例对象vm
。所以必须是把一个组件实例对象vc
注册到一个全局对象上,或者把Vue实例对象vm
注册到一个全局对象上。
例1:把组件实例对象vc
注册到Vue原型对象Vue.prototype
上,实现兄弟组件之间传值
Vue项目入口文件:main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 创建一个组件实例对象vc
const DemoVc=Vue.extend({})
const vc=new DemoVc()
// 在全局对象上注册该组件
Vue.prototype.$bus=vc
new Vue({
render: h => h(App),
}).$mount('#app')
根组件:App.vue
<template>
<div id="app">
<ComponentA/>
<ComponentB/>
</div>
</template>
<script>
import ComponentB from './components/ComponentB.vue'
import ComponentA from './components/ComponentA.vue'
export default {
name: 'App',
components: {
ComponentA,
ComponentB
},
}
</script>
<style ></style>
组件A:ComponentA.vue ,发送信息给组件B
<template>
<div>
<span>
组件A-{{myId}}-{{myName}}-{{myAge}}
</span>
<button @click="transferName">告诉组件B,组件A中的名字是:{{myName}}</button>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data(){
return{
myId:"001",
myName:"llz",
myAge:20,
}
},
methods: {
transferName(){
this.myAge++
// 通过this获取组件实例对象,然后通过实例对象this访问到$bus,然后调用$emit("事件名"),触发事件
// 在兄弟组件的钩子函数中调用this.$bus.$on("事件名",function)注册事件的监听
// 当调用this.$bus.$emit("事件名")时,兄弟组件中定义的回调函数就会触发。
this.$bus.$emit("getName",this.myName);//后面可以接无数的参数,传递给兄弟组件,但一般只建议传一个参数
},
},
}
</script>
组件B:ComponentB.vue ,接收组件A发出的信息
<template>
<div>
<span>
这是组件B,获取组件A中的姓名为:{{ myName }}
</span>
</div>
</template>
<style lang="less"></style>
<script>
export default {
data(){
return{
myName:"",
}
},
methods: {
showNameComponent(name){
this.myName=name;
}
},
created(){
// 在生命周期钩子中注册事件监听,以监听getName事件是否被触发
// 如果getName事件被触发,则调用showNameComponent()方法
this.$bus.$on("getName",this.showNameComponent)
},
beforeDestroy(){
// 在组件销毁前,需要通过生命周期钩子解除自定义事件的监听
// 因为:即使销毁当前组件,$bus上注册的事件监听还是存在
this.$bus.$off("getName")
}
}
</script>
例2:使用生命周期钩子把Vue实例对象vm
注册到Vue原型对象Vue.prototype
上(最常用)
只需替换例1中的Vue项目入口文件即可:main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
created(){
//通过生命周期钩子,在Vue实例初始化完成后,将Vue实例对象vm注册到Vue原型对象Vue.prototype上
Vue.prototype.$bus=this
}
}).$mount('#app')
插件使用与定义
Vue插件定义
Vue.ues()插件使用
vue与vue组件原型链
绑定样式
自定义指令
VUE生命周期
生命周期简介
通俗地讲,生命周期即Vue实例或组件从创建到被消灭的一系列过程,中间的各个节点被称为钩子,比如vue.js中created方法就是一个生命周期钩子函数一个vue实例被生成后会调用这个函数。
一个vue实例被生成后还要绑定到某个html元素上,之后还要进行编译,然后再插入到document中。每一个阶段都会有一个钩子函数,方便开发者在不同阶段处理不同逻辑。
一般可以在created函数中调用ajax获取页面初始化所需的数据。
要深刻理解生命周期的各个节点,就必须了解浏览器的渲染过程
- 构建DOM树
- 构建css规则树,根据执行顺序解析js文件。
- 构建渲染树Render Tree
- 渲染树布局layout
- 渲染树绘制
created:已创建,在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:已挂载,在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些操作。
通常created使用的次数多,而mounted是在一些插件或组件的使用中进行操作,
比如插件chart.js的使用: var ctx = document.getElementById(ID);
通常会有这一步,而如果你写入组件中,
你会发现在created中无法对chart进行一些初始化配置,
一定要等这个html渲染完后才可以进行,那么mounted就是不二之选。
生命周期 | 是否获取dom节点 | 是否获取data | 是否获取methods |
---|---|---|---|
beforeCreate | 否 | 否 | 否 |
created | 否 | 是 | 是 |
beforeMount | 否 | 是 | 是 |
mounted | 是 | 是 | 是 |
beforeCreate阶段
对浏览器来说,整个渲染流程尚未开始或者说准备开始,对vue来说,实例尚未被初始化,data observer和 event/watcher也还未被调用,在此阶段,对data、methods或文档节点的调用现在无法得到正确的数据。
created阶段
对浏览器来说,渲染整个HTML文档时,dom节点、css规则树与js文件被解析后,但是没有进入被浏览器render过程,上述资源是尚未挂载在页面上,也就是在vue生命周期中对应的created阶段,实例已经被初始化,但是还没有挂载至 $el上,所以我们无法获取到对应的节点,但是此时我们是可以获取到vue中data与methods中的数据的
beforeMount阶段
实际上与created阶段类似,节点尚未挂载,但是依旧可以获取到data与methods中的数据。
mounted阶段
对浏览器来说,已经完成了dom与css规则树的render,并完成对render tree进行了布局,而浏览器收到这一指令,调用渲染器的paint()在屏幕上显示,而对于vue来说,在mounted阶段,vue的template成功挂载在$el中,此时一个完整的页面已经能够显示在浏览器中,所以在这个阶段,即可以调用节点了(关于这一点,在笔者测试中,在mounted方法中打断点然后run,依旧能够在浏览器中看到整体的页面)。
生命周期图示
初始化阶段
created()从后端获取最初始的data数据,注册全局事件总线
通常用于初始化某些属性值,例如data中的数据,然后再渲染成视图。在created中,是无法进行DOM操作的。
模板编译阶段
挂载阶段
mounted()渲染完毕后被调用
通常在初始化页面完成后,对html的dom节点进行需要的操作。mounted可以获取渲染出来的所有属性值。
所以在这里进行动画定时器的开发,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 会被修改透明度的dom元素 -->
<h2 :style="{opacity}">好好吃,颜色会变淡</h2>
</div>
</body>
<script type="module">
import Vue from "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js";
Vue.config.productionTip = false//可以阻止 vue 在启动时生成生产提示
let vm = new Vue({
el: "#app",
data: {
opacity:1,//设置透明度
},
methods: {
},
mounted(){
//创建一个定时器,每隔16秒执行一次
setInterval(()=>{
// 在定时器中修改dom样式,触发vue页面重新渲染
this.opacity-=0.01
if(this.opacity<=0) {
this.opacity =1
console.log(this.opacity)
}
},16)
},
});
</script>
</html>
数据监听与更新阶段(已挂载)
销毁阶段
用于清除定时器、网络请求等还未结束的操作。
用来解除事件监听器、销毁定时器。
解除事件监听器指的是解除组件中的自定义事件的监听器,而不是原生Dom的事件监听器。
vm.
d
e
s
t
r
o
y
(
)
函
数
表
示
销
毁
当
前
组
件
/
V
U
E
实
例
b
e
f
o
r
e
D
e
s
t
r
o
y
(
)
生
命
周
期
函
数
表
示
即
将
销
毁
,
此
时
仍
然
可
以
使
用
子
组
件
的
实
例
、
m
e
t
h
o
d
s
、
w
a
t
c
h
。
到
了
d
e
s
t
r
o
y
e
d
(
)
生
命
周
期
函
数
,
此
时
已
经
被
销
毁
,
无
法
再
使
用
子
组
件
的
实
例
、
m
e
t
h
o
d
s
、
w
a
t
c
h
。
v
m
.
destroy()函数表示销毁当前组件/VUE实例 beforeDestroy()生命周期函数表示即将销毁,此时仍然可以使用子组件的实例、methods、watch。 到了destroyed()生命周期函数,此时已经被销毁,无法再使用子组件的实例、methods、watch。 vm.
destroy()函数表示销毁当前组件/VUE实例beforeDestroy()生命周期函数表示即将销毁,此时仍然可以使用子组件的实例、methods、watch。到了destroyed()生命周期函数,此时已经被销毁,无法再使用子组件的实例、methods、watch。vm.destroy()销毁组件时,已显示的原生Dom不会发生变化,但是原生Dom中的响应式数据无法使用了
vc.$nextTick(function)
https://zhuanlan.zhihu.com/p/174396758
VueRouter路由钩子activated()与deactivated()
这两个钩子要配合VueRouter的<keep-alive>
使用。详情见VueRouter的路由守卫章节
兼容性
Vue不支持IE8版本以下的浏览器
vue2.7
Vue2.7 的正式发布,预示着你在自己的 Vue2 项目中可以使用部分 Vue3 的特性了。
Vue2.7 是 Vue2.x 的最终次要版本。在这个版本之后,Vue2 进入了 LTS(长期支持),从现在开始持续 18 个月,并且将不再接收新功能。这意味着 Vue2 将在 2023 年底结束其生命周期。这应该为大多数生态系统迁移到 Vue3 提供充足的时间。
从vue2.6迁移到vue2.7可能会出现的问题:知乎链接
搭建环境
参考create-vue脚手架中的相关内容