Vue 2.0/3.0

官网

vue

准备

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
    </script>
</body>
</html>

下载拓展并将权限打开
(先安装livesever插件,才能打开vue拓展)
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
拓展的使用
在这里插入图片描述

指令语法

v-bind

<body>
    <div id="demo">
        <h1>插值语法</h1>
        <h3>{{name}}</h3>
        <h1>指令语法</h1>
        <a v-bind:href="book.url">{{book.name}}</a>
        <!-- 缩写 -->
        <!-- <a :href=""></a>  -->
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        //创建vue2.0实例
        new Vue({
            el:'#demo',
            data() {
                return {
                    name:"切尔西",
                    book:{
                        name:"the clash",
                        url:'https://www.baidu.com',
                    }
                }
            },
        }) 
    </script>
</body>

在这里插入图片描述

数据绑定

v-model

<body>
    <!-- 
        Vue中有2种数据绑定的方式:
            1、单向绑定(v-bind):数据只能从data流向页面
            2、双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
                备注:
                    1、双向绑定一般应用在表单类元素上(如:input、select等)
                    2、v-model:value 可以简写为 v-model,因为 v-model 默认收集的就是value值
     -->
    <div id="demo">
        <!-- 普通写法 -->
        <!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
        双向数据绑定:<input type="text" v-model:value="name"><br/> -->

        <!-- 简写 -->
        单向数据绑定:<input type="text" :value="name"><br/>
        双向数据绑定:<input type="text" v-model="name"><br/>

    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        //创建vue2.0实例
        new Vue({
            el:'#demo',
            data() {
                return {
                    name:"切尔西",
                }
            },
        }) 
    </script>
</body>

常用写法

<body>

    <div id="demo">
        <h1>{{name}}</h1>

    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        //创建vue2.0实例
        const vm =  new Vue({
            // el:'#demo',
            data() {
                return {
                    name:"切尔西",
                }
            },
        }) 
        console.log(vm);
        vm.$mount('#demo');
    </script>
</body>

_Object.defineProperty

<body>

    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
        let number = 18
        let person = {
            name:"哈弗茨",
            sex:"男",
            // age:18  //在这设定的age值可以随意枚举、修改和删除
        }

        // 默认不可枚举不可修改不可删除
        Object.defineProperty(person,'age',{
            // value:18,
            // enumerable:true, //控制属性是否可以枚举,默认值是false
            // writable:true, //控制属性是否可以被修改,默认值是false
            // configurable:true, //控制属性是否可以被删除,默认值是false

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

            //当有人修改person的age属性,set函数(setter)就会被调用,且会收到修改的具体值
            set(value){
                console.log("有人修改了age属性,且值是",value)
                number = value    
            }
            

        })

        console.log("person属性:",Object.keys(person)) // 提醒对象里面所有属性的属性名变成一个数组


    </script>

在这里插入图片描述

数据代理

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

<body>

    <!--  数据代理:通过一个对象代理另一个对象中属性的操作(读/写) -->
    <script type="text/javascript">

        // Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        let obj = {x:1}
        let obj1 = {y:2}

        Object.defineProperty(obj1,'x',{

            get(){

                return obj.x
            },

            set(value){

                obj.x = value
            }
            

        })


    </script>

</body>

在这里插入图片描述

Vue中的数据代理

<body>
    <div id="demo">
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
    </div>

    <script type="text/javascript">

        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    name:'剑桥',
                    address:'英国'
                }
            },
        })


    </script>

</body>

在这里插入图片描述
用 vm._data.name修改也行

<body>
    <div id="demo">
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
    </div>

    <script type="text/javascript">

        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        let data = {
            name:"剑桥",
            address:"英国"
        }
        const vm = new Vue({
            el:'#demo',
            data
        })


    </script>

</body>

在这里插入图片描述
验证:在这里插入图片描述

事件处理

事件的基本使用:
1、使用v-on:xxx 或 @xxx 绑定事件 xxx是事件名
2、事件的回调需要配置在methods对象中,最终会在vm上
3、methods中配置的函数,不要用箭头函数,否则this不是vm
4、methods中配置的函数,都是被vue所管理的函数,this 指向的是vm或组件实例对象
5、@cllick = “demo" 和 @click = "demo()"效果一直,但后者可以传参

<body>
    <div id="demo">
        <h2>名称:{{name}}</h2>
        <button v-on:click="toshow1">点我</button>
        <!-- 简写 -->
        <button @click="toshow2($event,6666)">点你</button>
    </div>

    <script type="text/javascript">

        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    name:'剑桥',
                }
            },
            methods: {
                toshow1(e){
                    alert("你好!")  // 
                    console.log(this) // this是vm
                    console.log(e.target.innerText)
                },
                toshow2(e,number){
                    console.log(e,number)
                }
            },
        })


    </script>

</body>

在这里插入图片描述
methods中配置箭头函数 this 指向不是vm
在这里插入图片描述

事件修饰符

<style>
    *{
        margin-top: 20px;
    }
    .demo1{
        height: 50px;
        background-color: blue;
    }
    .box1{
        padding: 5px;
        background-color: green;
    }
    .box2{
        padding: 5px;
        background-color: yellow;
    }
    .list{
        width: 200px;
        height: 200px;
        background-color: aqua;
        /* 滚动条 */
        overflow: auto; 
    }
    li{
        height: 100px;
    }
</style>


<body>
    <div id="demo">
        <h2>名称:{{name}}</h2>
        <!-- 组织默认事件 -->
        <a href="https://www.baidu.com" @click.prevent="toshow">点我提示不跳转</a>

        <!-- 阻止事件冒泡 -->
        <div class="demo1" @click="toshow">
            <button @click.stop="toshow">点我</button>
        </div>

        <!-- 事件只触发一次 -->
        <button @click.once="toshow">点一次会触发,再点不会触发</button>

        <!-- 使用事件的捕获模式 -->
        <div class="box1" @click.capture="showMsg(1)">
            div1
            <!-- 点击box2 输出 2 1 (冒泡是由内向外)-->
            <!-- 使用捕获之后是 1 2 (捕获是由外向内)-->
            <div class="box2" @click="showMsg(2)"> 
                div2
            </div>
        </div>

        <!-- 只有event.target是当前操作的元素时才触发事件 -->
        <!-- 
            @click 点击会冒泡 控制台输出 
            "<button>点我</button>
            <button>点我</button>"
            @click.self:当event.target是当前操作的元素时(div) 才触发事件 (toshow1)
         -->
        <div class="demo1" @click.self="toshow1">
            <button @click="toshow1">点我</button>
        </div>

        <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 -->
        <!-- 
            @wheel 鼠标滚轮事件 鼠标在滚动就会触发事件
            执行事件时会发现 触发了事件但是滚动条没往下走
            原因是 先去触发事件,回调执行完成之后才去执行默认行为,把滚动条往下一移动
            passive 事件的默认行为立即执行,无需等待事件回调执行完毕      
        -->
        <!-- @scorll 滚动条事件 滚动条到底往下拉就不会触发事件-->
        <ul @wheel.passive="roll" class="list">
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
        </ul>
    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    name:'剑桥',
                }
            },
            methods: {
                toshow(e){
                    // e.preventDefault();// 阻止默认事件
                    // e.stopPropagation(); // 阻止事件冒泡
                
                    alert("你好!")  
   
                },
                showMsg(msg){
                    console.log(msg)
                },
                toshow1(e){
                    console.log(e.target);
                    // alert("你好!")  
                },
                roll(){
                    for(let i = 0; i<100000; i++){
                        console.log("动起来")
                    }
                }
                
            },
        })


    </script>

</body>

事件修饰符的复合使用

<!-- 先阻止事件冒泡再阻止跳转 顺序可交换-->
<div class="demo1" @click="toshow">
    <a href="https//www.baidu.com" @click.stop.prevent="toshow">点我</a>
</div>

键盘事件

Vue中常用的按键别名
回车 => enter 删除 => delete(删除键和空格键)
空格 => space 退出 => esc 换行 => tab(特殊,必须配和keydown使用,因为按下tab键光标会切走失去焦点)
上=>up 下=>down 左=>left 右=>right

Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case (短横线命名) 例如:CapsLock => caps-lock

系统修饰键:ctrl alt shift meta(win)
(1) 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
(2) 配合keydown使用:正常触发事件

可以使用keyCode去指定具体按键 例如@keyup.13=“method” 不推荐

Vue.config.keyCodes.自定义键名 = 键码 可以去定制按键别名

<body>
    <div id="demo">
        <h2>名称:{{name}}</h2>
        <input type="text" placeholder="按下回车提示输入" @keyup.enter="toshow">
        
        <!-- <input type="text" placeholder="按下回车提示输入" @keyup.hc="toshow"> -->
    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。
        // Vue.config.keyCodes.自定义键名 = 键码 可以去定制按键别名
        Vue.config.keyCodes.hc = 13

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    name:'剑桥',
                }
            },
            methods: {
                toshow(e){

                    // console.log(e.key, e.keyCode)

                    // if(e.keyCode !== 13) return
                    console.log(e.target.value)
                    
                },
                
            },
        })


    </script>

</body>

按键的复合使用

<!-- 只有输入ctrl y 才能实现 -->
<input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="toshow">

输入姓名案例

<body>
    <div id="demo">
        
        <!-- 插值语法实现 -->
       <!-- 姓:<input type="text" v-model="name.fname"><br>
       名:<input type="text" v-model="name.lname">
       <div>全名:{{name.fname}}-{{name.lname}}</div> -->

        <!-- method方法实现 -->
       姓:<input type="text" v-model="name.fname"><br>
       名:<input type="text" v-model="name.lname"><br>
       全名:<span>{{fullname()}}</span>
       <!-- 加括号返回值 不加括号返回函数 -->

    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    name:{
                        fname: "张",
                        lname: "三"
                    }
                }
            },
            methods: {
                fullname(){
                    return this.name.fname+"-"+this.name.lname;
                }
                
            },
        })


    </script>

</body>

计算属性

<body>
    <div id="demo">
        
       姓:<input type="text" v-model="name.fname"><br>
       名:<input type="text" v-model="name.lname"><br>
       全名:<span>{{fullname}}</span>


    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    name:{
                        fname: "张",
                        lname: "三"
                    }
                }
            },
            computed:{
                fullname:{
                    // 当有人读取fullname时 get就会被调用且返回值就作为fullnme的值
                    // get什么时候调用 1、初次读取fullName时 2、所依赖的数据发生变化时
                    get(){
                        // console.log(this);//此处的this是vm
                        return this.name.fname+'-'+this.name.lname;
                    },
                    set(value){
                        const arr = value.split('-');
                        this.name.fname = arr[0];
                        this.name.lname = arr[1];
                    }
                }
            }
        })


    </script>

</body>

在这里插入图片描述
可以简写

 // 简写  只考虑读取不考虑修改
 fullname(){
     console.log('get被调用了')
     return this.name.fname+'-'+this.name.lname;
 }

监视属性

<body>
    <div id="demo">
        <h2>今天天气很{{info}}</h2>
        <button  @click="change">切换天气</button>
        <!-- 可以这么写 @XXX="yyy" yyy可以写一些简单的语句-->
        <!-- <button  @click="isHot = !isHot">切换天气</button> -->
        <!-- 注意 -->
        <!-- <button  @click="window.alert(1)">点</button> -->

    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    isHot:true,
                    // window
                }
            },
            computed:{
                info(){
                    return this.isHot ? "炎热" : "凉爽"
                }
            },
            methods: {
                change(){
                    this.isHot = !this.isHot

                }
            },
/*             watch:{
                isHot:{
                    immediate:true, //初始化时让handler调用一下
                    //handler什么时候调用? 当isHot发生改变时
                    handler(newValue,oldValue){
                        console.log("isHot被修改了",newValue,oldValue)
                    }
                }
            } */

        })
        vm.$watch('isHot',{
            immediate:true, //初始化时让handler调用一下
            //handler什么时候调用? 当isHot发生改变时
            handler(newValue,oldValue){
                console.log("isHot被修改了",newValue,oldValue)
            }
        })

    </script>

</body>

简写

watch:{

    // 简写 (前提是不需要immediate deep配置项 只有handler)
    isHot(newValue,oldValue){
        console.log("isHot被修改了",newValue,oldValue)
    }
}
--------------------------------
// 简写
vm.$watch('isHot',function(newValue,oldValue){
    console.log("isHot被修改了",newValue,oldValue)
})

深度监视

