Vue2学习笔记

Vue

(一)Vue简介

  • Vue是一个JavaScript框架,有其独特的使用规则,按照其规则使用,可以事半功倍
  • 它可以大大简化Dom操作
  • 具有响应式数据驱动
  • 官方文档 官方文档

一.Vue特点

  1. 采用组件化模式,提高代码复用率、且让代码更好维护(组件.vue==HTML+CSS+JS)
  2. 声明式编码,让编码人员无需直接操作DOM,提高开发效率
  3. 使用虚拟DOM+优秀的Diff算法,尽量复用DOM结点

(二)Vue核心基础

一、Vue基础

1.导入(安装)

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

2.el:挂载点

<div id="app">
  {
   
   { message }}
</div>
//第一种写法
var app = new Vue({
    
    
  el: '#app',  //el:挂载点
  data: {
    
      // data:数据对象
    message: 'Hello Vue!'
  }
})

//第二种写法
var app = new Vue({
    
    
  data: {
    
      // data:数据对象
    message: 'Hello Vue!'
  }
})
app.$mount('#app');  //挂载

挂载点 :选择HTML元素,Vue会管理该元素及其后代元素(不要挂载和标签)

注意:挂载只能一一对应,不能一对多,也不能多对一

3.data:数据对象

可以嵌套对象,数组等复杂数据,语法与JS语法一致

//data对象写法
data:{
    
    
    massage:'hello!'
}

//data函数写法
//注意!组件中必须用这种写法
data(){
    
    
    return{
    
    
        massage:'hello!'
    }
}
//注意!凡是由Vue管理的函数只能定义为普通函数,不能是箭头函数!!!

4.MVVM模型

  • M:模型(Model):对应data中的数据
  • V:视图(View):DOM
  • VM:视图模型(ViewModel):Vue实例对象
  • data中所有的属性最终都会出现在VM上(数据代理)

5.Object.defineProperty()回顾

let test={
    
    
    name:'test'
}

let a=18;

//为对象添加属性,第三个参数为配置项对象
Object.defineProperty(test,'age',{
    
    
    value:18,
    enumerable:true,  //控制属性是否可以枚举,默认为false
    writable:true,  //控制属性是否可以被修改,默认false
    configurable:true,  //控制属性是否可以被删除,默认false

    //当有人读取test的age属性时,get函数(getter)会被调用,而且返回值就是age的值
    get(){
    
    
        console.log('有人读取了age属性');
        return a;
    },

    //当有人修改test的age属性时,set函数(setter)会被调用,而且会收到修改的具体值
    set(value){
    
    
        console.log('有人修改了age属性,且值是'+value);
        a=value;
    }
})

6.数据代理(重点难点)

数据代理:通过一个对象代理另一个对象中属性的操作(读/写)

  • 数据代理的好处:更加方便的操作data中的数据
  • 基表原理:
    • 通过Object.defineProperty()将data对象中的所有属性添加到VM上
    • 为每一个添加到VM上的属性定义一个getter和setter
    • 在getter和setter内部去操作(读/写)data中对应属性

二、Vue指令

1.v-text

v-text : 设置标签的文本值(textContent)

<div id="app">
    <h1 v-text="massage+'!!!!'"></h1>
    <h1 v-text="massage+inf"></h1>
    <h1> {
   
   {massage}}啊!{
   
   {inf}}</h1>
</div>
var app=new Vue({
    
    
    el:'#app',
    data:{
    
    
        massage:'你好',
        inf:'世界'
    }
})

2.v-html

v-html : 设置标签的innerHTML

<div id="app">
    <h1 v-text="link"></h1>
    <h1 v-html="link"></h1>
</div>
var app=new Vue({
    
    
    el:'#app',
    data:{
    
    
        link:'<a href="https://www.baidu.com">百度</a>'
    }
})

注意:v-html存在安全性问题!!!

  • 在网站上动态渲染任何HTML是非常危险的,容易导致XSS攻击
  • 一定要在可信的内容上使用v-html,一定不能用在用户提交的内容上!

3.v-on

3.1 v-on : 为元素绑定事件
//语法
<div id="app">
    <input type="button" value="点我一下" v-on:事件="事件响应函数名">
    <input type="button" value="点我一下" @事件="事件响应函数名">  <!--更简单的写法-->
</div>

//实例
<div id="app">
    <input type="button" value="点我一下" @click="fun">
</div>
var app=new Vue({
    
    
    el:'#app',
    methods:{
    
      //事件响应函数写这里
        fun:function(){
    
    
            alert('你好啊!');
        }
    }
})
3.2 v-on补充:传递自定义参数,事件修饰符
<div id="app">
    <input type="button" value="点我一下" @click="fun(name)">
    <!-- $event占位,用于传递事件对象 -->
    <input type="text" @keyup.enter="fun($event,'李四')"> <!-- Vue提供了键盘按键的便捷绑定方式 @keyup.按键名="事件响应函数" -->
</div>
var app=new Vue({
    
    
    el:'#app',
    data:{
    
    
        name:'张三'
    },
    methods:{
    
     
        fun:function(event,name){
    
    
            console.log(event);
            alert('你好啊!'+name);
        }
    }
})

:更多详细用法查看文档!官方文档

3.3 Vue中的事件修饰符
  1. prevent:阻止默认事件(常用)
  2. stop:阻止事件冒泡(常用)
  3. once:事件只触发一次(常用)
  4. capture:使用事件的捕获模式
  5. self:只有event.target是当前操作元素时才触发事件
  6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕
.test5,.test2{
    
    
    width: 500px;
    height: 100px;
    padding: 20px;
    background-color: skyblue;
}
.test41{
    
    
    width: 500px;
    height: 100px;
    background-color: springgreen;
    padding: 20px;
}
.test42{
    
    
    width: 400px;
    height: 50px;
    background-color: skyblue;
}
.test6{
    
    
    width: 300px;
    height: 100px;
    overflow: auto;
}
.test61{
    
    
    width: 200px;
    height: 1000px;
    background-color: skyblue;
}
<div id="root">
    <!-- prevent:阻止默认事件(常用) -->
    <a href="https://www.baidu.com" @click.prevent='test1'>百度一下</a>
    <!-- stop:阻止事件冒泡(常用) -->
    <div class="test2" @click='test2'>
        <button @click.stop='test2'>test2</button>
    </div>
    <!-- once:事件只触发一次(常用) -->
    <button @click.once='test3'>test3</button>
    <!-- capture:使用事件的捕获模式 -->
    <div class="test41" @click.capture="test4('test41')">
        test41
        <div class="test42" @click="test4('test42')">
            test42
        </div>
    </div>
    <!-- self:只有event.target是当前操作元素时才触发事件 -->
    <div class="test5" @click.self='test5'>
        <button @click='test5'>test5</button>
    </div>
    <!-- passive:事件的默认行为立即执行,无需等待事件回调执行完毕 -->
    <div class="test6" @wheel.passive="test6">
        test6
        <div class="test61"></div>
    </div>
</div>
var vm=new Vue({
    
    
    el:'#root',
    data:{
    
    

    },
    methods:{
    
    
        test1(){
    
    
            alert('就不百度一下!');
        },
        test2(){
    
    
            alert('不会冒泡哟!');
        },
        test3(){
    
    
            alert('再点一次我就不会弹出来了!');
        },
        test4(i){
    
    
            alert(i+'捕获到了点击事件!');
        },
        test5(e){
    
    
            alert('你点的是'+e.target);
        },
        test6(){
    
    
            for(var i=0;i<10000000;i++){
    
    
                console.log('test6');
            }
        }
    }
})

:修饰符可以多个连着使用

3.4 键盘事件
  • Vue常用事件别名

    • enter:回车
    • delete:删除/退格
    • esc:退出
    • space:空格
    • tab:换行(特殊,必须配合keydown使用,因为tab默认事件是切换焦点)
    • up:上
    • down:下
    • left:左
    • right:右
  • Vue未提供别名的按键,可以使用按键原始的Key值去绑定,但是注意如果Key值为两个以上单词,转化为-连接命名(例如:CapsLock:caps-lock)

    //获取键值,键码
    //为某一元素绑定按键事件(keydown,keyup都行)
    console.log(event.key,event.keyCode);
    
  • 系统修饰键(用法特殊):ctrl,alt,shift,meta(win键)

    • 配合keyup使用:按下修饰键同时,再按下其他键,随后释放其他键,事件才触发。
    • 配合keydown使用:正常触发事件
  • 可以使用kkeyCode去指定具体按键,但是不推荐!!!

    • 不同键盘同一键键码可能不同
    • 浏览器逐渐不支持
  • Vue.config.keyCodes.自定义键名=键码,可以自定义键名,不推荐!!!

