Vue 2.0 学习笔记

Vue学习笔记

文章目录

一、数据代理实现

 let app = new Vue({
        el: "#app",
        data: {
            name: '张三',
            sex: "男"
        }

我们new出来的vue对象,他的data对象中一般存放我们需要绑定的值,而这些值我们可以在html中利用 {{name}} 的方式打印出来,他的实现过程如图

在这里插入图片描述

步骤大概如下,我们再data里面存放的数据,vue会将它存放在vue实例的_data属性下,同时利用 Object.defineProperty设置了get和set方法。

在控制台中可以利用===可以看出他们的对象是相同的
app._data.name === app.name
true

那为什么还要在对象外再挂一层对象呢?

其实就是为了编程的方便,在外面挂载了对象,我们在代码里面就可以直接写{{name}}就可以访问到了,而不需要去写{{_data.name}},其实他们的结果是一样的。

二、事件相关

1.事件修饰符

Vue中的事件修饰符:

  • 1.prevent: 阻止默认事件(常用)

  • 2.stop: 阻止事件冒泡(常用)

  • 3.once: 事件只触发一次(常用)

  • 4.capture: 使用事件的捕获模式

  • 5.self: 只有event.target是当前操作元素才触发事件

  • 6.passive: 事件的默认行为立即执行,无需等待事件回调
<!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 src="../js/vue.min.js"></script>
    <style>
        * {
            margin: 20px;
        }

        .wai {
            width: 300px;
            height: 200x;
            background-color: blue;
            display: inline-block;
        }

        .nei {
            width: 100px;
            height: 100px;
            background-color: pink;
        }

        .ulClass{
            display: inline-block;
            width: 80px;
            height: 150px;
            overflow-y: auto;
            background-color: bisque;
        }
        .ulClass>li{

            height: 50px;
            margin: 0px;
        
        }
    </style>
</head>

<body>
    <div>
        Vue中的事件修饰符:
        <li>1.prevent: 阻止默认事件(常用)</li>
        <li>2.stop: 阻止事件冒泡(常用)</li>
        <li>3.once: 事件只触发一次(常用)</li>
        <li>4.capture: 使用事件的捕获模式</li>
        <li>5.self: 只有event.target是当前操作元素才触发事件</li>
        <li>6.passive: 事件的默认行为立即执行,无需等待事件回调</li>
    </div>

    <div id="app">
        <h2>欢迎来到{{name}}学习</h2>
        <div>
            <h3>1.阻止默认事件</h3>
            <a href="../01_数据代理/1.回顾Object.defineProperty方法.html" @click="fun1">一个连接</a><br>
            <a href="../01_数据代理/1.回顾Object.defineProperty方法.html" @click.prevent="fun1">一个连接,加.prevent</a>
            <li>a标签它的默认行为就是点击后,会跳转到href的网址,如果你在Click后面增加Prevent的修饰符,它就会取消去跳转的行为</li>
        </div>

        <div>
            <h3>2.阻止事件冒泡(常用)</h3>
            <div class="wai" @click = "fun('外部')">外部
                <div class="nei" @click = "fun('内部')">内部</div>
            </div>

            <div class="wai" @click = "fun('外部')">外部内部阻止冒泡
                <div class="nei" @click.stop = "fun('内部')">内部,增加stop</div>
            </div>
            <li>stop就是用来阻止事件冒泡,阻止它的事件从内而外传播</li>
        </div>

        <div>
            <h3>3.事件只触发一次(常用)</h3>
            <button @click="fun1">按钮</button><br>
            <button @click.once="fun1">增加了once修饰的按钮</button>
            <li>once会让事件只会触发一次,触发过后就不会再被触发</li>
        </div>

        <div>
            <h3>4.capture: 使用事件的捕获模式</h3>
            <div class="wai" @click = "fun('外部')">外部
                <div class="nei" @click = "fun('内部')">内部</div>
            </div>点击内部 触发内部->外部

            <div class="wai" @click.capture = "fun('外部')">外部使用事件捕获触发
                <div class="nei" @click = "fun('内部')">内部</div>
            </div> 点击内部 触发外部->内部
            <li>事件的触发机制,先是捕获阶段,捕获阶段是从外到内,然后开始事件冒泡,事件冒泡是从内而外,用了capture修饰符可以让绑定的事件在捕获阶段就触发</li>
        </div>

        <div>
            <h3>5.self: 只有event.target是当前操作元素才触发事件</h3>
            <div class="wai" @click = "fun('外部')">外部
                <div class="nei" @click = "fun('内部')">内部</div>
            </div>

            <div class="wai" @click.self = "fun('外部')">外部使用事件捕获触发
                <div class="nei" @click = "fun('内部')">内部</div>
            </div> 
            <li>因为事件冒泡,点击内部也会触发外部,但是触发的时候带着event.target,如果是冒泡到外层,e.target==内层元素,加了self的话,只能当e.target等于自身方法才会触发,也可以阻止事件传播(外层阻止)</li>
        </div>

 

        <div>
            <h3>6.passive: 事件的默认行为立即执行,无需等待事件回调</h3>
            <h4>scroll是给滚动条加事件,wheel是给是给鼠标滚轮加事件</h4>
            <ul @wheel="fun2" class="ulClass">
                <li v-for="item in 10">{{"序号"+item}}</li>
            </ul>
           
            <ul @wheel.passive="fun2" class="ulClass">
                <li v-for="item in 10">{{"序号"+item}}</li>
            </ul>

            <ul @scroll="fun2" class="ulClass">
                <li v-for="item in 10">{{"序号"+item}}</li>
            </ul>
           
            <ul @scroll.passive="fun2" class="ulClass">
                <li v-for="item in 10">{{"序号"+item}}</li>
            </ul>
            <li>我们如果绑定的是滚轮事件的话,因为绑定的函数十分耗时,他会在执行完函数后再执行页面的滚动效果,从而给人非常卡的感觉,
                但如果使用了passive的话,它会优先执行默认行为,不需要等待事件执行完成
            </li>
        </div>


    </div>
</body>
<script type="text/javascript">

    let app = new Vue({
        el: "#app",
        data: {
            name: "事件修饰符"
        },
        methods: {
            fun1() {
                alert("事件被触发")
                console.log("事件被触发");
            },
            fun(msg) {
                alert(msg)
                console.log("事件被触发");
            },
            fun2() {
                for(var i = 0 ;i<100000;i++){
                    console.log(i);
                }
            },

        },
    })
</script>

</html>

2.键盘事件

  1. Vue中的常用按键别名
    1. 回车 => enter
    2. 删除 => delete(捕获删除和空格键)
    3. 退出 => esc
    4. 空格 => space
    5. 换行 => tab(特殊,必须配合其他案件)
    6. 上 => up
    7. 下 => down
    8. 左 => left
    9. 右 => right
  2. Vue未提供别名的按键可以使用按钮原始的key值去绑定,但是要注意多个单词连接 如 caps-lock(短横行命名)e.key是按下的按键名,e.keyCode是按下按键的Code

  3. 系统修饰键(用法特殊):ctrl、alt、shift、meta

    1. 配合keyup使用:按下修饰键同时,再按下其他键,随后释放其他键,事件才会被触发(配合的键不会被输入)

    2. 配合keydown使用,正常触发事件



  4. 也可以使用keyCode去指定具体的按键(不推荐MDN后续废除) @keyup.13
  5. Vue.config.keyCodes.自定义按键名 = 键码,可以定制按键别名,如:

    ​ Vue.config.keyCodes.huiche = 13;

    ​ @keyup.huiche=“fun”

<!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 src="../js/vue.min.js"></script>


</head>

<body>

    <div id="app">
        <h1>{{name}}</h1>
        <input type="text" placeholder="按下回车提示输入" @keyup.huiche="fun" />
        <ol>
            <li>Vue中的常用按键别名
                <ol>
                    <li>回车 => enter</li>
                    <li>删除 => delete(捕获删除和空格键)</li>
                    <li>退出 => esc</li>
                    <li>空格 => space</li>
                    <li>换行 => tab(特殊,必须配合其他案件)</li>
                    <li>上 => up</li>
                    <li>下 => down</li>
                    <li>左 => left</li>
                    <li>右 => right</li>
                </ol>
            </li>

            <li>Vue未提供别名的按键可以使用按钮原始的key值去绑定,但是要注意多个单词连接 如 caps-lock(短横行命名)e.key是按下的按键名,e.keyCode是按下按键的Code</li>
            <li>系统修饰键(用法特殊):ctrl、alt、shift、meta
                <ol>
                    <li>配合keyup使用:按下修饰键同时,再按下其他键,随后释放其他键,事件才会被触发(配合的键不会被输入)</li>
                    <li>配合keydown使用,正常触发事件</li>
                </ol>
            </li>
            <li>也可以使用keyCode去指定具体的按键(不推荐MDN后续废除) @keyup.13</li>

            <li>Vue.config.keyCodes.自定义按键名 = 键码,可以定制按键别名,如:<br>
                Vue.config.keyCodes.huiche = 13;<br>
                @keyup.huiche="fun"
            </li>
        </ol>
    </div>
</body>
<script type="text/javascript">
    Vue.config.keyCodes.huiche = 13;
    let app = new Vue({
        el: "#app",
        data: {
            name: "键盘事件"
        },
        methods: {
            fun(e) {
                console.log(e.key, e.keyCode);
                console.log(e.target.value);
            }

        },
    })
</script>

</html>

小技巧:事件修饰符和键盘事件可以可以连写,如@keyup.ctrl.y(按下ctrl+y才触发)或@click.stop.prevent(阻止默认行为也阻止事件冒泡)

三、计算属性与监视

1.计算属性-computed

  1. 要显示的数据不存在,要通过计算得来。

  2. 在 computed 对象中定义计算属性。

  3. 在页面中使用{{方法名}}来显示计算的结果。

    1. 原理:底层还是借助了Object。defineproperty方法提供getter和setter
    2. get函数什么时候执行
      1. 最开始初始化的时候读取一次
      2. 当依赖的数据发生了改变的时候,会被再次调用
    3. 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
    4. 备注:
      1. 计算属性最后会出现在vm上,直接读取使用即可
      2. 如果计算属性要被修改,那必须写set函数去响应修改,切set中要引起依赖数据发生改动。
    <!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 src="../js/vue.js"></script>
        <style>
    
        </style>
    </head>
    
    <body>
    
        <div id="app">
            <h2>欢迎来到{{name}}学习</h2>
            <div>
                <label>姓:</label>
                <input type="text" v-model="xi">
                <br>
                <label>名:</label>
                <input type="text" v-model="ming">
                <br>
                全名:{{xi+'.'+ming}}
                <br>
                全名使用computed:{{xingming}}
    
            </div>
        </div>
    </body>
    <script type="text/javascript">
    
        let app = new Vue({
            el: "#app",
            data: {
                name: "计算属性computed",
                xi: "",
                ming: ""
            },
            computed: {
                //完整写法
                xingming:{
    
                    get(){
                        return this.xi + '.' + this.ming; 
                    },
                    set(value){
                        const arr = value.split(".");
                        this.xi = arr[0];
                        this.ming = arr[1];
                    }
                }
    
                //简写 如果只考虑属性读取的话可以这样写
                // xingming() {
                //     return this.xi + '.' + this.ming;
                // },
            },
            methods: {
    
            },
        })
    </script>
    
    </html>
    

2.监视-watch

  1. 通过通过 vm 对象的$watch()或 watch 配置来监视指定的属性
  2. 当属性变化时, 回调函数自动调用, 在函数内部进行计算
  3. 默认是只监听一层的,可以配置deep来实现深层监视
<!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 src="../js/vue.js"></script>
    <style>

    </style>
    
</head>

<body>

    <div id="app">
        <h2>欢迎来到{{name}}学习</h2>
        <h3>今天天气很{{ganjue}}</h3>
        <br>
        <h3>(使用watch)今天天气很{{ganjue}}</h3>
        <br>
        <button @click="changeWatch">点击</button>

        <hr>
        <h3>a的值是{{numbers.a}}</h3>
        <button @click="numbers.a++">a的值+1</button>

        <h3>b的值是{{numbers.b}}</h3>
        <button @click="numbers.b++">b的值+1</button>


    </div>
</body>
<script type="text/javascript">

    let app = new Vue({
        el: "#app",
        data: {
            name: "监视属性Watch",
            isHot: true,
            numbers:{
                a:1,
                b:1,
            }
        },
        computed: {
            ganjue() {
                return this.isHot ? "炎热" : "凉爽";
            }
        },
        watch: {
            //完整写法
            // ganjue:{
            //     immediate:true,//在初始化的时候就会调用一次
            //     handler(newD,old){
            //         console.log("ganjue发生了改变",newD,old);
            //     }

            // },
            //简单写法
            // isHot(newD,old){
            //     console.log("isHot发生了改变",old,newD);
            // }
            numbers:{
                deep:true,//深度监视
                handler(newD,old){
                    console.log("numbers发生了改变",newD,old);
                }
            },
            // "numbers.a"(newD,old){
            //     console.log("numbers.a发生了改变",newD,old);
            // }
        },
        methods: {
            changeWatch() {
                this.isHot = !this.isHot;
            }
        },
    })
    //这种方式适合需要后续绑定的情况
    app.$watch("ganjue", {
        immediate: true,//在初始化的时候就会调用一次
        handler(newD, old) {
            console.log("ganjue发生了改变", newD, old);
        }
    })
</script>

</html>

总结:watch看上去比computed要麻烦,但是watch里面可以做一些异步操作

四、条件渲染

1.v-show

v-show后面跟上条件表达式,它控制的是节点的显示和隐藏,是可以有缓存值的,不会移除节点,适合做频繁的显隐藏操作

2.v-if,v-else-if

v-if是真正的显示或移除,适合不频繁操作。

五、循环遍历

1.v-for语法

V-for循环遍历数组时推荐使用of,语法格式为(item,index)

  • item:迭代时不同的数组元素的值
  • index:当前元素的索引

V-for循环遍历对象时推荐使用in,语法格式为(item,name,index)

  • item:迭代时对象的键名键值
  • name:迭代时对象的键名
  • index:当前元素的索引
<!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 src="../js/vue.js"></script>
    <style>

    </style>
</head>

<body>

    <div id="app">
        <h2>欢迎来到{{name}}学习</h2>
        <h2>遍历数组</h2>
        <ul>
            <li v-for = "(item,index) in personList" :key="index">
                {{item.name+' '+item.age}}
            </li>
        </ul>
        <h2>遍历对象</h2>
        <ul>
            <li v-for = "(item,name,index) in car" :key="index">
                {{item+' '+name+' '+index}}
            </li>
        </ul>

        <h2>遍历字符串</h2>
        <ul>
            <li v-for = "(item,index) in 'abcd'" :key="index">
                {{item+' '+index}}
            </li>
        </ul>

        <h2>遍历数字</h2>
        <ul>
            <li v-for = "(item,index) in 5" :key="index">
                {{item+' '+index}}
            </li>
        </ul>
    </div>
</body>
<script type="text/javascript">
    let app = new Vue({
        el: "#app",
        data:{
            name:"v-for",
            personList:[
                {id:1,name:"小明",age:19},
                {id:2,name:"小红",age:20},
                {id:3,name:"小张",age:21},
            ],
            car:{
                name:"奥迪A8",
                price:"1000万",
                remark:"四驱"
            }
        }
    })
</script>

</html>

2.key的作用与原理

  • key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据[新数据]生成[新的虚拟DOM],随后Vue进行[新虚拟DOM]与[旧虚拟DOM]的差异比较
  • 对比规则
  1. 旧虚拟DOM中找到了新虚拟DOM相同的key:

    1. 若虚拟DOM中内容没变,直接使用之前的的真实DOM!
    2. 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面之前的真实DOM.
  2. 旧虚拟DOM未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到页面

  3. 用index作为key可能会引发的问题:

    1. 若对数据进行逆序添加,逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新==>界面效果没有问题,但效率低
    2. 如果结构中还包含输入类DOM,会产生错误DOM更新==>界面有问题
  • key比较原理图

    在这里插入图片描述

  • 开发中如何选择key

    • 最好使用每条数据的唯一标识作为key.比如id,手机号,身份证号,学号等唯一值
    • 如果不存在对数据的逆序添加,逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index是作为key是没有问题的.

六、内置指令

1.v-cloak指令(没有值)

  • 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
  • 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题

2.v-once指令

  • v-once所在的节点初次动态渲染后,就视为静态内容了。
  • 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能

3.v-pre指令

  • 跳过其所在节点的编译过程
  • 可以利用它跳过没有使用指令的语法,没有使用插值语法的节点,加快编译

七、组件

1.为什么要使用组件

vue组件的作用是提高重用性,让代码可复用

2.组件用法

组件需要注册才能使用。组件有全局注册和局部注册两种方式。

2.1局部注册
    const student = Vue.extend({
        template: `
          <div>
          <h2>学生名称:{{ studentName }}</h2>
          <h2>学生年龄:{{ age }}</h2>
          </div>
        `,
        data() {
            /**
             * 使用return的方式是为了重复使用的时候,指向的都是一个全新数据,否则会有依赖关系
             */
            return {
                studentName: "苏州大学",
                age: 18
            }
        }
    })
    
  let app = new Vue({
        el: "#app",
        components:{student}, //局部注册
        data: {
            name: "组件"
        }
    })

2.2全局注册
    //全局注册
    const hello = Vue.extend({
        template: `
          <div>
          <h2>你好啊!{{ Name }}</h2>

          </div>
        `,
        data() {
            /**
             * 使用return的方式是为了重复使用的时候,指向的都是一个全新数据,否则会有依赖关系
             */
            return {
                Name: "奥特曼",

            }
        }
    })
    Vue.component("hello",hello);

注册局部组件,该组件只能再该实例作用域下有效;全局注册组件,在任何Vue实例都可以使用。

**注意:**若局部注册的组件名和全局注册的组件名重复,局部注册的组件会覆盖全局注册的组件

Vue组件的模板在某些情况下会受到HTML的限制,比如

内规定只允许、
等这些表格元素,所以在内直接使用组件是无效的。这种情况下,可以使用特殊的is属性来挂载组件。示例代码:

<div id="app">
    <table>
        <tbody is="my-component"></tbody>
    </table>
</div>
<script>
    Vue.component('my-component', {
        template: '<div>这里是组件的内容</div>'
    });
    var app = new Vue({
        el: '#app'
    })

</script>

tbody在渲染时,会被替换为组件的内容,常见的限制元素还有

    1. 、。

提示:如果使用的是字符串模板,是不受限制的。
除了template选项外,组件中还可以像Vue实例那样使用其他选项,比如data、computed、methods等。但是在使用data时,和实例稍有区别,data必须时函数,然后将数据return出去

3.VueComponent

  1. 组件的本质是一个名为VueComponent的构造函数,并且不是程序员定义的,而是Vue.extend函数生成的。

    并且每次生成的都是不一样的VueComponent的构造函数

  2. 每当我们使用组件标签(写组件标签时,),vue解析到组件标签时,会帮我们使用VueComponent构造函数创建一个VueComponent对象,帮我们执行 new VueComponent(options)

  3. 在组件配置中:
    data函数,methods中配置的函数,watch中配置的函数,computed中配置的函数的this指向的都是VueComponent组件对象。
    在vue实例配置中:
    data函数,methods中配置的函数,watch中配置的函数,computed中配置的函数的this指向的都是vue对象。

  4. 一个重要的内置关系

    VueComponent.prototype.proto===Vue.prototype

    这么做是为了让组件实例对象vc可以访问到Vue原型上的属性方法,已达到复用,因为Vue和VueComponent在很大程度上都是相同的(95%),所以像 m o u n t 和 mount和 mountwatch方法,定义在Vue的原型对象上,然后VueComponent的原型对象的原型对象指向Vue的原型对象,VueComponent和Vue的实例就可以使用同一份方法和属性,而不用写两份一样的。

    在这里插入图片描述

4.ref使用

大家在使用原生JS对DOM进行操作时肯定第一步是需要获取DOM元素的,比如通过id获取document.getElementById(“idName"),或者使用jQuery获取 jQuery对象$("#idName”),vue对此也实现了比较方便的获取操作DOM的用法 — ref属性。

语法:

//在需要ref管理的组件上 增加ref标签
<hello ref="hello"></hello>
<school  ref="school"></school>

//获取对应dom(普通标签获取的是dom)  
//vueComponent(组件上获取的vc对象)
 this.$refs.school

//可以修改子组件的值
this.$refs.school.schoolName = 2

5.prop使用

props是子组件访问父组件数据的唯一接口。

一个组件可以直接在模板里面渲染data里面的数据(双花括号)。

子组件不能直接在模板里面渲染父元素的数据。

如果子组件想要引用父元素的数据,那么就在prop里面声明一个变量(比如a),这个变量就可以引用父元素的数据。然后在模板里渲染这个变量(前面的a),这时候渲染出来的就是父元素里面的数据。

语法:

// 1.在需要传入值的组件上 设置需要传入的prop
<school name="苏州大学" addres="虎丘区" :agenum="agenum"></school>

// 2.在组件中接收对应的prop(三种写法)
//第一种 prop写法 ,简单 无限制
// props: ["name", "addres", "agenum"]

//第二种 prop写法  ,对类型进行限制
// props:{
//     name:String,
//     addres:String,
//     agenum:Number,
// }

//第三种 prop写法  ,对类型进行限制,同时对是否可传,默认值进行限制
props: {
  name: {
    type: String,
    require: true,//必要选项
  },
  agenum: {
    type: Number,
    default: 0,//默认值
  },
  addres: {
    type: String,
    required: true
  }
}
单向数据流: props是单向绑定的

当父组件的属性变化时,将传导给子组件,但是反过来不会。

每次父组件更新时,子组件的所有 prop 都会更新为最新值。

不要在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

在两种情况下,我们很容易忍不住想去修改 prop 中数据:

  1. Prop 作为初始值传入后,子组件想把它当作局部数据来用;
  2. Prop 作为原始数据传入,由子组件处理成其它数据输出。

对这两种情况,正确的应对方式是:
1.定义一个局部变量,并用 prop 的值初始化它:(解决子组件想用数据)

data() {
  return {
    //prop的值最好是不修改的,可以在data中利用其他名称获取这个值
    schoolName: this.name,
    address: this.addres,
  }
},
props: ["name", "addres", "agenum"]

2.定义一个计算属性,处理 prop 的值返回:(解决传入数据,处理后输出)

computed: {
  age() {
    return this["agenum"];
  }
},

6.$parent

$parent 也可以用来访问父组件的数据。

而且子组件可以通过$parent 来直接修改父组件的数据,不会报错!

可以使用props的时候,尽量使用props显式地传递数据(可以很清楚很快速地看出子组件引用了父组件的哪些数据)。

另外在一方面,直接在子组件中修改父组件的数据是很糟糕的做法,props单向数据流就没有这种顾虑了。

八、mixins(混入)

1.为什么要使用mixin

Mixin提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项(如data、methods、mounted等等)。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项

2.mixin用法

2.1定义一个mixin

export default   {
    data(){
        return {
            mxData1:"mixin"
        }
    },
    methods:{
        funMixin(){
            console.log('@@Mixins')
        }
    },
    created() {
        this.funMixin()
    }
}

2.2 在需要引入的vue组件中引用 (局部引入)

import Mixin from "@/mixins/mixin.js"

export default {
    name: "PropComp",
    mixins:[Mixin],
}

2.3 全局混入

混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。

在**main.js**中通过Vue.mixin()引入混入对象即可全局使用(作用于该Vue实例下的所有组件)

import Mixin from "@/mixins/mixin";
Vue.mixin(Mixin)

注意:同名的生命周期钩子进行了合并,合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

3.插件

vue插件的作用:主要是用于增强功能,可以把他看作是一个工具库,可以提供很多强大的功能,比如一些强大的自定义指令,一些强大的工具方法,过滤器等。

插件是一种能为Vue添加全局功能的工具代码。

一个插件可以是一个拥有install方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装他的应用实例和传递给**app.use()**的额外选项作为参数

install方法的第一个参数是Vue构造函数,第二个参数及其以后的参数是插件使用者传递的数据

定义插件

import Mixin from "@/mixins/mixin";
export default {
    install(Vue){
        console.log("@@install",Vue)
        //Vue是当前Vue的一个构造函数,我们可以在这里写一些功能增强,例如将一些东西挂载到Vue上

        //定义全局混入
        Vue.mixin(Mixin)

        //定义全局过滤器
        Vue.filter("mySlice",(value)=>{
            return value.slice(0,4)+"|过滤器生效"
        })

        //定义全局指令
        Vue.directive("fbind",{
            bind(element,binding){
                console.log("一上来")
                element.value = binding.value ;
                console.log(element)

            },
            //指令所在元素被插入页面时
            inserted(element,binding){
                console.log("插入页面")

                element.focus();
                console.log(element)
            },
            //指令所在的模板被重新解析
            update(element,binding){
                console.log("重新解析")
                element.vaule = binding.value;
                console.log(element.vaule,binding.value)

            }
        })

        //Vue原型上增加一个方法
        Vue.prototype.hello=()=>{
            console.log("全局方法,hello")
        }
    }
}

使用插件 Vue.use(plugins);

import plugins from "@/plugins/plugins";
Vue.use(plugins);

九、父子组件传值

vue中的父子组件传值,值得注意的是要遵守单向数据流原则。所谓单向数据流原则,简单的说就是父组件的数据可以传递给子组件,子组件也可以正常获取并使用由父组件传过来的数据;但是,子组件中不能直接修改父组件传过来的数据,必须要向父组件传递一个事件来父组件需要修改数据,即通过子组件的操作,在父组件中修改数据;这就是单项数据流

首先,需要构建一个组件的父子关系

父组件 app.vue

<template>
<div id="app">
  <School/>
  </div>
</template>

<script>
  import School from "@/components/School.vue";
  export default {
    name: 'App',
    components: {
      School
    },
    methods: {
      getSchoolName(value) {
        console.log("获取方法被触发了", value)
      }
    }
  }
</script>
<style>
</style>

子组件

<template>
<div>
  <h2>学校名称:{{ schoolName }}</h2>
  <h2>学校地址:{{ address }}</h2>
  <h2>校龄:{{ age }}</h2>
  <button>组件内向外传递schoolName</button>
  </div>
</template>

<script>
  export default {
    name: "School",
    data() {
      return {
        schoolName: "苏州大学",
        address: "苏州工业园区",
        age: 22
      }
    },
    watch: {
    },
    computed: {},
    methods: {
    },
  }
</script>

<style scoped>

</style>

1.使用prop传值(传递一个函数)

父组件利用prop传入一个修改自己data中值的方法

父组件

//给子组件prop传值,传递一个update-prent-data的值,在父组件中定义为一个修改函数
<school ref="school" :agenum="agenum" :update-prent-data="updatePrentData"></school>

//传入的方法
updatePrentData(value) {
  console.log('传入进去的方法被调用', value)
  this.agenum = value //修改的this是父组件
}

子组件

//1.接受prop传过来的值
props: {
  agenum: {
    type: Number,
    default: 0,//默认值
  },
  updatePrentData: {
    type: Function,//传递的方法
    required: true
  }
}

//2.设置需要修改值的时机(我这里就设置的点击触发) 
 <button @click="fun">组件内按钮 +1</button>
//绑定的函数
fun() {
  this.age++;
  //在这里回调传入进来的方法,就可以触发了
  this.updatePrentData(this.age)
}

2.通过给组件绑定自定义事件传值

父组件给子组件绑定一个自定义事件,传入一个修改函数,子组件触发这个事件的时候,把值传出来

父组件

//绑定自定义事件(这两种方式都可以)
<school @actfun="getSchoolName"></school>
<school v-on:actfun="getSchoolName"></school>

//传入的函数
getSchoolName(value){
  console.log("获取方法被触发了",value)
}

子组件

//1.还是定义一个按钮触发传值
<button @click="sendSchoolName">组件内向外传递schoolName</button>

//2.绑定的事件
sendSchoolName(){
  //通过this.$emit触发自定义事件,后面可以传入参数,也可以实现子向父传值
  this.$emit("actfun",this.schoolName);
}

3.通过ref来绑定触发事件(更灵活)

与设置自定义事件类似,父组件给子组件设置一个ref,后续利用ref绑定一个事件,传入一个修改函数,子组件触发这个事件的时候,把值传出来

父组件

this.$refs.绑定的ref值.$on("绑定的事件名",需要绑定的触发函数);

$once可以绑定一次性事件

//设置一个ref
<school ref="school"></school>

//可以在mounted函数中绑定事件(这种方式更灵活)
mounted(){
  //这种方式更加灵活,如等待某个请求返回后再绑定事件
  // this.$refs.school.$on("actfun",this.getSchoolName); //绑定自定义事件
  this.$refs.school.$once("actfun",this.getSchoolName);  //绑定一次性的自定义事件
	//可以延时绑定
  // setTimeout(()=>{
  //   console.log("事件绑定了")
  //   this.$refs.school.$on("actfun",this.getSchoolName);
  // },3000)
}

子组件

//1.还是定义一个按钮触发传值
<button @click="sendSchoolName">组件内向外传递schoolName</button>

//2.绑定的事件
sendSchoolName(){
  //通过this.$emit触发自定义事件,后面可以传入参数,也可以实现子向父传值
  this.$emit("actfun",this.schoolName);
}

4.通过自组件中$parent来触发传值(不推荐)

这种方式采用的是双向数据流

  • 父组件访问子组件:使用$children
  • 子组件访问父组件:使用$parent

子组件修改父组件值 this.$parent.可以访问到父组件中的方法和值

//1.还是定义一个按钮触发传值
<button @click="sendSchoolName">组件内向外传递schoolName</button>

//2.绑定的事件
sendSchoolName(){
  this.$parent.getSchoolName(this.schoolName)
}

5.解绑自定义事件

有一些事件,我们使用完后,需要解绑自定的事件,我们可以使用

this.$off()

用法

//解绑一个自定义事件
this.$off("get-school-name") 

//解绑多个自定义事件
this.$off(["get-school-name","getSchoolName"])

// 解绑所有自定义事件
// this.$off(); 

使用

//1.定义一个按钮触发解绑
<button @click="unBind"> 子组件解除绑定的自定义事件</button>
          
//2.绑定的函数
unBind(){
   console.log("子解绑事件触发")
   this.$off("get-school-name") //解绑一个自定义事件
}

十、全局事件总线

1. Vue 原型对象上包含事件处理的方法

  • $on(eventName, listener): 绑定自定义事件监听
  • $emit(eventName, data): 分发自定义事件
  • $off(eventName): 解绑自定义事件监听
  • $once(eventName, listener): 绑定事件监听, 但只能处理一次

2.为什么要使用全局事件总线

原因:我们项目中的任意两个组件之间需要传递数据,做一些联动处理

3.全局事件总线使用

  • 1.要让该对象在所有组件中都能被访问到,我们将它挂载在Vue对象上

  • 2.这个对象必须要拥有Vue原型上所有的事件处理方法

    Vue.prototype.bus = new Vue()
    
  • 3.实现

    • 3.1 在main.js中指定总线对象

      new Vue({
        render: h => h(App),
        beforeCreate() {//将app实例在创建前挂载到Vue的prototype上
          Vue.prototype.$bus = this
        }
      }).$mount('#app')
      
    • 3.2 绑定事件

      在需要改变值的组件里面绑定,传入的是事件触发后的回调函数

      this.定义的总线名称.$on("自定义事件名",事件触发后的回调函数)

      this.$bus.$on("deleteTodo",this.getSchoolName)
      
    • 3.3 触发事件

      在需要修改绑定事件组件值的地方调用

      this.定义的总线名称.$emit("需要触发的事件名", 回调函数的参数)

      this.$bus.$emit("deleteTodo", this.compData)
      
    • 3.4 解绑事件

      因为这是一个全局的对象,我们尽量不要什么方法都放上去,会导致对象过大,所以在我们绑定事件的组件被销毁的时候,我们可以解绑当前绑定的事件

      //事件解绑,必须有值在里面,否则将清除所有的事件
      this.$bus.$off("deleteTodo")
      

      最好在beforeDestroy钩子中,用$off去解绑当前组件用到的事件

      beforeDestroy() {
        this.$bus.$off("deleteTodo")
      }
      

4.消息订阅与发布

与总线有类似的地方,但是这里是直接引入第三方包

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

使用:

  1. 安装pubsub:npm i pubsub-js

  2. 引入:import PubSub from 'pubsub-js'

  3. 接收数据:A组件想接收数据,则应该在A组件中订阅消息,订阅的回调函数留在A组件自身

    PubSub.publish("订阅的名称",收到信息的回调)

    注意:回调函数第一个参数为消息名称,后面才是数据

    fun3(){
      console.log("开始订阅事件fun") ;
      //这个pid要留下来,后续解绑需要使用
      this.pid = PubSub.subscribe("psFun",this.psFun)
    },
    
  4. 发布端:提供数据

    PubSub.publish("发布的名称",需要传入的函数参数)

     PubSub.publish("psFun",this.compData)
    
  5. 在使用完毕后,需要取消订阅,可以在beforeDestroy钩子中

    PubSub.unsubscribe(订阅时的Pid)

注意:这种方式不能重复订阅,重复订阅,后面的回调会被调用多次,最好可以放在钩子函数中,只触发一次

十一、$nextTick

1.语法:this.$nextTick(回调函数)

2.作用:在下一次DOM更新结束后调用其指定的回调

3.什么时候使用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所制定的回调函数中执行

十二、动画效果

Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果”,即Vue的动画是作用是让元素在改变前添加一些过度效果。这样可以让用户在视觉和交互上有更好的体验

1.Vue的两个状态切换

在这里插入图片描述

2.触发顺序

对于v-show来说,当他的条件由false转为true为进入(v-if也一样),true转为false为离开

对于进入触发的css类为(默认):v-enterv-enter-tov-enter-active,其中v-enter-active是包括了v-enterv-enter-to两个过程,一般用于设置过度时间。

对于退出触发的css类为(默认):v-leavev-leave-tov-leave-active,其中v-leave-active是包括了v-leave、v-leave-to两个过程,一般用于设置过度时间。

3.transition 与 transition-group

Vue动画为我们提供了一个<transition></transition><transition-group></transition-group>组件用于包裹要添加过度动画的元素和一套规则。标签、动画进和出的两个过程及对应其动画类名的定义

/* 进入的起点、离开的终点 */
.hello-enter {
    transform: translate(-100%, -100%);
}

.hello-leave-to {
    transform: translate(100%, -100%);
}

.hello-enter-active, .hello-leave-active {
    transition: 0.3s linear;
}

/* 进入的终点、离开的起点 */
.hello-enter-to, .hello-leave {
    transform: translate(0%, 0%);
}

标签部分

<button @click="flag1=!flag1">修改flag1</button>
<button @click="flag2=!flag2">修改flag2</button>

<transition name="hello" appear>
  <CompB v-show="flag1"></CompB>
</transition>
<transition name="hello" appear>
  <CompC v-show="flag2"></CompC>
</transition>

<transition-group name="hello" appear>
  <CompB v-show="flag1" :key="1"></CompB>
  <CompC v-show="flag2" :key="2"></CompC>
</transition-group>

效果

4.引入动画库

Animate.css

安装:npm install animate.css --save

引入:import 'animate.css';

使用:

Animate官网

可以在官网上选择需要的动画效果

//enter-active-class="animate__bounceIn" 进入动画
//leave-active-class="animate__bounceOut" 离开动画

<transition name="animate__animated animate__bounce" appear
            enter-active-class="animate__bounceIn"
            leave-active-class="animate__bounceOut"
            >
  <CompB v-show="flag1"></CompB>
</transition>
<transition name="animate__animated animate__bounce" appear
            enter-active-class="animate__flip"
            leave-active-class="animate__flipOutY">
  <CompC v-show="flag2"></CompC>
</transition>

十三、配置代理

当我们使用axios来发起请求的时候,因为不是同源

同源:协议名一致,ip一致,端口号一致

所以请求会报跨域错误,这个我们可以在中间设立Nginx服务器,也可以利用vue-cli中的代理服务配置来跨域通信。

错误截图:

在这里插入图片描述

使用:

  1. 修改根目录下的vue.config.js

      devServer: {
        host: '0.0.0.0',
        // 当前vue项目运行的端口
        port: 5009,
          
        // proxy:"http://localhost:8082" //代理简单写法
        proxy: {
          '/redisDemo': {//前缀
            target: 'http://localhost:8082',  // 后台接口地址
            ws: false, //如果要代理 websockets,配置这个参数
            secure: false,  // 如果是https接口,需要配置这个参数
            changeOrigin: true,  //用于控制请求中的host值,是否改变成你代理的host,可以用来跨域
            // pathRewrite:{	// 重写路径,可以请求转发的时候消除前缀
            //   '^/redisDemo':''
            // }
          }
        }
      },
    

    vue项目里,我的所有接口的地址都是/redisDemo开头的,所以前面用 ‘/redisDemo’
    例如:我的请求地址 /redisDemo/xxxx/xxx
    当node服务器 遇到 以 ‘/redisDemo’ 开头的请求,就会把 target 字段里的值加上,那么请求地址就为变成了http://localhost:8082/redisDemo/xxxx/xxx

    pathRewrite: 简单来说,pathRewrite是使用proxy进行代理时,对请求路径进行重定向以匹配到正确的请求地址,

    例如我的请求http://localhost:8082/redisDemo/test/add ,如果配置了pathRewrite中的 '^/redisDemo':'' 那么请求地址就会被转发到http://localhost:8082/test/add 这个地址上

  2. 配置后重新启动项目,可以看到请求已经可以正常访问。

    在这里插入图片描述

  3. 如果有多个地址需要代理,可以再增加

     devServer: {
        host: '0.0.0.0',
        // 生产环境端口
        port: 5009,
        // proxy:"http://localhost:8082"
        proxy: {
          '/redisDemo': { //前缀
            target: 'http://localhost:8082',  // 后台接口地址
            ws: false,      
            secure: false,  
            changeOrigin: true,
          },
          '/appDemo': {
            target: 'http://localhost:8084',  // 后台接口地址
            ws: false,       
            secure: false, 
            changeOrigin: true, 
          }
        }
      },
    

缺点:因为这是一个代理服务器,他首先拿到请求会去本地的pulic文件夹中找一下,如果pulic文件夹下有的话,他就不再转发给后台服务器了。(可以通过加前缀的方式解决)

十四、插槽

作用:插槽是在子组件中某个位置插入父组件的自定义html结构和data数据

1.默认插槽

定义:将父组件的自定义html和data插入子组件的默认对应位置
特点: 父组件决定结构和数据

父组件

<template>
    <div class="ccc">
        <category title="美食">
            <img src="https://img1.baidu.com/it/u=319510654,3848329935&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800">
        </category>
        <category title="游戏">
            <ul>
                <li v-for="(item,index) in gameList" :key="index">{{ item }}</li>
            </ul>
        </category>
        <category title="电影">
            <slot><video controls src="/aa.mov"></video></slot>
        </category>
    </div>

</template>

<script>
import Category from "@/components/slot/Category.vue";

export default {
    name: "CategoryMain",
    components: {Category},
    data() {
        return {
            gameList: ["英雄联盟", "守望先锋", "红警"]
        }
    }

}
</script>

<style scoped>
.ccc {
    display: flex;
    justify-content: space-between;
    margin: 0px 150px;
}
</style>

子组件

<template>
    <div class="category">
        <h3>{{ title }}分类</h3>
        <!--设置一个默认插槽,用来存放同值-->
        <slot></slot>
    </div>
</template>

<script>
export default {
    name: "Category",
    props:['title']
}
</script>

<style scoped>
h3{
    text-align: center;
    width: 100%;
    height: 35px;
    background-color: #f88000;
}
.category{
    height: 400px;
    width: 300px;
    background-color: #0C8ED9;
}

ul{
    display: block;
}

img{
    width: 100%;
}
video{
    width: 100%;
}
</style>

2.具名插槽

定义: 具名插槽简单地说就是具有名字的插槽,具名插槽可以有多个插入位置,根据名字来识别对应的插槽
特点: 父组件决定结构和数据

父组件

<template>
    <div class="ccc">
        <category title="美食">
            <img slot="content"
                 src="https://img1.baidu.com/it/u=319510654,3848329935&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800">
            <a href="" slot="footer">更多美食</a>
        </category>
        <category title="游戏">
            <ul slot="content">
                <li v-for="(item,index) in gameList" :key="index">{{ item }}</li>
            </ul>
            <div class="footer" slot="footer">
                <a href="">单机游戏</a>
                <a href="">网络游戏</a>
            </div>

        </category>
        <category title="电影">
            <video slot="content" controls src="/aa.mov"></video>
            <!--这种方式会多一层div的结构,这种结构是没有必要的,可以用template来代替-->
            <!--            <div slot="footer">-->
            <!--                <div class="footer">-->
            <!--                    <a href="">单机游戏</a>-->
            <!--                    <a href="">网络游戏</a>-->
            <!--                </div>-->
            <!--                <h3>欢迎来观影</h3>-->
            <!--            </div>-->
            <!--当使用template来包裹需要插入的元素时,slot属性可以写成 v-slot:footer(更推荐)-->
            <template v-slot:footer>
                <div class="footer">
                    <a href="">单机游戏</a>
                    <a href="">网络游戏</a>
                </div>
                <h3>欢迎来观影</h3>
            </template>
        </category>
    </div>

</template>

<script>
import Category from "@/components/slot/Category.vue";

export default {
    name: "CategoryMain",
    components: {Category},
    data() {
        return {
            gameList: ["英雄联盟", "守望先锋", "红警"]
        }
    }

}
</script>

<style scoped>
.ccc {
    display: flex;
    justify-content: space-around;
    margin: 0px 150px;
}

.footer {
    display: flex;
    justify-content: space-around;
}
</style>

子组件

<template>
    <div class="category">
        <h3>{{ title }}分类</h3>
        <!--设置一个默认插槽,用来存放同值-->
        <slot name="content"></slot>
        <slot name="footer"></slot>
    </div>
</template>

<script>
export default {
    name: "Category",
    props: ['title']
}
</script>

<style scoped>
h3 {
    text-align: center;
    width: 100%;
    height: 35px;
    background-color: #f88000;
}

.category {
    height: 400px;
    width: 300px;
    background-color: #0C8ED9;
}

ul {
    display: block;
}

img {
    width: 100%;
}

video {
    width: 100%;
}
</style>

3.作用域插槽

定义: 作用域插槽的data数据固定写在子组件中,数据的html结构根据父组件传入的html结构来决定
特点: 子组件决定数据,父组件决定结构

父组件

<template>
    <div class="ccc">
        <category title="游戏">
            <template scope="{games}" slot="content">
                <ul>
                    <li v-for="(item,index) in games" :key="index">{{ item }}</li>
                </ul>
            </template>
        </category>

        <category title="游戏">
            <template scope="{games}" slot="content">
                <ol>
                    <li v-for="(item,index) in games" :key="index">{{ item }}</li>
                </ol>
            </template>
        </category>

        <!--推荐这这写法-->
        <category title="游戏">
            <template v-slot:content="{games}">
                <h4 v-for="(item,index) in games" :key="index">{{ item }}</h4>
            </template>
        </category>
    </div>

</template>

<script>
import Category from "@/components/slot/Category.vue";

export default {
    name: "CategoryMain",
    components: {Category},
    data() {
        return {}
    }

}
</script>

<style scoped>
.ccc {
    display: flex;
    justify-content: space-around;
    margin: 0px 150px;
}

.footer {
    display: flex;
    justify-content: space-around;
}
</style>

子组件

<template>
    <div class="category">
        <h3>{{ title }}分类</h3>
        <slot name="content" :games="gameList"> 没有值展示默认slot</slot>
    </div>
</template>

<script>
export default {
    name: "Category",
    props: ['title'],
    data(){
        return{
            gameList: ["英雄联盟", "守望先锋", "红警"]
        }
    }
}
</script>

<style scoped>
h3 {
    text-align: center;
    width: 100%;
    height: 35px;
    background-color: #f88000;
}
.category {
    height: 400px;
    width: 300px;
    background-color: #0C8ED9;
}

ul {
    display: block;
}

img {
    width: 100%;
}

video {
    width: 100%;
}
</style>

如果是 作用域插槽 同时又是 具名插槽

Vue 2.0写法:<template scope="{games}" slot="content">

Vue 3.0写法:<template v-slot:content="{games}">

注意: scope, slot,slot-scope 将在vue 3.0被废弃,现在更推荐使用v-slot

十五、Vuex

vuex官方解释

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

什么时候我们该使用它

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

在这里插入图片描述

1.安装

vue 2 安装vuex 3 ,vue 3安装 vuex 4

npm install vuex@3 --save

2.配置

2.1 建立一个单独的js文件 ,导出一个Vuex.Store对象

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //数据,相当于data
  state: {
  },
  getters: {
  },
  //里面定义方法,操作state方法
  mutations: {
  },
  // 操作异步操作mutation
  actions: {
  },
  modules: {}
})

2.2 在main.js中创建引入

store就是我们刚才导出的对象

import Vue from 'vue'
import App from './App.vue'
import store from "@/store/index"

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  store,
}).$mount('#app')