<body>
    <!-- 
        深度监视:
            (1)Vue中的watch默认不检测对象内部值的改变(一层)
            (2)配置deep:true可以检测对象内部值改变(多层)
        备注:
            (1)Vue自身可以检测对象內部值的改变,但Vue提供的watch默认不可以
            (2)使用watch时根据数据的具体结构,决定是否采用深度监视
     -->
    <div id="demo">
        <h2>a的值是:{{numbers.a}}</h2>
        <button  @click="numbers.a++">点击a +1</button>
        <h2>b的值是:{{numbers.b}}</h2>
        <button  @click="numbers.b++">点击b +1</button>
   
    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    numbers:{
                        a:1,
                        b:1
                    }
                }
            },
            watch:{
                // 监视多级结构中某个属性的变化
                'numbers.a':{
                    handler(){
                        console.log('a被改变了')
                    }
                },
                // 监视多级结构中所有属性的变化
                numbers:{
                    deep:true,
                    handler(){
                        console.log('numbers改变了')
                    }
                }
            }

        })


    </script>

</body>

watch对比computed

computed 和 watch 之间的区别:
1、computed 能完成的功能,watch都可以完成。
2、watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
1、被Vue管理的函数,最好写成普通函数,这样this的指向才是 vm 或组件实例对象。
2、所有不被 Vue 所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,这样 this 的指向才是 vm 或 组件实例对象。

<body>
    <div id="demo">
       姓:<input type="text" v-model="name.fname"><br>
       名:<input type="text" v-model="name.lname"><br>
       <!-- 全名:<span>{{fullname}}</span> -->
       全名:<span>{{name.fullname}}</span>
    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    name:{
                        fname: "张",
                        lname: "三",
                        fullname:"张-三"
                    }
                }
            },
            watch:{
                'name.fname'(val){
                    // 改动后延迟1s在全名中显示
                    setTimeout(()=>{
                        this.name.fullname = val + '-' + this.name.lname;
                    },1000)
                },
                'name.lname'(val){
                    this.name.fullname = this.name.fname + '-' + val;
                }
            }
/*             computed:{
                fullname(){
                    return this.name.fname+'-'+this.name.lname;
                }
            } */

        })


    </script>

</body>

绑定class样式

<style>

.basic{
    height: 100px;
    width: 500px;
    border-style: solid;
    border-width: 1px;
}
    .red{
        background-color: red;
    }
    .green{
        background-color: green;
    }
    .yellow{
        background-color: yellow;
    }
    .class1{
        font-size: 80px;
    }
    .class2{
        background-color: blue;
    }
    .class3{
        border-radius: 10px;
    }
    
</style>
<body>
    <div id="demo">
        <h2>{{title}}</h2>
        <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
        <div class="basic" :class="color" @click="changeColor">{{name}}</div>
        <br>
        <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数不确定,名字也不确定-->
        <div class="basic" :class="classArr" >{{name}}</div>
        <br>
        <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定,名字也确定,但动态决定要不要用-->
        <div class="basic" :class="classObj" >{{name}}</div>


    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    title:'绑定class样式',
                    name:'test',
                    color:'red',
                    classArr:['class1','class2','class3'],
                    classObj:{
                        class1:false,
                        class2m   :false
                    }
                }
            },
            methods: {
                changeColor(){
                    const arr = ['red','green','yellow']
                    const index = Math.floor(Math.random()*3)//随机 0,1,2
                    console.log(index)
                    this.color = arr[index]
                }
            },

        })


    </script>

</body>

绑定style样式

<style>
.basic{
    height: 100px;
    width: 500px;
    border-style: solid;
    border-width: 1px;
}
</style>
<body>  
    <div id="demo">
        <h2>{{title}}</h2>

        <div class="basic" :style="{fontSize: fsize+'px'}">{{name}}</div>
        <br>
        <div class="basic" :style="styleObj">{{name}}</div>
        <br>
        <div class="basic" :style="[styleObj,styleObj1]">{{name}}</div>
        <br>
        <div class="basic" :style="styleArr">{{name}}</div>

    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    title:'绑定style样式',
                    name:'test',
                    fsize:40,
                    styleObj:{
                        fontSize:'40px',
                        color:'red',
                    },
                    styleObj1:{
                        backgroundColor:'blue'
                    },
                    styleArr:[
                        {
                            fontSize:'40px',
                            color:'red',
                        },
                        {
                            backgroundColor:'green'
                        }
                    ]
                }
            },

        })

    </script>

</body>

条件渲染

v-if:
(1)v-if = “表达式”
(2)v-else-if = “表达式”
(3)v-else = “表达式”
适用于切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:v - if 可以和 v-else-if、v-else一起使用,但要求结构不能被“打断”
v-show:
v-show = "表达式“
适用于:切换频率较高的场景
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

<body>  
    <div id="demo">

        <!-- 使用v-show做条件渲染 -->
        <!-- <h2 style="display: none;">巴塞罗那</h2> -->
<!--         <h2 v-show="false" style="color: red;">{{name}}</h2>
        <h2 v-show="1===1" style="color: blue;">{{name}}</h2>
 -->
        <!-- 使用v-if做条件渲染 -->
        <!--  -->
        <h2 v-if="false" style="color: red;">{{name}}</h2>
        <h2 v-if="1===1" style="color: blue;">{{name}}</h2>

        <h2>当前n的值:{{n}}</h2>
        <button @click="n++">点我n+1</button>

        <div v-show="n===1" style="color: aqua;">1</div>
        <div v-show="n===2" style="color: aqua;">2</div>
        <div v-show="n===3" style="color: aqua;">3</div>

        <!-- v-else 和 v-else-if -->
        <div v-if="n===1" style="color: brown;">1</div>
        <div v-else-if="n===2" style="color: brown;">2</div>
        <div v-else-if="n===3" style="color: brown;">3</div>
        <div v-else="n===3" style="color: brown;">no number</div>

        <!-- v-if 和 template 的配合使用 -->
        <template v-if="n===1">
            <div>
                <h2></h2>
                <h2></h2>
                <h2></h2>
                <h2></h2>
            </div>
        </template>
    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    name:"巴塞罗那",
                    n:0
                }
            },

        })

    </script>

</body>

列表渲染

v-for

<body>  
    <div id="demo">
        <!-- 遍历数组 -->
        <h2>人员列表</h2>
        <ul>
            <li v-for="(val,index) in persons" :key="index">
                {{val.name}} - {{val.age}}
            </li>
        </ul>
        <!-- 遍历对象 -->
        <h2>汽车信息</h2>
        <ul>
            <li v-for="(val,k) in cars" :key="k">
                {{k}} - {{val}}
            </li>
        </ul>
        <!-- 遍历字符串 -->
        <h2>测试遍历字符串</h2>
        <ul>
            <li v-for="(char,index) in str" :key="index">
                {{char}} - {{index}}
            </li>
        </ul>
        <!-- 遍历数字 -->
        <h2>测试遍历指定次数</h2>
        <ul>
            <li v-for="(num,index) in 5" :key="index">
                {{num}} - {{index}}
            </li>
        </ul>
    </div>

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    persons:[
                        {id:"001", name:'张三', age:18},
                        {id:"002", name:'里斯', age:19},
                        {id:"003", name:'旺旺', age:20},
                    ],
                    cars:{
                        name:"AE86",
                        price:"20w",
                        color:"white"
                    },
                    str:'hello',
                }
            },

        })

    </script>

</body>

用index作为key可能会引发的问题:
1、若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ===》界面效果没问题,但效率低
2、如果结构中还包含有输入类的DOM(input),会产生错误DOM更新 =》界面有问题

列表过滤

<body>  
    <div id="demo">
        <!-- 遍历数组 -->
        <input type="text" placeholder="输入姓名" v-model="keyword">
        <ul>
            <li v-for="(val,index) in filpersons" :key="index">
                {{val.name}} - {{val.age}} - {{val.sex}}
            </li>

        </ul>

    </div>  

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        // 用watch实现
     /*    const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    keyword:'',
                    persons:[
                        {id:"001", name:'马冬梅', age:19, sex:'女'},
                        {id:"002", name:'周冬雨', age:20, sex:'女'},
                        {id:"003", name:'周杰伦', age:21, sex:'男'},
                        {id:"004", name:'屎壳郎', age:22, sex:'男'},
                    ],
                    filpersons:[]
                }
            },
            watch:{
                keyword:{
                    immediate:true,
                    handler(val){
                        this.filpersons = this.persons.filter((v)=>{
                            return v.name.indexOf(val) !== -1
                        })
                    }
                }
            }
            

        })
 */
        
        // 用computed实现
        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    keyword:'',
                    persons:[
                        {id:"001", name:'马冬梅', age:19, sex:'女'},
                        {id:"002", name:'周冬雨', age:20, sex:'女'},
                        {id:"003", name:'周杰伦', age:21, sex:'男'},
                        {id:"004", name:'屎壳郎', age:22, sex:'男'},
                    ],
                }
            },
            computed:{
                filpersons(){
                    return this.persons.filter((v)=>{
                        return v.name.indexOf(this.keyword) !== -1
                    })
                }
            }

        })
    </script>

</body>

列表排序

<body>  
    <div id="demo">
        <!-- 遍历数组 -->
        <input type="text" placeholder="输入姓名" v-model="keyword">
        <button @click="sortType=1">年龄升序</button>
        <button @click="sortType=2">年龄降序</button>
        <button @click="sortType=0">原顺序</button>
        <ul>
            <li v-for="(val,index) in filpersons" :key="val.id">
                {{val.name}} - {{val.age}} - {{val.sex}}
            </li>
        </ul>

    </div>  

    <script type="text/javascript">
 
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        // 用computed实现
        const vm = new Vue({
            el:'#demo',
            data() {
                return {
                    keyword:'',
                    sortType:0, // 0原顺序 1升序 2降序
                    persons:[
                        {id:"001", name:'马冬梅', age:23, sex:'女'},
                        {id:"002", name:'周冬雨', age:20, sex:'女'},
                        {id:"003", name:'周杰伦', age:21, sex:'男'},
                        {id:"004", name:'屎壳郎', age:22, sex:'男'},
                    ],
                }
            },
            computed:{
                filpersons(){
                    const arr = this.persons.filter((v)=>{
                        return v.name.indexOf(this.keyword) !== -1
                    })
                    //判断一下是否需要排序
                    if(this.sortType){
                        arr.sort((x,y) => {
                            return this.sortType === 1 ? x.age - y.age : y.age - x.age
                        })
                    }
                    return arr
                }
            },

        })

        // [4,2,1,6].sort((x,y)=>{return x-y}) // 1,2,4,6
    </script>

</body>

Vue监视数据

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

在Vue中修改数组中的某个元素的方法:
1、使用API:push() pop() shift() unshift() splice() sort() reverse()
2、Vue.set() 或 vm.$set()

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

<body>
    <div id="demo">
        <h2>学生信息</h2>
        <button @click="student.age++">年龄+1岁</button><br>
        <button @click="addSex">添加性别属性,默认值:男</button><br>
        <button @click="student.sex = '未知'">修改性别属性,默认值:男</button><br>
        <button @click="addFriend">在列表首位添加一个朋友</button><br>
        <button @click="updateFirstFs">修改第一个朋友的名字为:张三</button><br>
        <button @click="addHobby">添加一个爱好</button><br>
        <button @click="filterSmoke">过滤爱好中的抽烟</button><br>
        <button @click="updateHobby">修改第一个爱好为:开车</button><br>
        <h3>姓名:{{student.name}}</h3>
        <h3>年龄:{{student.age}}</h3>
        <h3 v-if="student.sex">性别:{{student.sex}}</h3>
        <h3>爱好:</h3>
        <ul>
            <li v-for="(h,index) in student.hobby" :key="index">
                {{h}}
            </li>
        </ul>
        <h3>朋友们:</h3>
        <ul>
            <li v-for="(f,index) in student.friends" :key="index">
                {{f.name}} - {{f.age}}
            </li>
        </ul>

    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    student: {
                        name: '蓝胖子',
                        age: 18,
                        hobby: ['抽烟', '喝酒', '烫头'],
                        friends: [{
                                name: 'tom',
                                age: 18
                            },
                            {
                                name: 'jerry',
                                age: 20
                            }
                        ]
                    }
                }
            },
            methods: {
                addSex() {
                    // Vue.set(this.student, 'sex', '男')
                    this.$set(this.student,'sex','男')
                },
                addFriend(){
                    this.student.friends.unshift({name:"messi",age:35})
                },
                updateFirstFs(){
                    this.student.friends[0].name = 'vivi';
                },
                addHobby(){
                    this.student.hobby.push("play")
                },
                updateHobby(){
                    // this.student.hobby.splice(0,1,'开车')
                    Vue.set(this.student.hobby,0,'开车')
                },
                filterSmoke(){
                    this.student.hobby = this.student.hobby.filter((h)=>{
                        return h!=='抽烟'
                    })
                }
            },

        })
    </script>