:键指定可以连着写实现Ctrl+y等事件绑定

4.v-show

v-show : 根据表达式真假,切换元素的显示和隐藏(操作样式:display)

<div id="app">
    <img src="" v-show="true"> <!-- 直接布尔值 -->
    <img src="" v-show="isShow"> <!-- 变量 -->
    <img src="" v-show="age>=18"> <!-- 表达式 -->
</div>
var app=new Vue({
    
    
    el:'#app',
    data:{
    
    
        isShow:true,
        age:20
    }
})

5.v-if/v-else-if/v-else

v-if : 根据表达式真假,切换元素显示和隐藏(操作Dom元素,直接将Dom元素移除或添加)

  • 用起来和普通编程语言条件分支语句一样
<div id="app">
    <img src="" v-if="true"> <!-- 直接布尔值 -->
    <img src="" v-if="isShow"> <!-- 变量 -->
    <img src="" v-if="age>=18"> <!-- 表达式 -->
    
    <div v-if="n===1">a</div>
    <div v-else-if="n===2">b</div>
    <div v-else-if="n===3">c</div>
    <div v-else>d</div>
    
    <!-- 需求:条件相同,显示不同内容 -->
    <div v-if="n===1">a</div>
    <div v-if="n===1">b</div>
    <div v-if="n===1">c</div>
    <!-- 解决方案一 会破环原HTML结构,进而影响样式等生效,不推荐使用-->
    <div v-if="n===1">
        <div>a</div>
        <div>b</div>
        <div>c</div>
    </div>
    <!-- 解决方案二 -->
    <template v-if="n===1">
        <div>a</div>
        <div>b</div>
        <div>c</div>
    </template>
    <!-- template只能与v-if一起使用,不能和v-show一起使用 -->
</div>
var app=new Vue({
    
    
    el:'#app',
    data:{
    
    
        isShow:true,
        age:20,
        n:2
    }
})

6.v-bind

v-bind : 设置元素属性(如:src,title,class等)

<div id="app">
    <img v-bind:src="imgsrc"> <!-- v-bind:属性名="属性值" -->
    <img :alt="ingalt+'!!!'"> <!-- 简写为 :属性名="属性值" -->
    <img v-bind:class="isActive?'active':''">
    <img v-bind:class="{active:isActive}">
</div>
var app=new Vue({
    
    
    el:'#app',
    data:{
    
    
        imgsrc:'./01.jpg',
        imgalt:'图片1',
        isActive:false
    }
})
6.1 通过class绑定样式
<div id="root">
    <!-- 字符串写法,适用于:样式的类名不确定,需要动态指定(只有一个样式(类)) -->
    <div :class="test1" @click="change1">test1</div>
    <!-- 数组写法,适用于:样式的个数不确定,名字不确定 -->
    <div class="basic" :class="test2" @click="change2">test2</div>
    <!-- 对象写法,适用于:样式个数确定,名字确定,但是用不用不确定 -->
    <div class="basic" :class="test3" @click="change3">test3</div>
</div>
.class1{
    
    
    width: 200px;
    height: 200px;
    background-color: skyblue;
}
.class2{
    
    
    width: 200px;
    height: 400px;
    background-color: skyblue;
}
.class3{
    
    
    width: 400px;
    height: 200px;
    background-color: skyblue;
}
.class4{
    
    
    width: 200px;
    height: 200px;
    background-color: slateblue;
}
.basic{
    
    
    width: 100px;
    height: 100px;
    background-color: skyblue;
}
.plus1{
    
    
    border: black solid 3px;
}
.plus2{
    
    
    border-radius: 10px;
}
.plus3{
    
    
    color: blue;
}
var vm=new Vue({
    
    
    el:'#root',
    data:{
    
    
        test1:'class1',
        test2:[],
        test3:{
    
    
            plus1:false,
            plus2:false,
            plus3:false,
        }
    },
    methods:{
    
    
        change1(){
    
    
            var classArr=['class1','class2','class3','class4'];
            var index=Math.floor(Math.random()*4);
            console.log(index);
            this.test1=classArr[index];
        },
        change2(){
    
    
            var classArr=['plus1','plus2','plus3'];
            if(this.test2.length<3){
    
    
                this.test2.push(classArr[this.test2.length]);
            }else{
    
    
                for(var i=0;i<3;++i){
    
    
                    this.test2.pop();
                }
            }
        },
        change3(){
    
    
            var a,b,c;
            a=Math.floor(Math.random()*2);
            b=Math.floor(Math.random()*2);
            c=Math.floor(Math.random()*2);
            if(a)this.test3.plus1=!this.test3.plus1;
            if(b)this.test3.plus2=!this.test3.plus2;
            if(c)this.test3.plus3=!this.test3.plus3;
        }
    }
})
6.2 通过style绑定
<div id="root">
    <!-- 对象写法 -->
    <div class="basic" :style="styleObj">test1</div>
    <!-- 数组方法 -->
    <div class="basic" :style="styleArr">test2</div>
</div>
.basic{
    
    
    width: 100px;
    height: 100px;
    background-color: skyblue;
    border-radius: ;
}
var vm=new Vue({
    
    
    el:'#root',
    data:{
    
    
        styleObj:{
    
    
            fontSize:'30px',
            color:'red',
        },
        styleArr:[
            {
    
    
                fontSize:'30px',
                color:'red'
            },
            {
    
    marginTop:'10px'},
            {
    
    
                border:'black solid 2px',
                borderRadius:'10%'
            }
        ]
    },
})

7.v-for

7.1 v-for : 根据数据生成列表结构
  • 可以遍历:数组、对象、字符串(不常用)、指定次数(不常用)
  • 用in或者of都行
<div id="root">
    <!-- 遍历数组 -->
    <h1>学生列表</h1>
    <ul>
        <li v-for="(item,index) in students" :key="index">
            {
   
   {item.id}}:{
   
   {item.name}}-{
   
   {item.age}}
        </li>
    </ul>

    <!-- 遍历对象 -->
    <h1>学生信息</h1>
    <ul>
        <li v-for="(value,key) of student" :key="key">
            {
   
   {key}}:{
   
   {value}}
        </li>
    </ul>

    <!-- 遍历字符串 -->
    <h1>hello</h1>
    <ul>
        <li v-for="(char,index) of str" :key="index">
            {
   
   {index}}:{
   
   {char}}
        </li>
    </ul>

    <!-- 遍历指定次数 -->
    <h1>12345</h1>
    <ul>
        <li v-for="(v,index) in 5" :key="index">
            {
   
   {index}}:{
   
   {v}}
        </li>
    </ul>
</div>
 var vm=new Vue({
    
    
     el:'#root',
     data:{
    
    
         students:[
             {
    
    id:0,name:'张三',age:18},
             {
    
    id:1,name:'李四',age:20},
             {
    
    id:2,name:'王五',age:18},
             {
    
    id:3,name:'赵六',age:19},
         ],
         student:{
    
    
             id:0,
             name:'张三',
             age:18
         },
         str:'hello'
     },
 })
7.2 v-for中的key属性(重点难点)

面试题:react、vue中key有什么作用?(key的原理是什么?)

要点:就是为了提高效率

  • 虚拟DOM中key的作用:key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新的虚拟DOM旧的虚拟DOM的差异比较(diff算法)
  • 比对规则:
    • 旧的虚拟DOM中找到了与新的虚拟DOM相同的key:
      • 若虚拟DOM中内容没变,直接使用之前的真实DOM!(提高效率)
      • 如果虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
    • 旧虚拟DOM中未找到与新的虚拟DOM相同的key:
      • 创建新的真实DOM,随后渲染到页面中
  • v-for用索引值(index)作为key可能会引发的问题(不写key,Vue就会默认这样使用):
    • 如果对数据进行逆序添加、逆序删除等破环顺序的操作:
      • 会产生没有必要的真实DOM更新,效率低
    • 如果结构中还有输入类DOM:
      • 会产生错误DOM更新,效率低,界面还会出错(乱序)
  • 开发中如何选择key
    • 最好使用每条数据的唯一标识作为key
    • 如果不存在对数据进行逆序添加、逆序删除等破环顺序的操作,仅仅用于渲染展示,使用索引(index)也是没有问题的

8.v-model