3.核心概念

vuex中一共有五个状态 State Getter Mutation Action Module

3.1 State

提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data

在vuex中state中定义数据,可以在任何组件中进行调用

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
//数据,相当于data
    state: {
        num: 0,
        addNum: 1,
        personList: [{name: "李四", age: "123"}]
    }
})

调用:

方法一:

在标签中直接使用

{{$store.state.name}}

方法二:

this.$store.state.全局数据名称

方法三:

从vuex中按需导入mapState函数

import { mapState } from "vuex";

computed: {
  ...mapState(["num", "personList"]),
},

注意::当前组件需要的全局数据,映射为当前组件computed属性

3.2 Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

在vuex中定义:

其中参数state参数是必须的,也可以自己传递一个参数,如下代码,进行计数器的加减操作,传递一个参数进来,来改变addNum的值

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //数据,相当于data
  state: {
    addNum: 1,
  },
  //里面定义方法,操作state方法
  mutations: {
    updateAddNum(state, value){
      console.log("修改AddNum" + state.addNum, state, value);
      state.addNum += value;
    },
  },
})

组件中使用

定义两个按钮进行加减操作

<button>+</button>&nbsp;
<button>-</button>&nbsp;

方法一:

注意:使用commit触发Mutation操作

<button @click="$store.commit('updateAddNum',1)">+</button>&nbsp;
<button @click="$store.commit('updateAddNum',-1)">-</button>&nbsp;