</body>

收集表单数据

若:<input type="text">,则v-model收集的是value值,用户输入的就是value值
若:<input type="radio">,则v-model收集的是value值,且要给标签配置value值
若:<input type="checkbox">
1、没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选 是布尔值)
2、配置input的value属性:
(1)v-model的初始值的非数组,那么收集的就是checked(勾选 or 未勾选 是布尔值)
(2)v-model的初始值是数组,那么收集的就是value组成的数组

v-model的三个修饰符
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤

<body>
    <div id="demo" @submit.prevent="demo">
        <form>
            账号:<input type="text" v-model.trim="userInfo.account"> <br><br>
            密码:<input type="password" v-model="userInfo.password"> <br><br>
            年龄:<input type="number" v-model.number="userInfo.age"> <br><br>
            性别:男<input type="radio" name="sex" v-model="userInfo.sex" value="male" ><input type="radio" name="sex" v-model="userInfo.sex" value="female" ><br><br>
            爱好:抽烟<input type="checkbox" v-model="userInfo.hobby" value="smoke">
            喝酒<input type="checkbox" v-model="userInfo.hobby" value="drink">
            烫头<input type="checkbox" v-model="userInfo.hobby" value="hothead"><br><br>
            所属校区:	
            <select v-model="userInfo.city">
                <option value="">请选择校区</option>
                <option value="HN">湖南</option>
                <option value="HB">湖北</option>
                <option value="ZJ">浙江</option>
            </select><br><br>
    
            
            其他信息:<textarea v-model.lazy="userInfo.other"></textarea><br><br>
    
            <input type="checkbox" v-model="userInfo.agree">阅读并接受 <a href="">《用户协议》</a><br><br>
            
            <button>提交</button>

        </form>


    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    userInfo:{
                        account:"",
                        password:"",
                        age:"",
                        sex:"male",
                        hobby:[],
                        city:'ZJ',
                        other:'',
                        agree:'',
                    }
                }
            },
            methods: {
                demo(){
                    console.log(JSON.stringify(this.userInfo))
                }
            },

        })
    </script>

</body>

过滤器

1、注册过滤器 Vue.filter(name,callback) 或 new Vue(filters:{})
2、使用过滤器:{{xxx | 过滤器名}} 或 v-bind: 属性 = “xxx | 过滤器名”
注意:
过滤器也可以接受额外参数、多个过滤器也可以串联
并没有改变原本的数据,是产生新的数据

<script type="text/javascript" src="../js/dayjs.min.js"></script>
<body>
    <div id="demo">
        <h2>显示格式化后的时间</h2>
        <!-- 计算属性实现 -->
        <h3>现在是:{{fmtTime}}</h3>
        <!-- methods实现 -->
        <h3>现在是:{{getfmtTime()}}</h3>
        <!-- 过滤器实现 把time作为value传进去 返回值替换{{}}-->
        <h3>现在是:{{time | timeFormater}}</h3>
        <!-- 过滤器实现 把time作为value传进去,小括号的值作为str 返回值替换{{}}-->
        <h3>现在是:{{time | timeFormater('YYYY-MM-DD')}}</h3>
        <!-- 过滤器嵌套 -->
        <h3>现在是:{{time | timeFormater('YYYY-MM-DD') | mySlice}}</h3>

        <!-- 使用 v-bind 不推荐 -->  <!-- <h3 x="bar">巴塞罗那</h3> -->
        <h3 :x="msg | mySlice1">巴塞罗那</h3>
        
    </div>

    <div id="demo1">
        <h2>{{msg | mySlice1}}</h2>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        //全局过滤器 在new Vue({}) 之前
        Vue.filter('mySlice1', function(value){
            return value.slice(0,3)
        })

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    time: Date.now(),
                    msg:'barcelona'
                }
            },
            // 计算属性实现
            computed: {
                fmtTime(){
                    return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
                }
            },
            // methods实现
            methods: {
                getfmtTime(){
                    return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
                }
            },
            // 局部的过滤器
            // 过滤器实现
            filters:{
                timeFormater(value, str='YYYY-MM-DD HH:mm:ss'){
                    return dayjs(value).format(str)
                },
                mySlice(value){
                    return value.slice(0,4)
                }
                
            }

        })
        
        new Vue({
            el:"#demo1",
            data(){
                return{
                    msg:"hello"
                }
            }
        })
    </script>
</body>

v - text 指令

<body>
    <div id="demo">
        <div>你好,{{word}}</div>
        <div v-text="name"></div>
        <div v-text="str"></div>
    </div>


    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    word:"假期",
                    name:"barce",
                    str:"<h3>早上好</h3>"
                }
            },

        })
    </script>

</body>

在这里插入图片描述

v-html 指令

v- html 有安全性问题:
(1)在网站上动态渲染任意html是非常危险的,容易导致xss攻击
(2)一定要在可信的内容上使用 v-html 不要用在用户提交的内容上

加入cookie值
在这里插入图片描述
可以读出 :
在这里插入图片描述

<body>
    <div id="demo">
        <div v-html="str"></div>
        <div v-html="str1"></div>
    </div>


    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    str:"<h3>早上好</h3>",
                    str1:'<a href=javascript:location.href="https://www.baidu.com/?"+document.cookie>资源,快来!</a>'
                }
            },

        })
    </script>

</body>

点击跳转之后则会暴露cookie
在这里插入图片描述
当 httpOnly 为true则不会暴露cookie
在这里插入图片描述

v- cloak

在这里插入图片描述

v-once

<body>
    <!-- 
        v-once指令:
            1、v-once所在节点在初次渲染后,就视为静态内容了。
            2、以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
     -->
    <div id="demo">
        <h2 v-once>初始化的n值是:{{n}}</h2>
        <h2>当前的n值是:{{n}}</h2>
        <button @click="n++">点+1</button>
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    n:1
                }
            },

        })
    </script>

</body>

v-pre

<body>
    <!-- 
        v-pre指令:
            1、跳过其所在节点的编译过程。
            2、可利用它跳过没有使用指令语法、插值语法的节点,加快编译
     -->
    <div id="demo">
        <h2 v-pre>hello Vue</h2>
        <h2 v-pre>当前的n值是:{{n}}</h2>
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    n:1
                }
            },

        })
    </script>

</body>

在这里插入图片描述

自定义指令

<body>
    <!-- 
        需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍
        需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
     -->
    <div id="demo">   
        <h2>hello Vue</h2>
        <h2>当前的n值是:<span v-text="n"></span></h2>
        <h2>放大10倍后的n值是:<span v-big="n"></span></h2>
        <button @click="n++">点 n +1</button>
        <!-- 分隔线 -->
        <hr>
        <input type="text" v-fbind:value="n">
    </div>
    <!-- 
        指令函数何时会被调用?
            1、指令与元素成功绑定时 (一上来就绑定)
            2、指令所在的模板被重新解析时
    -->
    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    n:1
                }
            },
            directives:{
                // element是获取的标签(DOM 元素),binding是绑定的数据
                big(element,binding){
                    element.innerText = binding.value*10
                },
                fbind:{
                    // 指令与元素成功绑定时 (一上来就绑定)
                    bind(element,binding){
                        element.value = binding.value
                    },
                    // 指令所在元素被插入页面时
                    inserted(element,binding){
                      element.focus()  
                    },
                    // 指令所在的模板被重新解析时
                    update(element,binding) {
                        element.value = binding.value
                    },
                }
            }

        })
    </script>

</body>

全局指令

<body>

    <div id="demo">   
        <h2>hello Vue</h2>
        <h2>当前的n值是:<span v-text="x"></span></h2>
        <!-- <h2>放大10倍后的n值是:<span v-big="x"></span></h2> -->
        <h2>放大10倍后的n值是:<span v-big-number="x"></span></h2>
        <button @click="x++">点 n +1</button>
        <!-- 分隔线 -->
        <hr>
        <input type="text" v-fbind:value="x">
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false //设置为 false 以阻止 vue 在启动时生成生产提示。

        Vue.directive('fbind',{
            // 指令与元素成功绑定时 (一上来就绑定)
            bind(element,binding){
            element.value = binding.value
            },
            // 指令所在元素被插入页面时
            inserted(element,binding){
            element.focus()  
            },
            // 指令所在的模板被重新解析时
            update(element,binding) {
            element.value = binding.value
            },

        })
/*         Vue.directive('big',function(element,binding){

            element.innerText = binding.value*10
            
        }) */

        const vm = new Vue({
            el: '#demo',
            data() {
                return {
                    x:1
                }
            },
            directives:{
                // element是获取的标签(DOM 元素),binding是绑定的数据
                'big-number'(element,binding){
                    element.innerText = binding.value*10
                },

            }

        })
    </script>

</body>

总结:
(1)局部指令:

new Vue({       
    directives:{指令名:配置对象}  
})
或
new Vue({
	directives{指令名:回调函数}
})

(2)全局指令:

Vue.directive(指令名:配置对象) 或 Vue.directive(指令名:回调函数)

生命周期

<body>
    <div id="demo">
        <!-- <h2 :style="{opacity:opacity}">欢迎来到加勒比</h2> -->
        <!-- 属性名和属性值(变量名)是相同的,就可以省略只写一个 -->
        <h2 :style="{opacity}">欢迎来到加勒比</h2>
    </div>

</body>
<script type="text/javascript">
    Vue.config.productionTip = false;

    new Vue({
        el: '#demo',
        data() {
            return {
                opacity: 1
            }
        },
        // Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
        mounted() {
            console.log('mounted',this)
            setInterval(() => {
                this.opacity -= 0.01;
                if (this.opacity <= 0) this.opacity = 1
            }, 16)
        }
    })

/*     const vm = new Vue({
        el:'#demo',
        data() {
            return {
                opacity:1
            }
        },
    })

    // 通过外部的定时器实现(不推荐)
    setInterval(() =>{
        vm.opacity -= 0.01;
        if(vm.opacity <= 0) vm.opacity = 1
    },16) */
</script>

完整生命周期

<body>

    <div id="demo">-->
        <h2>当前的值是:{{n}}</h2>
        <button @click="add">点 n +1</button>
        <button @click="bye">点我销毁vm</button>
    </div>

</body>
<script type="text/javascript">
    Vue.config.productionTip = false;

    new Vue({
        el: '#demo',
        data() {
            return {
                n:1
            }
        },
        methods: {
            add(){
                console.log("调用add")
                this.n++
            },
            bye(){
                console.log('bye')
                this.$destroy()
            }
        },
        watch:{
            n(){
                console.log("n变了")
            }
        },
        beforeCreate() {
            console.log('beforeCreate:')
            // console.log(this);
            // debugger;
        },
        created() {
            console.log('Created:')
            // debugger;
        },
        beforeMount() {
            console.log('beforeMout:')
            // debugger;
        },
        mounted() {
            console.log('mounted :')
            // debugger;
        },
        beforeUpdate() {
            console.log('beforeUpdate :')
            // console.log(this.n)
            // debugger;
        },
        updated() {
            console.log('update :')
            console.log(this.n)
            // debugger;
        },
        beforeDestroy() {
            console.log('beforeDestroy')
            console.log(this.n)
            this.add()
        },
        destroyed() {
            console.log('destroyed')
        },
    })
</script>

在这里插入图片描述
总结

<body>
    <!-- 
        常用的生命周期钩子:
            1、mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
            2、beforeDestroy:清楚定时器、解绑自定义事件、取消订阅消息等【收尾工作】

        关于销毁Vue实例
            1、销毁后借助Vue开发者工具看不到任何消息
            2、销毁后自定义事件会失效,但原生的DOM事件依然有效
            3、一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
     -->
    <div id="demo">
        <h2 :style="{opacity}">欢迎来到加勒比</h2>
        <button @click="opacity = 1">透明的设置为1</button>
        <button type="text/javascript" @click="stop">停止变换</button>
    </div>

</body>
<script type="text/javascript">
    Vue.config.productionTip = false;

    new Vue({
        el: '#demo',
        data() {
            return {
                opacity: 1
            }
        },
        methods: {
            stop(){
                this.$destroy()
            }
        },
        // Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
        mounted() {
            console.log('mounted',this)
            this.timer = setInterval(() => {
                this.opacity -= 0.01;
                if (this.opacity <= 0) this.opacity = 1
            }, 16)
        },
        beforeDestroy() {
            console.log("vue即将被销毁")
            clearInterval(this.timer)
        },
    })