8.1 v-model : 获取和设置表单元素的值(双向数据绑定)
<div id="app">
    <input type="text" v-model:value="massage"> <!-- 简写如下 -->
    <input type="text" v-model="massage">  <!-- 文本框内容与massage双向绑定(输入值改变,massage值会相应改变) -->
    <h1>{
   
   {massage}}</h1>
</div>
var app=new Vue({
    
    
    el:'#app',
    data:{
    
    
        massage:'你好!'
    }
})
8.2 v-mdel补充
  • 如果是文本框,则v-model绑定的是value值,用户输入的就是value

  • 如果是单选框,责v-model绑定的是value值,必须要给标签配置value

  • 如果是多选框,则:

    • 没有配置value,那么绑定的就是checked(true or false)
    • 配置了value
      • 如果v-modle的初始值是非数组,那么绑定的就是checked(true or false)
      • 如果v-modle的初始值是数组,那么绑定的就是value组成的数组
  • v-model修饰符

    • number:输入字符串转化为有效数字
    • lazy:失去焦点才收集更新数据
    • trim:去掉字符串首尾的空格
  • 实例

    <div id="root">
        <form @submit.prevent="output">
            <!-- v-model.trim 会自动去掉字符串前后空格 -->
            用户名: <input type="text" v-model.trim="userinf.username"> <br><br>
            密码: <input type="password" v-model="userinf.password" autocomplete="on"> <br><br>
            <!-- type="number" 可以限制用户输入只能是数字 -->
            <!-- v-model.number 可以自动将输入的内容转化为数值类型 -->
            年龄: <input type="number" v-model.number="userinf.age"> <br><br>
            性别:
            男<input type="radio" name="gender" value="" v-model="userinf.gender"><input type="radio" name="gender" value="" v-model="userinf.gender"> <br><br>
            班级:
            <select v-model="userinf.class">
                <option value="">请选择班级</option>
                <option value="一班">一班</option>
                <option value="二班">二班</option>
                <option value="三班">三班</option>
            </select> <br><br>
            爱好:
            学习<input type="checkbox" value="学习" v-model="userinf.hobby">
            打游戏<input type="checkbox" value="打游戏" v-model="userinf.hobby">
            开车<input type="checkbox" value="开车" v-model="userinf.hobby"> <br><br>
            <!-- v-model.lazy 失去焦点才会收集更新信息 -->
            其他信息:<textarea v-model.lazy="userinf.other"></textarea> <br><br>
            <input type="checkbox" v-model="userinf.agree">是否同意<a href="#">用户协议</a> <br><br>
            <button>提交</button>
        </form>
    </div>
    
    var vm = new Vue({
          
          
        el: '#root',
        data: {
          
          
            userinf:{
          
          
                username:'',
                password:'',
                age:'',
                class:'',
                hobby:[],
                other:'',
                agree:''
            }
        },
        methods: {
          
          
            output(){
          
          
                console.log(JSON.stringify(this.userinf));
            }
        },
    })
    

9.v-cloak

  • 没有值
  • 是一个特殊的属性,Vue实例创建完毕并接管容器后,会自动删除v-colak属性
  • 使用css配合v-cloak可以解决网速慢时页面展示出未被Vue解析的模板页面的问题
<!DOCTYPE html>
<html lang="ch">
<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>
    <style>
        [v-clock]{
      
      
            display: none;
        }
    </style>
</head>
<body>
    <!-- 整个页面核心结构 -->
    <div id="root">
        <h1 v-cloak>{
   
   {name}}</h1>
    </div>
</body>
<!-- 如果网络卡顿,Vue.js无法在打开页面时立即加载 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    var vm = new Vue({
      
      
        el: '#root',
        data: {
      
      
           name:'Vue'
        },
    })
</script>
</html>

10.v-once

  • v-once所在结点在初次动态渲染后,就会被视为静态内容
  • 以后数据改变不会引起v-once所在结点的更新,可以优化性能
<div id="root">
    <h1 v-once>初始n: {
   
   {n}}</h1>
    <h1 >n: {
   
   {n}}</h1>
    <button @click="n++">n+1</button>
</div>
var vm = new Vue({
    
    
    el: '#root',
    data: {
    
    
        n:1
    },
})

11.v-pre

  • Vue会跳过其所在结点的解析过程
  • 可以利用它跳过:没有用指令,插值等结点,提高性能
<div id="root">
    <h1>欢迎访问该系统!!!!!</h1>
    <h1 >n: {
   
   {n}}</h1>
    <button @click="n++">n+1</button>
</div>
var vm = new Vue({
    
    
    el: '#root',
    data: {
    
    
        n:1
    },
})

三、计算属性(重点难点)

计算属性:根据原有的属性经过加工处理计算出来的属性叫做计算属性(computed)

  • 原理:底层借助了Object.defineproperty方法提供的getter和setter
  • get什么时候执行
    • 初次读取计算属性
    • 当计算属性依赖的数据发生改变时会被再次调用
  • 优势:与methods实现相比,内部有缓存机制(可复用),效率更高
  • 注意:
    • 计算属性最终会被处理为vm的一个属性,直接读取使用即可
    • 计算属性如果需要被修改,则必须使用setter,setter中对应修改计算属性的依赖数据即可
<div id="root">
    性:<input type="text" v-model="firstname"> <br><br>
    名:<input type="text" v-model="lastname"> <br><br>
    性名: <span>{
   
   {name}}</span>
</div>
var vm=new Vue({
    
    
    el:'#root',
    data:{
    
    
        firstname:'张',
        lastname:'三'
    },
    computed:{
    
    
        //这里得写成一个对象,但是Vue会把它解析处理为vm中的属性
        name:{
    
    
            //getter,setter中的this都已经被Vue预先处理为了vm,方便使用
            get(){
    
    
                return this.firstname+'-'+this.lastname;
            },
            set(value){
    
    
                var values=value.split('-');
                this.firstname=values[0];
                this.lastname=values[1];
            }
        }
        //当只需要读取,不需要修改时,可以简写为
        // name(){
    
    
        //     return this.firstname+'-'+this.lastname;
        // }
    }
})

四、侦听(监视)属性

1.侦听(监视)属性

  • 当侦听(监视)属性变化时,handler自动调用,进行相关操作
  • 侦听(监视)属性必须存在,才能被侦听(监视)
<div id="root">
    <h1>你真是个大{
   
   {description}}</h1>
    <button @click='change'>点我</button>
</div>
var vm=new Vue({
    
    
    el:'#root',
    data:{
    
    
        isCool:true
    },
    methods:{
    
    
        change(){
    
    
            this.isCool=!this.isCool;
        }
    },
    computed:{
    
    
        description(){
    
    
            return this.isCool? '帅哥':'聪明';
        }
    },
    watch:{
    
    
        isCool:{
    
    
            immediate:true,  //初始化时让handler调用一次
            // 当被侦听(监视)的值变化时,handler被调用
            handler(newvalue,oldvalue){
    
    
                console.log('isCool被改变了,从'+oldvalue+'改成了'+newvalue);
            }
        },
        //当侦听(监视)时配置项只有handler时,可以简写为
        // isColl(){
    
    
        //     console.log('isCool被改变了,从'+oldvalue+'改成了'+newvalue);
        // }
    }
})

// 另一种写法
// vm.$watch('isCool',{
    
    
//     immediate:true,  //初始化时让handler调用一次
//     // 当被侦听(监视)的值变化时,handler被调用
//     handler(newvalue,oldvalue){
    
    
//         console.log('isCool被改变了,从'+oldvalue+'改成了'+newvalue);
//     }
// })
//当侦听(监视)时配置项只有handler时,可以简写为
// vm.$watch('isCool',function(){
    
    
//     console.log('isCool被改变了,从'+oldvalue+'改成了'+newvalue);
// })

2.深度侦听(监视)

  • Vue中的watch默认不侦听(监视)对象内部值的改变(一层)
  • 配置项deep:true可以侦听(监视)对象内部属性值的改变(多层)
  • Vue自身是可以侦听(监视)对象内部值改变,但是Vue提供的watch默认不可以
  • 使用watch时根据实际使用需求,决定是否采用深度侦听(监视)