方法二:

从vuex中按需导入mapMutations函数

<button @click="updateAddNum(1)">+</button>&nbsp;
<button @click="updateAddNum(-1)">-</button>&nbsp;

import { mapMutations } from "vuex";

methods: {
  ...mapMutations(["updateAddNum"])
}

注意::当前组件需要的全局数据,映射为当前组件methods属性

3.3 Action

Action和Mutation相似,一般不用Mutation 异步操作,若要进行异步操作,使用Action

原因:为了方便devtools打个快照存下来,方便管理维护。所以说这个只是规范,而不是逻辑的不允许,只是为了让这个工具能够追踪数据变化而已

增加一个延迟增加的方法

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //数据,相当于data
  state: {
    num: 0,
    addNum: 1,
  },
  getters: {
    personLength(state){
      return state.personList.length;
    }
  },
  //里面定义方法,操作state方法
  mutations: {
    addNumFun(state, value) {
      console.log("num增加" + state.addNum, state, value);
      state.num += state.addNum;
    },

  },
  // 操作异步操作mutation
  actions: {
    waitAdd(store, event){
      // console.log("等待 num增加" , store, event,this);
      setTimeout(()=>{
        this.commit("addNumFun",10)
      },500)
    }
  },

})

组件中使用

定义一个个按钮进行触发

  <button @click="waitAdd">等一等再加</button>