</script>

组件

非单文件组件

Vue使用组件的三大步骤:
(1)定义组件(创建组件)
(2)注册组件
(3)使用组件(写组件标签)

定义一个组件:使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样。但也有区别:
(1)el 不要写。最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
(2)data必须写成一个函数。避免组件被复用时,数据存在引用关系
备注:使用template可以配置组件结构

注册组件:
(1)局部注册:靠new Vue 传入components选项
(2)全局注册:靠Vue.component(‘组件名’,组件)

<body>
    
    <div id="demo">
        <h2>{{msg}}</h2>
        <hello></hello>
        <!-- 第三步:编写组件标签 -->
        <school></school>
        <hr>
        <student></student>
    </div>
    <hr>
    <div id="demo1">
        <hello></hello>
    </div>

</body>
<script type="text/javascript">
    Vue.config.productionTip = false;

    // 第一步创建组件
    // 创建school组件
    const school = Vue.extend({
        template:`
        <div>
            <h2>学校名:{{schoolName}}</h2>
            <h2>学校地址:{{address}}</h2>
            <button @click="show">点我提示</button>
        </div>
        `,
        data(){
            return{
                schoolName:"剑桥",
                address:'通辽'
            }
        },
        methods: {
            show(){
                alert(this.schoolName)
            }
        },
    })

    // 创建student组件
    const student = Vue.extend({
        template:`
            <div>
                <h2>学生名字:{{studentName}}</h2>
                <h2>学生年龄:{{age}}</h2>
            </div>
        `,
        data(){
            return{
                studentName:"张三",
                age:13
            }
        }
    })
    
    // 创建组件hello
    const hello = Vue.extend({
        template:`
        <div>
            <h2>全世界{{name}}联合起来!</h2>
        </div>
        `,
        data(){
            return{
                name:"无产阶级"
            }
        }
    })

    // 全局注册组件
    Vue.component('hello',hello)

    new Vue({
        el: '#demo',
        data() {
            return {
                msg:"hello"
            }
        },
        //第二步:注册组件(局部注册)
        components:{
            /* 
                可以用键值对 xuexiao:school 
                对应的组件标签就要变成 <xuexiao></xuexiao>
            */
            school,
            student
        }
        
    })

    new Vue({
        el:"#demo1"
    })
</script>

组件的命名方式

<body>
    <!-- 
        1、关于组件名:
            一个单词组成: 首字母小写:school             首字母大写:School
            多个单词组成: kebab-case命名:my-school     CamelCase命名:MySchool(需要Vue脚手架支持)
            注意:(1) 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
                (2) 可以使用name配置项指定组件再开发者工具中呈现的名字。例如下列:name:"qiu",

        2、关于组件标签:
            第一个写法:<school></school> 第二种写法:<school/>
            注意:不能使用脚手架时,<school/>会导致后续组件不能渲染

        3、一个简写方式:
            const school = Vue.extend(options) 可简写为:const school = options
     -->
    <div id="demo">
        <h2>{{msg}}</h2>
        <my-school></my-school>
        <!-- <my-school/> -->
        <school1></school1>
    </div>

</body>
<script type="text/javascript">
    Vue.config.productionTip = false;

    // 创建school组件
    const school = Vue.extend({
        // name:"qiu",
        template:`
        <div>
            <h2>学校名:{{schoolName}}</h2>
            <h2>学校地址:{{address}}</h2>
        </div>
        `,
        data(){
            return{
                schoolName:"剑桥",
                address:'通辽'
            }
        },
    })
    // 简写
    const school1 = {
        template:`
        <div>
            <h2>学校名:{{schoolName}}</h2>
            <h2>学校地址:{{address}}</h2>
        </div>
        `,
        data(){
            return{
                schoolName:"剑桥1",
                address:'通辽1'
            }
        },
    }

    new Vue({
        el: '#demo',
        data() {
            return {
                msg:"hello"
            }
        },
        //注册组件(局部注册)
        components:{

            'my-school':school,
            school1
        }
        
    })
</script>

组件的嵌套

<body>

    <div id="demo">
        <!-- 可以直接在vm中编写标签 -->
        <!-- <app></app> -->
    </div>

</body>
<script type="text/javascript">
    Vue.config.productionTip = false;
    // 创建student组件
    const student = Vue.extend({
        template: `
            <div>
                <h2>学生名字:{{studentName}}</h2>
                <h2>学生年龄:{{age}}</h2>
            </div>
        `,
        data() {
            return {
                studentName: "张三",
                age: 13
            }
        }
    })

    // 创建school组件
    const school = Vue.extend({
        template: `
        <div>
            <h2>学校名:{{schoolName}}</h2>
            <h2>学校地址:{{address}}</h2>
            <student></student>
        </div>
        `,
        data() {
            return {
                schoolName: "剑桥",
                address: '通辽'
            }
        },
        //注册组件(局部注册)
        // 注意创建student组件要在创建school组件之前
        components: {
            student
        }
    })

    // 创建组件hello
    const hello = Vue.extend({
        template: `
            <h2>全世界{{name}}联合起来!</h2>
        `,
        data() {
            return {
                name: "无产阶级"
            }
        }
    })

    // 定义app组件
    const app = Vue.extend({
        template: `
        <div>
            <hello></hello>
            <school></school>
        </div>
        `,
        components:{
            school,
            hello
        }
    })

    // 创建Vue
    new Vue({
        template: `<app></app>`,
        el: '#demo',
        //注册组件(局部注册)
        components: {app}

    })
</script>

在这里插入图片描述

 关于VueComponent:
        1、school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
        2、当我们写<school/> 或 <school></school> Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行 new VueComponent(options)
        3、特别注意:每次调用Vue.extend。返回的都是一个全新的VueComponent!!!
        4、关于this指向:
            (1) 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 他们的this均是【VueComponents实例对象】
            (2) new Vue()配置中:data函数、methods中的函数、watch中的函数、computed中的函数 他们的this均是【Vue实例对象】
        5、VueComponent的实例对象,简称vc(可称之为组件实例对象)  Vue实例对象,简称vm

原型对象

    //定义一个构造函数
    function Demo(){
        this.x = 1;
        this.y = 2;
    }
    // 创建一个Demo实例对象
    const d = new Demo()
    console.log(Demo.prototype)//显示原型属性
    console.log(d.__proto__)//隐式原型属性
    console.log(Demo.prototype===d.__proto__)// true
    //通过显示原型属性操作原型对象,追加一个k属性,值为99
    Demo.prototype.k=99;
    console.log("d:",d)

在这里插入图片描述
内置关系
在这里插入图片描述

<body>
    <!-- 
        重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
        为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性和方法
     -->
    <div id="demo">
        <school></school>
    </div>

</body>

<script type="text/javascript">
    Vue.config.productionTip = false;
    Vue.prototype.x = 99

    // 创建school组件
    const school = Vue.extend({
        // name:'qiu',
        template: `
        <div>
            <h2>学校名:{{schoolName}}</h2>
            <h2>学校地址:{{address}}</h2>
            <button @click="show">点我</button>
        </div>
        `,
        data() {
            return {
                schoolName: "剑桥",
                address: '通辽'
            }
        },
        methods: {
            show(){
                alert(this.x)
            }
        },
    })
    console.log(school.prototype.__proto__ === Vue.prototype)//true

    // 创建Vue
    new Vue({
        el: '#demo',
        //注册组件(局部注册)
        components: {school}

    })

</script>
单文件组件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Student.vue

<template>
    <!-- 组件的结构 -->
    <div>
        <h2>学生名字:{{studentName}}</h2>
        <h2>学生年龄:{{age}}</h2>
    </div>
</template>
<script>
    //组件交互的相关代码(数据、方法等等)

    export default {
        name: "Student",
        data() {
            return {
                studentName: "张三",
                age: 13
            }
        }
    }
</script>
<style>
</style>

School.vue

<template>
    <!-- 组件的结构 -->
    <div class="demo">
        <h2>学校名:{{schoolName}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="show">点我</button>
    </div>
</template>
<script>
    //组件交互的相关代码(数据、方法等等)

/*     // 三种暴露方式 第一种 在const 前面加export (分别暴露)
    const School = Vue.extend({
        data() {
            return {
                schoolName: "剑桥",
                address: '通辽'
            }
        },
        methods: {
            show(){
                alert(this.x)
            }
        },
    })
    // 第二种暴露方式 统一暴露
    // export {school}
    // 第三种暴露方式 默认暴露 (推荐)
    export default school */

    // 可以直接
    export default {
        name:"School",
        data() {
            return {
                schoolName: "剑桥",
                address: '通辽'
            }
        },
        methods: {
            show(){
                alert(this.x)
            }
        },
    }

</script>
<style>
    /* 组件的样式 */
    .demo{
        background-color: aqua;
    }
</style>

App.vue

<template lang="">
    <div>
        <img src="./assets/logo.png" alt="logo">
        <School></School>
        <Student></Student>
    </div>
</template>
<script>
    //引入组件
    import School from './components/School.vue'
    import Student from './components/Student.vue'
    export default {
        name:"App",
        components:{
            School,
            Student,
        }
    }
</script>
<style lang="">
</style>

render函数

Vue.js是完整版的Vue,包含核心功能+模板解析器
Vue.runtime.xxx.js是运行版的Vue,只包含核心功能,不包含模板解析器
因为Vue.runtime.xxx.js不包含模板解析器,所以不能使用template配置项,需要使用render函数接收到createElement函数去指定具体内容
在这里插入图片描述
默认配置不推荐修改
在这里插入图片描述
vue.config.js配置文件
使用vue inspect>output.js可以查看到Vue脚手架的默认配置。
修改vue.config.js配置文件可以进行个性化定制,参考官网 https://cli.vuejs.org/zh/config/。

ref属性

1、被用来给元素或子组件注册引用信息(id的替代者)
2、应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3、使用方式:
打标识:<h1 ref="title"></h1>
获取:this.$refs.title

<template lang="">
    <div>
        <h1 v-text="msg" ref="title"></h1>
        <button ref="btn" @click="show">点击输出上方的DOM元素</button>
        <School ref="sch"></School>
    </div>
</template>
<script>
    //引入组件
    import School from './components/School.vue'
    export default {
        name:"App",
        components:{
            School,
        },
        data(){
            return{
                msg:"hello Vue"
            }
        },
        methods:{
            show(){
                console.log(this.$refs.title)// 真实DOM元素
                console.log(this.$refs.btn)// 真实DOM元素
                console.log(this.$refs.sch)// School组件的实例对象
            }
        }
    }
</script>
<style lang="">
</style>

props配置

-------student.vue---------
<template>
    <!-- 组件的结构 -->
    <div>
        <h2>{{msg}}</h2>
        <h2>学生名字:{{name}}</h2>
        <h2>学生年龄:{{myAge}}</h2>
        <h2>学生性别:{{sex}}</h2>
        <button @click="show">修改年龄</button>
    </div>
</template>
<script>
    //组件交互的相关代码(数据、方法等等)

    export default {
        name: "Student",
        data() {
            return {
                msg:"全世界无产阶级联合起来",
                myAge:this.age
            }
        },
        methods:{
            show(){
                this.myAge++;
            }
        },
        //简单声明接收
        // props:['name','age','sex']
        //接收的同时对数据进行类型限制
/*         props:{
            name:String,
            age:Number,
            sex:String
        } */

        // 接收的同时对数据进行类型限制+默认值的指定+必要性的限制
        props:{
            name:{
                type:String,
                required:true
            },
            age:{
                type:Number,
                default:99
            },
            sex:{
                type:String,
                required:true
            }
        }
    }
</script>
-------App.vue
<template lang="">
    <div>
        <Student name='messi' sex='1' :age='35'></Student>
    </div>
</template>
<script>
    //引入组件
    import Student from './components/Student.vue'
    export default {
        name:"App",
        components:{
            Student,
        },
    }
</script>

mixin 混和

在这里插入图片描述
也可以在main.js中进行全局混合

import {football,football2} from './demo'
Vue.mixin(football)
Vue.mixin(football2)

插件

在这里插入图片描述

scoped

让样式在局部生效 防止冲突 <style scoped></style>

todoList -v 1.0

在这里插入图片描述
App.vue (样式略)

<template lang="">
<div id="root">
  <div class="todo-container">
    <div class="todo-wrap">
        <Top :addTodo="addTodo"></Top>
        <List 
          :todos="todos" 
          :checkTodo="checkTodo"
          :delTodo="delTodo"
        />
        <Feet 
          :todos="todos" 
          :checkAllTodo="checkAllTodo"
          :clearAllTodo="clearAllTodo"
        ></Feet>
    </div>
  </div>
</div>
</template>
<script>
//引入组件
import Top from "./components/Top";
import List from "./components/List";
import Feet from "./components/Feet";

export default {
  name: "App",
  components: {
    Top,
    List,
    Feet,
  },
  data() {
    return {
      todos: [
        { id:'1', title:"抽烟", done: true },
        { id:'2', title:"喝酒", done: false },
        { id:'3', title:"纹身", done: true },
      ],
    };
  },
  methods:{
    // 添加一个todo
    addTodo(todoObj){
      this.todos.unshift(todoObj)
    },
    // 勾选或取消一个todo
    checkTodo(id){
      this.todos.forEach((todo)=>{
        if(todo.id == id) todo.done = !todo.done
      })
    },
    // 删除一个todo
    delTodo(id){
      this.todos = this.todos.filter((todo)=>{
        return todo.id != id
      })
    },
    // 全选 or 取消全选
    checkAllTodo(done){
      this.todos.forEach((todo)=>{
        todo.done = done
      })
    },
    // 清楚所有已经完成的todo
    clearAllTodo(){
      this.todos = this.todos.filter((todo)=>{
        return !todo.done
      })
    }
  }
};
</script>

Top.vue

<template lang="">
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model.trim="inputValue" @keyup.enter="add"/>
  </div>
</template>
<script>
//第三方库用于生成唯一的字符串比uuid小
import {nanoid} from 'nanoid'
export default {
  name: "Top",
  props:['addTodo'],
  data(){
    return{
      inputValue:""
    }
  },
  methods: {
    add(){
      // 校验数据
      if(!this.inputValue) return
      //将输入用户包装成一个todo对象
      const todoObj = {id:nanoid(),title:this.inputValue,done:false}
      // 通知App组件去添加一个todo对象
      this.addTodo(todoObj)
      // 清空输入
      this.inputValue=""
    }
/*     add(e){
      // 校验数据
      if(!e.target.value.trim()) return
      //将输入用户包装成一个todo对象
      const todoObj = {id:nanoid(),title:e.target.value,done:false}
      // 通知App组件去添加一个todo对象
      this.addTodo(todoObj)
      // 清空输入
      e.target.value=""
    } */
  },
};
</script>

List.vue

<template lang="">
  <ul class="todo-main">
    <Item 
      v-for="todoObj in todos" 
      :key="todoObj.id" 
      :todo="todoObj" 
      :checkTodo="checkTodo"
      :delTodo="delTodo"/>
  </ul>
</template>
<script>
import Item from "./Item";
export default {
  name: "List",
  components: {
    Item,
  },
  props:['todos','checkTodo','delTodo']
  
};
</script>

Item.vue

<template lang="">
  <li>
    <label>
      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)" />
      <!-- 不推荐这种写法,因为修改了props,违反原则 -->
      <!-- <input type="checkbox" v-model="todo.done" /> -->
      <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" @click="del(todo.id)">删除</button>
  </li>
