vue(vue2)

文章目录

vue2

英文官网: https://vuejs.org/
中文官网: https://cn.vuejs.org/

一、vue简介

vue

  • Vue框架主要关注于视图(view)的一个框架
  • 动态构建用户界面的渐进式JavaScript框架(Vue框架提供核心的语法,可以在核心语法基础之上完善项目功能、路由、集中式管理等等渐渐完善项目。)
  • 它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型;
  • Vue全家桶是指vue + 周边核心插件(生态),统称为全家桶
Vue周边生态
  1. vue-cli(vue脚手架)
  2. vue-resource
  3. axios
  4. vue-router(路由)
  5. vuex(状态管理)
  6. element-ui(UI组件库)
渐进式

渐进式: Vue框架提供一些核心的语法,当项目当中需要使用路由功能,Vue框架没有这个功能,需要使用vue-router配置路由。当项目中需要使用一些通用组件效果,如:轮播图、遮罩层, element-ui。当项目中想要集中式管理数据,可以使用vuex等等。

vue框架特点
  • 组件化开发, 充分复用代码,提高开发效率
  • 声明式编程,操作数据即可,提高开发效率
  • 不用操作DOM, 数据发生变化,视图跟着变,不用操作DOM,只是关心数据
  • 采用虚拟DOM + 优秀DIFF算法,可以复用DOM
  • 总结:组件化、声明式编程、虚拟DOM+DIFF算法
快速体验
  • 官网: https://cn.vuejs.org/v2/guide/installation.html
  • 刚开始我们不需要把它想象的非常复杂,Vue的本质,就是一个JavaScript的库
  • 把它理解成一个已经帮助我们封装好的库,在项目中可以引入并且使用它即可
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 先讲Vue2.x -->
    <!-- 
        引入Vue的方式:
            1)在页面中通过CDN方式引入
            2)下载Vue源码,引入我们下载好的源码
            3)通过NPM工具,下载引入
            4)使用Vue脚手架去引入Vue
     -->
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>
    
    <!-- 定义的容器 -->
    <!-- 容器也叫模板,也叫视图 -->
    <div id="app">
        <h2>你好, {{ name }} === {{ age }} === {{ sex }}</h2>
    </div>

    <script>
        // new一个Vue,得到一个对象,这个对象叫Vue根实例,叫其它名字也可以
        // 当new Vue时,可以传递一个配置对象
        // 框架:要求你需要在合适的位置写合适的代码
        let vm = new Vue({
            // el表示vm实例要去哪个容器进行关联,el对应的值是一个CSS选择器
            // 作用就是让vm与容器进行关联
            el:"#app",
            // data是用来给模板或视图提供数据的
            // 在vue2.x中,data对应的值,可以是一个对象,也可以是一个函数,官方推荐使用函数的形式
            //    data函数需要返回一个对象
            // 在vue3.x中,data必须是一个函数,如果不是,直接报错
            // data:{}
            data(){
                return {
                    // return中的对象中的写的数据,就是用来给模板提供的数据
                    // name,age,sex,叫响应式数据,写在data中的数据叫响应式数据
                    // 所谓的响应式数据,是指数据变了,模板会自动刷新
                    // vm会代理data中的所有的数据  vm.name   vm.age    vm.sex
                    // 写在data中的所有的数据,都会挂载到vm对象上,成为vm的属性
                    name:"wc",
                    age:18,
                    sex:"man"
                }
            }
        });
        console.log(vm);

        // 总结:
            // 1)vm叫Vue实例   通过new Vue构造器new出来的     对象是属性的无序集合
            // 2)new Vue时,传递一个配置对象    el(让vm与模板进行关联)   data(给模板提供数据)
            // 3)响应式数据:数据变了,视图要重新渲染(自动刷新)
            // 4)data中的数据,都要挂载到vm实例上,作为vm实例的属性
            // 5)在vue中,不需要操作DOM,操作的是响应式数据
    </script>
</body>
</html>

vue语法

MVVM
  • MVVM是Model-View-ViewModel的简称,是目前非常流行的架构模式;
  • 通常情况下,我们也经常称Vue是一个MVVM的框架
  • Vue官方其实有说明,Vue虽然并没有完全遵守MVVM的模型,但是整个设计是受到它的启发的
    请添加图片描述
  • 胡子语法注意事项一
    • {{}} Mustache,插值语法,也叫小胡子语法
    • 插值语法只能在文本区域书写, 不能在标签属性当中书写, 可以书写多个【动态数据】
    • 插值语法只能在容器内部使用,容器外是不能使用的
    • 一个实例只能关联一个容器,多个不行,以后开发项目,容器一般就一个、VM实例一般也就是一个
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>
    <!-- 容器/模板 -->
    <div class="app">
        <!-- {{ }} 插值语法  胡子语法 -->
        <!-- 只能写在文本节点处,不能写在属性节点 -->
        <!-- <h2 title="{{ name }}">{{ name }}</h2> -->
    </div>

    <hr>

    <!-- 一个vm实例只能指定一个模板,一个容器 -->
    <!-- 一般一个项目,只有一个根模板 -->
    <!-- 指定多个,不行 -->
    <!-- <div class="app">
        <h2>{{name}}</h2>
    </div> -->

    <!-- data中的数据,只能写对应的模板中的,在模板之外,是不能使用数据的 -->
    <!-- <p>{{name}}</p> -->

    <script>
        let vm = new Vue({
            // body和html不能作为vm实例的模板
            // el:"body"
            el:".app",
            data(){
                return{
                    name:"xwc"
                }
            }
        });
    </script>
</body>
</html>
  • 胡子语法注意事项二
    • 小胡子中可以放表达式(任何有值的内容都是表达式)
    • 小胡子中可以书写实例的属性
    • 小胡子中可以书写JS表达式
    • 数学运算符、比较运算符、逻辑运算符,三元运算符等,最终会产生一个数值,也可以放小胡子中
    • 函数执行的结果也有返回值,也可以放小胡子中
    • 固定的某一个数值也是可以的,因为一个简单字符串、数字,就是最简单的表达式
    • if、switch、for循环等等这些不是表达式,是语句,不能在胡子语法里面书写
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>

    <div class="app">
        <!-- 任何有值的内容都是表达式 -->
        <p>{{ name }}</p>
        <p>{{ 1+2 }}</p>
        <p>{{ 2**2 }}</p>
        <p>{{ 1>2 }}</p>
        <p>{{ 2>1 ? "ok" : "bad" }}</p>
        <p>{{ "hello vue" * 2 }}</p>
        <!-- 函数调用也是一个值 -->
        <p>{{ "hello vue".toUpperCase() }}</p>
        <p>{{ address.toUpperCase() }}</p>
        <p>{{ if(true){ console.log("ok") } }}</p>
    </div>

    <script>
        let vm = new Vue({
            el:".app",
            // 在vue2中,data写成一个对象的形式也是OK的
            data:{
                // name age address是实例属性
                name:"wc",
                age:18,
                address:"bj"
            }
        });
    </script>
</body>
</html>

配置对象其他的写法

data的写法 和 $mount挂载
  • data第二种写法是函数的写法,data函数返回结果作为VM属性与属性值
  • 通过el可以使用VM实例与容器进行关联
  • $mount方法主要的作用是VM与容器进行关联【VM与容器关联第二种写法】, 传递的参数容器CSS选择器(字符串类型的)
  • $mount【挂载】:它是Vue.prototype原型的一个方法,VM可以调用。Vue类的实例方法一般都是以$开头的
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <h1>你好:{{ name }}</h1>
        <p>{{ age }} ==== {{sex}}</p>
    </div>

    <script>
        let vm = new Vue({
            // el:"#app",   // 作用:让vm实例与容器进行关联
            data(){
                return {
                    name:"wc",
                    age:18,
                    sex:"man"
                }
            }
        });
        console.log(vm);

        // $mount需要传递一个容器的CSS选择器(字符串类型)
        // $mount是Vue构造器的原型对象上的方法
        // 所以通过VM实例也可以调用
        vm.$mount("#app")

        // 总结vm实例和容器进行关联有两种方式
        //     1)通过el进行关联
        //     2)通过$mount进行关联
    </script>
</body>
</html>

vue开发者工具安装

二、响应式原理与事件绑定

响应式数据(属性)原理

  • 响应式数据【属性】: 在配置项data当中定义的属性,都是响应式数据,作为VM对象的响应式属性
  • 所谓的响应式是指数据变化,视图跟着变化
Vue2版本响应式数据实现原理
  • Vue框架中VM对象的响应式数据实现原理,利用的是Object.defineProperty实现的
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        
    </div>

    <script>
        // 问:下面的代码中有几个对象?
        // 答: 3个   vm叫实例对象   {}配置对象    data中返回一个对象 
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    msg:"hello vue"
                }
            }
        });
        // msg会挂载到vm实例上,作为vm的属性
        console.log(vm);

        // 在vm实例上,还有一个叫_data属性,它里面也包含了data中的数据
        // 之所以以_打头,表示不希望别人使用
        console.log(vm._data);

        // vm这个对对象 与 data函数调用返回的对象 是两个不同的对象
        console.log(vm.msg === vm._data.msg);
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        
    </div>

    <script>
       // vue2的响应式原理,靠之前的JS高级中的 Object.defineProperty

       let vm = {}; 
       let data = {};

       // 精细化设置属性  就是给data对象添加msg属性
       Object.defineProperty(data,"msg",{
            // 当获取属性时,自动走get,得到的值是,return后面的值
            get(){
                return vm.msg;
            },
            // 当给msg属性设置一个属性时,会走set,设置的新值会作为set的参数
            set(val){
                vm.msg = val;
                // 更新视图.....
            }
       })
       // 数据变了,我要更新视图,也就是说,数据变了,我肯定要知道
       // 当数据发生了变化,会走set,在set中可以更新视图...
       data.msg = "i love vue"
       console.log(data.msg);
    </script>
</body>
</html>

事件绑定

事件的绑定
  • 通过v-on指令来绑定事件,可以简写成 @事件名字,一般用简写的方式,用@替换。
  • 一个元素可以同时绑定多个事件,但是一般情况下只是绑定一个
  • VM对象方法的this问题, 方法不能书写箭头函数(箭头函数没有任何意义, 因为获取不到VM,获取不到响应式数据,获取到的是windows)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        .box{
            width: 200px;
            height: 200px;
            background-color: gold;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <h1>{{ msg }}</h1>
        <!-- 
            指令:说白了,就是标签的自定义属性 是以v-打头
            在vue中,通过指令来绑定事件
                v-on:click="handle"   给div绑定点击事件   click是点击事件
                v-on:事件名字  click  mouseenter  mouseout keyup....
                v-on:事件名字 = "事件处理函数(监听器)"
         -->
        <!-- <div class="box" v-on:click="handle"></div> -->
        <!-- v-on基本上不用,一般都使用简写   是@ -->
        <div class="box" @click="handle" @mouseover="over"></div>
        <!-- <button v-on:click="handle2">我是一个Button</button> -->
        <button @click="handle2">我是一个Button</button>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    msg:"hello vue"
                }
            },
            // methods中放模板中绑定的监听器
            // methods 加了s  
            // 下面的两个监听器,能不能挂载到vm实例上?
            methods:{
                // handle(){
                //     // console.log("handle...");
                //     // this表示vm实例  
                //     // console.log(this.msg);
                //     // msg是响应式数据,数据变了,界面要更新
                //     this.msg = "hi vue"
                // }
                // methods中的方法,不要写成箭头函数的形式
                // 是箭头函数,内部的this表示window


                // handle: ()=>{
                //     console.log(this);
                //     this.msg = "hi vue"
                // }

                handle:function(){
                    console.log(this);
                    this.msg = "hi vue"
                },

                handle2(){
                    console.log("点击了按钮~");
                },

                over(){
                    console.log("鼠标移入了~");
                }
            }
        });

        // 也在methos中的方法,也会挂载到vm实例上的
        vm.handle2()
    </script>
</body>
</html>
  • v-model指令:实现数据的双向绑定
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>

<body>

    <div id="app">
        <h2>{{name}}</h2>

        <!-- 
            在vue中指令都是以v-打头
            v-on指令:是用来绑定事件的 
            v-model指令:实现数据的双向绑定

            双向数据绑定:
                第一向:把data中的数据绑定到模板,如果data中的数据发生了变化,模板也会重新渲染,因为data中的数据是响应式数据
                第二向:通过模板(表单)修改了数据,Vue能监听到,监听到后,它会修改data中的数据。

        -->
        <input type="text" v-model="name">
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    name: "码路",
                }
            }
        });
    </script>

</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>

<body>

    <div id="app">
        <!-- 通过小胡子语法,可以把data中数据绑定到模板上,叫单向数据绑定 -->
        <!-- 通过小胡子,是把数据绑定到了文本节点处 -->
        <h2>{{name}}</h2>

        <!-- 能不能给属性节点也绑定一个动态的数据?答:可以  通过v-bind -->
        <!-- 使用小胡子,并不能给属性节点位置绑定动态数据 -->
        <!-- src="{{imgUrl}}"在小程序的语法中是OK,vue中不OK -->
        <!-- <img src="{{imgUrl}}" alt=""> -->

        <!-- 通过v-bind指令就可以给属性节点位置绑定动态数据 -->
        <!-- "imgUrl"不是字符串,不是字符串,不是字符串 -->
        <img v-bind:src="imgUrl" alt="">
        <!-- src="imgUrl"  "imgUrl"是字符中,是字符串,是字符串 -->
        <img src="imgUrl" alt="">

        <!-- v-bind:可以简写,简写成: -->
        <!-- 在真实开发中,v-bind都会简写,直接写: -->
        <!-- 加了: 和 不加: 区别非常大  加了: 表示绑定一个普通数据  不加:表示字符串 -->
        <img :src="imgUrl" alt="">
        <img src="imgUrl" alt="">

        <!-- v-bind叫单向数据绑定 -->
        <p title="msg">我一个孤独的P标签</p>
        <p :title="msg">我一个孤独的P标签</p>

        <!-- 
            单向绑定:
                1)通过小胡子  {{}}  把数据绑定到文本节点处
                2)通过 v-bind  把数据绑定到属性节点处  简写成:
         -->
        <!-- value 就可以给输入指定数据 -->
        <input type="text" value="hello vue">
        <!-- 由于我们在vlaue前面写了一个:  msg表示动态绑定的数据  单向绑定 -->
        <input type="text" :value="msg">
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    name: "码路",
                    imgUrl: "http://127.0.0.1:3000/1.png",
                    msg: "hello malu"
                }
            }
        });
    </script>

</body>

</html>
单项绑定
  1)通过小胡子  {{}}  把数据绑定到文本节点处
  2)通过 v-bind  把数据绑定到属性节点处  简写成:
双向绑定
  v-model指令:实现数据的双向绑定
  双向数据绑定:
      第一向:把data中的数据绑定到模板,如果data中的数据发生了变化,模板也会重新渲染,因为data中的数据是响应式数据
      第二向:通过模板(表单)修改了数据,Vue能监听到,监听到后,它会修改data中的数据。