方法一:

直接使用 dispatch触发Action函数

<button @click="$store.dispatch('waitAdd')">等一等再加</button>

方法二:

从vuex中按需导入mapActions函数

<button @click="waitAdd">等一等再加</button>

import { mapActions } from "vuex";

methods: {
		...mapActions(["waitAdd"]),
}

注意::当前组件需要的全局数据,映射为当前组件methods属性

3.4 Getter

类似于vue中的computed,进行缓存,对于Store中的数据进行加工处理形成新的数据

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //数据,相当于data
  state: {
    personList: [{name: "李四", age: "123"}]
  },
  getters: {
    personLength(state){
      return state.personList.length;
    }
  },
})

使用

方法一:

<h2>Person组件的总人数:{{$store.getters.personLength}}</h2>

this.$store.getters.全局数据名称

方法二:

从vuex中按需导入mapGetters函数

import { mapGetters } from "vuex";

computed: {
  ...mapGetters(["personLength"])
},

注意::当前组件需要的全局数据,映射为当前组件computed属性

3.5 Module

当遇见大型项目时,数据量大,store就会显得很臃肿

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

1.改写模块化配置文件

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);
// 模块化改造
const personOptions = {
  namespaced: true, state: {
    personList: [{name: "李四", age: "123"}],
  }, getters: {
    personLength(state) {
      return state.personList.length;
    }
  }, //里面定义方法,操作state方法
  mutations: {

    addPerson(state, value) {
      console.log("新增Person 增加" + state.personList, state, value);
      state.personList.unshift(value)
    }

  }, // 操作异步操作mutation
  actions: {
    waitAdd(store, event) {
      // console.log("等待 num增加" , store, event,this);
      setTimeout(() => {
        this.commit("addNumFun", 10)
      }, 500)
    }
  }
}