</template>
<script>
export default {
  name: "Item",
  // 声明接收todo对象
  props: ["todo","checkTodo",'delTodo'],
  methods:{
    // 勾选
    handleCheck(id){
      //通知App将对应的todo对象的demo值取反
      this.checkTodo(id)
    },
    // 删除
    del(id){
      if(confirm("确定删除吗?")){
        this.delTodo(id)
      }
    }
  }
};
</script>

Feet.vue

<template lang="">
  <div class="todo-footer" v-show="total">
    <label>
      <!-- <input type="checkbox" :checked="isAll" @click="checkAll"/> -->
      <input type="checkbox" v-model="isAll"/>
    </label>
    <span>
      <span>已完成{{doneTotal}}</span> / 全部{{total}}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>
<script>
export default {
  name: "Feet",
  props: ["todos","checkAllTodo","clearAllTodo"],
  computed: {
    isAll:{
      get(){
        return this.total === this.doneTotal && this.total > 0
      },
      set(value){
        this.checkAllTodo(value);
      }
    },
    total(){
      return this.todos.length;
    },
    doneTotal() {
/*       const x = this.todos.reduce((pre, current) => {
        console.log("@", pre, current);
        return pre + (current.done ? 1 : 0);
      }, 0);
      console.log("x:",x) */
      return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
    },
    /* doneTotal(){
      let i = 0;
      this.todos.forEach((todo) => {
        if(todo.done) i++
      })
      return i
    } */
  },
  methods:{
/*     checkAll(e){
      console.log("checkAll",e.target.checked);
      this.checkAllTodo(e.target.checked);
    }, */
    clearAll(){
      this.clearAllTodo()
    }
  }
};
</script>

浏览器本地储存

浏览器通过Window.sessionStorageWindow.localStorage属性来实现本地存储机制。
sessionStorage 存储的内容会随着浏览器窗口关闭而消失
localStorage存储的内容,需要手动清除才会消失
xxxxxStorage.getItem(xxx)如果xxx对应的value值获取不到,那么getItem的返回值是null
Json.parse(null)的结果依然是null
在这里插入图片描述
LocalStorage

<body>
    <h2>localStorage</h2>
    <button onclick="saveData()">保存数据</button>
    <button onclick="readData()">读取数据</button>
    <button onclick="delData()">删除数据</button>
    <button onclick="clearData()">清空数据</button>
    <script type="text/javascript">
        let p = {name:"张三",age:18}
        function saveData(){
            localStorage.setItem("msg","hello")
            localStorage.setItem("msg2",123)
            localStorage.setItem("person",JSON.stringify(p))
        }
        function readData(){
            console.log(localStorage.getItem('msg'))
            console.log(localStorage.getItem('msg2'))
            console.log(localStorage.getItem('person'))
            const result = localStorage.getItem("person")
            console.log(JSON.parse(result))
        }
        function delData(){
            localStorage.removeItem("msg2")
        }
        function clearData(){
            localStorage.clear()
        }
    </script>
</body>

sessionStorage

<body>
    <h2>sessionStorage</h2>
    <button onclick="saveData()">保存数据</button>
    <button onclick="readData()">读取数据</button>
    <button onclick="delData()">删除数据</button>
    <button onclick="clearData()">清空数据</button>
    <script type="text/javascript">
        let p = {name:"张三",age:18}
        function saveData(){
            sessionStorage.setItem("msg","hello")
            sessionStorage.setItem("msg2",123)
            sessionStorage.setItem("person",JSON.stringify(p))
        }
        function readData(){
            console.log(sessionStorage.getItem('msg'))
            console.log(sessionStorage.getItem('msg2'))
            console.log(sessionStorage.getItem('person'))
            const result = sessionStorage.getItem("person")
            console.log(JSON.parse(result))
        }
        function delData(){
            sessionStorage.removeItem("msg2")
        }
        function clearData(){
            sessionStorage.clear()
        }
    </script>
</body>

在这里插入图片描述
todoList的本地存储
App.vue

<script>
//引入组件
import Top from "./components/Top";
import List from "./components/List";
import Feet from "./components/Feet";

export default {
  name: "App",
  components: { 
    Top,
    List,
    Feet,
  },
  data() {
    return {
      todos: JSON.parse(localStorage.getItem("todos")) || [],
    };
  },
  watch: {
    todos: {
      deep: true,
      handler(value) {
        localStorage.setItem("todos", JSON.stringify(value));
      },
    },
  },
  methods: {
    // 添加一个todo
    addTodo(todoObj) {
      this.todos.unshift(todoObj);
    },
    // 勾选或取消一个todo
    checkTodo(id) {
      this.todos.forEach((todo) => {
        if (todo.id == id) todo.done = !todo.done;
      });
    },
    // 删除一个todo
    delTodo(id) {
      this.todos = this.todos.filter((todo) => {
        return todo.id != id;
      });
    },
    // 全选 or 取消全选
    checkAllTodo(done) {
      this.todos.forEach((todo) => {
        todo.done = done;
      });
    },
    // 清楚所有已经完成的todo
    clearAllTodo() {
      this.todos = this.todos.filter((todo) => {
        return !todo.done;
      });
    },
  },
};
</script>

组件自定义事件

绑定

App.vue

<template lang="">
    <div class="app">
        <h2>{{msg}}</h2>
        <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
        <School :getSchoolName="getSchoolName"></School>
        <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
        <!-- <Student v-on:sj="getStudentName"></Student> -->
        <!-- <Student @sj="getStudentName"></Student> -->
        <!-- 只触发一次 -->
        <!-- <Student @sj.once="getStudentName"></Student> -->
        <!-- 换种方式绑定事件 -->
        <Student ref="student"/>
    </div>
</template>
<script>
    //引入组件
    import School from './components/School.vue'
    import Student from './components/Student.vue'
    export default {
        name:"App",
        components:{
            School,
            Student,
        },
        data() {
          return {
            msg:"全世界无产阶级联合起来"
          }
        },
        methods:{
          getSchoolName(name){
            console.log("App收到了学校名:",name)
          },
          getStudentName(name){
            console.log("App收到了学生名:",name)
          }
        },
        mounted() {
          // console.log(this.$refs.student.studentName)
          this.$refs.student.$on('sj',this.getStudentName)
          //只触发一次
          // this.$refs.student.$once('sj',this.getStudentName)
        },
    }
</script>
<style lang="less">
  .app{
    background-color: gold;
    padding: 5px;
  }
</style>

School.vue