传参
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 在绑定一个监听器时,()可以加也可以不加,加()的目的是为了传参 -->
        <!-- " getMsg() " 这一行代码在你眼中并不是一个值 -->
        <button @click=" getMsg(123) ">传递一个参数</button>

        <!-- @click=后面是跟了一个""  千万不要当成字符串,它仅仅是vue中的语法 -->
        <!-- "" 仅仅是vue的语法,在""放JS表达式 -->
        <button @click=" getMsg2(123,'i love you') ">传递二个参数</button>

        <!-- 没有加() 在监听器中,默认第一个参数,就是事件对象 -->
        <button @click=" getMsg3 ">获取事件对象方式一</button>

        <!-- 加()的目的是为了传参 -->
        <!-- 如果添加了(),监听器中的第1个参数,就不是事件对象了 -->
        <!-- 如果还想获取事件对象,那么需要手动的传递事件对象 $event -->
        <!-- 事件对象:阻止默认事件,阻止冒泡,鼠标位置,键盘码.... -->
        <button @click=" getMsg4($event) ">获取事件对象方式二</button>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            // data中放响应式数据
            data() {
                return {
                    // 也就是说,可以把监听器写在data中,
                    // 不要写在这里,不要写在这里
                    getMsg4(e) {
                        console.log(e);
                    }
                }
            },
            // methods中方法
            methods: {
                getMsg(a) {
                    console.log(a);
                    // console.log("getMsg...");
                },
                getMsg2(a, b) {
                    console.log(a, b);
                },
                getMsg3(e) {
                    console.log(e);
                },
                // getMsg4(e){
                //     console.log(e);
                // }
            }
        });
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>

<body>

    <div id="app">
        <h2>{{name}}</h2>
        <button @click="getMsg1">不传递参数(第1个参数就是事件对象)</button> <br>

        <!-- 
            "getMsg2(110)" 不会去调用这个函数, ""也不是字符串的引号
            "getMsg2(110)" 也不是字符串,不是字符串,不是字符串
            "getMsg2(110)" vue内部会自己处理 

            前面讲过:函数调用在你的眼中是一个值

         -->
        <button @click="getMsg2(110)">传递1个参数</button> <br>
        <button @click="getMsg3(110, 666)">传递2个参数</button> <br>

        <!-- 在模板中,$event就是事件对象,下面用时,需要把事件对象传递下去 -->
        <!-- 事件对象:阻止默认事件,阻止冒泡,鼠标位置,键盘码... -->
        <button @click="getMsg4(888, 666, $event)">传递2个参数和事件对象</button> <br>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    name: "码路",
                }
            },
            methods: {
                // 如果上面没有传递参数,第一个参数默认就是事件对象
                getMsg1(e) {
                    console.log(e);
                    console.log("getMsg...");
                },
                // 如果上面传递了一个参数,第1个参数就不是事件对象
                getMsg2(num) {
                    console.log(num);
                },
                // 如果上面传递了两个参数,需要通过两个形参来接收
                getMsg3(n1, n2) {
                    console.log(n1, n2);
                },
                // 如果上面传递参数了,下面的事件对象也需要上面传递过来
                // 如果又想传参,又想得到事件对象,需要手动把事件对象传递下去
                getMsg4(n1, n2, e) {
                    console.log(n1, n2, e);
                }
            }
        });
    </script>

</body>

</html>
事件修饰符

修饰符:

  • .stop - 调用 event.stopPropagation()。
  • .prevent - 调用 event.preventDefault()。
  • .capture - 添加事件侦听器时使用 capture 模式。
  • .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
  • .{keyAlias} - 仅当事件是从特定键触发时才触发回调。
  • .once - 只触发一次回调。
  • .left - 只当点击鼠标左键时触发。
  • .right - 只当点击鼠标右键时触发。
  • .middle - 只当点击鼠标中键时触发。
  • .passive - { passive: true } 模式添加侦听器
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        .box{
            width: 200px;
            height: 200px;
            background-color: skyblue;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- form表单有默认的提交事件 -->
        <!-- a标签也有默认事件 -->
        <form action="http://www.baidu.com">
            <!-- <input type="submit" value="登录"> -->
            <!-- @click.prevent 阻止默认事件 -->
            <button @click.prevent="showMsg">点我</button>
        </form>
        <hr>
        <div class="box" @click="showMsg2">
            <button @click.stop="showMsg3">阻止冒泡</button>
        </div>
        <hr>
        <!-- 
            once表示只处罚一次,用的不多
         -->
        <button @click.once="showMsg4">只触发一次</button>

        <!-- 
            事件的修饰符可以链式来写,谁先谁后无所谓,了解:
                @click.stop.prevent.once
                @click.once.stop.prevent
         -->
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                 
                }
            },
            methods:{
                showMsg(e){
                    console.log("showMsg....");
                    // e.preventDefault(); // 阻止默认事件
                },
                showMsg2(e){
                    console.log("showMsg2...");
                },
                showMsg3(e){
                    console.log("showMsg3...");
                    // e.stopPropagation()
                },
                showMsg4(){
                    console.log("showMsg4...");
                }
            }
        });
    </script>
</body>
</html>
与表单元素一起使用的修饰符
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- v-model叫指令,用来收集表单中的数据 -->
        <!-- "people" 不是字符串 -->
        <!-- input上面有一个value,表示输入框中的值 -->
        <!-- 当输入框中的值修改了,那么data中的值也会修改 -->

        <!-- 
            按键修饰符:
                .enter 回车键
                .left  左键
                .right  右键
                .up  上键
                .down  下键
                .esc
                .a
                .b
                .c
                .....
         -->
        请输入你喜欢的人的名字,按回车打印出来:<input type="text" v-model="people" @keyup.enter="handle">
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    people:"wc"
                }
            },
            methods:{
                handle(e){
                    // console.log(e);
                    // if(e.keyCode === 13){
                    //     console.log("你喜欢的人的名字是:", this.people);
                    // }

                    console.log("你喜欢的人的名字是:", this.people);
                }
            }
        });
    </script>
</body>
</html>

三、指令

  • 指令英文【directive】
  • Vue框架借鉴angular框架小技巧,指令概念。
  • Vue框架给咱们提供很多指令, Vue框架中的指令,实质就是给标签新增自定义属性,只不过指令【自定义属性】都是以v-xxxx
  • 自定义属性

v-bind指令

  • v-bind: Vue框架提供的一个指令, 它主要的作用是,可以给标签绑定动态属性,可以简写成
  • 动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。
  • v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值
  • {{}}给标签绑定动态文本,v-bind给标签绑定动态属性
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- {{}}只能写在文件节点处,不能写在属性节点处 -->
        <!-- <img src="{{url}}" alt=""> -->

        <!-- 这样写也不行,url是死的三个字母,并不是动态数据 -->
        <!-- <img src="url" alt=""> -->

        <!-- v-bind作用是用于把数据绑定到属性节点处 -->
        <!-- 如果写了v-bind "url"不再是字符串了  "url"是表达式了 -->
        <!-- <img v-bind:src="url" alt=""> -->

        <!-- v-bind可以简写成: -->
        <!-- <img :src="url" alt=""> -->

        <!-- "haha是字符串" -->
        <!-- <p title="haha"></p> -->

        <!-- <p :title="haha">我是一个P标签</p> -->

        <!-- <input type="text" :value="ok"> -->

        <a :href="name">{{name}}</a>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    url:"https://cn.vuejs.org/images/logo.svg",
                    haha:"lala",
                    ok:"ok666",
                    name:"wangcai"
                }
            }
        });
    </script>