const sumOptions = {
  namespaced: true,
  state: {
    num: 1, addNum: 1,
  }, getters: {}, //里面定义方法,操作state方法
  mutations: {
    updateAddNum(state, value) {
      console.log("修改AddNum" + state.addNum, state, value);
      state.addNum += value;
    }, addNumFun(state, value) {
      console.log("addNumFun num增加" + state.addNum, state, value);
      state.num += state.addNum;
    }, oddAdd(state, value) {
      console.log("奇数 num增加" + state.addNum, state, value);
      if (state.num % 2 !== 0) {
        state.num += state.addNum;
      }
    },
  }, // 操作异步操作mutation
  actions: {
    waitAdd(store, event) {
      // console.log("等待 num增加" , store, event,this);
      setTimeout(() => {
        this.commit("sumAbout/addNumFun", 10)
      }, 500)
    }
  },
}
export default new Vuex.Store({
  modules: {
    personAbout: personOptions, sumAbout: sumOptions
  }
})

2.是否开启命名空间

namespaced: true: 开启命名空间,不开的话会合并对应数据,开启会按照你的模块变量名归类。

3.开启命名空间后,组件中读取state数据

//方式一 原生读取
this.$store.state.personAbout.list
//方式二 借助mapState读取
...mapState("personAbout",[ "personList"]),