<template>
    <!-- 组件的结构 -->
    <div class="school">
        <h2>学校名:{{schoolName}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="sendSchoolName">把学校名给App</button>
    </div>
</template>
<script>

    export default {
        name:"School",
        props:['getSchoolName'],
        data() {
            return {
                schoolName: "剑桥",
                address: '通辽'
            }
        },
        methods:{
          sendSchoolName(){
            this.getSchoolName(this.schoolName)
          }
        }
    }

</script>
<style lang="less" scoped>
    /* 组件的样式 */
    .school{
        background-color: aqua;
        padding: 5px;
    
    }
</style>

Student.vue

<template>
  <!-- 组件的结构 -->
  <div class="student">
    <h2>学生名字:{{ studentName }}</h2>
    <h2>学生年龄:{{ age }}</h2>
    <button @click="sendStudentName">把学生名给App</button>
  </div>
</template>
<script>
//组件交互的相关代码(数据、方法等等)

export default {
  name: "Student",
  data() {
    return {
      studentName: "张三",
      age: 13,
    };
  },
  methods: {
    sendStudentName() {
      // 触发student组件实例身上的sj事件
      this.$emit("sj",this.studentName)
    },
  },
};
</script>
<style lang="less" scoped>
.student {
  background-color: antiquewhite;
  padding: 5px;
  margin-top: 30px;
}
</style>
解绑

Student.vue

<template>
  <!-- 组件的结构 -->
  <div class="student">
    <h2>学生名字:{{ studentName }}</h2>
    <h2>学生年龄:{{ age }}</h2>
    <h2>当前的数字是:{{number}}</h2>
    <button @click="add">点数字 + 1</button>
    <button @click="sendStudentName">把学生名给App</button>
    <button @click="unbindsj">解绑事件</button>
    <button @click="death">销毁当前Student组件的实例 (vc)</button>
  </div>
</template>
<script>
//组件交互的相关代码(数据、方法等等)

export default {
  name: "Student",
  data() {
    return {
      studentName: "张三",
      age: 13,
      number:0
    };
  },
  methods: {
    add(){
      console.log("add被调用了")
      this.number++
    },
    sendStudentName() {
      // 触发student组件实例身上的sj事件
      this.$emit("sj",this.studentName)
      this.$emit("demo")
    },
    unbindsj(){
      // this.$off("sj")// 解绑一个自定义事件
      // this.$off(['sj','demo'])//解绑多个自定义事件
      this.$off()//解绑所有自定义事件
    },
    death(){
      // 销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效
      // 如果在main.js中销毁vm,vm的子组件和子组件的自定义事件都不奏效
      this.$destroy() 
    }
  },
};
</script>
<style lang="less" scoped>
.student {
  background-color: antiquewhite;
  padding: 5px;
  margin-top: 30px;
}
</style>

App.vue

<template lang="">
    <div class="app">
        <h2>{{msg}}</h2>
        <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
        <School :getSchoolName="getSchoolName"></School>
        <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
        <!-- <Student v-on:sj="getStudentName"></Student> -->
        <Student @sj="getStudentName" @demo="m"></Student>
        <!-- 只触发一次 -->
        <!-- <Student @sj.once="getStudentName"></Student> -->
        <!-- 换种方式绑定事件 -->
        <!-- <Student ref="student"/> -->
    </div>
</template>
<script>
    //引入组件
    import School from './components/School.vue'
    import Student from './components/Student.vue'
    export default {
        name:"App",
        components:{
            School,
            Student,
        },
        data() {
          return {
            msg:"全世界无产阶级联合起来"
          }
        },
        methods:{
          getSchoolName(name){
            console.log("App收到了学校名:",name)
          },
          getStudentName(name){
            console.log("App收到了学生名:",name)
          },
          m(){
            console.log("demo事件被触发了")
          }
        },
        mounted() {
          // console.log(this.$refs.student.studentName)
          // this.$refs.student.$on('sj',this.getStudentName)
          //只触发一次
          // this.$refs.student.$once('sj',this.getStudentName)
        },
    }
</script>
<style lang="less">
  .app{
    background-color: gold;
    padding: 5px;
  }
</style>
总结

在这里插入图片描述
todolist的自定义事件
在这里插入图片描述

全局事件总线:任意组件间的通信

main.js

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

Vue.config.productionTip = false

/* const Demo = Vue.extend({})
const d = new Demo
Vue.prototype.x = d
 */
 const vm = new Vue({

  render: h => h(App),
  beforeCreate(){
    // Vue.prototype.x = this
    Vue.prototype.$bus = this //安装全局事件总线
  }
}).$mount('#app')

在这里插入图片描述
在这里插入图片描述
todoList App.vue和 Item.vue之间的通信
在这里插入图片描述

消息订阅与发布

使用 pubsub.js ------ npm i pubsub-js
在这里插入图片描述
todoList 消息订阅发布
在这里插入图片描述

TodoList增加编辑按钮

$nextTick()
在这里插入图片描述

动画

动画效果

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!-- appear="true" 一开始就动画 可以直接是appear-->
    <transition name="h" :appear="true">
      <h1 v-show="isShow">hello</h1>
    </transition>

  </div>
</template>
<script>
  export default {
    name:"Test",
    data() {
      return {
        isShow:true
      }
    },
  }
</script>
<style scoped>
  h1{
    background-color: gold;
  }
  .h-enter-active{
    animation: demo 1s
  }
  .h-leave-active{
    animation: demo 1s reverse
  }
  @keyframes demo {
    from{
      transform: translateX(-100%);
    }
    to{
      transform: translateX(0px);
    }
  }
</style>

过度效果

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!-- appear="true" 一开始就动画 可以直接是appear-->
    <!-- transition 只适用单个元素 -->
    <!-- transition-group可以使用多个元素 要加key值  -->
    <transition-group name="h" :appear="true">
      <h1 v-show="!isShow" key="1">hello</h1>
      <h1 v-show="isShow" key="2">共产主义</h1>
    </transition-group>

  </div>
</template>
<script>
  export default {
    name:"Test2",
    data() {
      return {
        isShow:true
      }
    },
  }
</script>
<style scoped>
/*linear匀速*/
  h1{
    background-color: gold;
    /* transition: 0.5s linear; */
  }
  /* 进入的起点 离开的终点 */
  .h-enter,.h-leave-to{
    transform: translateX(-100%);
  }
  .h-enter-active, .h-leave.active{
    transition: 0.5s linear;
  }
  /* 进入的终点 离开的起点 */
  .h-enter-to,.h-leave{
    transform: translateX(0);
  }
</style>

第三方动画

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group 
      name="animate__animated animate__bounce" 
      enter-active-class="animate__swing"
      leave-active-class="animate__backOutDown"
      appear>
      <h1 v-show="isShow" key="1">hello</h1>
      <h1 v-show="isShow" key="2">共产主义</h1>
    </transition-group>

  </div>
</template>
<script>
// 官网 https://animate.style/
// 下载 npm install animate.css
import 'animate.css' //引入
  export default {
    name:"Test3",
    data() {
      return {
        isShow:true
      }
    },
  }
</script>
<style scoped>
  h1{
    background-color: gold;
  }
</style>

配置代理

这种方式的缺点:只能配置一个代理
会先访问public文件夹下的静态资源,如果同名的话,优先获取静态资源的数据不会访问服务器
在这里插入图片描述
可以配置多个代理
不加前缀即可访问public文件夹下的静态资源
App.vue

<template>
  <div class="app">
    <h2>{{ msg }}</h2>
    <button @click="getStuInfo">获取学生信息</button>
    <button @click="getCarInfo">获取汽车信息</button>
  </div>
</template>
<script>
import axios from "axios";
export default {
  name: "App",
  data() {
    return {
      msg: "全世界无产阶级联合起来",
    };
  },
  methods: {
    getStuInfo() {
      axios.get("http://localhost:8080/demo/students").then(
        (response) => {
          console.log("请求成功了", response.data);
        },
        (error) => {
          console.log("请求失败了", error.message);
        }
      );
    },
    getCarInfo() {
      axios.get("http://localhost:8080/test/cars").then(
        (response) => {
          console.log("请求成功了", response.data);
        },
        (error) => {
          console.log("请求失败了", error.message);
        }
      );
    },
  },
};
</script>
<style lang="less">
</style>

vue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false, // 关闭语法检查
  //开启代理服务器
  devServer: {
    proxy: {
      '/demo': {
        target: 'http://localhost:5000',
        pathRewrite:{'^/demo':''}, //重写路径 正则 把所有以demo开头的变为空
        ws: true, //用于支持webSocket
        changeOrigin: true //用于控制请求的host值
      },
      '/test': {
        target: 'http://localhost:5001',
        pathRewrite:{'^/test':''}, //重写路径 正则 把所有以demo开头的变为空
        ws: true, //用于支持webSocket
        changeOrigin: true //用于控制请求的host值
      },
    }
  }
})

vue-resource

在main.js中配置即可使用

import vueResource from 'vue-resource'

Vue.config.productionTip = false

//使用插件
Vue.use(vueResource)

把 axios 换成 this.$http即可
在这里插入图片描述

插槽

让父组件可以向子组件指定位置插入html结构,也是组件间通信的方式,适用于父组件=》子组件

默认插槽

App.vue

<template>
  <div class="container">
    <Category title="美食">
      <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg"> 
    </Category>
    <Category title="游戏">
      <li v-for="(item,index) in games" :key="index">{{item}}</li>
    </Category>
    <Category title="足球俱乐部">
      <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
    </Category>
  </div>
</template>
<script>
import Category from './components/Category';

export default {
  components: {Category},
  name: "App",
  data() {
    return {
      foods:['火锅','烧烤','烤肉','牛排'],
      games:['csgo','LOL','dota','gata'],
      clubs:['切尔西','巴塞罗那','阿森纳','拜仁']
    };
  },
  methods: {
  },
};
</script>
<style lang="less">
  .container{
    display: flex;
    justify-content: space-around;
  }
  // 也可写在Category.vue中
  img{
    width: 100%;
  }
  video{
    width: 100%;
  }
</style>

Category

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <!-- 定义插槽 -->
    <slot>我是一些文字,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>
<script>
export default {
  name:"Category",
  props:['title']
}
</script>
<style scoped>
  .category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
  }
  h3{
    text-align: center;
    background-color: brown;
  }
</style>

在这里插入图片描述

具名插槽

App.vue

<template>
  <div class="container">
    <Category title="美食">
      <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" />
      <a slot="footer" href="https://www.baidu.com">更多美食</a>
    </Category>
    <Category title="游戏">
      <ul slot="center">
        <li v-for="(item, index) in games" :key="index">{{ item }}</li>
      </ul>
      <div class="foot" slot="footer">
        <a href="https://www.baidu.com">单机游戏</a>
        <a href="https://www.baidu.com">网络游戏</a>
      </div>
    </Category>
    <Category title="足球俱乐部">
      <video slot="center"
        controls
        src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
      ></video>
      <template v-slot:footer>
        <div class="foot">
          <a href="https://www.baidu.com">经典</a>
          <a href="https://www.baidu.com">热门</a>
          <a href="https://www.baidu.com">推荐</a>
        </div>
        <h4>welcome!</h4>
      </template>
    </Category>
  </div>
</template>
<script>
import Category from "./components/Category";

export default {
  components: { Category },
  name: "App",
  data() {
    return {
      foods: ["火锅", "烧烤", "烤肉", "牛排"],
      games: ["csgo", "LOL", "dota", "gata"],
      clubs: ["切尔西", "巴塞罗那", "阿森纳", "拜仁"],
    };
  },
  methods: {},
};
</script>
<style lang="less">
.container,
.foot {
  display: flex;
  justify-content: space-around;
}
// 也可写在Category.vue中
img {
  width: 100%;
}
video {
  width: 100%;
}
</style>

Category.vue

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <!-- 定义插槽 -->
    <slot name="center">我是一些文字,当使用者没有传递具体结构时,我会出现</slot>
    <slot name="footer">我是一些文字,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>
<script>
export default {
  name:"Category",
  props:['title']
}
</script>
<style scoped>
  .category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
  }
  h3{
    text-align: center;
    background-color: brown;
  }
</style>
作用域插槽

数据在子组件
Category

<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <slot :games="games" msg="hello">默认一些内容</slot>
  </div>
</template>
<script>
export default {
  name: "Category",
  props: ["title"],
  data() {
    return {
      games: ["csgo", "LOL", "dota", "gata"],
    };
  },
};
</script>
<style scoped>
.category {
  background-color: skyblue;
  width: 200px;
  height: 300px;
}
h3 {
  text-align: center;
  background-color: brown;
}
</style>

App.vue

<template>
  <div class="container">
    <Category title="游戏">
      <!-- demo是对象 -->
      <template scope="demo">
        <ul>
          <li v-for="(item, index) in demo.games" :key="index">{{ item }}</li>
        </ul>
        <h4>{{ demo.msg }}</h4>
      </template>
    </Category>
    <Category title="游戏">
      <!-- es6 解构赋值 -->
      <template scope="{games}">
        <ol>
          <li v-for="(item, index) in games" :key="index">{{ item }}</li>
        </ol>
      </template>
    </Category>
    <Category title="游戏">
      <!-- 另一种写法 -->
      <template slot-scope="{ games }">
        <h4 v-for="(item, index) in games" :key="index">{{ item }}</h4>
      </template>
    </Category>
  </div>
</template>
<script>
import Category from "./components/Category";

export default {
  components: { Category },
  name: "App",
};
</script>
<style lang="less">
.container,.foot {
  display: flex;
  justify-content: space-around;
}
h4 {
  text-align: center;
}
</style>

Vuex

求和案例

vuex-Count.vue

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

<script>
	export default {
		name:'vuex-Count',
		data() {
			return {
				n:1, //用户选择的数字
			}
		},
		methods: {
			increment(){
				this.$store.commit('JIA',this.n)
			},
			decrement(){
				this.$store.commit('JIAN',this.n)
			},
			incrementOdd(){
				this.$store.dispatch('jiaOdd',this.n)
			},
			incrementWait(){
				this.$store.dispatch('jiaWait',this.n)
			},
		},
		mounted() {
			console.log('Count',this)
		},
	}
</script>

<style lang="css">
	button{
		margin-left: 5px;
	}
</style>

store/index.js

//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions——用于响应组件中的动作
const actions = {
	/* jia(context,value){
		console.log('actions中的jia被调用了')
		context.commit('JIA',value)
	},
	jian(context,value){
		console.log('actions中的jian被调用了')
		context.commit('JIAN',value)
	}, */
    
	jiaOdd(context,value){
		console.log('actions中的jiaOdd被调用了')
		if(context.state.sum % 2){
			context.commit('JIA',value)
		}
	},
	jiaWait(context,value){
		console.log('actions中的jiaWait被调用了')
		setTimeout(()=>{
			context.commit('JIA',value)
		},500)
	}

}
//准备mutations——用于操作数据(state)
const mutations = {

	JIA(state,value){
		console.log('mutations中的JIA被调用了')
		state.sum += value
	},
	JIAN(state,value){
		console.log('mutations中的JIAN被调用了')
		state.sum -= value
	}
}
//准备state——用于存储数据
const state = {
	sum:0 //当前的和
}

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

main.js

 const vm = new Vue({

  render: h => h(App),
  store,

}).$mount('#app')

getters的使用

在这里插入图片描述

4个map的使用方法

mapState 方法:映射state中的数据为计算属性
mapGetters 方法:映射getters中的数据为计算属性
mapActions: 生成与actions对话的方法
mapMutation: 生成与mutations对话的方法
在这里插入图片描述