3.计算属性(computed)和侦听(监视)属性(watch)的区别

  • computed能完成的功能,watch都可以完成。
  • watch能完成的功能,computed不一定能成功,例如:watch可以进行异步操作
  • 两个都行的用computed更简单
  • 注意!!
    • 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象
    • 所有不被Vue管理的函数(定时器回调函数,ajax回调函数,Promise的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象

4.侦听(监视)原理(重点难点)

侦听(监视):包括Vue对于数据改变,动态更新页面中用到该数据的DOM,以及watch中侦听(监视)数据地秤实现(同样的逻辑)

  • Vue会侦听(监视)data中所有层级的数据

  • 如何侦听(监视)对象中的数据?

    • 通过setter实现侦听(监视),而且要是在创建Vue实例对象时就传入的数据才会被Vue侦听(监视)

    • 对象中后追加的属性,Vue默认不侦听(监视),进而也不会做出响应式处理

    • 如果想让后追加的属性也可以被Vue侦听(监视),并响应式处理,需要使用:

      Vue.set()
      vm.$set()
      
  • 如何侦听(监视)数组中的数据?

    • 通过包裹数组更新元素方法实现,本质上就是两个处理

      • 通过原生对应的方法对数组进行更新
      • 重新解析模板,渲染页面
    • 在Vue中修改某数组中某元素一定要用如下几种方法:

      //这些方法用起来和原生JS中Array对象的这些方法一样,但是实际上底层逻辑不一样,Vue对这些方法做了如上述的包裹处理
      push()
      pop()
      shift()
      unshift()
      splice()
      sort()
      reverse()
      //Vue的set方法
      //注意!! 这两个方法用起来一样,但是都有局限,
      //不能为vm或者vm的根数据对象(即 data等)添加属性!!!
      Vue.set()
      vm.$set()
      
  • 实例

    <div id="root">
        <button @click="student.age++">年龄加一岁</button> <br>
        <button @click.once="addgender">添加性别属性,默认男</button> <br>
        <button @click.once="addaf">在朋友们最前面加一个朋友</button> <br>
        <button @click.once="updateff">修改第一个朋友的名字</button> <br>
        <button @click.once="addh">添加一个爱好</button> <br>
        <button @click.once="updatafh">修改第一个爱好</button> <br>
        <h1>姓名 {
         
         {student.name}}</h1>
        <h2>绰号 {
         
         {student.nickname}}</h2>
        <h3>年龄 {
         
         {student.age}}</h3>
        <h3 v-if="student.gender">性别 {
         
         {student.gender}}</h3>
        <h3>朋友们</h3>
        <ul>
            <li v-for="(f,index) in student.friends" :key="index">
                {
         
         {f.name}}--{
         
         {f.age}}
            </li>
        </ul>
        <h3>爱好</h3>
        <ul>
            <li v-for="(h,index) in student.hobby" :key="index">
                {
         
         {h}}
            </li>
        </ul>
    </div>
    
    var vm = new Vue({
          
          
        el: '#root',
        data: {
          
          
            student: {
          
          
                name: '张三',
                nickname: '法外狂徒',
                age: 20,
                friends: [
                    {
          
           name: '罗老师', age: 18 },
                    {
          
           name: '李四', age: 22 },
                ],
                hobby: ['抽烟', '赌博', '干坏事']
            }
        },
        methods: {
          
          
            addgender() {
          
          
                // 第一种
                // Vue.set(this.student,'gender','男');
                // 第二种
                this.$set(this.student, 'gender', '男');
            },
            addaf() {
          
          
                this.student.friends.unshift({
          
           name: '王五', age: 40 });
            },
            updateff() {
          
          
                this.student.friends[0].name = '王大炮'
            },
            addh() {
          
          
                this.student.hobby.push('开车');
            },
            updatafh() {
          
          
                // 第一种
                // this.student.hobby.splice(0,1,'学习');
                // 第二种
                // Vue.set(this.student.hobby, 0, '学习');
                // 第三种
                this.$set(this.student.hobby, 0, '学习');
            }
        }
    })
    

五、过滤器

过滤器:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑处理)

  • 语法:
    • 注册过滤器:Vue.filter(name,callback)(全局过滤器) ,new Vue({filters:{}})(局部过滤器)
    • 使用过滤器:{ {xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”
  • 备注:
    • 过滤器相对于一个函数,但是使用时不需要调用(即使用())默认会接收需要被过滤的数据作为第一个参数,也可以接收额外的参数
    • 多个过滤器可以串联
    • 过滤器不会改变原始数据,而是对原始数据进行操作,如何产生新的数据
    • 一个中文前端插件库:BootCDN
      • 日期时间处理库(功能强大):moment.js
      • 轻量日期时间处理库:day.js

六、自定义Vue指令

  • 定义语法:

    • 局部指令

      new Vue({
              
              
          directives:{
              
              
              指令名:配置对象
              //配置对象
              bind() //指令与元素成功绑定的时候调用
              inserted() //指令所在元素被插入页面时调用
              update() //指令所在模板被重新解析时调用
          }  //对象形式
          
          //简写
          directives:{
              
              
          	指令名(){
              
              }  //写成一个函数
              //这个函数相对于定义了上述配置项的bind()和update()
      	}
      })
      
    • 全局指令

      Vue.directive(指令名,配置对象)
      Vue.directive(指令名,配置函数)
      
  • 备注

    • 指令定义时不加v-,但是使用的时候需要加v-
    • 指令名如果是多个单词,使用kebab-case命名方式,不用驼峰命名方式。

七、生命周期(重点难点)

  • 生命周期,又叫做生命周期回调函数、生命周期函数、生命周期钩子
  • 生命周期是Vue在关键时刻帮我们调用了一些特殊名称的函数
  • 生命周期函数名称不可更改,但是函数逻辑是由程序员根据需求编写的
  • 生命周期函数中的this指向的是vm或组件实例对象

Vue生命周期

常用的生命周期钩子

  • mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等(初始化操作)
  • beforeDestroy:清除定时器、解除自定义事件绑定、取消订阅消息等(收尾工作)

销毁Vue实例

  • 销毁后借助Vue开发者工具将看不见任何信息
  • 销毁后自定义事件会失效,但是Vue的工作成果(最后一次更新后的真实DOM)会保留,原生DOM的事件依然有效
  • 一般不会在beforeDestroy钩子中操作Vue实例相关数据,因为即使操了,Vue也不会再进行更新等流程

(三)Vue组件化编程

一、简介

  • 模块:向外提供特定功能的JS文件

    • JS文件在实际开发中很多很复杂
    • 可以复用JS,简化JS的编写,提高效率
    • 模块化:当应用的JS都以模块形式来编写,那么这个应用就是一个模块化的应用
  • 组件:用来实现局部(特定)功能效果的代码及依赖数据集合(html/css/js/img/mp3…)

    • 实际开发中一个界面的功能很复杂
    • 复用编码,简化项目编码,便于理清项目结构,提高运行效率
    • 组件化:当应用都是以组件的形式来搭建编写的,那么这个应用就是一个组件化的应用
    • 非单文件组件:一个文件中包含了一个到多个组件
    • 单文件组件:一个文件中只包含一个文件

二、基础

1.非单文件组件

1.1 基础使用
  • 定义组件

    • 使用Vue.extend(options)创建,其中options和创建Vue实例时传入的配置项基基本一样,有如下区别
      • el配置项不能使用,因为最终组件都会被一个Vue实例管理,由Vue(大哥)决定他及他的组件(小弟)为哪个容器服务
      • data必须用函数形式,因为组件为了被复用,用函数才能避免数据存在引用关系
      • 注:组件中用template配置组件结构
  • 注册组件

    • 局部注册:创建Vue实例时的components配置项
    • 全局注册:Vue.component(‘组件名’,组件);
    • 组件名不要与HTML标签名冲突
  • 使用组件

    • 组件标签(类似于HTML标签)
  • 实例

    <div id="root">
        <h1>root</h1>
        <heade></heade>
        <hr>
        <bodyer></bodyer>
        <foote></foote>
    </div>
    <div id="root1">
        <h1>root1</h1>
        <foote></foote>
    </div>
    
    // 定义组件header
    var heade=Vue.extend({
          
          
        template:`
            <div>
            	<h1>欢迎访问{
           
           {name}}系统!</h1>
            </div>
    `,
        data(){
          
          
            return {
          
          
                name:'智慧张三'
            }
        }
    })
    // 定义组件bodyer
    var bodyer=Vue.extend({
          
          
        template:`
            <div>
            	<h1>{
           
           {name}}列表</h1>
            	<ul>
                    <li v-for="(s,index) in students">
                    	{
           
           {s.name}}---{
           
           {s.age}}
                    </li>
                </ul>
            </div>
    `,
        data(){
          
          
            return {
          
          
                name:'学生',
                students:[
                    {
          
          name:'张三',age:18},
                    {
          
          name:'罗老师',age:30},
                ]
            }
        }
    })
    
    var foote=Vue.extend({
          
          
        template:`
            <div>
            	<h2>版本号{
           
           {num}}!</h2>
            </div>
    `,
        data(){
          
          
            return {
          
          
                num:'v1.0.1'
            }
        }
    })
    
    // 注册组件(全局)
    Vue.component('foote',foote)
    
    var vm = new Vue({
          
          
        el: '#root',
        data: {
          
          
    
        },
        // 注册组件(局部)
        components:{
          
          
            heade,
            bodyer
        }
    })
    
    new Vue({
          
          
        el: '#root1',
    })
    
1.2 组件命名等注意事项
  • 组件命名

    • 一个单词
      • 首字母小写
      • 首字母大写
    • 多个单词
      • kebab-case命名法
      • 单词首字母全大写(需要Vue脚手架支持)
    • 注意:
      • 组件命名需要回避HTML标签
      • 可以使用name属性指定组件在Vue开发者工具中的展现名字(组件定义时)
  • 组件标签

    • 第一种写法:双标签,如:<bodyer></bodyer>
    • 第二种写法:自闭合标签,如:<bodyer/>(需要Vue脚手架支持,否则其后的组件不能被渲染)
  • 定义简写

    var heade=Vue.extend(options)
    //简写
    var heade=options;
    
    
    
1.3 关于VueComponent函数
  • 组件本质上是一个名为VueComponent构造函数,而且不需要由我们定义,是Vue.extend生成的
  • 我们在使用组件时,Vue解析到会帮我们创建对应组件的实例对象,即执行new VueComponent(options);
  • 每次调用Vue.extend,返回的都是一个全新的VueComponent!!!
  • 关于this指向
    • 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this都指向VueComponent实例对象
    • new Vue()配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this都指向Vue实例对象
1.4 一个重要的内置关系
  • VueComponent.prototype.__proto__ === Vue.prototype
  • 目的:让组件实例对象也可以访问到Vue原型上的属性、方法

2.单文件组件

(四)脚手架(CLI)的使用

一、初始化脚手架

  • 脚手架是Vue官方提供的标准化开发工具(开发平台)
  • 官方文档
  • 安装步骤
    • npm install -g @vue/cli 全局安装@vue/cli (只需要安装一次)
    • 在你要创建项目的目录下,使用命令创建项目 vue create xxxx
    • 启动项目 npm run serve
    • 备注:
      • 如果出现下载缓慢,请配置npm淘宝镜像: npm config set registry https://registry.npm.taobao.org --global

        npm config set disturl https: //npm.taobao.org/dist --global

      • Vue脚手架隐藏了所有的webpack相关的配置,如想要查看具体的wekpack配置,请执行: vue inspect > output.js

二、关于不同版本Vue

  • vue.js与vue.runtime.xxx.js的区别:
    • vue.js是完整版的vue。包含了核心功能+模板解析器
    • vue.runtime.xxx.js是运行版的vue,只包含了核心功能,没有模板解析器。
  • 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数将收到的createELement函数去指定具体内容。

三、vue.config.js配置文件

  • 使用output.js可以查看到脚手架默认配置。
  • 使用vue.config.js可以对脚手架进行个性化定制,详见:脚手架配置
//单文件组件示例
<template>
    <div>
        <School></School>
        <Student></Student>
    </div>
</template>

<script>
import School from './components/Test1.vue';
import Student from './components/Test2.vue';

export default {
    name:'App',
    components:{
        School,
        Student
    }
}
</script>

<style>

</style>

四、ref属性

  • 用来给元素或者子组件注册引用信息(相当于id)

  • 应用在html标签上获取真实的DOM元素,应用在组件标签上是组件的实例对象(vc)

  • 使用方式

    <template>
        <div>
            <h1 ref="h1">你好</h1>
            <School ref="sch"></School>
            <button ref="but" @click="console">输出refs</button>
            <Student></Student>
        </div>
    </template>
    
    <script>
    import School from './components/Test1.vue';
    import Student from './components/Test2.vue';
    
    export default {
            
            
        name:'App',
        components:{
            
            
            School,
            Student
        },
        methods: {
            
            
            console(){
            
            
                console.log(this.$refs);
            }
        },
    }
    </script>
    

五、配置项props

功能:让组件接收外部传过来的数据

备注:props是只读的,Vue底层会监视你对props的修改,如果进行了修改,就会发出警告

export default {
    
    
    name:'Student',
    data(){
    
    
        return {
    
    
            mas:'你好,我是:'
        }
    },
    // 第一种方式:(只是简单接收)
    // props:['name','gender','age'],
    // 第二种方式:(限制数据类型)
    // props:{
    
    
    //     name:String,
    //     gender:String,
    //     age:Number
    // }
    // 第三种方式:(详细配置)
    props:{
    
    
        name:{
    
    
            type:String,  //类型
            required:true  //必要性
        },
        gender:{
    
    
            type:String,
            default:'男'   //默认值
        },
        age:{
    
    
            type:Number, 
            required:true
        }
    }
}

六、混合(混入)

功能:把某些组件共用的配置提取出来,成为一个混合对象

使用方式

//定义并暴露混合
export const mixin={
    
    
    data(){
    
    
        return {
    
    
            message:'你好啊!!'
        }
    },
    methods: {
    
    
        nihao(){
    
    
            alert("Hello!");
        }
    },
    mounted(){
    
    
        console.log("挂载了组件");
    },
}

//使用混合
//局部使用
import {
    
    mixin} from './mixin';
mixins:[mixin]

//全局使用(在main.js中引入注册混合)
import {
    
    mixin} from './mixin';
Vue.mixin(mixin);

七、插件

功能:增强Vue

本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用所需要的数据

//定义插件
export default {
    
    
    install(Vue){
    
    
        Vue.prototype.test=()=>{
    
    
            alert('你好!');
        }
    }
}

//引入及使用(在main.js中)
import plugin from './plugins';
Vue.use(plugin);

八、scoped样式

作用:让样式只在局部(当前组件生效)其他地方不生效,这样来避免组件间样式冲突

<style scopes></style>

九、web Storage

  • 存储内容大小一般支持5MB左右(不同的浏览器可能不一样)
  • 浏览端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制。
  • 相关API:
    • setItem('key','value');该方法接收一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则直接更新对应的值
    • getItem('key');该方法根据键获取存储中的值,如果没有,返回null
    • removeItem('key');该方法根据键删除存储中的相应的键值对
    • clear();清空存储的信息
  • SessionStorage(会话存储)存储的内容会随着浏览器关闭而消失
  • LocalStorage(本地存储)存储的内容,需要手动清除才会消失

十、组件自定义事件

  • 一种组件间通信方式,子组件===>父组件

  • 绑定与触发事件

    //父组件中
    <!-- 为子组件绑定自定义事件,实现子给父传递数据(v-on/@)-->
    <School @test="getName"></School>  <!-- 普通事件绑定的修饰符仍然可用,如once等 -->
    <!-- 为子组件绑定自定义事件,实现子给父传递数据(ref 更加灵活) -->
    <School ref="school"></School>
    
    //父组件中
    methods: {
          
          
        getName(name){
          
          
            console.log(name+'拿到了');
        }
    },
    mounted(){
          
          
        setTimeout(()=>{
          
          
            this.$refs.school.$on('test',this.getName);  //使用$once方法,事件只触发一次
            //注意:通过该方法绑定事件时,回调建议配置在methods中或者使用箭头函数,否则回调中(this.getName中)的this指向的是触发事件的子组件
        },3000)
    }
    
    //子组件中触发事件
    this.$emit('test',this.name);  
    
  • 解除事件绑定

    //在子组件中
    this.$off('test');  //解除事件test绑定
    this.$off(['test1','test2']);  //解除事件test1,test2的绑定
    this.$off();  //解除所有事件绑定
    
  • 组件绑定原生DOM事件,需要使用native修饰符

十一、全局事件总线(GlobalEventBus)(重点)

  • 一种组件间通信的方式,任意组件间通信(基于组件自定义事件)

  • 安装全局事件总线:(实际上就是造一个中转的组件实例对象,该VC可以被所有其他组件访问到)

    //在main.js的root组件中
    beforeCreate(){
          
          
        Vue.prototype.$bus=this  //$bus就是当前应用的vm,也就作为上述要造的vc
    }
    
  • 使用事件总线

    • 接收数据的组件:给$bus绑定自定义事件,事件的回调在自身

      methods:{
              
              
      	test(data){
              
              }  //回调函数
      }
      mounted(){
              
              
      	this.$bus.$on('事件名',this.test);  //绑定事件
      }
      
    • 发送数据的组件

      this.$bus.$emit('事件名',data);  //触发事件
      
  • 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

十二、消息订阅与发布(pubsub)

  • 一种组件间的通信方式,适用于任意组件间通信。

  • 使用

    • 安装第三方库:(这里用的是pubsub-js,也可以用其他的)npm i pubsub-js

    • 引入:import pubsub from 'pubsub-js'

    • 接收数据的组件中订阅消息,在自身编写回调函数

      methods(){
              
              
          test(data){
              
              }
      }
      mounted(){
              
              
          this.pubid=pubsub.subscribe('消息名',this.test);   //订阅消息
      }
      
    • 提供数据的组件发送消息

      pubsub.publish('消息名',data);
      
    • 最好在beforeDestroy钩子中,用pubsub.unsubscribe(this.pubid)去取消消息订阅

十三、$nextTick

  • this.$nextTick(回调函数)
  • 作用:在下一次DOM更新结束后执行其回调函数。
  • 什么时候用,当改变数据后,要基于更行后的DOM进行某些操作的时候,要在nextTick所指定的回调中执行。

十四、Vue封装的过度与动画

  • 在插入、更新或者移除DOM元素时,会在合适的时候给元素添加样式类名

  • v-enter == v-enter-active ==> v-enter-to v-leave == v-leave-active ==> v-leave-to

  • 使用方法

    • 准备好样式:

      • v-enter:进入的起点
      • v-enter-active:进入的过程中
      • v-enter-to:进入的终点
      • v-leave:离开的起点
      • v-leave-active:离开的过程中
      • v-leave-to:离开的终点
    • 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="test">  <!-- 指定了name属性,需要将类名对应为相应的(例如test-enter) -->
          	<h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    • 备注:如果有多个元素需要动画效果,则需要使用:transition-group,且每个元素都要指定key

    实例一:

    <button @click="change">显示/隐藏</button>
    <transition name="test" appear>
        <div v-show="isShow" class="a">
            <h1>{
         
         {mes}}</h1>
        </div>
    </transition>
    
    <button @click="change">显示/隐藏</button>
    .test-enter-active{
          
          
        animation: test 1s;
    }
    
    .test-leave-active{
          
          
        animation: test 1s reverse;
    }
    @keyframes test{
          
          
        from{
          
          
            transform: translateX(-100%);
        }
        to{
          
          
            transform: translateX(0);
        }
    }
    

    实例二:

    <transition name="test1" appear>
        <div v-show="isShow" class="a">
            <h1>{
         
         {mes}}</h1>
        </div>
    </transition>
    
    <button @click="change">显示/隐藏</button>
    .test1-enter, .test1-leave-to{
          
          
        transform: translateX(-100%);
    }
    
    .test1-leave, .test1-enter-to{
          
          
        transform: translateX(0);
    }
    
    .test1-leave-active, .test1-enter-active{
          
          
        transition: 1s linear;
    }
    

    实例三:

    <button @click="change">显示/隐藏</button>
    <transition-group name="test2" appear>
        <div v-show="isShow" class="a" key="1">
            <h1>{
         
         {mes}}</h1>
        </div>
        <div v-show="!isShow" class="a" key="2">
            <h1>{
         
         {mes}}</h1>
        </div>
    </transition-group>
    
    .test2-enter, .test2-leave-to{
          
          
        transform: translateX(-100%);
    }
    
    .test2-leave, .test2-enter-to{
          
          
        transform: translateX(0);
    }
    
    .test2-leave-active, .test2-enter-active{
          
          
        transition: 1s linear;
    }
    
  • 动画库推荐:animate.css

    • 安装 npm i animate.css
    • 引入 import 'animate.css';
    • 官方文档 animate.css
    <button @click="change">显示/隐藏</button>
    <transition 
                appear
                name="animate__animated animate__bounce"
                enter-active-class="animate__rubberBand"
                leave-active-class="animate__backOutDown"
                >
        <div v-show="isShow" class="a">
            <h1>{
         
         {mes}}</h1>
        </div>
    </transition>
    
  
  ```javascript
  import 'animate.css';
//记得配置这个methods
  change(){
      this.isShow=!this.isShow;
  }

十五、插槽

  • 作用:让父组件可以向子组件中指定位置插入HTML结构,也是一种组件间通信方式 父组件===>子组件

  • 使用

    • 默认插槽

      //父组件
      <template>
          <List title="汽车">
              <img src="" alt="">
          </List>
          <List title="食物">
              <ul>
                  <li v-for="(f,i) in foods" :key="i">{
             
             {f}}</li>
              </ul>
          </List>
          <List title="游戏">
              <!-- <ul>
                  <li v-for="(g,i) in games" :key="i">{
             
             {g}}</li>
              </ul> -->
          </List>
      </template>
      
      <script>
      import List from "./components/Test1.vue";
      export default {
        name: "App",
        components: { List },
        data() {
          return {
            foods: ["海底捞", "烧烤"],
            cars: ["劳斯莱斯", "兰博基尼"],
            games: ["推箱子", "连连看"],
          };
        },
      };
      </script>
      <style>
      .lists,
      .a {
        display: flex;
        justify-content: space-around;
      }
      img {
        width: 100%;
      }
      </style>
      
      //子组件
      <template>
          <div class="list">
              <h1>{
             
             {title}} 列表</h1>
              <slot>没有内容了。。。</slot>
          </div>
      </template>
      
    • 具名插槽

      //父组件
      <template>
          <div class="lists">
              <List title="汽车">
                  <a slot="test" href="#">更多</a>
              </List>
              <List title="食物">
                  <div slot="test" class="a">
                      <a href="#">中餐</a>
                      <a href="#">西餐</a>
                  </div>
              </List>
              <List title="游戏">
                  <!-- 在用template标签时可以这样用,也可以slot="test" -->
                  <template v-slot:test>   
                      <div class="a">
                          <a href="#">单机</a>
                          <a href="#">联机</a>
                      </div>
                      <h2>快来游玩吧!</h2>
                  </template>
              </List>
          </div>
      </template>
      
      //子组件
      <template>
          <div class="list">
              <h1>{
             
             {title}} 列表</h1>
              <slot name="test">没有内容了111。。。</slot>
          </div>
      </template>
      
    • 作用域插槽

      • 数据在子组件中,但是根据数据生成什么样的结构由父组件决定
      //父组件
      <template>
          <div class="lists">
              <List title="手机">
                  <template slot="test2" scope="{phones}">
                      <ul>
                          <li v-for="(p,i) in phones" :key="i">{
             
             {p}}</li>
                      </ul>
                  </template>
              </List>
              <List title="手机">
                  <!-- vue2.6后新的写法 -->
                  <template v-slot:test2="{phones}">
                      <ol>
                          <li v-for="(p,i) in phones" :key="i">{
             
             {p}}</li>
                      </ol>
                  </template>
              </List>
          </div>
      </template>
      
      //子组件
      <template>
          <div class="list">
              <h1>{
             
             {title}} 列表</h1>
              <slot name="test2" :phones="phones">没有内容222</slot>
          </div>
      </template>
      <script>
      export default {
          name:'List',
          data(){
              return {
                  phones:['小米','华为']
              }
          },
          props:['title']
      }
      </script>
      
      <style scoped>
          .list{
              background-color: skyblue;
              width: 200px;
              height: 300px;
          }
          h1{
              text-align: center;
          }
          li{
              background-color: orange;
              font-size: 18px;
              margin-bottom: 8px;
          }
      </style>
      

(五)Vue使用Ajax

一、axios:功能强大的网络请求库

安装 :npm npm上axios地址,网上找都行,到处都是

1.axios基本使用

<div id="app">
    <button @click="get">get</button>
    <br>
    <button @click="post">post</button>
</div>
var app=new Vue({
    
    
    el:'#app',
    methods:{
    
    
        get:function(){
    
    
            axios.get("http://127.0.0.1:3000/get?num=4")  //自己写的测试接口,下同
                .then(
                function(res){
    
    
                    console.log(res);
                },function(err){
    
    
                    console.log(err);
                }
            )
        },
        post:function(){
    
    
            axios.post("http://127.0.0.1:3000/post",{
    
    num:5})
                .then(
                function(res){
    
    
                    console.log(res);
                },function(err){
    
    
                    console.log(err);
                }
            )
        }
    }
})

二、Vue脚手架配置服务器代理

  • 方法一

    // 开启代理服务器(方式一)
    devServer: {
          
          
        proxy: 'http://localhost:5000'
    },
    
    students(){
          
          
        axios.get("http://localhost:8081/students").then(
            (res)=>{
          
          
                console.log(res.data);
            },
            (err)=>{
          
          
                console.log(err.massage);
            }
        )
    }
    
    • 优点:配置简单,请求资源时直接发送给本机服务器(8081)即可。
    • 缺点:不能配置多个代理,不能灵活选择是否让代理转发请求
    • 工作方式:当请求时,优先访问本机服务器,如果本机服务器没有资源,才会转发请求到对应服务器
  • 方法二

    // 开启代理服务器(方式二)
    devServer: {
          
          
        proxy: {
          
          
            '/s': {
          
            //当请求前缀为/s时代理服务器转发请求到target
                target: 'http://localhost:5000',
                pathRewrite:{
          
          '^/s':''},  //代理服务器转发请求时,将前缀/s去掉
                ws: true,  //用于支持websocket
                changeOrigin: true  //用于控制请求头中的Host值(true: localhost:5000 false :localhost:8081)
            },
            '/c': {
          
            //当请求前缀为/s时代理服务器转发请求到target
                target: 'http://localhost:5001',
                pathRewrite:{
          
          '^/c':''},  //代理服务器转发请求时,将前缀/s去掉
                ws: true,  //用于支持websocket
                changeOrigin: true  //用于控制请求头中的Host值(true: localhost:5000 false :localhost:8081)
            }
        }
    }
    
    students(){
          
          
        axios.get("http://localhost:8081/s/students").then(
            (res)=>{
          
          
                console.log(res.data);
            },
            (err)=>{
          
          
                console.log(err.massage);
            }
        )
    },
    cars(){
          
          
        axios.get("http://localhost:8081/c/cars").then(
            (res)=>{
          
          
                console.log(res.data);
            },
            (err)=>{
          
          
                console.log(err.massage);
            }
        )
    }
    
    • 优点:可以配置多个代理,且灵活控制
    • 配置繁琐,请求时需要加前缀

三、vue-resource插件(了解)

  • 安装 npm i vue-resource
  • 使用
    • 引入 import vueResource from 'vue-resource'
    • 使用 Vue.use(vueResource);
    • 使用 this.$http.get(...)

(六)Vuex(重点)

一、Vuex简介

  • 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读写),也是组件间通信方式之一,适用于任意组件间通信

  • Github地址:vuex

  • 原理:

vuex原理图

二、Vuex搭建

  • 安装:npm i vuex

  • 创建文件:src/store/index.js

    // 引入Vue
    import Vue from 'vue'
    // 引入Vuex
    import Vuex from 'vuex'
    // 应用Vuex插件
    Vue.use(Vuex);
    
    // 创建并暴露store
    export default new Vuex.Store({
          
          
        actions:{
          
          },
        mutations:{
          
          },
        state:{
          
          }
    })
    
  • 在main.js中配置store

    // 引入store
    import store from './store'
    
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    Vue.use(vuex);
    
    new Vue({
          
          
      render: h => h(App),
      store
    }).$mount('#app')
    

三、基本使用

  • 初始化数据state、配置actions,mutations

    actions:{
          
          
        //响应组件中的动作
        fun(context,value){
          
          
            //context:上下文,相当于一个minstore,在里面有所有这里可能需要用到的数据和方法
            	//context.dispatch('action中的方法名',value) 可以对数据做多步中间处理
            	//context.state.test可以直接访问到state中的数据,这里不要直接去修改数据,可以读取数据做条件判断等,
            		//如果直接修改数据,vue开发者工具无法捕捉到
            	//context.commit('mutations中的方法名',data) 提交操作
        }
        //业务逻辑全部写在这里
    },
    mutations:{
          
          
        //执行操作
        //这里的方法名约定全大写,方便与actions中的方法进行区分
        FUN(state,value){
          
          
            //state 就是state,这里就对数据进行基本操作(不关心业务逻辑,只关心基本增删改查操作)
        }
    },
    state:{
          
          
        //数据
        test:0
    }
    
  • 组件中读取数据,发起操作

    //读取数据
    $store.state.test
    //发起操作
    $store.dispatch('action中的方法名',data)
    //如果不需要action做中间处理,则直接提交操作
    $store.commit('mutations中的方法名',data)
    

1. getters配置项

  • 概念:当state中的数据需要经过加工后再共享使用的时候,可以用getters加工

  • 用法:

    getters:{
          
          
        bigTest(state){
          
          
            return state.test*10;
        }
    }
    //用法形如组件中的computed计算属性,但是getters可以在组件间复用
    

2.四个map方法

引入import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'

2.1 mapState
  • 用于帮助我们映射state中的数据为computed计算属性

    computed:{
          
          
        //对象写法
        ...mapState({
          
          a:'test1',b:'test2',c:'test3'})
        //数组写法(不能重新命名,生成的计算属性名与state对象中的属性名一致,传递的参数也得与state对象中的属性名一致)
        ...mapState(['test1','test2','test3'])
    }
    
2.2 mapGetters
  • 用于帮助我们映射getters中的数据为computed计算属性

    computed:{
          
          
        //对象写法
        ...mapGetters({
          
          a:'test1',b:'test2',c:'test3'})
        //数组写法(不能重新命名,生成的计算属性名与Getters对象中的属性名一致,传递的参数也得与Getters对象中的属性名一致)
        ...mapGetters(['test1','test2','test3'])
    }
    
2.3 mapActions
  • 用于帮助我们生成与actions对话的方法

    methods:{
          
          
        //对象写法
        ...mapActions({
          
          a:'test1',b:'test2',c:'test3'})
        //生成的函数
        //a(value){
          
          
        //    $store.dispatch('test1',value);
        //}
        //数组写法(不能重新命名,生成的函数名与actions对象中的方法名一致,传递的参数也得与actions对象中的方法名一致)
        ...mapActions(['test1','test2','test3'])  
    }
    
2.4 mapMutations
  • 用于帮助我们生成与mutations对话的方法

    methods:{
          
          
        //对象写法
        ...mapMutations({
          
          a:'TEST1',b:'TEST2'})
        //生成的函数
        //a(value){
          
          
        //    $store.commit('TEST1',value);
        //}
        //数组写法(不能重新命名,生成的函数名与mutations对象中的方法名一致,传递的参数也得与mutations对象中的方法名一致)
        ...mapMutations(['TEST1','TEST2'])  
    }
    

3.模块化+命名空间

  • 目的:让代码更好的维护,让多种数据分类更加明确

  • 使用方法

    • 创建并暴露模块

      //在model1.js中
      export default model1={
              
              
          //开启命名空间
          namespaced:true,
          state:{
              
              },
          actions:{
              
              },
          mutations:{
              
              },
          getters:{
              
              }
      }
      
      //在model2.js中
      export default model2={
              
              
          //开启命名空间
          namespaced:true,
          state:{
              
              },
          actions:{
              
              },
          mutations:{
              
              },
          getters:{
              
              }
      }
      
    • 在store模块中引入并使用其他模块

      //在index.js中
      // 引入Vue
      import Vue from 'vue'
      // 引入Vuex
      import Vuex from 'vuex'
      //引入其他模块
      import model1 from './model1'
      import model2 from './model2'
      // 应用Vuex插件
      Vue.use(Vuex);
      
      // 创建并暴露store
      export default new Vuex.Store({
              
              
          modules:{
              
              
              model1,
              model2
          }
      })
      
    • 读取state中数据的方法(在读取时要以某一种形式指明数据所在的模块)

      //直接读取
      this.$store.state.model1.test1
      //借助mapState读取
      ...mapState('model1',['test1','test2'])
      
    • 读取getters中数据的方法

      //直接读取
      this.$store.getters['model2/test1']
      //借助mapGetters
      ...mapGetters('model2',['test1','test2'])
      
    • 调用dispatch与Actions对话的方法

      //直接对话
      this.$store.dispatch('model1/test1',data)
      //借助mapActions
      ...mapActions('model1',['test1','test2'])
      
    • 调用commit与Mutations对话的方法

      //直接对话
      this.$store.commit('model2/test1',data)
      //借助mapMutations
      ...mapMutations('model2',['TEST1','TEST2'])
      

(七)路由 vue-router(重点)

一、vue-router简介

vue-router是vue的一个插件库,专门用来实现SPA(单页面)应用

1.SPA简介

  • 单页面Web应用(Single page web application,SPA)
  • 整个应用只有一个完整的页面
  • 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
  • 数据通过Ajax请求获取

2.路由

  • 什么是路由
    • 一个路由就是一组映射关系(key-value)
    • key是路径,value可能是function或component
  • 路由分类
    • 后端路由
      • 理解:value是function,用于处理客户端提交的请求
      • 工作过程:服务器收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
    • 前端路由
      • 理解:value是component,用于展示页面内容
      • 工作过程:当浏览器的路径改变的时候,对应的组件就会显示

二、基本使用

  • 安装vue-router npm i vue-router

  • 引入 import VueRouter from 'vue-router'

  • 应用插件 Vue.use(VueRouter);

  • 创建并暴露一个router

    //在src/router/index.js下
    //引入vue-router
    import VueRouter from 'vue-router'
    //引入相关组件
    import Component1 from '../component/Component1'
    import Component2 from '../component/Component2'
    
    //创建并暴露router
    export default router= new VueRouter({
          
          
        routes:[
            {
          
          
                path:'/component1',
                component:Component1
            },
            {
          
          
                path:'/component2',
                component:Component2
            }
        ]
    })
    
  • 实现切换

    <router-link active-class="active" to="/component1">Component1</router-link>
    <!-- active-class指定被选中时的样式,to指定跳转路径 -->
    <!-- router-link 最终呈现出来是一个a标签 -->
    
  • 指定显示位置

    <router-view></router-view>
    
  • 注意!

    • 在开发中,路由组件通常放在src/pages下,一般的组件通常放在src/components下
    • 切换路径进而切换显示的组件时,默认是在销毁、挂载组件
    • 每个组件都有自己的$route属性,里面存储着自己的路由信息
    • 整个应用只有一个router,通过每个路由组件的$router属性访问

三、多级路由

  • 配置路由规则,在一级路由里使用children进行配置

    export default router= new VueRouter({
          
          
        routes:[
            {
          
          
                path:'/component1',
                component:Component1,
                //配置子级路由,可以无限套娃
                children:[
                    {
          
          
                        path:'child1',  //children中的路径不需要写/
                        component:Child1
                    },
                    {
          
          
                        path:'child2',
                        component:Child2
                    }
                ]
            }
        ]
    })
    
  • 跳转(to中的路径需要写完整)

    <router-link to="/component1/child1">Child1</router-link>  
    

四、路由传参

1. query参数

  • 传递参数

    <!-- 字符串写法 -->
    <router-link :to="`/component1/child1/test1?a=${data.a}&b=${data.b}`">Test1</router-link>
    <!-- 对象写法 -->
    <router-link 
    	:to="{
    		path:'/component1/child1/test1',
            query:{
            	a:data.a,
             	b:data.b
            }
    	}"
    >Test1</router-link>
    
  • 接收参数

    this.$route.query.a
    

2.路由命名(name配置项)

  • 使用

    export default router= new VueRouter({
          
          
        routes:[
            ...
                {
          
          
                    name:'c1',  //配置名字
                    path:'xxxx',
                    component:xxxxx
                },
    	]
    })
    
  • 简化跳转

    <!-- 主要是在子级路由跳转时,即路径较长,或者需要传递参数,使用对象写法 -->
    <router-link 
    	:to="{
    		name:'c1',
            query:{
            	...
            }
    	}"
    >Test1</router-link>
    

3.params参数

  • 在配置中声明params参数

    name:'c1'
    path:'xxxx/:a/:b'  //使用占位符声明params参数
    
  • 传递参数

    <!-- 字符串写法 -->
    <router-link :to="`/xxxx/${data.a}/${data.b}`">Test1</router-link>
    <!-- 对象写法 -->
    <router-link 
    	:to="{
    		name:'c1',   //这里必须用name,不能用path!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            query:{
            	a:data.a,
             	b:data.b
            }
    	}"
    >Test1</router-link>
    
  • 读取参数

    this.$route.params.a
    

4.路由的props配置

作用,让路由组件更方便接收到参数

{
    
    
    ...
    //第一种写法:props值为对象,该对象中所有的key-value的组合最终会通过props传给对应组件
    //props:{a:100,b:'hello'}
    //这种写法不能动态赋值
    
    //第二种写法:props值为true,路由收到的所有**params**参数通过props传给对应组件
    //props:true
    //这种方法有局限性,只能处理params参数
    
    //第三种写法:props值为函数,函数返回一个对象,函数的第一个参数是$route,对象中所有的key-value的组合最终会通过props传给对应组件
    props($route){
    
    
        return {
    
    
            a:$router.query.xxx
            b:$router.params.xxx
        }
    }
}

五、<router-link>的replace属性

  • 作用:控制路由跳转时操作浏览器历史记录的模式
  • 浏览器的历史记录有两种写入模式:分别是pushreplace,push是追加记录(压栈),replace是替换,默认是push
  • 开启replace模式:<router-link replace>.....</router-link>

六、编程式路由导航

  • 作用:不需要再借助<router-link>,路由跳转更加灵活

  • 具体方法

    //$router的两个API
    this.$router.push({
          
            //push模式跳转
        name:'xxx',
        query:{
          
          
            ...
        }
    })
    //配置与to的对象写法一样
        
    this.$router.replace({
          
            //replace模式跳转
        name:'xxx',
        params:{
          
          
            ...
        }
    })
    
    //历史记录操作方法,$router的三个API
    this.$router.back() //后退一步
    this.$router.forward() //前进一步
    this.$router.go()  //传递参数,正数前进,负数后退具体的步数
    

七、缓存路由组件

  • 作用:让不展示的路由组件保持挂载,不被销毁

  • 使用

    <keep-alive include="xxx">
    	<router-view></router-view>
    </keep-alive>
    <!-- 
    	keep-alive标签中的内容将会被缓存
    	include是一个配置项,里面值为**组件名**,其中包含的组件才会被缓存,如果不配置include,则keep-alive中的组件都会被缓存
    	include中需要包含多个组件时 :include="['xxx1','xxx2']"
    -->
    

八、activated与deactivated生命周期钩子

  • 作用:路由组件独有,用于捕获路由组件的激活状态
  • activated 路由组件被激活时触发
  • deactivated 路由组件失活时触发

九、路由守卫

1.全局守卫

//在暴露router之前,使用router相应API设置

//全局前置守卫:初始化时执行、每次路由切换前执行(控制访问权限等)
router.beforeEach((to,from,next)=>{
    
    
    //to是即将跳转的路由的相关信息
    //from是跳转前的路由相关信息
    //next是一个函数,调用该函数即跳转路由
})

//全局后置守卫:初始化时执行、每次路由切换后执行(控制页签名称document.title变化等)
router.afterEach((to,from)=>{
    
    
    //to是即将跳转的路由的相关信息
    //from是跳转前的路由相关信息
})

//补充:路由配置中提供程序员存放其他信息(标识信息等)的配置项meta

2.独享守卫

  • 某一个路由特有的
//配置在路由配置中
routes:[
    ...
    {
    
    
        ...
        beforeEnter(to,from,next){
    
    
        	...
        	//这里的三个参数与全局前置守卫一致
    	}
    }
]

3.组件内守卫

  • 组件独享的守卫

    //在组件配置中
    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter(to,from,next){
          
          }
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave(to,from,next){
          
          }
    //这里的三个参数与全局前置守卫一致
    

十、路由器工作模式

  • 对与一个路径来说,hash值就是#及其后面的内容
  • hash值不会包含在HTTP请求中,即has值不会带给服务器
  • hash模式
    • 优点
      • 兼容性较好
    • 缺点
      • 如果以后将地址通过第三方手机app分享,如果app校验严格,则地址会标记为不合法
      • 地址中带着/#/,不美观
  • history模式
    • 优点
      • 地址干净美观
    • 缺点
      • 兼容性略差
      • 应用部署上线时需要后端人员支持,解决刷新页面服务器404问题(本质上是因为刷新时浏览器将路径全部发送给了服务器,请求服务器导致)
  • 补充:Nodejs解决刷新页面服务器404问题方法(借助中间件)
    • 比如:connect-history-api-fallback npm上的connect-history-api-fallback
      • 安装:npm i connect-history-api-fallback
      • 引入:const history = require('connect-history-api-fallback');
      • 使用:app.use(history()); (注意要在配置静态资源前使用)

(八)Vue的常用UI组件库

一、移动端常用UI组件库

1.Vant

2.Cube UI

3.Mint UI

二、PC端常用UI组件库

1.Element UI

2.IView UI

猜你喜欢

转载自blog.csdn.net/m0_45882335/article/details/122514193