4.开启命名空间后,组件中读取getters数据

//方式一 原生读取
this.$store.getters["personAbout/personLength"]
//方式二 借助mapGetters读取
...mapGetters("personAbout",["personLength"])

5.开启命名空间后,组件中调用Mutation方法

//方式一 原生调用
this.$store.dispatch("personAbout/addPerson",person)
//方式二 借助mapMutations读取
...mapMutations("personAbout",["addPerson"])

6.开启命名空间后,组件中调用Action方法

//方式一 原生调用
this.$store.commit("personAbout/add_Person",person)
//方式二 借助mapMutations读取
...mapMutations("personAbout",["addPerson"])

默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

十六、Vue Router

用 Vue + Vue Router 创建单页应用非常简单:通过 Vue.js,我们已经用组件组成了我们的应用。当加入 Vue Router 时,我们需要做的就是将我们的组件映射到路由上,让 Vue Router 知道在哪里渲染它们。

1.SPA页面

SPA:单页富应用
整个网页只有一个HTML网页。

2.准备工作

2.1 安装

vue 2 安装vue-router@3 ,vue 3安装 vue-router@4

npm install vue-router@3 --S

2.2 配置

安装完成后,在src文件夹下新建router文件夹,在里面新建index.js文件

import Vue from 'vue'
import VueRouter from 'vue-router'
import DongHua from "@/components/DongHua.vue";