多组件共享数据

vuex-person.vue

<template>
  <div>
    <h1>人员列表</h1>
    <h3 style="color: red">Count组件求和为:{{ sum }}</h3>
    <input type="text" placeholder="请输入名字" v-model="name" />
    <button @click="add">添加</button>
    <ul>
      <li v-for="p in personList" :key="p.id">{{ p.name }}</li>
    </ul>
  </div>
</template>
<script>
import { nanoid } from "nanoid";
export default {
  name: "vuex-person",
  data() {
    return {
      name: "",
    };
  },
  computed: {
    personList() {
      return this.$store.state.personList;
    },
    sum() {
      return this.$store.state.sum;
    },
  },
  methods: {
    add() {
      const personObj = { id: nanoid(), name: this.name };
      this.$store.commit("ADD_PERSON", personObj);
      this.name = "";
    },
  },
};
</script>

在这里插入图片描述

模块拆分

store
在这里插入图片描述
components
在这里插入图片描述

路由

基本使用

(1)路由组件通常放在pages文件夹,一般组件通常放在components文件夹
(2)通过切换,“隐藏”的路由组件,默认是被销毁掉的,需要的时候再去挂载
(3)每个组件都有自己的$route属性,里面存储自己的路由信息
(4)整个应用只有一个router,可以通过组件的$router属性获取到

pages
在这里插入图片描述
components
在这里插入图片描述
App.vue

<template>
  <div>
    <div class="row">
      <Banner/>
    </div>
    <div class="row">
      <div class="col-xs-2 col-xs-offset-2">
        <div class="list-group">
					<!-- 原始html中我们使用a标签实现页面的跳转 -->
          <!-- <a class="list-group-item active" href="./about.html">About</a> -->
          <!-- <a class="list-group-item" href="./home.html">Home</a> -->

					<!-- Vue中借助router-link标签实现路由的切换 -->
					<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
          <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
        </div>
      </div>
      <div class="col-xs-6">
        <div class="panel">
          <div class="panel-body">
						<!-- 指定组件的呈现位置 -->
            <router-view></router-view>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
	import Banner from './components/Banner'
	export default {
		name:'App',
		components:{Banner}
	}
</script>

index.html

<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">

main.js
在这里插入图片描述
router/index.js

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'


//创建并暴露一个路由器
export default new VueRouter({
	routes:[
		{
			path:'/about',
			component:About
		},
		{
			path:'/home',
			component:Home,
		}
	]
})

在这里插入图片描述
在这里插入图片描述

嵌套路由

在这里插入图片描述
在这里插入图片描述

路由query参数

message.vue

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

        <!-- 跳转路由并携带query参数,to的对象写法 -->
        <!-- path:'/home/message/detail' 也可改为 name:'xiangqing' -->
        <router-link :to="{
          path:'/home/message/detail',
          query:{
            id:m.id,
            title:m.title
          }
        }"
        >
          {{m.title}}
        </router-link>
			</li>
		</ul>
    <hr>
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name:'Message',
		data(){
			return{
				messageList:[
					{id:"1",title:"消息1"},
					{id:"2",title:"消息2"},
					{id:"3",title:"消息3"}
				]
			}
		}
	}
</script>

在这里插入图片描述

命名路由

在这里插入图片描述

params参数

在这里插入图片描述
注意:路由携带params参数时,若使用to的对象写法,必须使用name配置,不能使用path配置项

路由的prop配置

第一种写法

{
	name:'xiangqing',
	path:'detail/:id/:title',
	component:Detail,
	
	//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
	props:{a:1,b:'hello'}
}

第二种写法
在这里插入图片描述
第三种写法
在这里插入图片描述

replace属性

(1)作用:控制路由跳转时操作浏览器历史记录的模式
(2)浏览器的历史记录有两种写入方式:分别为push和replace,push是追加的历史记录,replace是替换当前的记录。路由跳转时候默认为push
(3)开启replace模式

<router-link replace .....>About</router-link>
编程式路由导航

banner.vue

<template>
	<div class="col-xs-offset-2 col-xs-8">
		<div class="page-header"><h2>Vue Router Demo</h2></div>
		<button @click="back">后退</button>
		<button @click="forward">前进</button>
		<button @click="go">跳转</button>
	</div>
</template>

<script>
	export default {
		name:'Banner',
		methods:{
			back(){
				console.log(this.$router)
				this.$router.back()
			},
			forward(){
				this.$router.forward()
			},
			go(){
				this.$router.go(-2) //正数前进 负数后退
			}
		}
	}
</script>

在这里插入图片描述
在这里插入图片描述

缓存路由组件

在这里插入图片描述
缓存多个

      <keep-alive :include="['New','Message']">
        <router-view></router-view>
      </keep-alive>
两个新的生命周期钩子

activated 路由组件被激活时触发
deactivated 路由组件失活时触发

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

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

全局路由守卫
index.js

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'

//创建并暴露一个路由器
const router = new VueRouter({
	routes:[
		{
			name:'guanyu',
			path:'/about',
			component:About,
			meta:{title:'关于'}
		},
		{
			name:'shouye',
			path:'/home',
			component:Home,
			meta:{title:'首页'},
	 		children:[
				{
					name:'xinwen',
					path:'news',
					component:News,
					meta:{isAuth:true,title:'新闻'},
				},
				{
					name:'xiaoxi',
					path:'message',
					component:Message,
					meta:{isAuth:true,title:'消息'},
					children:[
						{
							name:'xiangqing',
							path:'detail',
							component:Detail,
							meta:{isAuth:true,title:'详情'},

							//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
							// props:{a:1,b:'hello'}

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

							//props的第三种写法,值为函数
							props($route){
								return {
									id:$route.query.id,
									title:$route.query.title,
									a:1,
									b:'hello'
								}
							}

						}
					]
				}
			]
		}
	]
})

//全局前置路由守卫——初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
	console.log("前置路由守卫",to,from)
	// if(to.name === 'xinwen' || to.name === 'xiaoxi'){
	if(to.meta.isAuth){	//判断是否需要鉴权
		if(localStorage.getItem('star') === 'messi'){
			next()
		}else{
			alert("名字不对,无权查看")
		}
	}else{
		next()   
	}
})

//全局后置路由守卫——初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
	console.log("后置路由守卫",to,from)
	document.title = to.meta.title || "路由demo"
})

export default router

在这里插入图片描述
独享路由守卫:
在这里插入图片描述
组内路由守卫
About.vue

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

<script>
export default {
  name: "About",
  /* beforeDestroy() {
			console.log('About组件即将被销毁了')
		},*/
  /* mounted() {
			console.log('About组件挂载完毕了',this)
			window.aboutRoute = this.$route
			window.aboutRouter = this.$router
		},  */

  // 通过路由规则,进入该组件时被调用
  beforeRouteEnter(to, from, next) {
    console.log("About-beforeRouteEnter", to, from);
    if (to.meta.isAuth) {
      //判断是否需要鉴权
      if (localStorage.getItem("star") === "messi") {
        next();
      } else {
        alert("名字不对,无权查看");
      }
    } else {
      next();
    }
  },
  // 通过路由规则,离开该组件时被调用
  beforeRouteLeave(to, from, next) {
    console.log("About-beforeRouteLeave", to, from);
	next()
  }
};
</script>

Vue3.0

分析工程结构

main.js

//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'

//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更轻)

const app = createApp(App)
//挂载
app.mount("#app")

// createApp(App).mount('#app')


/* // Vue2.0

//引入Vue
import Vue from 'vue'

const vm = new Vue({
    render:h => h(App)
})

vm.$mount('#app') */
<template>
  <!-- Vue3组件中的模板结构可以没有根标签 -->
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

setUp

setUp不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性

<template>
	<h1>一个人的信息</h1>
	<h2>姓名:{{name}}</h2>
	<h2>年龄:{{age}}</h2>
	<h2>性别:{{sex}}</h2>
	<h2>a的值是:{{a}}</h2>
	<button @click="sayHello">说话(Vue3所配置的——sayHello)</button>
	<br>
	<button @click="sayWelcome">说话(Vue2所配置的——sayWelcome)</button>
	<br>
	<button @click="test1">测试一下在Vue2的配置中去读取Vue3中的数据、方法</button>
	<br>
	<button @click="test2">测试一下在Vue3的setup配置中去读取Vue2中的数据、方法</button>

</template>

<script>
	// import {h} from 'vue'
	export default {
		name: 'App',
		data() {
			return {
				sex:'男',
				a:100
			}
		},
		methods: {
			sayWelcome(){
				alert('欢迎来到剑桥学习')
			},
			test1(){
				console.log(this.sex)
				console.log(this.name)
				console.log(this.age)
				console.log(this.sayHello)
			}
		},
		//此处只是测试一下setup,暂时不考虑响应式的问题。
		setup(){
			//数据
			let name = '张三'
			let age = 18
			let a = 200

			//方法
			function sayHello(){
				alert(`我叫${name},我${age}岁了,你好啊!`)
			}
			function test2(){
				console.log(name)
				console.log(age)
				console.log(sayHello)
				console.log(this.sex)
				console.log(this.sayWelcome)
			}

			//返回一个对象(常用)
			return {
				name,
				age,
				sayHello,
				test2,
				a
			}

			//返回一个函数(渲染函数)要在上面import h
			// return ()=> h('h1','剑桥')
		}
	}
</script>

在这里插入图片描述

ref函数

<template>
	<h1>一个人的信息</h1>
	<h2>姓名:{{name}}</h2>
	<h2>年龄:{{age}}</h2>
	<h2>工作岗位:{{job.type}}</h2>
	<h2>薪资:{{job.salary}}</h2>
	<button @click="changeInfo">修改人的信息</button>
</template>

<script>
	import {ref} from 'vue'
	export default {
		name: 'App',
		//此处只是测试一下setup,暂时不考虑响应式的问题。
		setup(){
			//数据
			let name = ref('小凯')
			let age = ref(18)
			let job = ref({
				type:"java工程师",
				salary:'10k'
			})
			function changeInfo(){
				name.value = '哈弗茨'
				age.value = '19'
				// console.log(name,age)
				console.log(job.value)
				job.value.type = "打工人"
				job.value.salary = "0"
			}

			//返回一个对象(常用)
			return {
				name,
				age,
				job,
				changeInfo
			}
		}
	}
</script>

reactive

<template>
  <h1>一个人的信息</h1>
  <h2>姓名:{{ person.name }}</h2>
  <h2>年龄:{{ person.age }}</h2>
  <h3>工作种类:{{ person.job.type }}</h3>
  <h3>工作薪水:{{ person.job.salary }}</h3>
  <h3>爱好:{{ person.hobby }}</h3>
  <h3>测试的数据c:{{ person.job.a.b.c }}</h3>
  <button @click="changeInfo">修改人的信息</button>
</template>

<script>
	import {ref,reactive} from 'vue'
	export default {
		name: 'App',
		//此处只是测试一下setup,暂时不考虑响应式的问题。
		setup(){
			//数据
/* 			let name = ref('小凯')
			let age = ref(18)
			let job = reactive({
				type:"java工程师",
				salary:'10k',
				a:{
					b:{
						c:666
					}
				}
			})
			let hobby = reactive(['抽烟','喝酒','纹身'])
			function changeInfo(){
				name.value = '哈弗茨'
				age.value = '19'
				// console.log(name,age)
				console.log(job)
				job.type = "打工人"
				job.salary = "0"
				job.a.b.c = 11111111111
				hobby[2] = '烫头'
			}
			
 */

			let person = reactive({
				name:'小凯',
				age:18,
				job:{
				type:"java工程师",
				salary:'10k',
					a:{
						b:{
							c:666
						}
					}
				},
				hobby:reactive(['抽烟','喝酒','纹身'])
			})

			//方法
			function changeInfo(){
				person.name = '芒特'
				person.age = 28
				person.job.type = '革命者'
				person.job.salary = '0'
				person.job.a.b.c = 999
				person.hobby[0] = '学习'
			}

			//返回一个对象(常用)
			return {
				person,
				changeInfo
			}
		}
	}
</script>

Vue2.0 与 3.0 响应式对比

  • vue2.0实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。
  • vue3.0实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
    • 通过Reflect(反射): 对源对象的属性进行操作。
  new Proxy(data, {
  	// 拦截读取属性值
      get (target, prop) {
      	return Reflect.get(target, prop)
      },
      // 拦截设置属性值或添加新属性
      set (target, prop, value) {
      	return Reflect.set(target, prop, value)
      },
      // 拦截删除属性
      deleteProperty (target, prop) {
      	return Reflect.deleteProperty(target, prop)
      }
  })

reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

Vue3-computed计算属性

<template>
	<h1>一个人的信息</h1>
	姓:<input type="text" v-model="person.firstName">
	<br>
	名:<input type="text" v-model="person.lastName">
	<br>
	<span>全名:{{person.fullName}}</span>
	<br>
	全名:<input type="text" v-model="person.fullName">
</template>

<script>
	import {reactive,computed} from 'vue'
	export default {
		name: 'Demo',
		setup(){
			//数据
			let person = reactive({
				firstName:'张',
				lastName:'三'
			})
			//计算属性——简写(没有考虑计算属性被修改的情况)
			/* person.fullName = computed(()=>{
				return person.firstName + '-' + person.lastName
			}) */

			//计算属性——完整写法(考虑读和写)
			person.fullName = computed({
				get(){
					return person.firstName + '-' + person.lastName
				},
				set(value){
					const nameArr = value.split('-')
					person.firstName = nameArr[0]
					person.lastName = nameArr[1]
				}
			})

			//返回一个对象(常用)
			return {
				person
			}
		}
	}
</script>

Vue3-watch

<template>
	<h2>当前求和为:{{sum}}</h2>
	<button @click="sum++">点我+1</button>
	<hr>
	<h2>当前的信息为:{{msg}}</h2>
	<button @click="msg+=''">修改信息</button>
	<hr>
	<h2>姓名:{{person.name}}</h2>
	<h2>年龄:{{person.age}}</h2>
	<h2>薪资:{{person.job.j1.salary}}K</h2>
	<button @click="person.name+='~'">修改姓名</button>
	<button @click="person.age++">增长年龄</button>
	<button @click="person.job.j1.salary++">涨薪</button>
</template>

<script>
	import {ref,reactive,watch} from 'vue'
	export default {
		name: 'Demo',
		setup(){
			//数据
			let sum = ref(0)
			let msg = ref('你好啊')
			let person = reactive({
				name:'张三',
				age:18,
				job:{
					j1:{
						salary:20
					}
				}
			})

			//情况一:监视ref所定义的一个响应式数据
			/* watch(sum,(newValue,oldValue)=>{
				console.log('sum变了',newValue,oldValue)
			},{immediate:true}) */

			//情况二:监视ref所定义的多个响应式数据
			/* watch([sum,msg],(newValue,oldValue)=>{
				console.log('sum或msg变了',newValue,oldValue)
			},{immediate:true}) */

			/* 
				情况三:监视reactive所定义的一个响应式数据的全部属性
						1.注意:此处无法正确的获取oldValue
						2.注意:强制开启了深度监视(deep配置无效)
			*/
			/* watch(person,(newValue,oldValue)=>{
				console.log('person变化了',newValue,oldValue)
			},{deep:false}) //此处的deep配置无效 */

			//情况四:监视reactive所定义的一个响应式数据中的某个属性
/* 			watch(()=>person.name,(newValue,oldValue)=>{
				console.log('person的name变化了',newValue,oldValue)
			})  */

			//情况五:监视reactive所定义的一个响应式数据中的某些属性
			/* watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
				console.log('person的name或age变化了',newValue,oldValue)
			})  */

			//特殊情况
			watch(()=>person.job,(newValue,oldValue)=>{
				console.log('person的job变化了',newValue,oldValue)
			},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效


			//返回一个对象(常用)
			return {
				sum,
				msg,
				person
			}
		}
	}
</script>
let sum = ref(0)
 let person = ref({
	name:'张三',
	age:18,
	job:{
		j1:{
			salary:20
		}
	}
})

console.log(person)

watch(sum,(newValue,oldValue)=>{
	console.log('sum的值变化了',newValue,oldValue)
})

watch(person,(newValue,oldValue)=>{
	console.log('person的值变化了',newValue,oldValue)
},{deep:true})
//或者
/* 			watch(person.value,(newValue,oldValue)=>{
				console.log('person的值变化了',newValue,oldValue)
			})
 */

watchEffect

不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性

setup(){
	//数据
	let sum = ref(0)
	let msg = ref('你好啊')
	let person = reactive({
		name:'张三',
		age:18,
		job:{
			j1:{
				salary:20
			}
		}
	})
	
	//监视
	/* watch(sum,(newValue,oldValue)=>{
		console.log('sum的值变化了',newValue,oldValue)
	},{immediate:true}) */
	
	watchEffect(()=>{
		const x1 = sum.value
		const x2 = person.job.j1.salary
		console.log('watchEffect所指定的回调执行了')
	})
	
	//返回一个对象(常用)
	return {
		sum,
		msg,
		person
	}
}

vue3-生命周期

Demo.vue

<template>
	<h2>当前求和为:{{sum}}</h2>
	<button @click="sum++">点我+1</button>
</template>

<script>
	import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
	export default {
		name: 'Demo',
		
		setup(){
			console.log('---setup---')
			//数据
			let sum = ref(0)

			//通过组合式API的形式去使用生命周期钩子
			onBeforeMount(()=>{
				console.log('---onBeforeMount---')
			})
			onMounted(()=>{
				console.log('---onMounted---')
			})
			onBeforeUpdate(()=>{
				console.log('---onBeforeUpdate---')
			})
			onUpdated(()=>{
				console.log('---onUpdated---')
			})
			onBeforeUnmount(()=>{
				console.log('---onBeforeUnmount---')
			})
			onUnmounted(()=>{
				console.log('---onUnmounted---')
			})

			//返回一个对象(常用)
			return {sum}
		},
		//通过配置项的形式使用生命周期钩子
		//#region 
		beforeCreate() {
			console.log('---beforeCreate---')
		},
		created() {
			console.log('---created---')
		},
		beforeMount() {
			console.log('---beforeMount---')
		},
		mounted() {
			console.log('---mounted---')
		},
		beforeUpdate(){
			console.log('---beforeUpdate---')
		},
		updated() {
			console.log('---updated---')
		},
		beforeUnmount() {
			console.log('---beforeUnmount---')
		},
		unmounted() {
			console.log('---unmounted---')
		},
		//#endregion
	}
</script>

App.vue

<template>
	<button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
	<Demo v-if="isShowDemo"/>
</template>

<script>
	import {ref} from 'vue'
	import Demo from './components/Demo'
	export default {
		name: 'App',
		components:{Demo},
		setup() {
			let isShowDemo = ref(true)
			return {isShowDemo}
		}
	}
</script>

hook函数

类似于vue2.0的mixin, 优势复用代码
在这里插入图片描述

toRef

<template>
	<h4>{{person}}</h4>
	<h2>姓名:{{name}}</h2>
	<h2>年龄:{{age}}</h2>
	<h2>薪资:{{job.j1.salary}}K</h2>
	<button @click="name+='~'">修改姓名</button>
	<button @click="age++">增长年龄</button>
	<button @click="job.j1.salary++">涨薪</button>
</template>

<script>
	import {ref,reactive,toRef,toRefs} from 'vue'
	export default {
		name: 'Demo',
		setup(){
			//数据
			let person = reactive({
				name:'张三',
				age:18,
				job:{
					j1:{
						salary:20
					}
				}
			})

			// const name1 = person.name
			// console.log('%%%',name1)

			// const name2 = toRef(person,'name')
			// console.log('####',name2)

			const x = toRefs(person)
			console.log('******',x)

			//返回一个对象(常用)
			return {
				person,
				// name:toRef(person,'name'),
				// age:toRef(person,'age'),
				// salary:toRef(person.job.j1,'salary'),
				...toRefs(person)
			}
		}
	}
</script>

shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景: 不希望数据被修改时。
person = readonly(person)
person = shallowReadonly(person)

toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
<template>
	<h4>当前求和为:{{sum}}</h4>
	<button @click="sum++">点我++</button>
	<hr>
	<h2>姓名:{{name}}</h2>
	<h2>年龄:{{age}}</h2>
	<h2>薪资:{{job.j1.salary}}K</h2>
	<h3 v-show="person.car">座驾信息:{{person.car}}</h3>
	<button @click="name+='~'">修改姓名</button>
	<button @click="age++">增长年龄</button>
	<button @click="job.j1.salary++">涨薪</button>
	<button @click="showRawPerson">输出最原始的person</button>
	<button @click="addCar">给人添加一台车</button>
	<button @click="person.car.name+='!'">换车名</button>
	<button @click="changePrice">换价格</button>
</template>

<script>
	import {ref,reactive,toRefs,toRaw,markRaw} from 'vue'
	export default {
		name: 'Demo',
		setup(){
			//数据
			let sum = ref(0)
			let person = reactive({
				name:'张三',
				age:18,
				job:{
					j1:{
						salary:20
					}
				}
			})

			function showRawPerson(){
				const p = toRaw(person)
				p.age++//页面不会发生变化,不是响应式的
				console.log(p)
			}

			function addCar(){
				let car = {name:'奔驰',price:40}
				person.car = markRaw(car)
			}

			function changePrice(){
				person.car.price++
				console.log(person.car.price)
			}

			//返回一个对象(常用)
			return {
				sum,
				person,
				...toRefs(person),
				showRawPerson,
				addCar,
				changePrice
			}
		}
	}
</script>

customRef 创建自定义ref

<template>
	<input type="text" v-model="keyWord">
	<h3>{{keyWord}}</h3>
</template>

<script>
	import {ref,customRef} from 'vue'
	export default {
		name: 'App',
		setup() {
			//自定义一个ref——名为:myRef
			function myRef(value,delay){
				let timer
				return customRef((track,trigger)=>{
					return {
						get(){
							console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
							track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
							return value
						},
						set(newValue){
							console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
							clearTimeout(timer)
							timer = setTimeout(()=>{
								value = newValue
								trigger() //通知Vue去重新解析模板
							},delay)
						},
					}
				})
			}

			// let keyWord = ref('hello') //使用Vue提供的ref
			let keyWord = myRef('hello',500) //使用程序员自定义的ref
			
			return {keyWord}
		}
	}
</script>

provide 与 inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
      	......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
      
    2. 后代组件中:

      setup(props,context){
      	......
          const car = inject('car')
          return {car}
      	......
      }
      

      在这里插入图片描述

响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
<script>
	import {ref, reactive,toRefs,readonly,isRef,isReactive,isReadonly,isProxy } from 'vue'
	export default {
		name:'App',
		setup(){
			let car = reactive({name:'奔驰',price:'40W'})
			let sum = ref(0)
			let car2 = readonly(car)

			console.log(isRef(sum))//true
			console.log(isReactive(car))//true
			console.log(isReadonly(car2))//true
			console.log(isProxy(car))//true
			console.log(isProxy(sum))//false
			return {...toRefs(car)}
		}
	}
</script>

Fragment

  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用

Teleport

Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

<teleport to="移动位置">
	<div v-if="isShow" class="mask">
		<div class="dialog">
			<h3>我是一个弹窗</h3>
			<button @click="isShow = false">关闭弹窗</button>
		</div>
	</div>
</teleport>

Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
      
    • 使用Suspense包裹组件,并配置好defaultfallback

      <template>
      	<div class="app">
      		<h3>我是App组件</h3>
      		<Suspense>
      			<template v-slot:default>
      				<Child/>
      			</template>
      			<template v-slot:fallback>
      				<h3>加载中.....</h3>
      			</template>
      		</Suspense>
      	</div>
      </template>
      

      suspense和异步组件配合可以使setup返回一个promise实例
      在这里插入图片描述
      在这里插入图片描述

一些改变

  • Vue3.0中对这些API做出了调整:

  • 将全局的API,即:Vue.xxx调整到应用实例(app)上

    2.x 全局 API(Vue3.x 实例 API (app)
    Vue.config.xxxxapp.config.xxxx
    Vue.config.productionTip移除
    Vue.componentapp.component
    Vue.directiveapp.directive
    Vue.mixinapp.mixin
    Vue.useapp.use
    Vue.prototypeapp.config.globalProperties
  • 过度类名的更改:

  • Vue2.x写法

    .v-enter,
    .v-leave-to {
      opacity: 0;
    }
    .v-leave,
    .v-enter-to {
      opacity: 1;
    }
    
  • Vue3.x写法

    .v-enter-from,
    .v-leave-to {
      opacity: 0;
    }
    
    .v-leave-from,
    .v-enter-to {
      opacity: 1;
    }
    
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      <my-component
        v-on:close="handleComponentEvent"
        v-on:click="handleNativeClickEvent"
      />
      
    • 子组件中声明自定义事件

      <script>
        export default {
          emits: ['close']
        }
      </script>
      
  • 移除过滤器(filter)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值