</body>
</html>```
### v-once指令(了解)
- v-once用于指定元素或者组件只渲染一次
- 当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过
- 该指令可以用于性能优化
- 如果是子节点,也是只会渲染一次

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app"  v-once>
        <!-- v-once表示只会第一次渲染数据到模板中,后面数据变了,模板也不会刷新了 -->
        <!-- <h2 v-once>计数器:{{number}}</h2> -->
        <h2>计数器:{{number}}</h2>
        <button @click="increment">加1</button>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    number:0
                }
            },
            methods:{
                increment(){
                    this.number++
                }
            }
        });
    </script>
</body>
</html>

v-text指令(了解)

  • 用于更新元素的 textContent:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <h2>{{msg}}</h2>
        <!-- "msg" 不是字符串,不是字符中,不是字符串,不是字符串 -->
        <!-- {{}}是v-text的语法糖,说白了,就是简写形式 -->
        <!-- 基本上不用,一般使用{{}} -->
        <h2 v-text="msg"></h2>

        <hr>

        <!-- v-text并不会解析标签 -->
        <div v-text="msg2"></div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    msg:"hello vue",
                    msg2:"<p style='color:red;'>我是一个P标签</p>"
                }
            }
        });
    </script>
</body>
</html>

v-html指令

  • 默认情况下,如果我们展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析
  • 如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 如果绑定的内容是html标签字符串,使用v-html会解析这个字符串 -->
        <!-- 解析html标签字符串,也是有安全隐患的,html标签字符串不是被信任的,不要使用v-html  -->
        <div v-html="msg2"></div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    msg:"hello vue",
                    msg2:"<p style='color:red;'>我是一个P标签</p>"
                }
            }
        });
    </script>
</body>
</html>

v-pre指令(了解)

  • v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签
  • 跳过不需要编译的节点,加快编译的速度;
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <!-- v-pre是一个指令,了解,作用:跳过模板编译 -->
        <!-- 模板中有些标签,不需要编译,可以使用v-pre,跳过模板编译,性能就高了 -->
        <div v-pre>
            <h2>{{msg}}</h2>
            <p>{{count}}</p>
        </div>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    msg: "hello vue",
                    count: 10
                }
            }
        });
    </script>
</body>
</html>

v-cloak指令(了解)

  • 这个指令保持在元素上直到关联组件实例结束编译
  • 和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        [v-cloak]{
            display: none;
        }
    </style>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 需求:如果vm和模板没有关联好,不要去编译{{}} -->
        <h2 v-cloak>{{msg}}</h2>
    </div>

    <script>
        setTimeout(() => {
            let vm = new Vue({
                el: "#app",
                data() {
                    return {
                        msg: "hello vue"
                    }
                }
            });
        }, 3000);
    </script>
</body>
</html>

v-if指令

  • 在某些情况下,我们需要根据当前的条件决定某些元素或组件是否渲染,这个时候我们就需要进行条件判断了
  • Vue提供了下面的指令来进行条件判断:
    • v-if
    • v-else
    • v-else-if
    • v-show
  • v-if、v-else、v-else-if用于根据条件来渲染某一块的内容
    • 这些内容只有在条件为true时,才会被渲染出来
    • v-if是惰性的
    • 当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉
    • 当条件为true时,才会真正渲染条件块中的内容;
    • v-if每一次展示都是创建一个新的DOM, 每一次隐藏都需要移出、干掉DOM进行隐藏。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        button {
            margin-top: 100px;
            width: 400px;
            height: 50px;
        }
        .box{
            width: 400px;
            height: 200px;
            background: yellowgreen;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- v-if后面也是写表达式 -->
        <!-- " true " 不是字符串,不是字符串,不是字符串 -->
        <!-- v-if后面如果是真,就渲染DOM元素或组件 -->
        <!-- <h1 v-if=" true ">今天是08月08号,再过一段时间就9月份了</h1> -->

        <!-- v-if后面表达式只要转化成真,DOM元素或组件就会渲染 -->
        <!-- <h1 v-if=" 1+1 ">今天是08月08号,再过一段时间就9月份了</h1> -->

        <!-- v-if后面如果转化成假,这个DOM元素或组件压根不会渲染 -->
        <!-- <h1 v-if=" 1-1 ">今天是08月08号,再过一段时间就9月份了</h1> -->

        <button @click="handle">让盒子显示或隐藏</button>
        <!-- v-if可以控制一个DOM元素创建或销毁 -->
        <!-- 频繁地创建或销毁一个DOM元素,性能也很低 -->
        <!-- 换句话说,如果一个DOM元素,需要频繁地显示或隐藏,建议不要使用v-if -->
        <!-- v-if是惰性的  如果判断值本身就是false,压根就不会创建 -->
        <!-- 每一次显示,都是创建出一个新的DOM元素,每一隐藏都是移除,销毁掉这个DOM元素 -->
        <div class="box" v-if="show"></div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    // 响应式数据
                    show:true
                }
            },
            methods:{
                handle(){
                    this.show = !this.show
                }
            }
        });
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        button {
            margin-top: 100px;
            width: 400px;
            height: 50px;
        }
        .box{
            width: 400px;
            height: 200px;
            background: yellowgreen;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- v-if后面也是写表达式 -->
        <!-- " true " 不是字符串,不是字符串,不是字符串 -->
        <!-- v-if后面如果是真,就渲染DOM元素或组件 -->
        <!-- <h1 v-if=" true ">今天是08月08号,再过一段时间就9月份了</h1> -->

        <!-- v-if后面表达式只要转化成真,DOM元素或组件就会渲染 -->
        <!-- <h1 v-if=" 1+1 ">今天是08月08号,再过一段时间就9月份了</h1> -->

        <!-- v-if后面如果转化成假,这个DOM元素或组件压根不会渲染 -->
        <!-- <h1 v-if=" 1-1 ">今天是08月08号,再过一段时间就9月份了</h1> -->

        <button @click="handle">让盒子显示或隐藏</button>
        <!-- v-if可以控制一个DOM元素创建或销毁 -->
        <!-- 频繁地创建或销毁一个DOM元素,性能也很低 -->
        <!-- 换句话说,如果一个DOM元素,需要频繁地显示或隐藏,建议不要使用v-if -->
        <!-- v-if是惰性的  如果判断值本身就是false,压根就不会创建 -->
        <!-- 每一次显示,都是创建出一个新的DOM元素,每一隐藏都是移除,销毁掉这个DOM元素 -->
        <div class="box" v-if="show"></div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    // 响应式数据
                    show:true
                }
            },
            methods:{
                handle(){
                    this.show = !this.show
                }
            }
        });
    </script>
</body>
</html>
  • template元素
    • 因为v-if是一个指令,所以必须将其添加到一个元素上
    • 如果我们希望切换的是多个元素,此时我们渲染div,但是我们并不希望div这种元素被渲染,这个时候,我们可以选择使用template
    • template元素可以当做不可见的包裹元素,并且在v-if上使用,但是最终template不会被渲染出来,有点类似于小程序中的block
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <!-- <div id="app">
        <div v-if="number==1">
            <div>haha</div>
            <div>haha</div>
            <div>haha</div>
        </div>

        <div v-else-if="number==2">
            <div>xixi</div>
            <div>xixi</div>
            <div>xixi</div>
        </div>

        <div v-else>
            <div>hehe</div>
            <div>hehe</div>
            <div>hehe</div>
        </div>
    </div> -->

    <div id="app">
        <!-- template标签,也叫幽灵标签 -->
        <template v-if="number==1">
            <div>haha</div>
            <div>haha</div>
            <div>haha</div>
        </template>

        <template v-else-if="number==2">
            <div>xixi</div>
            <div>xixi</div>
            <div>xixi</div>
        </template>

        <template v-else>
            <div>hehe</div>
            <div>hehe</div>
            <div>hehe</div>
        </template>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    number: 1
                }
            }
        });
    </script>
</body>
</html>
  • v-show
  • v-show: 也是Vue框架提供一个指令, 可以让元素进行显示与隐藏?【功能与v-if类似】
  • v-show: 右侧属性一般需要的是布尔值|JS表达式
  • v-show是通过样式display实现元素的显示(block)与隐藏(none)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        *{
            margin: 0px;
            padding: 0px;
        }
        .box{
            width: 400px;
            height: 200px;
            margin: 150px 0px;
            background-color: gold;
            animation: donghua 2s linear 0s infinite;
        }
        /* 定义一个动画 */
        @keyframes donghua {
            from{
                transform: rotate(0deg);
            }
            to{
                transform: rotate(360deg);
            }
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <button @click="handle">通过v-show控制元素的显示与隐藏</button>
        <!-- v-show后面不是字符串 -->
        <div class="box" v-show="show"></div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    // 响应式数据
                    show:true
                }
            },
            methods:{
                handle(){
                    this.show = !this.show
                }
            }
        });

        // v-if和v-show的区别?
        //   v-if是控制元素的创建或销毁,每次创建出来的都是新的,创建或销毁DOM元素,很耗性能。
        //   v-if后面的条件是false,它是惰性的,DOM元素压根不会创建
        //   v-show后面的条件不管是true或false,DOM元素都会创建,它是通过display来控制元素的显示或隐藏
        //   v-show不支持template标签,v-if支持template标签
        //   v-if会导致浏览器的重排(重排一定会引起重绘),v-show导致浏览器的重绘。
        //   如果需要频繁控制元素的显示或隐藏,那么使用v-show,不频繁,也可以使用v-if,尽可能使用v-show
    </script>
</body>
</html>
  • v-show和v-if的区别

    • v-show和v-if的用法看起来是一致的,也是根据一个条件决定是否显示元素或者组件:
    • v-show是不支持template
    • v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有存在的,只是通过CSS的display属性来进行切换
    • v-if当条件为false时,其对应的原生压根不会被渲染到DOM中
    • v-if直接操作DOM创建与销毁,频繁的操作DOM,很耗性能的。
    • 如果我们的原生需要在显示和隐藏之间频繁的切换,那么使用v-show
    • 如果不会频繁的发生切换,那么使用v-if
    • v-if与v-show在控制元素的显示与隐藏的时候,尽可能使用v-show
  • 为什么做元素的显示与隐藏的效果,使用v-show性能会优化一些那?

  • v-if:通过创建DOM、移出DOM完成元素的显示与隐藏,会导致浏览器重排。

  • v-show: 通过样式display实现元素的显示与隐藏,会导致浏览器重绘。

  • 虽然重绘、重排都消耗电脑的性能,但是重绘消耗的性能更少一些。

  • v-show与v-if选择的时候,尽可能使用v-show

v-model指令

  • 表单提交是开发中非常常见的功能,也是和用户交互的重要手段

    • 比如用户在登录、注册时需要提交账号密码
    • 比如用户在检索、创建、更新信息时,需要提交一些数据
  • v-model可以用来收集表单中的数据

  • v-model指令可以在表单 input、textarea以及select元素上创建双向数据绑定

  • 它会根据控件类型自动选取正确的方法来更新元素

  • 尽管有些神奇,但 v-model 本质上不过是语法糖,它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 不使用v-model,实现双向绑定 -->
        <!-- <input type="text" :value="msg" @input="inputChange"> -->

        <!-- 在vue中,如果监听器中的代码就一行,可以写在模板中,不建议使用 -->
        <input type="text" :value="msg" @input="msg = $event.target.value">

        <hr>

        <!-- v-model就是一个语法糖: :value + @input -->
        <!-- <input type="text" v-model="msg"> -->

        <!-- 模拟登录 -->
        <label for="account">
            账号:<input type="text" id="account" v-model="account">
        </label>
        <label for="pwd">
            密码:<input type="password" id="pwd" v-model="pwd">
        </label>
        <button @click="loginClick">登录</button>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    msg:"i love vue",
                    account:"",
                    pwd:""
                }
            },
            methods:{
                inputChange(e){
                    this.msg = e.target.value
                },
                loginClick(){
                    console.log("用户名:",this.account);
                    console.log("密码:",this.pwd);
                    // axios
                    console.log("发送网络请求,实现登录....");
                }
            }
        });
    </script>
</body>
</html>
  • v-model的原理
  • 官方有说到,v-model的原理其实是背后有两个操作
  • v-bind绑定value属性的值
  • v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中
<input v-model="searchText" />
等价于:
<input :value="searchText" @input="searchText = $event.target.value" />

  • v-model绑定textarea
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <textarea cols="30" rows="10" v-model="content"></textarea>
        <p>输入的内容:{{content}}</p>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    content:""
                }
            }
        });
    </script>
</body>
</html>
  • v-model绑定checkbox
    • v-model绑定checkbox:单个勾选框和多个勾选框
      单个勾选框:v-model即为布尔值
    • 此时input的value属性并不影响v-model的值
    • 多个复选框: 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组
    • 当选中某一个时,就会将input的value添加到数组中

单选
<!-- 此时v-model就是 :checked和@chaneg的语法糖 -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- checkbox可以作为一个是否同意的单选框 -->
        <label for="agree">
            <!-- 单个选框(是否同意按钮) v-model的值是布尔值 -->
            <input type="checkbox" id="agree" v-model="isAgree"> 同意xxx协议
        </label>
        <h2>单选框:{{isAgree}}</h2>

        <hr>

        <!-- checkbox多选框:v-model绑定的就是一个数组了 -->
        <!-- 每一个input上,必须有一个value -->
        <div class="hobbies">
            <h2>请选择你的爱好:</h2>
            <label for="sing">
                <!-- 单个选框(是否同意按钮) v-model的值是布尔值 -->
                <input type="checkbox" id="sing" v-model="hobbies" value="sing"> 唱歌
            </label>
            <label for="coding">
                <input type="checkbox" id="coding" v-model="hobbies" value="coding"> 打代码
            </label>
            <label for="basketball">
                <input type="checkbox" id="basketball" v-model="hobbies" value="basketball"> 打篮球
            </label>
        </div>

        <h2>爱好:{{hobbies}}</h2>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    isAgree:false,
                    hobbies:[]
                }
            }
        });
    </script>
</body>
</html>
  • v-model绑定radio
    • v-model绑定radio,用于选择其中一项
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <div class="gender">
            <label for="male">
                <!-- v-model收集的也是value值 -->
                <input id="male" name="sex" type="radio" v-model="gender"  value="male"></label>
            <label for="female">
                <input id="female" name="sex" type="radio" v-model="gender" value="female"></label>
            <h2>性别: {{gender}}</h2>
        </div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    gender:"female"
                }
            }
        });
    </script>
</body>
</html>
  • v-model绑定select
    • 和checkbox一样,select也分单选和多选两种情况
    • 单选:只能选中一个值,v-model绑定的是一个值
    • 多选:可以选中多个值,v-model绑定的是一个数组
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- select的单选 -->
        <!-- v-model需要写在select身上 -->
        <select v-model="fruit">
            <option value="apple">苹果</option>
            <option value="orange">橘子</option>
            <option value="banana">香蕉</option>
        </select>
        <h2>单选:{{fruit}}</h2>

        <hr>

        <!-- multiple 多选 -->
        <!-- size=3 表示可视区中显示几个选项 -->
        <!-- 收集多个,v-model绑定的就是一个数组 -->
        <select multiple size="3" v-model="fruit2">
            <option value="apple">苹果</option>
            <option value="orange">橘子</option>
            <option value="banana">香蕉</option>
        </select>
        <h2>多选:{{fruit2}}</h2>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    fruit:"banana",
                    fruit2:["apple"]
                }
            }
        });
    </script>
</body>
</html>

v-model修饰符

  • lazy

    • 默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定的属性进行同步
    • 如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车)才会触发
  • number

    • 将绑定的字符串类型,转换为数字类型
  • trim

    • 自动过滤用户输入的首尾空白字符,可以给v-model添加 trim 修饰符
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- lazy 把input事件切换成change事件 -->
        <input type="text" v-model.lazy="msg">
        <h2>msg:{{msg}}</h2>

        <hr>

        <input type="text" v-model.number="number">
        <h2>number:{{number}} --- {{ typeof number }}</h2>

        <hr>

        <input type="text" v-model.trim="str">
        <h2>str:{{str}} --- {{ typeof str }}</h2>

        <hr>

        <!-- 多外修改符是可以连用的 -->
        <input type="text" v-model.lazy.number.trim="str">
        <h2>str:{{str}} --- {{ typeof str }}</h2>

    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    msg:"I love Vue~",
                    // 数据  ==>  状态
                    number:0,
                    str:"hello vue"
                }
            }
        });
    </script>
</body>
</html>

v-for指令

  • 在真实开发中,我们往往会从服务器拿到一组数据,并且需要对其进行渲染。可以使用v-for来完成

    • v-for的基本格式是 “item in 数组”
    • 数组通常是来自data或者prop,也可以是其他方式
    • 如果我们需要索引,可以使用格式: “(item, index) in 数组”
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <ul>
            <!-- v-for写在谁的身上,最终就遍历出多少个谁 -->
            <!-- v-for="(item,index) in data" -->
            <li v-for="(item,index) in students">
                {{ item.name }}  ---  {{ index }}
            </li>
        </ul>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    students: [{
                        id: 1,
                        name: "貂蝉"
                    },
                    {
                        id: 2,
                        name: '妲己'
                    },
                    {
                        id: 3,
                        name: '大桥'
                    },
                    {
                        id: 4,
                        name: '小乔'
                    },
                    {
                        id: 5,
                        name: '周瑜'
                    }
                }
            }
        });
    </script>
</body>
</html>
  • v-for也支持遍历对象

  • 一个参数: “value in object”;

  • 二个参数: “(value, key) in object”;

  • 三个参数: “(value, key, index) in object”;

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <h2>遍历对象</h2>
        <p v-for="value in obj">
            {{value}}
        </p>

        <hr>

        <p v-for="(value,key) in obj">
            {{value}} --- {{key}}
        </p>

        <hr>

        <p v-for="(value,key,index) in obj">
            {{value}} --- {{key}} --- {{index}}
        </p>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    obj: {
                        name: "wangcai",
                        age: 18,
                        sex: '男'
                    }
                }
            }
        });
    </script>
</body>
</html>
  • v-for同时也支持数字的遍历

    • 每一个item都是一个数字;
  • v-for也可以遍历其他可迭代对象(Iterable)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <h2>遍历数字</h2>
        <p v-for="(num,index) in 5">
            {{num}} --- {{index}}
        </p>

        <hr>

        <h2>遍历字符串</h2>
        <p v-for="(str,index) in 'ILoveVue'">
            {{str}} --- {{index}}
        </p>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                 
                }
            }
        });
    </script>
</body>
</html>
  • v-for与ref综合
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        ul {
            width: 1000px;
            height: 50px;
            list-style: none;
            display: flex;
            margin: 10px auto;
            border: 1px solid yellowgreen;
        }

        li {
            flex: 1;
            text-align: center;
            line-height: 50px;
        }
    </style>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <ul>
            <!-- 在vue中,也是可以获取DOM元素的,通过ref,类似于ID-->
            <!-- 现在,你的肉眼看到的是一个li,但是实际是有N个li,每一个li都有对应的索引 -->
            <li v-for="(item,index) in arr" @click="handle(index)" :ref="index">
                {{item.title}} ---- {{index}}
            </li>
        </ul>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    arr: [
                        { id: 1, title: 'new' },
                        { id: 2, title: '游戏' },
                        { id: 3, title: '直播' },
                        { id: 4, title: '财经' },
                        { id: 5, title: '汽车' },
                        { id: 6, title: '娱乐' },
                        { id: 7, title: '生活' },
                        { id: 8, title: '感情' },
                        { id: 9, title: '购物' },
                        { id: 10, title: '美食' },
                    ]
                }
            },
            methods:{
                handle(index){
                    // console.log("index:",index);
                    // console.log(this.$refs[index][0]);
                    this.$refs[index][0].style.background = "gold";
                }
            }
        });
    </script>
</body>
</html>
  • Tab选项卡
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .tab-box{
            box-sizing: border-box;
            margin: 20px auto;
            width: 300px;
        }
        .tab-box .tab{
            position: relative;
            top: 1px;
            display: flex;
        }
        .tab-box .tab li{
            list-style: none;
            cursor: pointer;
            border: 1px solid #ddd;
            background-color: #eee;
            padding: 5px 10px;
            margin-right: 10px;
        }
        .tab-box .tab li.active{
            color: lightcoral;
            background-color: #fff;
            border-bottom-color: #fff;
        }
        .tab-box .con{
            display: none;
            box-sizing: border-box;
            padding: 10px;
            height: 100px;
            border: 1px solid #ddd;
        }
        .tab-box .con.active{
            display: block;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app" class="tab-box">
        <ul class="tab">
            <!-- 动态绑定class还没有讲 -->
            <li v-for="(item,index) in list" :class="{ active: selected === index }" @click="change(index)">{{item.title}}</li>
        </ul>
        <div v-for="(item,index) in list" class="con" :class="{ active: selected === index }">
            {{item.content}}
        </div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    list: [
                        { id: 1, title: "音乐", content: "成都-赵雷" },
                        { id: 2, title: "电影", content: "新蝙蝠侠" },
                        { id: 3, title: "电视剧", content: "甄嬛传" },
                    ],
                    selected:0
                }
            },
            methods:{
                change(index){
                    if(index === this.selected) return;
                    this.selected = index;
                }
            }
        });
    </script>
</body>
</html>

四、计算属性,侦听器

计算属性computed

复杂data的处理方式(计算属性)

在模板中可以直接通过插值语法显示一些复杂data中的数据

  • 对多个data数据进行运算、三元运算符来决定结果、数据进行某种转化后显示
  • 在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算
  • 在模板中放入太多的逻辑会让模板过重和难以维护
  • 如果多个地方都使用到,那么会有大量重复的代码

解决模板中放复杂data

  • 将逻辑抽取到一个method中,放到methods的options中
  • 但是,这种做法有一个直观的弊端,就是所有的data使用过程都会变成了一个方法的调用
  • 另外一种方式就是使用计算属性computed

计算属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        请输入你的姓:<input type="text" v-model="firstName"> <br>
        请输入你的名:<input type="text" v-model="lastName"> <br>
        <!-- vue官方不推荐在{{}}语法中做运算 -->
        <!-- 如果有复杂运算,建议把复杂运算放到计算属性中 -->
        <!-- <p>全名:{{ firstName + lastName }}</p> -->

        <p>全名:{{ allName() }}</p>
        <p>全名:{{ allName() }}</p>
        <p>全名:{{ allName() }}</p>
        <p>全名:{{ allName() }}</p>
        
        <hr>

        <!-- 计算属性写的时候,是函数的形式,用的时候是普通属性的形式 -->
        <!-- 计算属性不能加()去调用 -->
        <!-- 在模板中,第1次使用计算属性时,会执行计算属性对应的函数 -->
        <!-- 后面如果计算属性依赖的数据没有发生变化,计算属性会缓存起来 -->
        <p>全名:{{ allName2 }}</p>
        <!-- 下面使用计算属性,都是从缓存中取出来的属性,性能高 -->
        <p>全名:{{ allName2 }}</p>
        <p>全名:{{ allName2 }}</p>
        <p>全名:{{ allName2 }}</p>
    </div>
    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    firstName:"wang",
                    lastName:"Cai"
                }
            },
            methods:{
                // 把复杂运算放到方法中,也是OK的
                allName(){
                    console.log("我是方法....");
                    return this.firstName + this.lastName
                }
            },
            computed:{
                // 计算属性:根据已有的状态,计算出一个新的状态
                // 状态 ==> 数据

                // 最常见的形式,也是写成函数的形式
                // 但是实际上是一个属性值
                allName2(){
                    console.log("我是计算属性....");

                    // 计算属性的值,取决于函数的返回值
                    // return 123;

                    // 当计算属性依赖的数据变化了,计算属性会重新计算
                    // return this.firstName + this.lastName;

                    // 在计算属性中尝试写异步代码?
                    // 答:计算属性不能写异步代码。如:定时器,ajax,事件绑定...
                    // setTimeout(()=>{
                    //     return this.firstName + this.lastName;
                    // },2000)

                    // 在计算属性中,this表示vm
                    // console.log(this);
                    return this.firstName + this.lastName;
                }
            }
        });
        console.log(vm);
    </script>
</body>
</html>

完整写法

  • 计算属性还有另外一种写法:第一种写法->函数写法
  • 计算属性还有第二种写法:第二种写法->对象
  • 注意:计算属性,一般都是简写方式,很少使用完成写法!!!
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        请输入你的姓:<input type="text" v-model="firstName"> <br>
        请输入你的名:<input type="text" v-model="lastName"> <br>

        <p>全名:{{ allName }}</p>

        <button @click="updateName">修改名字为:李-四</button>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    firstName:"Wang",
                    lastName:"Cai"
                }
            },
            methods:{
                updateName(){
                    // 计算属性allName也会挂载到vm上的
                    this.allName = "李-四"
                }
            },
            computed:{
                // allName(){
                //     return this.firstName + this.lastName;
                // }

                // 计算属性的另一种写法(用的不多) 对象的形式
                allName:{
                    // 当在模板中使用计算属性时,会自动走get
                    get(){
                        return this.firstName + "-" + this.lastName;
                    },
                    // 当修改计算属性时,会走set,但是一般情况下,不会修改计算属性
                    set(val){
                        // val是计算属性的新值
                        // console.log("set...");

                        // console.log(val);
                        let arr = val.split("-")
                        this.firstName = arr[0]
                        this.lastName = arr[1]
                    }
                }
            }
        });
    </script>
</body>
</html>

购物车案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        table,
        tr,
        td {
            border: 1px solid skyblue;
            border-collapse: collapse;
        }

        td {
            width: 150px;
            height: 50px;
            text-align: center;
            line-height: 50px;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 使用表格制作购物车 -->
        <table>
            <tr>
                <th>商品名称</th>
                <th>单价</th>
                <th>数量</th>
                <th>总价</th>
            </tr>
            <tr v-for="(item,index) in cart">
                <td>{{item.title}}</td>
                <td>{{item.price}}</td>
                <td>
                    <button @click="minus(item)">减一</button>
                    {{item.count}}
                    <button @click="add(item)">加一</button>
                </td>
                <td>{{ item.price * item.count }}</td>
            </tr>
        </table>
        <h2>所有商品总价:{{total}}</h2>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    cart: [{
                        id: 1,
                        title: "手机",
                        price: 1999,
                        count: 1
                    },
                    {
                        id: 2,
                        title: "电脑",
                        price: 9992,
                        count: 1
                    },
                    {
                        id: 3,
                        title: "衣服",
                        price: 29,
                        count: 1
                    },
                    {
                        id: 4,
                        title: "洗衣机",
                        price: 5321,
                        count: 1
                    },
                    {
                        id: 5,
                        title: "篮球",
                        price: 19,
                        count: 1
                    },
                    {
                        id: 6,
                        title: "球杆",
                        price: 2999,
                        count: 1
                    },
                ]
                }
            },
            computed:{
                total(){
                    // 得到购物车中所有商品的单价*数量
                    // 第一种方式
                    // 遍历
                    // let sum = 0;
                    // for(let i=0; i<this.cart.length; i++){
                    //     sum += this.cart[i].price * this.cart[i].count;
                    // }
                    // return sum;

                    // 第二种方式
                    // let sum = 0;
                    // this.cart.forEach(item=>{
                    //     sum += item.price * item.count;
                    // })
                    // return sum;

                    // 第三种方式
                    return this.cart.reduce((prev,next)=>prev+next.price*next.count,0)
                }
            },
            methods:{
                minus(item){
                    if(item.count > 1){
                        item.count -= 1;
                    }
                },
                add(item){
                    item.count += 1;
                },
            }
        });
    </script>
</body>
</html>
计算属性 vs methods
  • methods它主要的作用是给VM实例添加方法----方法
  • computed:它是利用已有的属性与属性值创建出一个新的属性与属性值----属性
  • 区别1: methods方法在使用的时候一般需要加上小括号, 计算出来的属性, 在使用的时候是不需要加小括号的
  • 区别2:计算属性算出来的数值有缓存机制, 计算出一个可以多次使用

1)methods使用时一般加上小括号,计算属性不需要加小括号
2)vm可以代理methods中的所有的方法,vm也可以代理computed中所有的计算属性
3)计算属性是有缓存的,第1次使用时,会计算,会把计算的结果放到缓存中,
后面再去使用计算属性,直接从缓存中获取结果,当计算属性依赖的数据发生了变化
才会重新计算。
4)方法是每使用一次,就会调用一次
5)计算属性中不能写异步代码,计算属性有两种写法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <h2>{{ showMsg() }}</h2>
        <h2>{{ showMsg() }}</h2>
        <h2>{{ showMsg() }}</h2>
        <h2>{{ showMsg() }}</h2>
        <h2>{{ showMsg() }}</h2>
        <hr>
        <h2>{{ showTotal }}</h2>
        <h2>{{ showTotal }}</h2>
        <h2>{{ showTotal }}</h2>
        <h2>{{ showTotal }}</h2>
        <h2>{{ showTotal }}</h2>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                 
                }
            },
            methods:{
                showMsg(){
                    console.log("我是方法");
                    return "method"
                }
            },
            computed:{
                showTotal(){
                    console.log("我是计算属性");
                    return "computed"
                }
            }
        });
        console.log(vm);
    </script>
</body>
</html>

侦听器watch

侦听器

监听VM响应式属性写法

  • 第一种函数写法
  • 第二种对象写法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        请输入搜索的关键字:<input type="text" placeholder="关键字" v-model="keyword">
    </div>
    <!-- 
        开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中, 当数据变化时,template会自动进行更新来显示最新的数据, 但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了 -->
    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    keyword:""
                }
            },
            watch:{
                // 表示侦听keyword  如果keyword发生了变化  就可以被侦听到
                // 函数的写法
                // keyword(){
                //     console.log("我侦听到了keyword的变化");
                // }

                // 对象的写法
                keyword:{
                    // 对象的写法,需要在对象中写一个handler
                    // 当keyword发生了变化,会自动执行handler
                    // hander名字不能随便写
                    handler(){
                        console.log("我侦听到了keyword的变化");
                    }
                }
            }
        });
    </script>
</body>
</html>
侦听器watch的配置选项

默认情况下,watch只是在侦听第1层数据,对于内部属性的变化是不会做出响应的

  • 这个时候我们可以使用一个选项deep进行更深层的侦听
  • 注意前面我们说过watch里面侦听的属性对应的也可以是一个Object
  • watch监听, 监听的是VM身上响应式属性的属性值的变化->VM身上的属性

另外一个属性,是希望一开始的就会立即执行一次

  • 使用immediate选项
  • 无论后面数据是否有变化,侦听的函数都会有限执行一次
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <button @click="a.b.count++">+</button>
        <span>{{a.b.count}}</span>
        <button @click="a.b.count--">-</button>
        <h3>{{a.b.msg}}</h3>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    a: {
                        b: {
                            count: 1,
                            msg: '我爱你'
                        }
                    }
                }
            },
            watch:{
                // 侦听a这个响应式数据  函数的写法
                // a(){
                //     console.log("i love u");
                // }

                // 如果想侦听到对象中所有的属性,需要写成对象的形式
                a:{
                    deep:true, // 尝试侦听
                    immediate:true, // 一上来,先执行一次侦听器
                    handler(){
                        // console.log("i love u");

                        // 在侦听器中可以写异步代码
                        setTimeout(()=>{
                            this.a.b.msg = "lalala"
                        },3000)
                    }
                }
            }
        });
    </script>
</body>

</html>

计算属性与侦听器有什么区别?
1)计算属性有缓存,侦听器没有缓存
2)计算属性不支持异步,侦听器支持异步
3)计算属性需要返回值,侦听器不需要返回值
4)计算属性的值不能在data中定义,侦听器需要在data中定义

案例

百度搜索
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        请你输入搜索的关键字: <input type="text" v-model="keyword.a.b">
        <ul>
            <li v-for="(item,index) in arr">
                {{item.q}}
            </li>
        </ul>
    </div>

    <script>
        // https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&wd=nba&req=2&csor=1&cb=cb&_=1648605785333
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    keyword:{
                        a:{
                            b:"vue"
                        }
                    },
                    arr:[]
                }
            },
            watch:{
                keyword:{//监听ketword,即输入的值
                    deep:true,
                    immediate:true,

                    handler(){
                        let that = this;//使arr指向vm
                        window.cb = function(data){
                            console.log("data:",data.g);
                            // this.arr = data.g
                            that.arr = data.g;
                        }

                        let script = document.createElement("script");
                        script.src = `https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&wd=${this.keyword.a.b}&req=2&csor=1&cb=cb&_=1648605785333`;
                        document.body.prepend(script);
                    }
                }
            }
        });
    </script>
</body>
</html>
购物车
const books = [{
        id: 1,
        name: '《精通Vue》',
        date: '2022',
        price: 85.00,
        count: 1
    },
    {
        id: 2,
        name: '《小程序实战教程》',
        date: '2022-2',
        price: 59.00,
        count: 1
    },
    {
        id: 3,
        name: '《React从入门到精通》',
        date: '2022-10',
        price: 39.00,
        count: 1
    },
    {
        id: 4,
        name: '《算法之美》',
        date: '2022-3',
        price: 128.00,
        count: 1
    },
    {
        id: 5,
        name: '《图解HTTP》',
        date: '2022-8',
        price: 88.00,
        count: 1
    },
]
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <script src="./data/data.js"></script>
    <style>
        table {
            border-collapse: collapse;
            /* text-align: center; */
        }

        thead {
            background-color: #f5f5f5;
        }

        th,
        td {
            border: 1px solid #aaa;
            padding: 8px 16px;
        }

        .active {
            background-color: skyblue;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <template v-if="books.length">
            <table>
                <thead>
                    <tr>
                        <td>序号</td>
                        <td>书籍名称</td>
                        <td>出版日期</td>
                        <td>价格</td>
                        <td>购物数量</td>
                        <td>操作</td>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(item,index) in books" :class="{active:index === currentIndex}" @click="rowClick(index)">
                        <td>{{ index + 1 }}</td>
                        <td>{{ item.name }}</td>
                        <td>{{ item.date }}</td>
                        <td>{{ formatPrice(item.price) }}</td>
                        <td>
                            <button :disabled="item.count<=1" @click.stop="decrement(index,item)">-</button>
                            {{item.count}}
                            <button @click.stop="increment(index,item)">+</button>
                        </td>
                        <td>
                            <button @click.stop="removeBook(index,item)">删除</button>
                        </td>
                    </tr>
                </tbody>
            </table>
            <h2>总价:{{ formatPrice(totalPrice) }}</h2>
        </template>
        <template v-else>
            <h1>购物车空空如也,请添加你喜欢图书!</h1>
        </template>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    books:books,
                    currentIndex:-1
                }
            },
            computed:{
                totalPrice(){
                    return this.books.reduce((prev,next)=>prev + next.price*next.count,0)
                }
            },
            // 过滤器,在Vue3中淘汰了
            // 对数据进行过滤,进行格式化
            // filters: {
            //     // 也是定义成函数的形式
            //     //  <td td > {{ item.price | formatPrice }}</td >
            //     formatPrice(val) {
            //         // return 666;
            //         return "¥" + val
            //     }
            // },
            methods:{
                formatPrice(price){
                    return "¥"+price
                },
                decrement(index,item){
                    item.count--
                },
                increment(index,item){
                    item.count++
                },
                removeBook(index,item){
                    this.books.splice(index,1)
                },
                rowClick(index){
                    this.currentIndex = index;
                }
            }
        });
    </script>
</body>
</html>

当调用下面的7个API时,模板也会重新渲染,因为这7个API可以改变原数组
// push()
// pop()
// shift()
// unshift()
// splice()
// sort()
// reverse()

五、动态类名与行内样式

绑定class

绑定class有两种方式
  • 第一种写法:动态类名支持单个状态写法
  • 第二种写法:对象的写法
  • 第三种写法:数组的写法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        *{
            margin: 0px;
            padding: 0px;
        }
        .box{
            width: 500px;
            height: 500px;
            background-color: gold;
        }
        .box1{
            font-size: 30px;
        }
        .ok{
            width: 200px;
            height: 200px;
            background-color: skyblue;
            color: green;
        }
        .bad{
            width: 200px;
            height: 200px;
            background-color: pink;
            color: red;
        }
        .box3{
            cursor: pointer;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 现在两个类名都是写死的 -->
        <!-- <div class="box box1">我是一个孤独的DIV</div> -->

        <!-- 动态类名的写法  box2是一个状态 -->
        <!-- 在一个标签上,即可以写动态类型,也可以写非动态类型 -->
        <div :class="box2" class="box3">动态类名-单个状态的写法</div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    box2:"bad"
                }
            }
        });
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        .one{
            color: red;
        }
        .two{
            background-color: gold;
        }
        .three{
            font-size: 18px;
        }
        .four{
            cursor: pointer;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 动态类名 对象的定法
            :class后面跟一个对象  { K:V }
        -->
        <div :class="obj">
            千里之行,始于足下。——老子
        </div>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    obj:{
                        // 键表示类名  值表示是否有这个类
                        one:true,
                        two:true,
                        three:false,
                        four:0
                    }
                }
            }
        });
    </script>
</body>
</html>

当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue(当数组元素是对象时,可以直接修改。)
当你修改数组的长度时,例如:vm.items.length = newLength
由于 JavaScript 的限制,Vue不能检测对象属性的添加或删除(添加用$set(),删除一个响应式数据,使用$delete
vm.obj.three = true; 不能让模板重新渲染
给obj添加一个属性,也期望这个数据也是响应式的
通过打点形式去添加,不OK
// vm.$set() 动态地添加一个响应式数据
// vm.$set(vm.obj,"three",true)
删除一个响应式数据,使用$delete
// vm.$delete(vm.obj,"color","red")
修改数组
this.$set(原数组, 索引值, 需要赋的值)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        .one{
            color: red;
        }
        .two{
            background-color: gold;
        }
        .three{
            font-size: 18px;
        }
        .four{
            cursor: pointer;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <div :class="['one','two','three']">
            精诚所至,金石为开。——蔡锷
        </div>
        <hr>
        <p :class="arr">
            成功的秘诀,在永不改变既定的目的
        </p>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    arr:["one","two"]
                }
            }
        });
        // 总结动态类名:
        //    1):class="box1"   就可以操作box1这个状态了
        //    2):class="obj"  obj:{ a:true,b:true,c:false }   有a和b类,没有c类  操作obj对象
        //    3):class="arr"   arr:[a,b,c]  操作数组
    </script>
</body>
</html>

菜单的制作

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        ul{
            width: 150px;
            list-style-type: none;
            background-color: gold;
        }
        li{
            width: 100%;
            height: 50px;
            text-align: center;
            line-height: 50px;
            border-bottom: 1px solid white;
            color: red;
        }
        .active{
            background-color: skyblue;
        }
    </style>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <ul>
            <li v-for="(item,index) in list" @click="hander(index)" :class="{active:currentIndex===index}">
                {{item.title}} -- {{index}}
            </li>
        </ul>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    list: [
                        { id: 1001, title: '母婴' },
                        { id: 1002, title: '家电' },
                        { id: 1003, title: '医用' },
                        { id: 1004, title: '手机' },
                        { id: 1005, title: '直播' },
                        { id: 1006, title: '游戏' },
                        { id: 1007, title: '酒水' },
                        { id: 1008, title: '娱乐' },
                        { id: 1009, title: '美食' },
                        { id: 1010, title: '财经' }
                    ],
                    // 存储触摸的li
                    currentIndex:-1
                }
            },
            methods:{
                hander(index){
                    console.log(index);
                    this.currentIndex = index;
                }
            }
        });
    </script>
</body>
</html>

绑定style

动态行内样式语法

CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 写死的行内样式,不是动态的行内样式 -->
        <!-- <div style="width: 300px; height: 100px; background: gold;">
            任何人都应当有自尊心,自信心,独立性,不然就是奴才
        </div> -->

        <!-- :style后面有两种写法:对象的写法 -->
        <!-- 如果css属性中包含- 需要转成小驼峰的写法 -->
        <div :style="{width:'200px', height:'200px', backgroundColor:'red'}">
            任何人都应当有自尊心,自信心,独立性,不然就是奴才
        </div>
        <hr>
        <p :style="obj">
            任何人都应当有自尊心,自信心,独立性,不然就是奴才
        </p>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    // vm.obj.color = "red" 通过这种形式添加的数据并不是响应式数据
                    // 数据也确实添加上去了
                    // 不是响应式数据,就意味着页面不会更新
                    // 添加color不是响应式数据
                    obj:{
                        width:"200px",
                        height:"200px",
                        // vm.obj.background = "gold"   修改响应式数据,模板会更新
                        background:"skyblue"
                    }

                    // 后期想给obj上添加响应式数据,如何添加?
                    // 答:$set

                    // 删除一个响应式数据,使用$delete
                }
            }
        });
        vm.$set(vm.obj,"color","red")
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- <p :style="[{color:'red',background:'skyblue'},{height:'300px'}]">
            任何人都应当有自尊心,自信心,独立性,不然就是奴才
        </p> -->
        <hr>
        <!-- <p :style="[obj1,obj2]">
            任何人都应当有自尊心,自信心,独立性,不然就是奴才
        </p> -->
        <hr>
        <p :style="arr">
            任何人都应当有自尊心,自信心,独立性,不然就是奴才
        </p>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    obj1:{color:'red',background:'skyblue'},
                    obj2:{height:'300px'},
                    arr:[
                        {color:'red',background:'skyblue'},
                        {height:'300px'},
                    ]
                }
            }
        });

        // 总结动态的行内样式
        //   1):style="对象"  对象也可以放到状态中   操作状态   重点掌握对象的写法
        //   2):style="数组"  了解
    </script>
</body>
</html>

动态行内样式
第一种写法:对象的写法
<p :style="obj"> xxx</p>
obj:{
width:“200px”,
height:“200px”
}
第二种写法:数组的写法
<p :style="arr"> xxx</p>
arr:[
{color:‘red’,background:‘skyblue’},
{height:‘300px’},
]

改变字体

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- <button @click="handle">点击放大P标签中的字体大小</button> -->
        <button @click="fs+=5">点击放大P标签中的字体大小</button>
        <p :style="{fontSize: fs+'px'}">任何人都应当有自尊心,自信心,独立性,不然就是奴才</p>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    fs:16
                }
            },
            methods:{
                handle(){
                    this.fs += 5
                }
            }
        });
    </script>
</body>
</html>

调色板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        *{
            margin:0;
            padding: 0;
        }
        .box{
            width: 400px;
            height: 200px;
            background-color: #000;
        }
        input{
            margin: 10px 0px;
        }
    </style>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- <div class="box" :style="{background:rgb}"></div> -->
        <div class="box" :style="{background:`rgb(${r},${g},${b})`}"></div>
        <p>
            R: <input type="range" min="0" max="255" v-model.number="r"><span>{{r}}</span>
        </p>
        <p>
            G: <input type="range"  min="0" max="255" v-model.number="g"><span>{{g}}</span>
        </p>
        <p>
            B: <input type="range"  min="0" max="255" v-model.number="b"><span>{{b}}</span>
        </p>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    r:0,
                    g:0,
                    b:0
                }
            },
            computed:{
                // rgb(){
                //     return `rgb(${this.r},${this.g},${this.b})`
                // }
            }
        });
    </script>
</body>
</html>

六、数组更新检测与Vue生命周期

数组响应式注意事项

数组更新检测

数组更新检测

  • Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新
    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()
  • 上面的方法会直接修改原来的数组
  • 某些方法不会替换原来的数组,而是会生成新的数组,比如 filter()、concat() 和 slice();
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <ul>
            <li>{{name}}</li>
            <li>{{age}}</li>
            <li>{{sex}}</li>
            <li>{{hobby}}</li>
        </ul>
        <button @click="updateHobby">修改睡觉为打代码</button>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    name:"wc",
                    age:18,
                    sex:"man",
                    hobby:["睡觉","吃饭","打豆豆"]
                }
            },
            methods:{
                updateHobby(){
                    // 这样修改,数据已经变了,但是模板没有刷新
                    // 这个修改数据,并非是响应式的
                    // 如果数组中的数据是基本数据类型,通过索引去修改它,界面并不会更新
                    // this.hobby[0] = "打代码";

                    // 数据变了,界面也没有刷新
                    // 通过数组的length修改数据,并排是响应式的
                    // this.hobby.length = 2;

                    // 调用数组的push,pop方法,也是响应式的
                    // this.hobby.push("写代码")
                    // 下面的7个方法,会引起界面更新
                    // push()
                    // pop()
                    // shift()
                    // unshift()
                    // splice()
                    // sort()
                    // reverse()
                    // this.hobby.pop()
                    // this.hobby.splice(0,1,"打代码")

                    // map返回一个加工后的新数组
                    // console.log(this.hobby.map(item=>item=="睡觉" ? "打代码" : item));
                    this.hobby = this.hobby.map(item=>item=="睡觉" ? "打代码" : item);
                }
            }
        });
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <p>a==>{{a}}</p>
        <p>a.b==>{{a.b}}</p>
        <p>a.b.c==>{{a.b.c}}</p>
        <hr>
        <p>{{arr}}</p>
        <button @click="handle">修改data中的数据</button>
    </div>

    <script>
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    // a是响应式数据   b也是响应式数据    c也是响应式数据
                    // 在data中,一个对象不管嵌套多深,所有数据都是响应式数据
                    a: {
                        b: {
                            c: 100
                        }
                    },
                    // 
                    arr: ["xq", 123, { name: 'wangcai' }, true]
                }
            },
            methods:{
                handle(){
                    // 数据变了,但是界面没有更新,不是响应式的
                    // this.arr[0] = "666";

                    // 调用数据的7个方法,是响应式的

                    // 如果数组中的元素是一个对象的话,通过索引去修改对象中的属性,也会引起界面的更新
                    this.arr[2].name = "xiaoqiang";
                }
            }
        });

        // 总结数据更新检测:
        //      在data中定义的数据都是响应式数据,有特殊情况:
        //           数组中的元素有可能是响应式的【数组中的元素是对象】,也有可能不是响应式的【基本类型】
        //           调用数组上的7个方法,也会引起界面更新
    </script>
</body>
</html>

Vue生命周期

Vue框架生命周期函数

请添加图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 

     -->
    <!-- 定义的容器 -->
    <div id="app">
        <p :style="{opacity:tmd}">Vue是一个很NB的框架!!!</p>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    tmd:1
                }
            },
            // 生命周期函数,也叫钩子函数
            // 这个函数会在合适的时机自动调用
            // 函数的名字,不能随便,都是vue定死的
            // 每一个钩子函数,都有特定的含义
            mounted(){
                setInterval(()=>{
                    this.tmd -= .1;
                    if(this.tmd<=0) this.tmd = 1;
                },500)
            }
        });
    </script>
</body>
</html>
生命周期函数完整版(八个)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <button @click="add">加1</button>
        <span ref="cur">{{count}}</span>
        <button @click="minus">减1</button>
        <br>
        <button @click="handle">销毁vm</button>
    </div>

    <script>
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                    count:1
                }
            },
            methods:{
                add(){
                    this.count++
                },
                minus(){
                    this.count--
                },
                handle(){
                    // 销毁vm
                    this.$destroy(); 
                }
            },
            // 生成周期函数,最先执行执行的是beforeCreate,
            // 说明:vm还没有初始化完成   不能通过vm使用data中的方法和methods中的方法
            // 项目中没有什么用,了解就OK了
            beforeCreate(){
                // vm还没有初始化配置完毕,在这里不能获取vm的属性或方法
                console.log("初始化阶段:beforeCreate","vm没有初始化完毕",this.count);
            },
            // vm实例已经初始化完成了
            // 有用,在项目中,通常可以在这里发送ajax请求
            created(){
                // 此处,就可以获取vm的属性或方法了
                console.log("初始化阶段:created","vm初始化完毕", this.count);
            },
            // vm挂载之前调用
            // 项目中用的也不多 
            beforeMount(){
                // 可以获取vm实例,可以得到vm实例上的属性或方法,但是不能获取真实DOM
                console.log("挂载阶段:beforeMount","vm挂载之前执行一次",this.count,this.$refs.cur);
            },
            // vm已经挂载完毕了
            mounted(){
                // 挂载完毕,就可以获取DOM元素
                // 在这里,也可以发生ajax请求  个人一般在mounted中发请求
                console.log("挂载阶段:mounted","vm挂载完毕",this.$refs.cur);
            },
            // 更新阶段,当响应式数据发生了变化,就会触发一次
            beforeUpdate(){
                console.log("更新阶段:beforeUpdate",this.count);
            },
            // 更新阶段:当vm的响应式数据发生变化,更新界面会触发一次
            updated(){
                // 能不能在updated中更新状态?
                // 答:不能,会导致死循环
                console.log("更新阶段:updated",this.count);
            },
            // vm销毁之前调用
            beforeDestroy(){
                console.log("销毁阶段:beforeDestroy",this.count);
            },
            // vm销毁完毕:处理后事,如关闭之前开的一些定时器,或其它的收尾工作
            destroyed(){
                console.log("销毁阶段:destroyed",this.count);
            }
        });

        // 总结:
        //    初始化阶段:beforeCreate  created  只会执行一次
        //    挂载阶段:beforeMount  mounted  只会执行一次
        //    更新阶段:beforeUpdate  updated  只响应式数据发生变化,都会调用,调用N次
        //    销毁阶段:beforeDestroy  destroyed  
        //      销毁后,并不是说界面看不见了,vm实例还可以访问,但是它不工作了!!!!
    </script>
</body>
</html>

七、非单文件组件VC

组件化开发

Vue特点: 组件化、声明式编程、虚拟DO+DIFF

  • 组件:component
组件化开发

组件化开发思想

  • 将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了
  • 现在整个的大前端开发都是组件化的天下
  • 无论从三大框架(Vue、React、Angular),还是跨平台方案的Flutter,甚至是移动端都在转向组件化开发,包括小程序的开发也是采用组件化开发的思想
  • 组件: 复用的【代码 + 资源(图片、样式、视频)】集合在一起即为组件

以组件化的思想考虑整个项目

  • 将一个完整的页面分成很多个组件
  • 每个组件都用于实现页面的一个功能块
  • 而每一个组件又可以进行细分
  • 而组件本身又可以在多个地方进行复用
  • 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
  • 任何的应用都会被抽象成一颗组件树

请添加图片描述

组件的基本的使用

Vue框架中从书写角度出发:分为两大类组件

  • 单文件组件: 一个文件即为一个组件, 这个文件的尾缀务必是.vue
  • 非单文件组件: 一个文件里面,可以定义多个组件,这个文件尾缀.html.

非单文组件使用分为三步骤

  • 第一步: 定义
    • 定义一个组件:利用的是Vue.extend方法去定义组件
    • extend方法里面也需要传递配置对象,extend配置对象与Vue配置对象几乎一模一样!!!
  • 第二步: 注册
    • 注册,某一个地方需要组件,进行注册。在components配置项进行注册
  • 第三步: 使用
    • 使用,【是以自定义标签的形式使用,首字母一般大写】

注意

  • 不能书写el、组件的响应式数据务必比是函数写法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 
        vue的特点:
            1)组件化开发    component
            2)声明式编程
            3)虚拟DOM+DIFF

     -->
    <!-- 定义的容器 -->
    <div id="app">
        <Hello></Hello>
        <Hello></Hello>
        <Hello></Hello>
        <Hello></Hello>
        <Hello></Hello>
    </div>

    <script>

        // 在extend中传组件的配置项
        // 之前new Vue时,传递的配置项,在Vuex.extend中基本上都可以配置
        let Hello = Vue.extend({
            // el 只有要根组件中才可以配置el,在普通组件中不能配置el
            template:"<h2>我是Hello组件</h2>", // 配置当前组件对应的模板
        })


        // 注册组件分两类:
        //    1)全局注册   先不讲
        //    2)局部注册   
        let vm = new Vue({
            el:"#app",
            data(){
                return {
                 
                }
            },
            components:{
                Hello
            }
        });
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
    <style>
        *{
            margin: 0px;
            padding: 0px;
        }
        .box{
            width: 300px;
            height: 150px;
            background-color: skyblue;
        }
        button{
            width: 50px;
            height: 25px;
        }
        .box1{
            width: 300px;
            height: 150px;
            margin: 10px 0;
            background-color: pink;
        }
    </style>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <!-- 使用组件就相当于使用标签 -->
        <Count></Count> <br>
        <Count></Count> <br>
        <Count></Count> <br>
        <Count></Count> <br>
        <Count></Count> <br>
        <Box></Box>  <br>
        <Box></Box>  <br>
        <Box></Box>  <br>
        <Box></Box>  <br>
    </div>

    <script>
        let Hello = Vue.extend({
            template: "<h1>我是Hello组件</h1>"
        })

        let Count = Vue.extend({
            components:{
                Hello
            },
            data(){
                return{
                    count:0
                }
            },
            methods:{
                add(){ this.count++ },
                minus(){ this.count-- }
            },
            template: `
                <div class="box">
                    <Hello></Hello>
                    <button @click="minus">-</button>
                    <span>{{count}}</span>
                    <button @click="add">+</button>
                </div>
            `
        })

        let Box = Vue.extend({
            data(){
                return{
                    f:16
                }
            },
            template:`
                <div class="box1">
                    <p :style="{fontSize:f+'px'}" @click="f++">我是一个孤独的P标签</p>
                </div>
            `
        })

        let vm = new Vue({
            el: "#app",
            components:{
                Count,
                Box
            },
            data() {
                return {

                }
            }
        });
    </script>
</body>

</html>

为什么组件的响应式务必要书写为函数

<script>
    function data() {
        return {
            a: 1
        };
    }

    let c1 = data();
    let c2 = data();
    c1.a = 100;
    console.log(c1 === c2);
    console.log(c1, c2);
</script>
根组件的template
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <div>我是一个DIV</div>
    </div>

    <script>
        // 对于根组件来说,可以在两个地方,指定模板
        //    1)el对应容器内部的html代码段
        //    2)配置template,用来指定模板
        //    如果指定了template,它的优先级更高
        let vm = new Vue({
            el: "#app",
            template:`
                <div>haha</div>
            `,
            data() {
                return {

                }
            }
        });
        vm.$mount("#app")
    </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
    </div>

    <script>
        // 定义Count组件
        const Count = Vue.extend({
            data() {
                return {
                    count: 1
                };
            },
            template: `
                <div>
                   <button @click="count++">加</button>
                   <span>{{count}}</span>
                   <button @click="count--">减</button>    
                </div>
             `
        });
        // 定义Box组件
        const Box = Vue.extend({
            data() {
                return {
                    f: 16
                };
            },
            template: `
               <div>
                  <p :style="{fontSize:f+'px'}" @click="f+=10">I love Vue!!!</p> 
               </div>
            `
        })
        
        // 定义App组件
        const App = Vue.extend({
            components:{
                Count,
                Box
            },
            template:`
                <div>
                    App组件
                    <hr>
                    <Count></Count>
                    <Count/>
                    <hr>
                    <Box/>
                    <Box/>
                </div>
            `
        })
        
        let vm = new Vue({
            components:{
                App
            },
            template:`
                <div>
                    <App></App>
                </div>
            `,
            data() {
                return {

                }
            }
        });
        vm.$mount("#app")
    </script>
</body>
</html>
全局注册组件

定义为全局组件 : 定义一次, 可以在任意地方直接使用,不需要注册。

  • 第一个参数:全局组件的名字(字符串) 第二个参数: 组件(不能书写为字符串)
  • 全局组件一般在这种情况下才会使用: 项目当中很多地方(组件),大家频繁使用某一个功能
  • 你就可以把这个功能封装为全局组件,定义一次,可以在任意地方直接使用【不需要引入、不需要注册】直接使用。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app1">
        <div>
            我是DIV1
            <hr>
            <Count></Count>
        </div>
    </div>

    <div id="app2">
        <div>
            我是DIV2
            <hr>
            <Count></Count>
        </div>
    </div>

    <script>
        // 定义Count组件
        const Count = Vue.extend({
            data() {
                return {
                    count: 1
                };
            },
            template: `
                <div>
                   <button @click="count++">加</button>
                   <span>{{count}}</span>
                   <button @click="count--">减</button>    
                </div>
             `
        });

        // 注册组件分两种:
        //    1)局部注册   在哪里注册,在对应的模板中使用
        //    2)全局注册   只需要注册一次,可以在任何地方使用

        // 全局注册  在项目中,有些组件为了使用方法,都会使用全局注册
        Vue.component("Count",Count)

        let vm1 = new Vue({
            el:"#app1",
            // components:{
            //     Count
            // },
            data(){
                return {
                 
                }
            }
        });
        let vm2 = new Vue({
            el:"#app2",
            // components:{
            //     Count
            // },
            data(){
                return {
                 
                }
            }
        });
    </script>
</body>
</html>
VM与组件的关系

Vue框架中组件, 实质是Vue.extend函数返回的构造函数VueComponent

  • 组件它是一个构造函数,那么在使用组件的时候,不应该new Student,咱们不需要手动去调用
  • Vue框架查看网页, 看到了标签,Vue框架帮你new VueComponent

请添加图片描述

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">

    </div>

    <script>
        // 定义Count组件
        // extend返回一个构造函数,叫VueComponent
        const Count = Vue.extend({
            data() {
                return {
                    count: 1
                };
            },
            template: `
                <div>
                   <button @click="count++">加</button>
                   <span>{{count}}</span>
                   <button @click="count--">减</button>    
                </div>
             `
        });
        // VueComponent   VueComponent是一个类,构造器
        // 按理说,我们需要去new VueComponent  得到一个普通组件实例
        // 但是我们没有new   Vue底层会帮我们new 
        // 一个组件说白了,也是一个对象,只不过这个对象不需要我们去new 
        console.log(Count);
        // c叫组件对象  这是我们new出来的
        let c = new Count();
        // 也可以使用Vue原型对象上的属性或方法
        console.log(c.$data);

        // Vue  vm   VueCompoent  vc


        // Vue也叫类,也叫构造器
        // new Vue得到一个vm实例
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    a:1
                }
            }
        });

        // console.log(vm.$data);
    </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>

<body>
    <!-- 定义的容器 -->
    <div id="app">
        <Student></Student>
    </div>

    <script>
        Vue.prototype.coding = function(){
            console.log("开始打代码了...");
        }

        // 当我们使用Vue.extend 得到一个VueComponent构造器
        // 不需要我们new  Vue底层会帮我们new 
        let Student = Vue.extend({
            data() {
                return {
                    info: 'I love Vue!!!'
                };
            },
            template: `
                <h1 @click="changeInfo">{{info}}</h1>
            `,
            methods: {
                changeInfo() {
                    // this.info = "I love React!!!"
                    // 问:this是谁?
                    // 答:vc
                    // 组件内的this,不是vm,不是vm,不是vm,是VueComponent类的实例
                    // 看上去和vm很像,但实际上,两者是不同东西
                    // 组件内的this,就是VueComponet类的实例,可以叫它VC
                    // this.coding();  

                    console.log(this.__proto__.__proto__ === Vue.prototype);
                }
            }
        });


        let vm = new Vue({
            el: "#app",
            components:{
                Student
            },
            data() {
                return {
                    a: 1
                }
            },
            methods:{
                ok(){
                    // this表示vm
                    console.log(this);
                }
            }
        });
    </script>
</body>
</html>

Vue.extend创建组件简写方式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
     <!-- 引入Vue.js,人家向外暴露一个Vue类  new Vue -->
    <script src="../lib/vue2.7.8.js"></script>
</head>
<body>  
    <!-- 定义的容器 -->
    <div id="app">
        <!-- <Abc></Abc> -->
        <App></App>
    </div>

    <script>
        // 是Vue.extend的简写形式
        // 定义组件
        let App = {
            // 为了在调试工具中显示正确的组件名,通常会配置name选项
            name:"App",
            data(){
                return{
                    name:"wc"
                }
            },
            template:`
                <div>
                    <h1 @click="handler">{{name}}</h1>
                </div>
            `,
            methods:{
                handler(){
                    this.name = "xq";
                    console.log(this);
                }
            }
        }

        let vm = new Vue({
            el:"#app",
            components:{
                // Abc表示注册名  App表示组件名
                // "Abc":App

                App
            },
            data(){
                return {
                 
                }
            }
        });
    </script>
</body>
</html>

八、脚手架

Vue的开发模式

目前我们使用vue的过程都是在html文件中,通过template编写自己的模板、脚本逻辑、样式等,但是随着项目越来越复杂,我们会采用组件化的方式来进行开发

  • 每个组件都会有自己的模板、脚本逻辑、样式等
  • 所以在真实开发中,我们可以通过一个后缀名为 .vue 的single-file components (单文件组件) 来解决,并且可以使用webpack或者vite或者rollup等构建工具来对其进行处理。

单文件组件的特点

  • 代码的高亮(安装必要插件)
  • ES6、CommonJS的模块化能力
  • 组件作用域的CSS
  • 可以使用预处理器来构建更加丰富的组件,比如TypeScript、Babel、Less、Sass等

如何支持SFC

  • VSCode对SFC的支持,安装两个插件
  • 插件一:Vetur,从Vue2开发就一直在使用的VSCode支持Vue的插件
  • 插件二:Volar,官方推荐的插件

Vue CLI脚手架

Vue的脚手架就是Vue CLI

  • 脚手架其实是建筑工程中的一个概念,在我们软件工程中也会将一些帮助我们搭建项目的工具称之为脚手架
  • CLI是Command-Line Interface, 翻译为命令行界面
  • 我们可以通过CLI选择项目的配置和创建出我们的项目
  • Vue CLI已经内置了webpack相关的配置,我们不需要从零来配置

Vue CLI 安装和使用

  • 全局安装: npm install @vue/cli -g
  • 升级Vue CLI(如果是比较旧的版本,可以通过下面的命令来升级): npm update @vue/cli -g
  • 通过Vue的命令来创建项目: Vue create 项目的名称

vue create 项目的过程
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

九、父传子与todolist案例

组件的通信

父子传递数据给子组件

父子组件之间通信

  • 父组件传递给子组件:通过props属性
  • 子组件传递给父组件:通过$emit触发事件

请添加图片描述
父组件传递给子组件

  • 父组件有一些数据,需要子组件来进行展示,可以通过props来完成组件之间的通信
  • Props是你可以在组件上注册一些自定义的attribute
  • 父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值

Props有两种常见的用法

  • 方式一:字符串数组,数组中的字符串就是attribute的名称
  • 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等
// ======== main.js

// 引入vue核心库
import Vue from 'vue'
// 引入App组件
import App from './App.vue'

Vue.config.productionTip = false

// 初始化项目唯一的vm实例
// 把App组件渲染到容器中
new Vue({
  render: h => h(App),
}).$mount('#app')
// ======== App.vue
<template>
  <div id="app">
    <h1>父组件 --- 王健林</h1>
    <hr>
    <!-- car="二八大杠" 叫自定义属性,父传子就是靠自定义属性 -->
    <!-- :money="money" 也是自定义属性,值不是字符串,值是number -->
    <!-- :changeAppMoney="changeAppMoney" 也是自定义属性  值是一个方法  -->
    <MyComponent1 
      :car="car" 
      :money="money" 
      :changeAppMoney="changeAppMoney"
      :changeAppCar="changeAppCar"
    ></MyComponent1>
    <hr>
    <MyComponent2 :msg="msg"></MyComponent2>
  </div>
</template>

<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
  name: 'App',
  components: {
    MyComponent1,
    MyComponent2
  },
  data(){
    return{
      money:10000000,
      car:"二八大杠",
      msg:"hello vue"
    }
  },
  methods:{
    // 定义修改状态的方法
    changeAppMoney(){
      this.money = "1个小目标"
    },
    changeAppCar(){
      this.car = "鬼火"
    }
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
// ======== MyComponent1.vue
<template>
    <div>
        <h1>王思聪组件</h1>
        <p>收到父的money ---- {{money}}</p>
        <p>收到父的car ---- {{car}}</p>
        <!-- <button @click="changeAppMoney">修改钱</button> -->
        <button @click="updateMoney">修改钱</button>
        
        <button @click="changeAppCar">修改车</button>
    </div>
</template>

<script>

export default{
    name:"MyComponent1",
    // props代表子组件接收父组件的数据
    // props有多种写法,第一种写法,props后面写一个数组
    // props中写自定义属性的名字
    props:["car","money","changeAppMoney","changeAppCar"],
    mounted(){
        // console.log(typeof this.car);
        // console.log(typeof this.money);

        // console.log(this.changeAppMoney);
    },
    data(){ return {

    } },
    methods:{
        updateMoney(){
            // 调用父传递过来的方法
            this.changeAppMoney();
        }
    }
}

</script>

<style lang="less" scoped>

</style>

type的类型都可以是哪些

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
// ======== MyComponent2.vue

<template>
  <!-- 在vue2中,需要有一个唯一的根标签 -->
  <div>
    <h1>我是子组件MyComponent2</h1>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  name: "MyComponent2",
  // props:["msg"],
  // props的第二种写法:对象的写法
  props: {
    // msg后面也可以再配置一个对象
    //   msg:{
    //       type:String, // 期望父传递的是字符串类型,如果不是,会发出警告
    //       default:"haha", // 如果父没有传递msg,默认值是haha
    //   }

    // String  Number  Boolean  Array  Object  Date  Function  Symbol
    msg: {
      type: String, 
      required:true, // msg数据必须要传,如果不传就发出警告
    },
  },
  data() {
    return {};
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>

Prop 的大小写命名

  • HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符
  • 这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名;

非Prop的Attribute

  • 当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的Attribute
  • 常见的包括class、style、id属性等
  • 当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中

禁用Attribute继承

  • 不希望组件的根元素继承attribute,可以在组件中设置 inheritAttrs: false
  • 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素
  • 可以通过 $attrs来访问所有的 非props的attribute

TodoList

https://todomvc.com/examples/vue/#/all

/*index.css*/
body {
    background: #fff;
}

.btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
}

.btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
}

.btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
}

.btn:focus {
    outline: none;
}

.todo-container {
    width: 600px;
    margin: 0 auto;
}

.todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}

/*header*/
.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}

.todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}

/*main*/
.todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}

.todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}

/*item*/
li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
}

li label {
    float: left;
    cursor: pointer;
}

li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
}

li button {
    float: right;
    display: none;
    margin-top: 3px;
}

li:before {
    content: initial;
}

li:last-child {
    border-bottom: none;
}

/*footer*/
.todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}

.todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}

.todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}

.todo-footer button {
    float: right;
    margin-top: 5px;
}
<!-- todo.html -->
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>React App</title>

    <link rel="stylesheet" href="index.css">
</head>

<body>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <div class="todo-header">
                    <input type="text" placeholder="请输入你的任务名称,按回车键确认" />
                </div>
                <ul class="todo-main">
                    <li>
                        <label>
                            <input type="checkbox" />
                            <span>xxxxx</span>
                        </label>
                        <button class="btn btn-danger" style="display:none">删除</button>
                    </li>
                    <li>
                        <label>
                            <input type="checkbox" />
                            <span>yyyy</span>
                        </label>
                        <button class="btn btn-danger" style="display:none">删除</button>
                    </li>
                </ul>
                <div class="todo-footer">
                    <label>
                        <input type="checkbox" />
                    </label>
                    <span>
                        <span>已完成0</span> / 全部2
                    </span>
                    <button class="btn btn-danger">清除已完成任务</button>
                </div>
            </div>
        </div>
    </div>
</body>
</html>
// App.vue
<template>
  <div class="todo-container">
    <h4>{{todos}}</h4>
    <h1>{{isAllDone}}</h1>
    <div class="todo-wrap">
      <TodoHeader :addTodo="addTodo"></TodoHeader>
      <TodoMain :todos="todos" :updateDone="updateDone" :deleteTodo="deleteTodo"></TodoMain>
      <TodoFooter 
        :todos="todos" 
        :todoIsDone="todoIsDone" 
        :isAllDone="isAllDone"
        :updateAllDone="updateAllDone"
        :deleteDoneTodo="deleteDoneTodo"
      ></TodoFooter>
    </div>
  </div>
</template>

<script>
import TodoHeader from "./components/TodoHeader.vue";
import TodoMain from "./components/TodoMain.vue";
import TodoFooter from "./components/TodoFooter.vue";
export default {
  name: "App",
  components: {
    TodoHeader,
    TodoMain,
    TodoFooter,
  },
  data() {
    return {
      // todos:[
      //   {id:"01",title:"学习vue",done:true},
      //   {id:"02",title:"学习react",done:false},
      //   {id:"03",title:"学习小程序",done:false},
      // ]
      todos:JSON.parse(localStorage.getItem("TODO")) || []
    };
  },
  methods: {
    // 更新todo的Done
    updateDone(id,done){
      // 01 false
      // 03 true
      // console.log(id,done);
      // for循环遍历
      // for(let i=0; i<this.todos.length; i++){
      //   if(this.todos[i].id == id){
      //     this.todos[i].done = done
      //   }
      // }

      // forEach
      // this.todos.forEach(todo=>{
      //   if(todo.id == id){
      //     todo.done = done;
      //   }
      // })

      // 使用map
      // this.todos = this.todos.map(item=>{
      //   if(item.id == id){
      //     return { ...item, done}
      //   }
      //   return item;
      // })
      
      // 使用findIndex
      //let index = this.todos.findIndex((item) => item.id === id);
      //this.todos[index].done = done;
      // 简化后的
      this.todos = this.todos.map(item=>item.id==id?{...item,done}:item)
    },
    // 删除单个todo
    deleteTodo(id){
      // this.todos = this.todos.filter(item=>{
      //   if(item.id==id){
      //     return false;
      //   }else{
      //     return true
      //   }
      // })
      
      //使用findIndex,splice
      //let index = this.todos.findIndex((item) => item.id === id);
      //this.todos.splice(index, 1);
      
      this.todos = this.todos.filter(item=>item.id==id?false:true)
    },
    // 添加todo
    addTodo(todo){

      // find:如果找到了,返回对应的todo 如果没有找到,返回und
      let repeat = this.todos.find(item=>item.title === todo.title);
      console.log(repeat);

      // if(!repeat){
      //   this.todos.push(todo)
      // }else{
      //   alert(`【${todo.title}】任务已存在`)
      // }

      // !repeat && this.todos.push(todo);

      !repeat ? this.todos.push(todo) : alert(`${todo.title}】任务已存在`)
    },
    // 全选和反选
    updateAllDone(done){
      this.todos = this.todos.map(item=>({...item,done}))
    },
    // 清除已完成
    deleteDoneTodo(){
      this.todos = this.todos.filter(item=>!item.done)
    }
  },
  computed:{
    // 统计已完成的数据
    todoIsDone(){
      // 过滤出done为true的todo
      return this.todos.filter(item=>item.done)
    },
    // 统计是否全部已完成
    isAllDone(){
      return this.todos.every(item=>item.done)
    }
  },
  watch:{
    todos(){
      // 侦听todos,todos数据一旦变化,就会被侦听到
      // 需要把数据持久化存储  localStorage
      localStorage.setItem("TODO",JSON.stringify(this.todos))
    }
  }
};
</script>

<style lang="less">
</style>
// TodoHeader.vue
<template>
  <div class="todo-header">
    <input 
      v-model="values"
      ref="getValues"
      type="text" 
      placeholder="请输入你的任务名称,按回车键确认" 
      @keyup.enter="enterHandler"
    />
  </div>
</template>

<script>
export default {
  name: "TodoHeader",
  props: ["addTodo"],
  data() {
    return {};
  },
  methods: {
     // 获取输入框的数据有多种方法
    //   1)v-model
    //   2)使用事件对象
    //   3)ref
    enterHandler(e){
      console.log(this.values);
      console.log(this.$refs.getValues.value);
      if(e.target.value.trim() === ""){
        alert("输入内容不能为空~")
        return;
      }

      // 拼装一个todo
      let newTodo = {id:Date.now(), title:e.target.value, done:false};
      // console.log(newTodo);
      this.addTodo(newTodo)

      e.target.value = ""; // 清空输入框
    }
  },
};
</script>

<style lang="less" scoped>
</style>
// TodoMain.vue
<template>
  <ul class="todo-main">
    <li 
      v-for="(todo,index) in todos" 
      :key="todo.id"
      @mouseenter="enterHander(index)"
      :class="{active:currentIndex === index}"
    >
      <label>
        <input type="checkbox" :checked="todo.done" @change="handler(todo,$event)" />
        <span>{{todo.title}}</span>
      </label>
      <button class="btn btn-danger" v-show="todo.done" @click="clickHandler(todo.id)">删除</button>
    </li>
  </ul>
</template>

<script>
export default {
  name: "TodoMain",
  props: ["todos","updateDone","deleteTodo"],
  data() {
    return {
      currentIndex:-1
    };
  },
  methods: {
    // 鼠标移入li
    enterHander(index){
      // console.log(index);
      this.currentIndex = index;
    },
    // 点击单选框
    handler({id},e){
      // console.log(id);
      // console.log(e.target.checked);
      this.updateDone(id,e.target.checked)
    },
    // 点击删除
    clickHandler(id){
      // 子调用父传递的方法
      this.deleteTodo(id)
    }
  },
};
</script>

<style lang="less" scoped>
.active{
  color: yellowgreen;
  background-color: #eee;
}
</style>
// TodoFooter.vue
<template>
  <div class="todo-footer">
    <label>
      <!-- <input type="checkbox" :checked="isAllDone" @change="changeHandler" /> -->
      <input type="checkbox" :checked="todoIsDone.length === todos.length && todos.length>0" @change="changeHandler" />
    </label>
    <span> <span>已完成{{todoIsDone.length}}</span> / 全部{{todos.length}} </span>
    <button class="btn btn-danger" @click="clickHandler">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "TodoFooter",
  props: ["todos","todoIsDone","isAllDone","updateAllDone","deleteDoneTodo"],
  data() {
    return {};
  },
  methods: {
    changeHandler(e){
      this.updateAllDone(e.target.checked)
    },
    // 点击清除已完成
    clickHandler(){
      this.deleteDoneTodo();
    }
  },
};
</script>

<style lang="less" scoped>
</style>

十、自定义指令

自定义指令

  • 在Vue中,代码的复用和抽象主要还是通过组件
  • 通常在某些情况下,你需要对DOM元素进行底层操作,这个时候就会用到自定义指令
    自定义指令分为两种
  • 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用
  • 自定义全局指令:app的 directive 方法,可以在任意组件中被使用
自定义局部指令
// MyComponent1.vue
<template>
  <!-- 
        前面学习过各种各样的指令:
            v-html  v-text  v-model  v-if  v-elseif  v-else  v-show  v-for  v-bind   v-on   v-cloak   v-pre  v-once

        组件:
            代码复用

        指令:
            对DOM元素进行底层操作时,可以使用自定义指令

        自定义指令分两类:
            1)自定义局部指令:只能在当前组件中使用
            2)自定义全局指令:在任意组件中使用

        自定义局部指令:
            也是一个配置项,叫directives

     -->
  <div>
      <h1>{{msg}}</h1>
      <hr>
      <h1 v-upper="msg"></h1>
      <hr>
      <h2 v-upper="msg2"></h2>
  </div>
</template>

<script>
export default {
  name: "MyComponent1",
  props: [],
  data() {
    return {
        msg:"hello vue",
        msg2:"abcdef"
    };
  },
  methods: {},
  // 自定义指令是配置在directives选项中的
  directives: {
    // 自定义指令,也是写成函数的形式
    // 使用时,需要以v-打头  v-upper
    // 当使用v-upper时,下面的函数就会执行一次
    // 把字符串中的小写字母变大写
    // upper(element,options) {
    //     // element 表示当前绑定指令的真实DOM节点
    //     // console.log("upper....");
    //     // console.log(element);
    //     // options表示当前指令一些配置项参数(对象)
    //     // console.log(options);

    //     element.innerHTML = options.value.toUpperCase();
    // },
  },
};
</script>

<style lang="less" scoped>
</style>
// MyComponent2.vue
<template>
  <div>
      <input v-focus type="text">
  </div>
</template>

<script>
export default {
  name: "MyComponent2",
  props: [],
  data() {
    return {};
  },
  methods: {},
  directives: {
    // 自定义局部指令
    // focus() {},
  },
};
</script>

<style lang="less" scoped>
</style>
// App.vue
<template>
  <div id="app">
    App
    <!-- 在MyCommont1组件中定义的自定义指令,在其它组件中是不能使用的 -->
    <p v-upper="msg3"></p>
    <hr>
    <MyComponent2></MyComponent2>
    <hr>
    <MyComponent1></MyComponent1>
  </div>
</template>

<script>
import MyComponent1 from "./components/MyComonent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
  name: 'App',
  data(){
    return{
      msg3:"abc"
    }
  },
  components: {
    MyComponent1,
    MyComponent2
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
自定义全局指令
// main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 自定义全局指令
// 第一个参数,表示自定义指令的名字,定义时,不要加v-
// 第二个参数,是一个回调函数
Vue.directive("upper",(element,options)=>{
  element.innerHTML = options.value.toUpperCase();
})

Vue.directive("focus",{
    // 配置钩函数  会在合适的时机,自动调用
    // 当被绑定的元素插入到 DOM 中时……
    inserted(element){
      // element是对应的DOM元素
      console.log("element:",element);
      console.log("inserted........");
      element.focus(); // 自动获取焦点
    }
})

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

十一、过滤器

过滤器的使用

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)

  • 过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示
局部过滤器
// MyComponent1.vue
<template>
  <div>
    <!-- 使用过滤器,需要对time时间进行格式化,进行过滤 -->
    <h1>{{ time | timeFormat }}</h1>   
  </div>
</template>

<script>
// 引入moment  moment是专门用来处理时间
import moment from "moment";
export default {
  name: "MyComponent1",
  props: [],
  data() {
    return {
      time: Date.now(),
    };
  },
  methods: {},
  // 在filters选项中,可以配置局部过滤器
  // 在这里配置的局部过滤器,只能在当前组件中使用
  //   filters: {
  //       // 定义了一个过滤器,叫timeFormat
  //       // 在模板中,就可以使用过滤器了
  //       timeFormat(params){
  //         //   console.log(params);
  //         // 利用moment对时间进行格式化
  //         // return 666;

  //         // 需要return一个格式化后的时间
  //         return moment(params).format("YYYY-MM-DD")
  //       }
  //   },
};
</script>

<style lang="less" scoped>
</style>
// MyComponent2.vue
<template>
  <div>
      <h1>{{ time | timeFormat }}</h1>
  </div>
</template>

<script>
export default {
  name: "MyComponent1",
  props: [],
  data() {
    return {
        time:Date.now(),
    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>
// App.vue
<template>
  <div id="app">
    App
    <hr>
    <MyComponent1></MyComponent1>
    <hr>
    <MyComponent2></MyComponent2>
  </div>
</template>

<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
  name: 'App',
  data(){
    return{
      msg3:"abc"
    }
  },
  components: {
    MyComponent1,
    MyComponent2
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
全局过滤器
// main.js
import Vue from 'vue'
import App from './App.vue'

import moment from "moment"

Vue.config.productionTip = false

// 定义全局过滤器
// 第一个参数是过滤器的名字
// 第二个参数是回调函数  
Vue.filter("timeFormat",(val)=>{
  return moment(val).format("YYYY-MM-DD")
})


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

十二、自定义插件

插件通常用来为 Vue 添加全局功能

  • 添加全局方法或者 property。如:vue-custom-element
  • 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  • 通过全局混入来添加一些组件选项。如 vue-router
  • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
// plugins/myplugins.js

// 开发自定义插件,目的是给项目提供一些全局的功能 
import moment from "moment"
// plugins对象,叫插件对象
let plugins = {
    // vue规定,一个插件对象身上,必须要有install方法
    // 当Vue.use时,内部会自动执行indtall方法
    install(Vue, options) {
        // 第一个参数是Vue构造函数  
        // 第二个参数是Vue.use时,传递的第二个参数
        // console.log(Vue);
        // console.log(options);

        // 把自定义指令封装到一个插件中
        Vue.directive("upper", (element, options) => {
            element.innerHTML = options.value.toUpperCase();
        })

        // 把过滤器封装到插件中
        Vue.filter("timeFormat", (val) => {
            return moment(val).format("MM-DD")
        })

        // 还可以在Vue的原型对象上,添加公共的方法
        Vue.prototype.$wc = function () {
            alert("这是一只小狗,叫wc")
        }

        // 注册全局组件
        Vue.component("Count", {
            data() {
                return {
                    count: 0
                }
            },
            methods: {
                add() { this.count++ },
                minus() { this.count-- }
            },
            // render函数后面说
            render: function (createElement) {
                return createElement(
                  'h1',   
                  {},
                  "我是Count组件"
                )
              },
        })

        // 后面会讲两个非常重要的插件,vue-router   vuex
    }
}

// 对外暴露插件  为了让别的模块去使用插件
export default plugins;
// MMyComponent1.vue
<template>
  <div>
      <h1 v-upper="msg"></h1>
      <Count></Count>
  </div>
</template>

<script>
export default {
  name: "MyComponent1",
  props:[],
  data() {
    return {
        msg:"hello vue"
    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>
// MMyComponent2.vue
<template>
  <div>
    <h1>{{ time | timeFormat }}</h1>
    <Count></Count>
  </div>
</template>

<script>
export default {
  name: "MyComponent2",
  props:[],
  data() {
    return {
      time:Date.now()
    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>
// MMyComponent3.vue
<template>
  <div>
    <button @click="fn">点我</button>
    <Count></Count>
  </div>
</template>

<script>
export default {
  name: "MyComponent3",
  props: [],
  data() {
    return {};
  },
  methods: {
    fn() {
      // this  表示VC
      this.$wc();
    },
  },
};
</script>

<style lang="less" scoped>
</style>
// App.vue
<template>
  <div id="app">
    App
    <hr>
    <MyComponent1></MyComponent1>
    <hr>
    <MyComponent2></MyComponent2>
    <hr>
    <MyComponent3></MyComponent3>
  </div>
</template>

<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
import MyComponent3 from "./components/MyComponent3.vue"
export default {
  name: 'App',
  data(){
    return{
      msg3:"abc"
    }
  },
  components: {
    MyComponent1,MyComponent2,MyComponent3
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
// main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 使用插件
import myplugins from "./plugins/myplugins"
Vue.use(myplugins,{name:"upper"})

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

十三、自定义事件与todolist案例

自定义事件

什么情况下子组件需要传递内容到父组件呢

  • 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容
  • 子组件有一些内容想要传递给父组件的时候

自定义事件的操作步骤

  • 首先,我们需要在子组件中定义好在某些情况下触发的事件名称
  • 其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中
  • 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
// App.vue
<template>
  <div class="app">
    <h1>我是父组件 王健林</h1>
    <hr>
    <!-- 
      在使用子组件的位置绑定自定义事件

      事件源:MySon
      事件类型:wc
      监听器:handler

      如何触发MySon事件?
      答:在事件源(组件)内部通过$emit手动写代码触发!!!
          $emit("wc",500)
     -->
    <MySon @wc="handler" ref="cur"></MySon>
  </div>
</template>

<script>
import MySon from "./components/MySon.vue"
export default {
  name: 'App',
  components: {
    MySon
  },
  // 当组件挂载完毕执行一次mounted
  mounted(){  
    // 当代码走到这里,说明,组件都挂载,渲染完毕
    // 问:能不能获取组件实例? 答:可以

    // console.log(this.$refs.cur); // 得到VC实例

    // 可以通过$on绑定自定义事件
    // 当xq事件发生了,处罚监听器
    this.$refs.cur.$on("xq",(val)=>{
      // 谁触发了xq事件,就可以传递数据
      console.log("val:",val);
    })
  },  
  methods:{
    handler(val){
      // val是子传递给父的数据
      console.log("val:",val);
    }
  }
}
</script>

<style lang="less">
*{
  margin: 0;
  padding: 0;
}
html,body{
  width: 100%;
  height: 100%;
}
.app{
  width: 100%;
  height: 100%;
  background-color: pink;
}
</style>
// MySon.vue
<template>
  <div class="son">
      <h1>我是子组件 王思聪</h1>
      <!-- @click="clickHandler" 不叫自定义事件 -->
      <button @click="clickHandler">触发自定义事件</button>
  </div>
</template>

<script>
export default {
  name: "MySon",
  props:[],
  data() {
    return {
        money:500,
        car:"凤凰牌自行车"
    };
  },
  methods: {
      clickHandler(){
        //   console.log("clickHandler...");
        // 在这里,触发自定义事件

        // 在vc身上没有是$emit,沿着原型链可以去Vue的原型对象上找到$emit
        // console.log(this);

        // this.$emit("wc",this.money)

        this.$emit("xq",this.car)
      }
  },
};
</script>

<style lang="less" scoped>
div.son{
    width: 300px;
    height: 200px;
    background-color: gold;
    margin: 50px;
}
</style>

自定义事件应用于TodoList(简易版)

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <!-- 之前:传递一个addTodo方法,子中调用方法,子中数据传给父 -->
      <!-- <TodoHeader :addTodo="addTodo"></TodoHeader> -->

      <!-- 现在:自定义事件实现子传子 -->
      <!-- @addTodo="addTodo" 自定义事件 -->
      <!-- 当addTodo事件发生了,就会调用addTodo方法 -->
      <!-- 在子中就可以触发自定义事件 -->
      <TodoHeader @addTodo="addTodo"></TodoHeader>

      <!-- <TodoMain
        :todos="todos"
        :deleteTodo="deleteTodo"
        :updateDone="updateDone"
      ></TodoMain> -->
      <TodoMain
        :todos="todos"
        @deleteTodo="deleteTodo"
        @updateDone="updateDone"
      ></TodoMain>

      <!-- <TodoFooter
        :isAllDone="isAllDone"
        :updateAllDone="updateAllDone"
        :todoIsDone="todoIsDone"
        :todos="todos"
        :deleteDoneTodo="deleteDoneTodo"
      ></TodoFooter> -->
      <TodoFooter
        :isAllDone="isAllDone"
        @updateAllDone="updateAllDone"
        :todoIsDone="todoIsDone"
        :todos="todos"
        @deleteDoneTodo="deleteDoneTodo"
      ></TodoFooter>
    </div>
  </div>
</template>

<script>
import TodoHeader from "./components/TodoHeader.vue";
import TodoMain from "./components/TodoMain.vue";
import TodoFooter from "./components/TodoFooter.vue";
export default {
  name: "App",
  data() {
    return {
      // todos: [
      //   { id: "01", title: "学习vue", done: true },
      //   { id: "02", title: "学习小程序", done: false },
      //   { id: "03", title: "学习react", done: false },
      // ],
      todos: JSON.parse(localStorage.getItem("TODO")) || [],
    };
  },
  watch: {
    todos() {
      localStorage.setItem("TODO", JSON.stringify(this.todos));
    },
  },
  methods: {
    // 添加todo的方法
    addTodo(todo) {
      // console.log("todo:", todo);
      // this.todos.push(todo);

      // 如果同名的任务存在了,不需要添加了
      let repeat = this.todos.find((item) => item.title === todo.title);
      // if (!repeat) {
      //   // 找不到,没有
      //   this.todos.push(todo);
      // } else {
      //   alert(`【${todo.title}】 此任务已存在!!!`);
      // }

      !repeat
        ? this.todos.push(todo)
        : alert(`${todo.title}】 此任务已存在!!!`);
    },
    // 删除todo
    // 需要传递给子,子调用方法,子需要把id传给父
    deleteTodo(id) {
      // console.log("id:", id);
      // 有三条数据,删除done为true的
      // 换句话说,就是把done为false过滤出来
      // 删除已完成的,等价与过滤出没有完成的
      // this.todos = this.todos.filter(item=>{
      //   if(item.id == id){
      //     return false
      //   }else{
      //     return true
      //   }
      // })

      this.todos = this.todos.filter((item) => (item.id == id ? false : true));
    },
    // 更新todo的状态(完成和未完成)
    updateDone(id, flag) {
      // console.log(id, flag);
      // for (let i = 0; i < this.todos.length; i++) {
      //   if (this.todos[i].id == id) {
      //     console.log("--------");
      //     this.todos[i].done = flag;
      //   }
      // }

      // this.todos.forEach((todo) => {
      //   if (todo.id == id) {
      //     todo.done = flag;
      //   }
      // });

      // map可以对数组中每一个元素进行加工
      // map返回加工后的新数组
      // this.todos = this.todos.map((todo) => {
      //   if (todo.id == id) {
      //     return { ...todo, done: flag };
      //   }
      //   return todo;
      // });

      this.todos = this.todos.map((todo) =>
        todo.id == id ? { ...todo, done: flag } : todo
      );
    },
    // 全选或反选
    updateAllDone(flag) {
      // console.log("flag:", flag);
      // for (let i = 0; i < this.todos.length; i++) {
      //   this.todos[i].done = flag;
      // }

      this.todos = this.todos.map((todo) => ({ ...todo, done: flag }));
    },
    // 清除已完成
    deleteDoneTodo() {
      // 所谓的清除已完成,就是把没有完成的过滤出来
      this.todos = this.todos.filter((todo) => !todo.done);
    },
  },
  computed: {
    // 统计所有todo是否都已完成
    isAllDone() {
      // let flag = true;
      // for(let i=0; i<this.todos.length;i++){
      //   if(!this.todos[i].done){
      //     // 有todo没有完成
      //     return false;
      //   }
      // }
      // return flag;

      // every如果所有的的元素都满足条件,最终的结果就是true
      return this.todos.every((todo) => todo.done);
    },
    // 统计已完成的个数
    todoIsDone() {
      // let newTodos = [];
      // for (let i = 0; i < this.todos.length; i++) {
      //   if (this.todos[i].done) {
      //     newTodos.push(this.todos[i]);
      //   }
      // }
      // console.log(newTodos);
      // return newTodos;

      return this.todos.filter((todo) => todo.done);
    },
  },
  components: {
    TodoHeader,
    TodoMain,
    TodoFooter,
  },
};
</script>

<style lang="less">
</style>

子组件

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox" :checked="isAllDone" @change="changeHandler" />
    </label>
    <span>
      <span>已完成{{ todoIsDone.length }}</span> / 全部{{ todos.length }}
    </span>
    <button class="btn btn-danger" @click="clickHandler">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "TodoFooter",
  // props: [
  //   "isAllDone",
  //   "updateAllDone",
  //   "todoIsDone",
  //   "todos",
  //   "deleteDoneTodo",
  // ],
  props: ["isAllDone", "todoIsDone", "todos"],
  data() {
    return {};
  },
  methods: {
    changeHandler(e) {
      // console.log(e.target.checked);
      // 之前:调用父传递过来方法
      // this.updateAllDone(e.target.checked);

      // 现在:触发自定义事件
      this.$emit("updateAllDone", e.target.checked);
    },
    // 点击清除已完成
    clickHandler() {
      // 之前:调用父传递过来方法
      // this.deleteDoneTodo();

      // 现在:触发自定义事件
      this.$emit("deleteDoneTodo");
    },
  },
};
</script>

<style lang="less" scoped>
</style>
<template>
  <div class="todo-header">
    <input
      type="text"
      @keyup.enter="enterHandler"
      placeholder="请输入你的任务名称,按回车键确认"
    />
  </div>
</template>

<script>
export default {
  name: "TodoHeader",
  // props: ["addTodo"],
  data() {
    return {};
  },
  methods: {
    // 获取输入框的数据有多种方法
    //   1)v-model
    //   2)使用事件对象
    //   3)ref
    enterHandler(e) {
      if (e.target.value.trim() === "") {
        alert("输入的todo任务不能为空!!!");
        return;
      }
      let newTodo = {
        id: Date.now(),
        title: e.target.value.trim(),
        done: false,
      };

      // 下面的代码是调用父传递过来的方法
      // this.addTodo(newTodo);

      // 现在是子中触发自定义事件
      // 在触发自定义事件时,可以把数据传递给父
      this.$emit("addTodo", newTodo);

      e.target.value = ""; // 清空输入框
    },
  },
};
</script>

<style lang="less" scoped>
</style>
<template>
  <ul class="todo-main">
    <li
      v-for="(todo, index) in todos"
      :key="todo.id"
      @mouseenter="enterHandler(index)"
      :class="{ active: currentIndex === index }"
    >
      <label>
        <input
          @change="changeHandler(todo, $event)"
          type="checkbox"
          :checked="todo.done"
        />
        <span>{{ todo.title }}</span>
      </label>
      <button class="btn btn-danger" v-show="todo.done" @click="del(todo.id)">
        删除
      </button>
    </li>
  </ul>
</template>

<script>
export default {
  name: "TodoMain",
  // props: ["todos", "deleteTodo", "updateDone"],
  props: ["todos"],
  data() {
    return {
      currentIndex: -1,
    };
  },
  methods: {
    // 摸谁让谁变亮
    enterHandler(index) {
      this.currentIndex = index;
    },
    // 当点击删除按钮
    del(id) {
      // console.log(id);
      // 之前:调用父传递过来方法
      // this.deleteTodo(id);

      // 现在:触发自定义事件
      this.$emit("deleteTodo", id);
    },
    // 点击todo前面的复选框
    changeHandler(todo, e) {
      // console.dir(e.target.checked);
      // 之前:调用父传递过来方法
      // this.updateDone(todo.id, e.target.checked);

      // 现在:触发自定义事件
      this.$emit("updateDone", todo.id, e.target.checked);
    },
  },
};
</script>

<style lang="less" scoped>
.active {
  color: yellowgreen;
  background-color: #eee;
}
</style>

事件总线

非父子组件之间的通信

  • 全局事件总线
  • Provide/Inject
// ErZi1.vue
<template>
  <div class="erzi1">
      <h1>我是儿子1组件</h1>
      <button @click="$bus.$emit('maotai','一车茅台')">给老二一车茅台</button>
  </div>
</template>

<script>
export default {
  name: "ErZi1",
  props:[],
  data() {
    return {

    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
.erzi1{
    width: 400px;
    height: 200px;
    background-color: gold;
    margin: 50px 0px;
}
</style>
// ErZi2.vue
<template>
  <div class="erzi2">
    <h1>我是儿子2组件</h1>
    <SunZi></SunZi>
  </div>
</template>

<script>
import SunZi from "./SunZi.vue"
export default {
  name: "ErZi2",
  props: [],
  data() {
    return {};
  },
  mounted(){
    this.$bus.$on("maotai",val=>{
      console.log("val:",val);
    })
  },
  methods: {},
  components:{
    SunZi
  }
};
</script>

<style lang="less" scoped>
.erzi2 {
  width: 400px;
  height: 200px;
  background-color: pink;
  margin: 50px 0px;
}
</style>
// SunZi.vue
<template>
  <div class="sunzi">
      <span>我是孙子组件</span>
      <button @click="clickHandler">点击给爷爷100</button>
  </div>
</template>

<script>
export default {
  name: "SunZi",
  props:[],
  data() {
    return {
      money:"100万"
    };
  },
  methods: {
    clickHandler(){
      // 发布
      this.$bus.$emit("money",this.money)
    }
  },
};
</script>

<style lang="less" scoped>
.sunzi{
    width: 300px;
    height: 100px;
    background-color: skyblue;
    margin: 20px 0px;
}
</style>
// App.vue
<template>
  <div id="app">
    <h1>我是App组件----{{money}}</h1>
    <ErZi1></ErZi1>
    <ErZi2></ErZi2>
  </div>
</template>

<script>
import ErZi1 from "./components/ErZi1.vue"
import ErZi2 from "./components/ErZi2.vue"
export default {
  name: 'App',
  data(){
    return{
      money:0
    }
  },
  mounted(){
    // 在mounted中进行订阅
    // 绑定一个自定义事件
    this.$bus.$on("money",(val)=>{
      console.log("爷爷接收money:",val);
      this.money = val
    })
  },
  components: {
    ErZi1,
    ErZi2
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
// main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// let $bus = new Vue(); // $buts就相当于是vm实例  通过$bus可以得到$on  $emit

// Vue.prototype.$bus = new Vue(); 

new Vue({
  beforeCreate(){
    // this表示vm
    // 配置全局事件总线,说白了,就是在Vue原型对象上添加$bus属性,值是vm
    // this是vm,不是vc
    // 在$bus身上有,$on和$emit  $on可以用来接收数据   $emit可以用来发送数据
    Vue.prototype.$bus = this;
  },
  render: h => h(App),
}).$mount('#app')

// 目前为止,vue中数据通信的方案?
//   1)props  自定义属性   一般是父传子  如果传递的是方法,也可以实现子传父
//   2)emit  自定义事件   子传子    子可以把数据传递给父
//   3)事件总线   $bus  值是一个vm   $on订阅   $emit发布   

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值