Vue.use(VueRouter)      //Vue中使用router插件


//路由对应表
const pageRoutes = [
  { path: '/', component: DongHua },
  {
    path: '/dongHua',
    name: 'dongHua',
    //懒加载
    component: () => import('../components/DongHua.vue')
  },
  {
    path: '/proxyComp',
    name: 'proxyComp',
    component: () => import('../components/PropComp.vue')
  },
  {
    path: '/slotComp',
    name: 'slotComp',
    component: () => import('../components/slot/CategoryMain.vue')
  },
]

const router = new VueRouter({
  routes: pageRoutes,
  mode: 'history'
});

export default router  //导出路由实例,在main.js中导入使用

在main.js中引入

import Vue from 'vue'
import App from './App.vue'
import router from "@/router/index";

Vue.config.productionTip = false

const a = new Vue({
  render: h => h(App),
  router,      //在vue实例中使用router
}).$mount('#app')

2.3利用router标签测试跳转

<template>
<div id="app">
  <router-link to="/proxyComp">proxyComp</router-link>
  <router-link to="/dongHua">dongHua</router-link>
  <router-link to="/slotComp">slotComp</router-link>
  <router-view></router-view>
  </div>
</template>

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

3.基本标签

3.1 router-link

组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。

<router-link to="/routerName">

to 表示目标路由的链接。当被点击后,内部会立刻把 to 的值传到 router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象。

active-class 属性 ,默认值: “router-link-active”

设置链接激活(选中)时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。

3.2 router-view

<router-view> 组件是一个 functional 组件,渲染路径匹配到的视图组件。<router-view> 渲染的组件还可以内嵌自己的 <router-view>(嵌套路由),根据嵌套路径,渲染嵌套组件。

name 属性 默认值: “default”

如果 <router-view>设置了名称,则会渲染对应的路由配置中 components 下的相应组件。

3.3 注意点
  1. pages文件夹一般放路由组件,components文件夹一般放页面组件
  2. 路由切换会导致对应的组件被销毁
  3. 如果是路由组件,会在vueComponent对象上增加两个对象
    1. $route 配置的对应路由规则,如path属性“/home”等数据
    2. $router 整个应用的路由器,有且只有一个

4.嵌套路由

嵌套路由又称为子路由,在实际应用中,通常由多层嵌套的组件组合而成。同样地,URL中各段动态路景观也按某种结构对应嵌套的各层组件

4.1 编写一级路由组件

因为会嵌套二级路由,所以内部也会有一个router-view标签

<template>
<div id="app">  
  <router-link to="/parentComp">parentComp</router-link>
  <router-view class="viewD"></router-view>
  </div>
</template>

<script>
  export default {
    name: 'App',
  }
</script>
<style>
  .viewD{
    height: 100%;
  }
  html,body,#app{
    height: 100%;
    overflow: hidden;
  }
</style>

4.2 编写二级路由组件

<template>
<div class="d1">
  <h1>ParentComp</h1>
  <div class="d2">
    <h2>children view</h2>
    <router-link to="/parentComp/childrenA">proxyComp</router-link>
    |
    <router-link to="/parentComp/childrenB">dongHua</router-link>
    |
    <router-view></router-view>
  </div>
  </div>

</template>

<script>
  export default {
    name: "ParentComp"
  }
</script>

<style scoped>
  .d1 {
    width: 100%;
    height: 100%;
    background-color: #00C0EF;
    padding: 20px;
    text-align: center;
  }

  .d2 {
    width: 60%;
    height: 60%;
    background-color: #1b6d85;
    margin-left: 20%;

  }
</style>

4.3 编写二级路由表

利用路由表对象children属性,可以在这个属性下增加二级路由

import Vue from 'vue'
import VueRouter from 'vue-router'
import DongHua from "@/components/DongHua.vue";

Vue.use(VueRouter)      //Vue中使用router插件


//路由对应表
const pageRoutes = [
  { path: '/', component: DongHua },
  {
    path: '/dongHua',
    name: 'dongHua',
    //懒加载
    component: () => import('../components/DongHua.vue')
  },
  {
    path: '/parentComp',
    name: 'parentComp',
    //懒加载
    component: () => import('../components/router/ParentComp.vue'),
    children:[
      {path: '/',},
      {
        path: 'childrenA',
        name: 'childrenA',
        //懒加载
        component: () => import('../components/router/ChildrenA.vue')
      },{
        path: 'childrenB',
        name: 'childrenB',
        component: () => import('../components/router/ChildrenB.vue')
      }
    ]
  },

]


const router = new VueRouter({
  routes: pageRoutes,
  mode: 'history'
});


export default router         //导出路由实例,在main.js中导入使用

5.路由的query参数

1.传递参数

<!--跳转并携带query参数,to的字符串写法-->
<router-link v-for="(item,index) in detailList" :key="index"
:to="'/parentComp/detail?id='+item.id+'&msg='+item.msg">
  {{ ' detail ' + item.id + ' |' }}
</router-link>

<!--跳转并携带query参数,to的对象写法-->
<router-link v-for="(item,index) in detailList" :key="index"
:to="{
     path:'/parentComp/detail',
     query:{
     id:item.id,
     msg:item.msg
     }}">
  {{ ' detail ' + item.id + ' |' }}
</router-link>

2.接收参数

// js中使用
this.$route.query.参数数据

//插值语法中使用
{{$route.query.参数数据}}

6.命名路由

1.作用:可以简化路由的跳转

2.使用:

  • 2.1 给路由命名

    给路由配置上加上name属性

    在这里插入图片描述

  • 2.2 简化跳转

    <!--简化前,需要写完整的路径-->
    <router-link to="/parentComp/childrenA">childrenA</router-link>
    
    <!--简化后,直接通过名字跳转-->
    <router-link :to="{name:'childrenA'}">childrenA</router-link>
    
    <!--跳转并携带query参数,name写法-->
    <router-link 
      :to="{
             name:'detail',
             query:{
             id:666,
             msg:'你好'
             }
           }"
    >跳转</router-link>
    

7.路由的param参数

1.设置路由占位符

在path中修改path后面用:变量名的方式进行占位

{
  path: 'detail/:id/:msg',
  name: 'detail',
  component: () => import('../components/router/DetailComp.vue')
}

2.设置路由跳转

<!--路由跳转,path 携带params参数(不推荐)-->
<router-link :to="{
                  path:'/parentComp/detail/'+item.id+'/'+item.msg
                  }"
>跳转</router-link>

<!--路由跳转,name 携带params参数(推荐)-->
<router-link :to="{
                  name:'detail',
                  params:{
                  id:66,
                  msg:'你好'
                  }}"
>跳转</router-link>

3.接收参数

// js中使用
this.$route.params.参数数据

//插值语法中使用
{{$$route.params.参数数据}}

8.路由的props配置

1.作用:让路由组件更方便的收到参数

2.使用:

  • 2.1第一种写法:props值为对象

    该对象中所有的key-value的组合最终都会通过props传给Detail组件

    缺点:值是常量,没办法修改

{
  path: 'detail',
  name: 'detail',
  component: () => import('../components/router/DetailComp.vue'),
  props:{a:900} //缺点:值是常量,没办法修改
}

//组件内接收参
 props:["a"],
  • 2.2 第二种写法:props值为布尔值

    该对象中所有的key-value的组合最终都会通过props传给Detail组件

    缺点:只能利用params参数

    //配置
    {
      path: 'detail',
      name: 'detail',
      component: () =>          import('../components/router/DetailComp.vue'),
      props:true
    }
    
    //传参
    :to="{
    name:'detail',
      params:{
        id:item.id,
        msg:item.msg
       }
    }"
    
    //组件内接参
    props:["id","msg"],
    
  • 2.3 第三种写法:props值为函数

    该函数返回的对象中每一组key-value都会通过props传递给Detail组件

    //配置
     props($route) {
       //传参注意:query属性每次都会进入刷新,但params同页面跳转不刷新
       return {
         id: $route.query.id,
         msg: $route.query.msg
       }
     }
    
    //传参
    :to="{
    name:'detail',
      query:{
        id:item.id,
        msg:item.msg
       }
    }"
    
    //组件内接参
    props:["id","msg"],
    

9. **< router-link >**的replace属性

1.作用:控制路由跳转时操作浏览器历史记录的模式

2.浏览器的历史记录有两种写入方式:分别为 pushreplacepush 是追加历史记录,replace 是替换当前记录,路由跳转时候默认为 push(有记录模式)

3.如何开启 replace 模式: <router-link replace ......>News</router-link>

存储历史记录的数据结构为栈结构

push 就是点击一个网址会压栈一条记录进去

replace 就是替换掉栈顶的一条数据

10.编程式路由导航

1.作用:不借助 < router-link > 实现路由跳转,让路由跳转更加灵活

2.具体编码:

// 路由跳转,有历史记录
this.$router.push({
  name: 'detail',
  query: {
    id: item.id,
    msg: item.msg
  }
});

// 路由跳转,无历史记录
this.$router.replace({
  name: 'detail',
  query: {
    id: item.id,
    msg: item.msg
  }
})

// 路由回退到上一条记录
this.$router.back()

// 路由前进到上一条记录
this.$router.forward()

// 路由前进或后退,当num为负数表示后退几条记录,正数则表示前进几条记录
this.$router.go(num)

11.缓存路由组件

1.作用:让不展示的组件保持挂载,不被销毁

2.具体实现:

​ 通过在 < router-view > 外层包裹 < keep-alive > 标签,并用 include指出需要缓存的 组件名

<keep-alive include="News">
  <router-view></router-view>
</keep-alive>

如果include里面要指定多个组件名的话
<keep-alive :include="['NewsTwo','News']">
  <router-view></router-view>
</keep-alive>

12.路由激活与失活的生命周期钩子

1.作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态

2.具体名字:

  1. activated 路由组件被激活时触发。
  2. deactivated 路由组件失活时被触发

13.路由守卫

作用:对路由进行权限控制

分类:全局守卫,独享守卫,组件内守卫

meta参数:路由表配置项中可以用来存储需要的值,已便后续去使用。

1.全局路由守卫

全局前置守卫beforeEach):初始化时执行,每次路由切换执行 通常用来做全局的路由挑战权限验证

全局后置守卫afterEach):路由切换成功后会执行,可以用来做一些收尾工作,记录日志等

//路由守卫
//全局路由前置守卫,初始化时执行,每次路由切换执行 通常用来做全局的路由挑战权限验证
router.beforeEach((to, from, next) => {
  // to 需要切换到的地址
  // from 来自那一个地址
  // next可以通过传递不同的参数,给路由重定向或者放行
  console.log("全局前置守卫", to, from);

  // 判断是否需要权限验证
  if (to.meta.isAuth) { //判断当前路由是否需要进行权限控制
    if (localStorage.getItem("role") === "管理员") {
      next() //放行
    } else {
      alert("暂无权限查看")
      next({path: "/proxyComp"}) //可以让他强制跳转到登陆页操作
    }
  } else {
    next();
  }
})

//全局路由后置守卫,路由切换成功后会执行,可以用来做一些收尾工作,记录日志等
router.afterEach((to, from) => {
  console.log("全局后置守卫", to, from);
  console.log("记录日志成功,页面跳转到"+to.name)
})
2.路由独享守卫

路由独享守卫beforeEnter):通过在路由表中利用beforeEnter关键字来指定需要执行的守卫函数,它会在路由跳转该组件的时候被执行。功能与前置守卫类似,但是它更适合一些需要特殊路由处理的组件。

{
  path: '/parentComp',
    name: 'parentComp',
      //接收一个函数,也可以接收一个函数数组
      //beforeEnter:[fun1,fun2],
      beforeEnter:(to,from,next)=>{
        console.log("路由独享守卫",to,from);
        next();
      },
}
3.组件内的路由钩子
  1. 组件路由进入守卫beforeRouteEnter:通过路由规则,进入当前组件时调用
  2. 组件路由离开守卫beforeRouteLeave:通过路由规则,离开当前组件时调用
  3. 组件路由更新守卫beforeRouteUpdate:通过路由规则,使当前组件发生更新时调用,例如获取不同的参数重新进入当前页面渲染时

通过路由规则:指的是通过路由跳转进入页面,而不是直接在页面上引用。

beforeRouteEnter(to,from,next){
  console.log("组件内进入守卫",to,from)
  next();
},
  
beforeRouteLeave(to,from,next){
  console.log("组件内离开守卫",to,from)
  next();
},
  
beforeRouteUpdate(to,from,next){
  console.log("组件内更新守卫",to,from)
  next();
}

14.路由器的两种工作模式

1.hash模式

  • 1 对于url来说,#及其后面的内容就是hash值;
  • 2 hash值不会包含在http请求中,即:hash值不会带给服务器。
  • 3 hash模式的特点:url地址中永远带着#号,不美观。但兼容性好

2.history模式

  • 1 地址干净,美观
  • 2 兼容性比hash模式相比略差。
  • 3 应用部署上线时,刷新页面服务端会报404的问题,不部署在相同的端口是没问题的。

router中修改模式

修改 router 中的mode参数

const router = new VueRouter({
    routes: pageRoutes,
    mode: 'history'
   	//mode:"hash"
});

在这里插入图片描述

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LvhaoIT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值