Vue超详细(已完结)

Vue

Hello World!

模板语法

插值语法 {{}}

用于解析标签体内容

<!--引入vue.js-->
<script src="js/vue.js" type="text/javascript"></script>





<div id="root">
    <h1>
        Hello {{name}}
    </h1>
</div>

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

   //创建vue实例
   new Vue({
    // 在el配置项中 用css选择器选择对象
    el:'#root',
    //data中用于存储数据,供el所指定的对象所使用,
    data:{
        name:'pyy'
    }

   })
</script>

指令语法,用于解析标签(标签属性,绑定事件。。。)

vue有很多指令,形式都是 v-***

v-bind: 例:v-bind:href=“url” 多层级关系用.点召唤

可以简写成 : (冒号)

动态的绑定事件

<div id="root">
    <br>
    <a v-bind:href="url" target="_blank" >哩哔哔哩</a>
    <a :href="url" target="_blank" >也是哩哔哔哩</a>
</div>

 //创建vue实例
   new Vue({
    //用css选择器选择对象
    el:'#root',
    //data中用于存储数据,供el所指定的对象所使用,
    data:{
        url:'https://www.bilibili.com/'
    }

   })

数据绑定

1、单向数据绑定(v-bind:):数据只能从data流向页面

2、双向数据绑定(v-model:):数据不仅可以从data流向页面,还可以从页面流向data

注:v-model:只能应用在表单类元素上,输入的元素

 
    <div id="pyy">

        单向数据绑定:<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
        new Vue({
            el:'#pyy',
            data:{
                name:'pj'
            }


        })
    </script>

el和data的第二种写法

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

    </div>

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

        const v = new Vue({
            //data的第二种写法  函数式,在组件中,data必须为函数式写法,可以简写data(){}   不能写箭头,用了箭头就变成window的函数了
            data:function() {
                return {
                    name:'pj';
                }
            }
        })
        //与el效果一样,此为第二种写法
        v.$mount('#pyy');


    </script>

MVVM模型

<body>

    <!-- 容器 -->
    <!-- MVVM模型
        M:model模型,data中的数据
        V:view视图
        VM: viewmodel视图模型   Vue实例


        data中的所有属性,最终都会出现在vm(Vue实例)身上
        vm(Vue实例)中的所有属性都可以在vue视图中直接用
    
    -->
    <div id="pyy">
        <h1>{{name}}</h1>
        <h1>{{age}}</h1>

    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false
        const vm = new Vue({
            data(){
                return{
                    name:'pj',
                    age:24

                }
            }
        })
        vm.$mount('#pyy');
       
    </script>

数据代理

js中的 Object.defineProperty

 <script>
        let num = 20;
        let person = {

            name:'pyy',
            sex:'men'

        }

        Object.defineProperty(person,'age',{
            //value:18,//age属性的值
            //enumerable:true,//是否可以被遍历,默认是false
            //writable:true,//是否可以被修改,默认是false
            //configurable:true,//是否可以被删除。默认是false

            //当age属性被调用的时候,则执行getter方法,返回其中的返回值  这个值就是age
            get(){
                console.log("我看看几岁了    ");
                return num
            },
            //当age属性被修改的时候,调用setter方法,将修改的值传递给num,从而修改age的值
            set(value){
                num = value
                console.log('我今年是'+value+'岁');
            }


        })

    </script>

什么是数据代理

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

Vue中的数据代理

通过vm对象来代理data对象中属性的操作(读、写)

好处:更加方便的操作data中的数据

基本原理:通过Object.defineProperty()把data对象中所有属性添加到vm上

为每一个属性都指定一个getrer、setter。

事件处理

<!-- 事件使用
   使用v-on:事件名或@事件名
   事件的回调函数需要在配置项methods中,最后会在vm(vue实例对象)中
   回调函数不能用箭头,否则this关键字就会变成window
   this指向的是vm或者是组件实例对象
   简写方式:@click="notShow($event,'gun')" 可以传递参数
   完整写法:v-on:click="show"  也可以不传递参数
-->
<div id="pyy">
   <button v-on:click="show" >点我</button>
   <button @click="notShow($event,'gun')" >别点我</button>
</div>
   <script type="text/javascript">
       Vue.config.productionTip = false
       const vm = new Vue({
           methods:{
               show(){
                   alert("点我干嘛");
               },
               notShow(e,e2){
                   console.log(e,e2);
                   alert("说了别点还点我");
               }
           }
       })
       vm.$mount('#pyy');
   </script>

事件修饰符

 <style>
        .wai {
            margin-top: 50px;
            width: 100px;
            height: 100px;
            background-color: pink;

        }
        .nei{
            width: 55px;
            height: 55px;
            background-color: greenyellow;
        }
    </style>


<!-- 

        vue中的修饰符
        prevent:阻止默认事件
        stop:阻止事件冒泡(禁止套娃,触发里层的效果,不让外层触发)
        once:事件只触发一次
        capture:使用事件的捕获模式(先捕获再冒泡,在外层加了这个后,会在捕获阶段就执行)
        self:只有event.target是当前操作的元素时才会触发事件
        passive:事件的默认行为立即执行,无需等待事件回调执行完毕(用的较少)


		修饰符可以连续写,可以达到多重效果 例  @click.stop.prevent="nei"
    -->

    <div id="pyy">
        <!-- 加了 prevent  就不能跳转了 -->
        <a href="https://www.baidu.com" @click.prevent="baidu">百度一下,你就知道。</a>
        <!-- 加了 stop  就只出现里面的一个弹框,外部的弹框不会弹了,阻止了冒泡 -->
        <div class="wai" @click="nei">
            <button @click.stop="nei">点我</button>

        </div>
        <!-- 加了 once  就只能点击一次了 -->
        <button @click.once="one">点我一下</button>

        <!-- 加了 capture  会先执行外部,后执行内部 -->
        <div class="wai" @click.capture="wai">
            d1
           <div class="nei" @click="nei">
                d2
           </div>

        </div>
        <!-- 加了 self   如果点击了并且当前点击的target是自己,那么则会触发,如果是冒泡冒上来的则不会触发-->
        <div class="wai" @click.self="ziji">
            <button @click="ziji">点我自己</button>

        </div>

    </div>


    <script type="text/javascript">
        Vue.config.productionTip = false
        const vm = new Vue({

            methods: {

                baidu() {

                    alert("不准跳");

                },
                nei() {
                   console.log("d2");

                },
                wai() {
                   console.log("d1");

                },
                one(){
                    alert("一下");
                },
                ziji(e){
                    console.log(e.target);
                }

            }


        })
        vm.$mount('#pyy')

    </script>

键盘事件

 <!-- 
        键盘监听事件

        @keyup:键盘抬起时触发
        @keydown:键盘按下时触发
        常用按键别名:
        回车===enter
        删除===delete
        退出===esc
        空格===space
        换行===tab(特殊,要配合keydown使用)
        上===up
        下===down
        左===left
        右===right
        vue未提供别名的按键,可以使用按键原始的key值去绑定    例:Caps-lock
        ctrl、alt、shift、win键在配合keyup使用的时候需要按下抬起其他任意的键才能触发,keydown正常触发

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

 		指定组合按键:例   @keyup.crrl.y   按下ctrl+y才能触发,别的都不能触发
     -->
        <div id="pyy">
            <!-- 按下回车触发事件 -->
            <input type="text" placeholder="输入内容" @keyup.enter="show">
        </div>
    <script type="text/javascript">
        Vue.config.productionTip = false
        const vm = new Vue({
            methods:{
                show(e){
                    console.log(e.target.value);
                }
            }
        })
        vm.$mount('#pyy')
    </script>

计算属性

用过已有的属性计算得来的属性,

借助了 Object.defineProperty 的getter和setter

内部有缓存机制,效率高,调试方便

计算属性最终会出现在vm上,直接读取即可

修改计算属性时,需通过set函数去修改,并且把修改的值,赋给所依赖的属性

 <div id="pyy">
        姓:<input type="text" v-model="firstName">
        名:<input type="text" v-model="lastName">
        全名:<span>{{fullName}}</span>
    </div>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            data: {
                firstName: '庞',
                lastName: '杰'
            },
            computed: {
                fullName: {
                    get() {
                        //get方法在第一次读取fullName时被调用,和所依赖的数据发生变化是被调用
                        /*
                        vm.fullName = '诸葛-孔明'
                        get被调用
                        '诸葛-孔明'
                        */
                        console.log('get被调用');
                        return this.firstName + '-' + this.lastName
                    },
                    set(value) {
                        //将传进来的值,按照-分开姓和名,然后赋值给firstName和lastName
                        const temp = value.split('-');
                        this.firstName = temp[0];
                        this.lastName = temp[1];
                    }
                } 
            }
        })
        vm.$mount('#pyy')
    </script>

一般情况不做修改,只有展示效果。这种情况可以使用简写模式

  ```javascript

computed: {
fullName(){
return this.firstName + ‘-’ + this.lastName
}
}
```

监视属性

当被监视的属性变化时,回调函数自动调用,进行相关操作

监视属性有两种写法

深度监视

vue中的watch默认不监视对象内部中数据的改变

配置deep:true后,就可以监视对象内部数据的的改变

如果不需要深度监视,则不开启,影响效率

 <div id="pyy">

        <h1>今天很{{info}}</h1>
        <button @click="change">切换</button>

        <hr>

        <h3>x的值是{{nums.x}}</h3><br>
        <button @click="nums.x++">x++</button><br>
        <h3>y的值是{{nums.y}}</h3><br>
        <button @click="nums.y++">y++</button><br>
    </div>


 <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            data: {
                isHot: true,
                nums:{
                    x:0,
                    y:0
                }
            },
            computed: {
                info() {
                    return this.isHot ? 'hot' : 'dream'
                }
            },
            methods: {
                change() {
                    this.isHot = !this.isHot;
                }
            },
            //监视属性,第一种写法
            watch: {
                isHot: {
                    immediate: true,//初始化时让handler调用一下
                    //参数:修改后新的值,修改前旧的值
                    handler(newValue, oldValue) {
                    console.log(newValue, oldValue);
                    }
                },
                //监视多急结构中某个属性的变化
                'nums.x':{
                    handler(){
                        console.log('x++');
                    }
                },
                //监视多急结构中所有属性的变化
                nums:{
                    deep:true,
                    handler(){
                        console.log('nums');

                    }
                }
            }
        })

        vm.$mount('#pyy');
        //监视属性,第二种写法
        // vm.$watch('isHot', {
        //     immediate: true,//初始化时让handler调用一下
        //     //参数:修改后新的值,修改前旧的值
        //     handler(newValue, oldValue) {
        //         console.log(newValue, oldValue);
        //     }
        // });



    </script>

绑定样式

 .basic{
          width: 400px;
          height: 400px;
          border: 1px solid #000000; 
       }
       .red{
           background-color: red;
       }
       .green{
           background-color: green;
       }
       .blue{
           background-color: blue;
       }
       .pj{

           border-radius: 30%;
       }
 <div id="pyy">
        <!-- 绑定css样式,字符串写法,,适用于样式的名字不确定,需要动态的指定 -->
        <!-- 例:点击div随机切换颜色 -->
        <div class="basic" v-bind:class="color" @click="change">
            {{name}}
        </div>
        <hr>
        <!-- 绑定css样式,数组写法,,适用于样式的名字不确定,个数也不确定 -->
        <div class="basic" v-bind:class="colorArr" >
            {{name}}
        </div>
        <!-- 绑定css样式,对象写法,,适用于样式的名字确定,个数也确定 但是谁用谁不用还没确定 -->
        <div class="basic" v-bind:class="colorObj" >
            {{name}}
        </div>
    </div>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            data:{
                name:'pyy',
                color:'red',
                colorArr:['blue','pj'],
                colorObj:{
                    red:true,
                    pj:true
                }
            },
            methods: {
                change(){
                    const arr = ['red','green','blue']
                    const index = Math.floor(Math.random()*3);
                    this.color = arr[index];
                }
            },
        })
        vm.$mount('#pyy')
    </script>

条件渲染

v-show:‘true/false’ 控制显示与不显示,但是语句还在,只是加了个属性 style=“display: none;” 切换频率较高时使用

v-if:‘true/false’ 直接把整个语句都干掉 (元素可能拿不到了) 切换频率较低时使用 与 v-else-if v-else 一起使用,注:结构不能被打断。

 <div id="pyy">
        <h2>{{name}}:{{n}}</h2>
        <button @click="n++">n++</button>

        <!-- <h3 v-show="n===1">李逍遥</h3>
        <h3 v-show="n===2">赵灵儿</h3> -->


        <!-- <h3 v-if="n===1">李逍遥</h3>
        <h3 v-if="n===2">赵灵儿</h3> -->

        <h3 v-if="n===0">庞杰</h3>
        <h3 v-else-if="n===1">李逍遥</h3>
        <h3 v-else-if="n===2">赵灵儿</h3>
        <h3 v-else>邪剑仙</h3>
        <!-- v-if与 template配合使用-->
        <template v-if="n===6">
                <h3>我是谁</h3>
                <h3>我是谁</h3>
                <h3>我是谁</h3>
                <h3>我是谁</h3>
        </template>

    </div>


    <script>
        Vue.config.productionTip = false

        const vm = new Vue({

            data: {
                name: 'pyy',
                n: 0
            }

        })
        vm.$mount('#pyy')
    </script>

列表渲染

v-for 用于展示列表数据

<div id="pyy">
        <!-- 遍历数组  用的较多 -->
        <!-- p就是数组中的每一个元素,index就是索引 :key="index"  每一个的标识-->
        <ul>
            <li v-for="(p,index) in persons" :key="index">
                {{index}}:{{p.name}}-{{p.age}}
            </li>
        </ul>
        <!-- 遍历对象  用的较多 -->
        <!-- value就是对象中的值,index就是索引 :key="index"  每一个的标识-->
        <ul>
            <li v-for="(value,index) in human" :key="index">
                {{index}}:{{value}}
            </li>
        </ul>
        <!-- 遍历字符串  用的较少 -->
        <ul>
            <li v-for="(value,index) in str" :key="index">
                {{index}}:{{value}}
            </li>
        </ul>
    </div>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            data:{
                persons:[
                    {id:1001,name:'pyy',age:40},
                    {id:1002,name:'pxx',age:30},
                    {id:1003,name:'pzz',age:20}
                ],
                human:{
                    name:'pj',
                    height:185,
                    weight:75
                },
                str:'pangjie'
            },
        })
        vm.$mount('#pyy')
    </script>

列表过滤

  <div id="pyy">
        <input type="text" placeholder="请输入姓名" v-model="keyWords">
        <ul>
            <li v-for="(p,index) in filPersons" :key="p.id">
                {{p.name}}-{{p.age}} 
            </li>
        </ul>
    </div>

    <script>
        Vue.config.productionTip = false
        //使用监视来进行列表过滤
        // const vm = new Vue({
        //     data:{
        //         keyWords:'',
        //         persons:[
        //             {id:1001,name:'马冬梅',age:40},
        //             {id:1002,name:'赵四',age:30},
        //             {id:1003,name:'赵灵儿',age:20},
        //             {id:1004,name:'郭冬临',age:30}
        //         ],
        //         filPersons:[]
        //     },
        //     watch:{
        //         keyWords:{
        //         immediate:true,
        //         handler(newValue){
        //             //过滤方法
        //            this.filPersons = this.persons.filter((p)=>{
        //             // 如果输入框的内容在姓名中找不到,会返回-1,如果有,则会返回在第几位(从零开始)
        //                 return p.name.indexOf(newValue) != -1;
        //             })
        //         }
        //      }
        //     }
        // })
        // vm.$mount('#pyy')




        //使用计算属性来进行列表过滤,计算属性和监视属性都能完成的情况下,尽量用计算属性
        const vm = new Vue({
            data:{
                keyWords:'',
                persons:[
                    {id:1001,name:'马冬梅',age:40},
                    {id:1002,name:'赵四',age:30},
                    {id:1003,name:'赵灵儿',age:20},
                    {id:1004,name:'郭冬临',age:30}
                ],
            },
            computed:{
                filPersons:{
                    get(){
                    //过滤方法
                    return this.persons.filter((p)=>{
                    // 如果输入框的内容在姓名中找不到,会返回-1,如果有,则会返回在第几位(从零开始)
                        return p.name.indexOf(this.keyWords) != -1;
                    })
                }
                }
                
             }
            
        })
        vm.$mount('#pyy')


    </script>

列表排序

 <div id="pyy">
        <input type="text" placeholder="请输入姓名" v-model="keyWords">
        <button @click="sort=1">年龄升序</button>
        <button @click="sort=2">年龄降序</button>
        <button @click="sort=0">原升序</button>
        <ul>
            <li v-for="(p,index) in filPersons" :key="p.id">
                {{p.name}}-{{p.age}} 
            </li>
        </ul>
    </div>
 <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            data:{
                sort:0,
                keyWords:'',
                persons:[
                    {id:1001,name:'马冬梅',age:44},
                    {id:1002,name:'赵四',age:33},
                    {id:1003,name:'赵灵儿',age:23},
                    {id:1004,name:'郭冬临',age:37}
                ],
            },
            computed:{
                filPersons:{
                    get(){
                    //过滤方法
                    const arr = this.persons.filter((p)=>{
                    // 如果输入框的内容在姓名中找不到,会返回-1,如果有,则会返回在第几位(从零开始)
                        return p.name.indexOf(this.keyWords) != -1;
                    })
                    //判断是否需要排序,0为false,1、2为真,需要排序
                    if(this.sort){
                        arr.sort((a,b)=>{
                            return this.sort ===1 ? a.age - b.age : b.age - a.age;
                    })
                    }
                    return arr;
                }
                }
                
             }
            
        })
        vm.$mount('#pyy')


    </script>

vue数据监测

总结

原理:vue会监视data中所有层次的数据

通过setter实现监视,并且在new Vue 的时候就传入监测的数据

对象中后追加的数据vue不做响应式处理(get、set),要想后加的数据有响应式处理,

请使用vue的api 例:

​ this.$set(this.student,‘sex’,‘男’) this是vue实例

​ Vue.set(this.student,‘sex’,‘男’) Vue 是vue的全局变量

数组中的数据是通过包裹数组的方法实现的,也就是,调用原生的数组方法,然后在解析模板,进而对页面进行更新。

修改数组的元素时,要用方法修改。例:push(),pop(),shift(),unshift(),splice(),reverse()

或者 Vue.set() vm.$set() 注:这两个方法不能给vue实例的根数据添加属性

 <div id="app">
        <h2>学生信息</h2>
        <hr>
        <button @click="student.age++">年龄+1岁</button><br><br>
        <button @click="addSex">添加性别属性,默认值:男</button><br><br>
        <button @click="addFriend" >在列表首位添加一个朋友</button><br><br>
        <button @click="updateFirstFriendName">修改第一个朋友名字为张三</button><br><br>
        <button @click="addHobby">添加一个爱好</button><br><br>
        <button @click="updateFirstHobby">修改第一个爱好为开车</button><br><br>
        <hr>
        <h4>姓名:{{student.name}}</h4>
        <h4>年龄:{{student.age}}</h4>
        <h4 v-if="student.sex">性别:{{student.sex}}</h4>
        <h4>爱好</h4>
        <ul>
            <li v-for="(h,index) in student.hobby " ::key="index">
                {{h}}
            </li>
        </ul>
        <h4>朋友</h4>
        <ul>
            <li v-for="(f,index) in friends " ::key="index">
                {{f.name}}----{{f.age}}
            </li>
        </ul>
    </div>
    <script>
         Vue.config.productionTip = false
         const vm = new Vue({
            data:{
                student:{
                name:'peter',
                age:18,
                hobby:['抽烟','喝酒','烫头']
                },
                friends:[
                    {name:'tom',age:19},
                    {name:'kity',age:22},
                    {name:'lisa',age:17}
                ]
            },
            methods: {
                addSex(){
                    this.$set(this.student,'sex','男')
                    //Vue.set(this.student,'sex','男')
                },
                addFriend(){
                    this.friends.unshift({name:'jack',age:33});
                },
                updateFirstFriendName(){
                    this.friends[0].name = '张三';
                },
                addHobby(){
                    this.student.hobby.push('看电影')
                },
                updateFirstHobby(){
                    this.student.hobby.splice(0,1,'开车');
                }
            },
         })
         vm.$mount('#app')
    </script>

收集表单数据

v-model的三个修饰符:

lazy失去焦点在收集数据

number:输入的字符串转为有效的数字

trim:输入收尾空格过滤掉

<div id="app">
        <form @submit.prevent = 'demo'>
            <!-- trim去掉前后空格 -->
            <label >账号:<input type="text"  v-model.trim="user.accont" ></label><br><br>
            <label >密码:<input type="password"  v-model="user.password"></label><br><br>
            <!-- number   使其输入为number类型 -->
            <label >年龄:<input type="number"  v-model.number="user.age"></label><br><br>
            <!-- type属性为radio单选按钮时,需要手动写value -->
            <label >性别:</label>
            <input type="radio"  v-model="user.sex" value="man"><input type="radio"  v-model="user.sex" value="women"><br><br>
            <!-- type属性为checkbox多选框时,需要手动写value  而且用数组存放,否则返回的就是布尔值-->

            <label >爱好:</label>
            <input type="checkbox"  v-model="user.hobby" value="study">学习
            <input type="checkbox"  v-model="user.hobby" value="play">玩游戏
            <input type="checkbox"  v-model="user.hobby" value="watch">看电视
            <br><br>
            地址:
            <select  v-model="user.address">
                <option value="beijing">北京</option>
                <option value="tianjin">天津</option>
                <option value="hebei">河北</option>
                <option value="dongbei">东北</option>
            </select>
            <br><br>
            其他信息
            <!-- lazy   失去焦点的时候再把内容放入vm中 -->
            <textarea  cols="30" rows="10" v-model.lazy="user.otherInfo"> 
            </textarea>
            <br><br>
            <p><input type="checkbox"  v-model="user.agree">阅读并接受<a href="www.baidu.com">《用户协议》</a></p>
            <br><br>
            <button >提交</button>
        </form>
    </div>
    <script>
         Vue.config.productionTip = false

         const vm = new Vue({
            data:{
                user:{
                    name:'',
                    password:'',
                    age:'',
                    sex:'man',
                    hobby:[],
                    address:'tianjin',
                    otherInfo:'',
                    agree:''
                }
            },
            methods:{
                demo(){
                    if(!this.user.agree){
                        alert("请先阅读并同意用户协议");
                        return;
                    }
                    console.log(JSON.stringify(this.user));
                }
            }
         })
         vm.$mount('#app')
    </script>

过滤器(vue3已经移除了)

搭配插值语法{{}} 或者v-bind使用

 <div id="app">
        <h2>时间格式化 {{time}}</h2>
        <h3>计算属性:{{formatTime}}</h3>
        <h3>方法属性:{{fmethTime()}}</h3>
        <!-- 将time作为 firstFilter的第一个参数传递-->
        <h3>过滤器1:{{time | firstFilter}}</h3>
        <!-- 将time作为 secondFilter的第一个参数传递  括号内容作为第二个参数传递-->
        <h3>过滤器2:{{time | secondFilter('YYYY-MM-DD')}}</h3>
        <!-- 将time作为 firstFilter的第一个参数传递   将firstFilter的返回值作为 mySlice 的第一个参数传递-->
        <h3>全局过滤器:{{time | firstFilter | mySlice}}</h3>
    </div>
    <script>
        Vue.config.productionTip = false
        //定义一个全局过滤器
        Vue.filter('mySlice',function(value){
            //截取前四位
            return value.slice(0,4);
        })
        const vm = new Vue({
            data: {
                time: '1677421202346'
            },
            computed: {
                formatTime() {
                    return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
                }
            },
            methods:{
                fmethTime(){
                    return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
                }
            },
            //在vue实例里面定义的过滤器只能在这个vue实例中用别的vue实例用不了
            filters:{
                firstFilter(value){
                    return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
                },
                secondFilter(value,str){
                    return dayjs(value).format(str);
                }
            }
        })
        vm.$mount('#app')
    </script>

内置指令

v-text :向其所在的节点中渲染文本内容

<h2 v-text="time"> </h2>

 data: {
		time: '1677421202346'
       },

v-html:可以解析标签 可以识别html结构

如果在网站上动态渲染任意html是非常危险的,容易导致xss攻击。不要用在用户提交上。

<h2 v-html="time"> </h2>

 data: {
		time: '</h4>1677421202346</h4>'
       },

v-cloak:本质是一个特殊属性,vue实例创建完毕并接管容器后会删掉v-cloak属性

搭配css可以解决网速慢是页面显示{{xxx}}的情况

v-once:所在节点在初次动态渲染后,就视为静态内容了,以后数据发生变化就不会影响它了。

v-pre: 跳过所在节点的编译,在容器中如果有不需要编译的标签,可以加一个v-pre,这样就加快了编译速度。

自定义指令

 <div id="app">
        <h2>n的值是: <span v-text="n"></span></h2>
        <h2>n的值乘十是: <span v-ten="n"></span></h2>
        <h2>n的值乘二十是: <span v-big="n"></span></h2>
        <button @click="n++">n++</button>
        <input type="text" name="" id="" v-fbind:value="n">
    </div>
    <script>
        Vue.config.productionTip = false
        //全局指令,和过滤器写法一样
        Vue.directive('big',function(el, binding){
            el.innerText = binding.value * 20;
        })
        const vm = new Vue({
            data: {
                n: '1'
            },
            //自定义指令的配置项,指令名不要用驼峰形式命名,可以用-连接
            directives: {
                //参数一:当前指令所在的标签
                //参数二:包含指令的信息,名字,使用时的名字,value值===指令所用到属性值
                //简写,函数式,在指令与元素绑定成功时会被调用,就是指令所在的模板被重新解析时会被调用
                ten(el, binding) {
                    //将属性值,赋值给指令所在标签文本
                    el.innerText = binding.value * 10;
                },
                //较为完整的写法
                fbind: {
                    //指令与元素绑定成功
                    bind(el, binding) {
                        el.value = binding.value;
                    },
                    //指令所属标签插入页面时,焦点
                    inserted(el, binding) {
                        el.focus();
                    },
                    //指令所在的模板被重新解析
                    update(el, binding) {
                        el.value = binding.value;
                    }
                }
            }
        })
        vm.$mount('#app')
    </script>

生命周期

beforeCreate(){} --此时无法通过vm访问到data中的数据和methods中的方法

created(){}----此时可以通过vm访问到data中的数据和methods中的方法

这个阶段vue开始解析模板,生成虚拟dom,但是页面还不能显示解析好的内容

beforeMount(){}—页面上显示的是未经过vue编译的dom

将虚拟dom转为真实dom

mounted(){}—vue完成模板解析并把初始的真实dom放入页面中后,调用mounted(){}。

beforeUpdate(){} —数据是新的,但是页面还是旧的。

updated(){}—数据和页面都是新的

beforeDsetory(){}-----一般在此阶段关闭定时器,解绑自定义事件,等一些收尾操作

destoryed—销毁时自定义事件会失效,但是原生dom事件依然有效,

组件

局部功能的代码集合。复用编码,提高效率。

非单文件组件

 <div id="app">
        <h2>{{hello}}</h2>
        <!-- 使用组件(第三步) -->
        <ren></ren>
        <hr>
        <pj></pj>
        <pyy></pyy>
    </div>
    <script>
         Vue.config.productionTip = false
        //创建组件(第一步)
        const person =  Vue.extend({
            data(){
                return {
                    name:'pyy',
                    address:'erath'
                }
            },
            template:`
                <div>
                    <h2>我是谁?{{name}}</h2>
                    <h2>我在哪?{{address}}</h2>
                </div>
            `
        });
        const pj = Vue.extend({
            //data要写成函数,
            data(){
                return {
                    name:'pj',
                    level:'100',
                }
            },
            //配置组件架构
            template:`
                <div>
                    <h2>我是谁?{{name}}</h2>
                    <h2>我多少级?{{level}}</h2>
                    <button @click="level++">等级加一</button>
                </div>
            `
        })
        const pyy = Vue.extend({
            data(){
                return {
                    name:'pyy',
                }
            },
            template:`
                <div>
                    <h2>ta是谁?{{name}}</h2>
                </div>
            `
        })
        //注组件(第二步)(全局组件)
        Vue.component('pyy',pyy)
         const vm = new Vue({
            data:{
                hello:'你好,庞杰'
            },
            //注组件(第二步)(局部组件)
            components:{
                //标签名:组件名
                ren:person,
                //如果二者相同可以简写
                pj
            }
         })
         vm.$mount('#app')
    </script>

组件嵌套

创建一个,一人之下万人之上的组件,来管理其他组件

 <div id="pyy">
        
       
    </div>

    <script>
         Vue.config.productionTip = false
         const pang = Vue.extend({
            name:'pang',
            //data要写成函数,
            data(){
                return {

                    name:'pj',
                    level:'100',

                }
            },
            //配置组件架构
            template:`
                <div>
                    <h2>我是谁?{{name}}</h2>
                    <h2>我多少级?{{level}}</h2>
                    <button @click="level++">等级加一</button>
                </div>
            `
        })
        //创建组件(第一步)
        const person =  Vue.extend({
            name:'person',
            data(){
                return {
                    name:'pyy',
                    address:'erath'
                }
            },
            template:`
                <div>
                    <h2>我是谁?{{name}}</h2>
                    <h2>我在哪?{{address}}</h2>
                    <pang></pang>
                </div>
            `,
            components:{
                pang
            }
        });
       
        const app = Vue.extend({
            name:'app',
            components:{
                //标签名:组件名
                 ren:person,
                //如果二者相同可以简写
            },
            //需要用div包裹起来,否则只能识别出一个标签
            template:`
            <div>
                <ren></ren>
                
            </div>
            `,
           
        })

         const vm = new Vue({
            template:`
               
                <app></app>
            `,
            //注组件(第二步)(局部组件)
            components:{
               app
            }
         })
         vm.$mount('#pyy')
    </script>

VueComponent构造函数

组件本质是一个名为VueComponent的构造函数 在Vue.extend时生成 一个全新的VueComponent

vue解析组件标签时,就会创建一个组件的实例对象,即: new VueComponent()

这时,这里的 this就是VueComponent的实例对象

组件实例对象也可以访问到vue原型上的属性、方法。

单文件组件

一个组件一个.vue文件,配合脚手架使用。

脚手架

ref属性:

ref=“xxx”

this.$refs.xxx

被用来给元素或子组件注册引用信息(代替id)

应用在html标签上获取的是真实DOM元素,用在组件上获取到的是组件实例

App.Vue

<template>
  <div>

    <h2 v-text="message" ref="title"></h2>
    <button @click="log" ref="btn">输出信息</button>
   <School ref="ss"></School>
  </div>
</template>

<script>
import School from './components/School.vue'
export default {
    name:'App',
    components:{
        School
    },
    data() {
      return {
        message:'hello Vue'
      }
    },
    methods: {
        log(){
          console.log(this.$refs.title);//真实Dom元素
          console.log(this.$refs.btn);//真实Dom元素
          console.log(this.$refs.ss);//School组件的实例
        }
    },

}
</script>

<style>

</style>

School.Vue

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

  </div>

</template>

<script>
export default {

    name:'School',
    data(){
        return {
            address:'earth'
        }
    }
}
</script>

<style>
.school{
    background-color: pink;
}
</style>

main.js(基本不会改动里面的内容)

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

Vue.config.productionTip = false

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

props配置

让组件接收外部传进来的数据

App.vue

<template>
  <div>
    <!-- 传递数据 -->
    <!-- 将age用v-bind数据绑定,name引号中的内容就不是字符串了,就会被解析成js表达式 -->
   <Student name="庞杰" :age="24" sex="男"></Student>
   <Student name="赵丽颖"  sex="女"></Student>
  
  </div>
</template>

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

}
</script>

<style>

</style>

Student.vue

<template>
  <div class="school">
    <h2 >{{msg}}</h2>
    <h2 >姓名:{{name}}</h2>
    <h2 >年龄:{{myAge}}</h2>
    <h2 >性别:{{sex}}</h2>
  
    <button @click="myAge++" >长了一岁</button>
  </div>

</template>

<script>
export default {

    name:'Student',
    data(){
        return {
            msg:'我是一个人!!',
            //age是外部传递进来的变量,尽量不要修改。如果要修改age的值
            //定义一个中间变量,用来存放age
            myAge:this.age
        }
    },
    //第一种接收数据方式,简单接收
    //props:['name','age','sex']

    //第二种接收数据方式,限制接收类型
    // props:{
    //   name:String,
    //   age:Number,
    //   sex:String
    // }    
    //第三种接收数据方式(最完整),限制接收类型+是否必传+是否有默认值
    props:{
      name:{
        typeof:String,
        required:true,//必须传递
      },
      age:{
        typeof:Number,
        default:18//人如果不传递age属性,那么默认为18岁
      },
      sex:{
        typeof:String,
        required:true
      }
    }
}
</script>

<style>
.school{
    background-color: pink;
}
</style>

mixin(混入/混合)

在不同组件中使用相同的东西,则可以把这个东西单独提出来,—混合对象。

谁用谁导入

App.vue

<template>
  <div>
    <!-- 传递数据 -->
    <!-- 将age用v-bind数据绑定,name引号中的内容就不是字符串了,就会被解析成js表达式 -->
   <Student ></Student>
   <School ></School>
  
  </div>
</template>

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

}
</script>

<style>

</style>

School.vue

<template>
  <div class="school">
    <h2 @click="show">姓名:{{name}}</h2>
    <h2 >地址{{address}}</h2>
  
  </div>

</template>

<script>
//import {mixin} from '../mixin';

export default {

    name:'School',
    data(){
        return {
            name:'zhonghuan',
            address:'erath'
        }
    },
   //混合配置,数组的形式,用几个放几个
    //mixins:[mixin]
   
}
</script>

<style>
.school{
    background-color: pink;
}
</style>

Student.vue

<template>
  <div class="school">
    <h2 @click="show">姓名:{{name}}</h2>
    <h2 >年龄:{{age}}</h2>
    <h2 >性别:{{sex}}</h2>
  
  </div>

</template>

<script>

//导入混合文件(局部),属性、方法会混合到一起,如果属性名或方法名一样,则按照自己的来,不会按照混合文件的内容
//import {mixin} from '../mixin';
export default {

    name:'Student',
    data(){
        return {
            name:'pyy',
            age:'24',
            sex:'men',
            a:'aaa'
        }
    },
    //混合配置,数组的形式,用几个放几个
    //mixins:[mixin]
   
}
</script>

<style>
.school{
    background-color: pink;
}
</style>

mixin.js

export const mixin = {

    methods:{
        show(){
          alert(this.name);
          console.log('hello');
        }
      },
      data(){
        return{
            a:'a'
        }
      }
}

main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
 //导入混合文件(全局)会在所有组件和vm身上加入混合中的东西(包括app。vue实例)三思而后行
 import {mixin} from './mixin'
 Vue.mixin(mixin);
Vue.config.productionTip = false

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

插件

用于增强Vue

本质:包含install方法的一个对象,install的第一个参数是vue,第二个以后的参数是插件使用者传递的数据

定义插件:对象.install = function(Vue,options){

​ 全局过滤器。。。

​ 全局指令。。。

​ 全局混入。。。

​ 实例方法。。。

}

使用插件:Vue.use()

scoped样式

局部的,在.vue文件里的style标签加一个这个属性

那么这个style标签里的所有装饰只能服务于本文件里的架构了。

TodoList案例

尝试组件化编码

App.vue

<template>
 <div >
  <div class="todo-container">
    <div class="todo-wrap">
     <MyHeader :addTodo="addTodo"></MyHeader>
     <MyList :todos="todos" :changeTodo="changeTodo" :deleteTodo="deleteTodo"></MyList>
      <MyFooter :todos="todos" :checkedAll="checkedAll" :clearDoneTodo="clearDoneTodo"></MyFooter>
    </div>
  </div>
</div>
</template>

<script>

  import MyHeader from './components/MyHeader.vue'
  import MyFooter from './components/MyFooter.vue'
  import MyList from './components/MyList.vue'
 
export default {
    name:'App',
    data() {
        return {
            todos:[
                {id:'1001',title:'吃饭',done:true},
                {id:'1002',title:'睡觉',done:true},
                {id:'1003',title:'打豆豆',done:false}
            ],
        }
    },
    components:{
       MyHeader,MyFooter,MyList
    },
    methods:{
      //添加方法:参数就是输入框所输入内容所在的对象
      addTodo(todoObj){
        //添加到集合的头部 
        this.todos.unshift(todoObj);
      },
      //修改方法,根据id去修改todo对象的done 
      changeTodo(id){
        //遍历todos集合,找到与参数id一直的对象
        this.todos.forEach((todo)=>{
          //将该对象的done值取反
          if(todo.id === id) todo.done = !todo.done
        })
      },
      //删除方法,根据id删掉改对象
      deleteTodo(id){
        //过滤一下,将符合条件的留下,形成一个新的集合,并赋值给todos
        this.todos = this.todos.filter((todo)=>{
            return todo.id !== id;
        })
      },
      //全选/全不选方法,参数就是,底部多选框的值
      checkedAll(value){
        //将参数赋值给数组中的每一个对象的done值
        this.todos.forEach((todo)=>{
          todo.done = value;
        })
      },
      //过滤掉已经完成的
      clearDoneTodo(){
        this.todos =  this.todos.filter((todo)=>{
          return !todo.done
        })
      }
    }
}
</script>

<style>
/*base*/
body {
  background: #fff;
}

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

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

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

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

MyHeader.vue

<template>
   <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model.trim="title" @keyup.enter="show"/>
    </div>
</template>

<script>
import {nanoid} from 'nanoid'
export default {
    name:'MyHeader',
    data() {
        return {
            title:''
        }
    },
    props:['addTodo'],
    methods:{
        show(){
            //检查输入内容
            if(this.title.trim() ==='' || this.title.trim() ==='null' || this.title.trim() ==='undefined') return alert("输入内容不能为空!null undefined ")
            //将输入内容装进对象
            const item = {id:nanoid(),title:this.title,done:false};
            //调用添加方法
            this.addTodo(item);
            //清空输入框中内容
            this.title='';
        }
    },
} 
</script>

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

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

MyList.vue

<template>
   <ul class="todo-main">
       <MyItem 
       v-for="todoObj in todos" 
       :key="todoObj.id" 
       :todo="todoObj" 
       :changeTodo="changeTodo" 
       :deleteTodo="deleteTodo">
       </MyItem>
       
      </ul>
</template>

<script>
import MyItem from './MyItem.vue'
export default {
    name:'MyList',
    
    components:{
        MyItem
    },
    props:['todos','changeTodo','deleteTodo']
    

}
</script>

<style scoped>

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

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

MyItem.vue

<template>
   <li>
          <label>
            <input type="checkbox" :checked="todo.done" @change="changeDone(todo.id)"/>
            <span>{{todo.title}}</span>
          </label>
          <button class="btn btn-danger" @click="handleDelete(todo.id)" >删除</button>
        </li>
</template>

<script>
export default {
    name:'MyItem',
    data() {
        return {
            
        }
    },
    props:['todo','changeTodo','deleteTodo'],
    methods:{
        //将修改的多选框对象的id作为参数
        changeDone(id){
            //调用爷爷传给爸爸的修改方法
            this.changeTodo(id);
        },
        //将删除的对象的id作为参数
        handleDelete(id){
            if(confirm('确定删除吗')){

                this.deleteTodo(id);
            }

        }
    }
}
</script>

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

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

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

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

li:before {
  content: initial;
  display: inline-block;
}

li:last-child {
  border-bottom: none;
}
li:hover {
    background-color: #dddddd;
}
li:hover button{
    display: block;
}
</style>

MyFooter.vue

<template>
  <div class="todo-footer" v-show="total">
        <label>
          <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
          <span>已完成{{completed}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
      </div>
</template>

<script>
export default {
    name:'MyFooter',
    props:['todos','checkedAll','clearDoneTodo'],
    computed:{
        total(){
            return this.todos.length
        },
        completed(){
            //循环遍历(第一个参数上一次循环返回的值,第二个参数,当前循环的对象)
            return this.todos.reduce((pre,todo)=>{
                //如果done为真,就加一
                return pre + (todo.done ? 1 : 0);
            },0)
        },
        //计算属性的set/get方法
        isAll:{
            get(){
                //返回值布尔类型,判断是否全选
                return this.total === this.completed && this.total > 0;
            },
            set(value){
                //调用更改全部多选框的方法,将底部多选框的值传递进去
                this.checkedAll(value)
            }

        }
    },
    methods:{
        //调用App的清除方法
        clearAll(){
            this.clearDoneTodo();
        }
    }
    
}
</script>

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

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

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

.todo-footer button {
  float: right;
  margin-top: 5px;
}
</style>

webStorage

存储内容大小一般支持5mb

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

setItem(‘key’,‘value’)

getItem(‘key’) 返回vlaue值

removeItem(‘key’) 删除

clear() 清空

完善todoList案例

添加了watch监视属性和默认数据读取

App.vue

<template>
 <div >
  <div class="todo-container">
    <div class="todo-wrap">
     <MyHeader :addTodo="addTodo"></MyHeader>
     <MyList :todos="todos" :changeTodo="changeTodo" :deleteTodo="deleteTodo"></MyList>
      <MyFooter :todos="todos" :checkedAll="checkedAll" :clearDoneTodo="clearDoneTodo"></MyFooter>
    </div>
  </div>
</div>
</template>

<script>

  import MyHeader from './components/MyHeader.vue'
  import MyFooter from './components/MyFooter.vue'
  import MyList from './components/MyList.vue'
import { watch } from 'vue'
 
export default {
    name:'App',
    data() {
        return {
                      //从本地读取数据

            todos: JSON.parse(localStorage.getItem('todos'))  || [],
        }
    },
    components:{
       MyHeader,MyFooter,MyList
    },
    methods:{
      //添加方法:参数就是输入框所输入内容所在的对象
      addTodo(todoObj){
        //添加到集合的头部 
        this.todos.unshift(todoObj);
      },
      //修改方法,根据id去修改todo对象的done 
      changeTodo(id){
        //遍历todos集合,找到与参数id一直的对象
        this.todos.forEach((todo)=>{
          //将该对象的done值取反
          if(todo.id === id) todo.done = !todo.done
        })
      },
      //删除方法,根据id删掉改对象
      deleteTodo(id){
        //过滤一下,将符合条件的留下,形成一个新的集合,并赋值给todos
        this.todos = this.todos.filter((todo)=>{
            return todo.id !== id;
        })
      },
      //全选/全不选方法,参数就是,底部多选框的值
      checkedAll(value){
        //将参数赋值给数组中的每一个对象的done值
        this.todos.forEach((todo)=>{
          todo.done = value;
        })
      },
      //过滤掉已经完成的
      clearDoneTodo(){
        this.todos =  this.todos.filter((todo)=>{
          return !todo.done
        })
      }
    },
    watch:{
      todos:{
        deep:true,
        handler(value){
                      //本地存储,(存储到硬盘)
          localStorage.setItem('todos',JSON.stringify(value))
        }
      }
    }
   
   

}
</script>

<style>
/*base*/
body {
  background: #fff;
}

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

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

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

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}







</style>

自定义事件

组件之间的通信方式,适用于 子组件—》父组件

App.vue

<template>
  <div>
   <!-- 子传父 -->
   <School :getSchoolName="getSchoolName"></School>
   <!-- 自定义事件 pyy 就是Studnt实例对象的一个自定义事件,,调用的方法就是getStudnetName-->
   <!-- @click.native="show"    native修饰符,表示使用原生click事件,如果不加这个修饰符则会被判定为自定义事件 -->
   <Student v-on:pyy ="getStudnetName" @click.native="show"></Student>
   <!-- 使用ref  直接获取到student实例对象,直接在这个对象身上添加自定义事件 此方法比较灵活-->
   <!-- <Student ref="student"></Student> -->
   
  
  </div>
</template>

<script>
import Student from './components/Student.vue'
import School from './components/School.vue'
 
export default {
    name:'App',
    components:{
        Student,School
    },
    methods:{
      getSchoolName(name){
        console.log("app收到学校名称",name);
      },
       getStudnetName(name){
        console.log("app收到学生姓名",name);
      }
    },
    mounted(){
      //获取到student实例对象,并且添加一个自定义事件调用getStudnetName方法
     //this.$refs.student.$on('pyy',this.getStudnetName)
    }
   
   

}
</script>

<style>

</style>

School.vue

<template>
  <div class="school">
    <h2 >姓名:{{name}}</h2>
    <h2 >地址{{address}}</h2>
    <button @click="sendSchoolName">发送名字</button>
  </div>

</template>

<script>


export default {

    name:'School',
    data(){
        return {
            name:'zhonghuan',
            address:'erath'
        }
    },
    props:['getSchoolName'],
    methods:{
      sendSchoolName(){
        this.getSchoolName(this.name);
      }
    }

   
}
</script>

<style>
.school{
    background-color: pink;
}
</style>

Student.vue

<template>
  <div class="school">
    <h2 >姓名:{{name}}</h2>
    <h2 >年龄:{{age}}</h2>
    <h2 >性别:{{sex}}</h2>
    <button @click="sendStudnetName">用自定义事件发送名字</button>
    <button @click="unbind ">解绑自定义事件</button>
    
  </div>

</template>

<script>


export default {

    name:'Student',
    data(){
        return {
            name:'pyy',
            age:'24',
            sex:'men',
            a:'aaa'
        }
    },
   methods:{
      sendStudnetName(){
        //触发自定义事件pyy 并且传递一个参数
        this.$emit('pyy',this.name)
      },
      unbind(){
        //解绑自定义事件,一个事件直接写'事件名'  多个事件 写成数组 ['事件名','事件名'],不写参数则解绑全部自定义事件
        this.$off('pyy');
        console.log('pyy解绑成功')
      }
   }
   
}
</script>

<style>
.school{
    background-color: pink;
}
</style>

全局事件总线

任意组件之间通信

安装全局事件总线

new Vue({
  render: h => h(App),

  beforeCreate(){
    //安装安装全局事件总线
    Vue.prototype.$bus = this;
  }
}).$mount('#app')

使用事件总线

methods:{
    demo(data){
        ...
    }
}
    .....
mounted(){
    this.$bus.$on('pyy',this.demo)
}

提供数据

this.$bus.$emit('pyy',data)

将TodoList案例修改成全局事件总线方式

main.js

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

Vue.config.productionTip = false

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

App.vue

<template>
 <div >
  <div class="todo-container">
    <div class="todo-wrap">
     <MyHeader @addTodo="addTodo"></MyHeader>
     <MyList :todos="todos" ></MyList>
      <MyFooter :todos="todos" @checkedAll="checkedAll" @clearDoneTodo="clearDoneTodo"></MyFooter>
    </div>
  </div>
</div>
</template>

<script>

  import MyHeader from './components/MyHeader.vue'
  import MyFooter from './components/MyFooter.vue'
  import MyList from './components/MyList.vue'
import { watch } from 'vue'
 
export default {
    name:'App',
    data() {
        return {
          //从本地读取数据
            todos: JSON.parse(localStorage.getItem('todos'))  || [],
        }
    },
    components:{
       MyHeader,MyFooter,MyList
    },
    methods:{
      //添加方法:参数就是输入框所输入内容所在的对象
      addTodo(todoObj){
        //添加到集合的头部 
        this.todos.unshift(todoObj);
      },
      //修改方法,根据id去修改todo对象的done 
      changeTodo(id){
        //遍历todos集合,找到与参数id一直的对象
        this.todos.forEach((todo)=>{
          //将该对象的done值取反
          if(todo.id === id) todo.done = !todo.done
        })
      },
      //删除方法,根据id删掉改对象
      deleteTodo(id){
        //过滤一下,将符合条件的留下,形成一个新的集合,并赋值给todos
        this.todos = this.todos.filter((todo)=>{
            return todo.id !== id;
        })
      },
      //全选/全不选方法,参数就是,底部多选框的值
      checkedAll(value){
        //将参数赋值给数组中的每一个对象的done值
        this.todos.forEach((todo)=>{
          todo.done = value;
        })
      },
      //过滤掉已经完成的
      clearDoneTodo(){
        this.todos =  this.todos.filter((todo)=>{
          return !todo.done
        })
      }
    },
    watch:{
      todos:{
        deep:true,
        handler(value){
          //本地存储,(存储到硬盘)
          localStorage.setItem('todos',JSON.stringify(value))
        }
      }
    },
	mounted(){
		this.$bus.$on('changeTodo',this.changeTodo);
		this.$bus.$on('deleteTodo',this.deleteTodo)
	},
    //在销毁之前解绑,
	beforeDestroy(){
		this.$bus.$off('changeTodo');
		this.$bus.$off('deleteTodo');
	}
   
   

}
</script>

<style>
/*base*/
body {
  background: #fff;
}

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

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

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

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}







</style>

MyList.vue

<template>
   <ul class="todo-main">
       <MyItem 
       v-for="todoObj in todos" 
       :key="todoObj.id" 
       :todo="todoObj" >
       </MyItem>
       
      </ul>
</template>

<script>
import MyItem from './MyItem.vue'
export default {
    name:'MyList',
    
    components:{
        MyItem
    },
    props:['todos']
    

}
</script>

<style scoped>

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

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

MyItem.vue

<template>
   <li>
          <label>
            <input type="checkbox" :checked="todo.done" @change="changeDone(todo.id)"/>
            <span>{{todo.title}}</span>
          </label>
          <button class="btn btn-danger" @click="handleDelete(todo.id)" >删除</button>
        </li>
</template>

<script>
export default {
    name:'MyItem',
    data() {
        return {
            
        }
    },
    props:['todo'],
    methods:{
        //将修改的多选框对象的id作为参数
        changeDone(id){
            //使用全局事件总线
            // this.changeTodo(id);
            this.$bus.$emit('changeTodo',id);
        },
        //将删除的对象的id作为参数
        handleDelete(id){
            if(confirm('确定删除吗')){

                //this.deleteTodo(id);
                this.$bus.$emit('deleteTodo',id);
            }

        }
    }
}
</script>

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

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

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

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

li:before {
  content: initial;
  display: inline-block;
}

li:last-child {
  border-bottom: none;
}
li:hover {
    background-color: #dddddd;
}
li:hover button{
    display: block;
}
</style>

MyHeader.vue

<template>
   <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model.trim="title" @keyup.enter="show"/>
    </div>
</template>

<script>
import {nanoid} from 'nanoid'
export default {
    name:'MyHeader',
    data() {
        return {
            title:''
        }
    },
    
    methods:{
        show(){
            //检查输入内容
            if(this.title.trim() ==='' || this.title.trim() ==='null' || this.title.trim() ==='undefined') return alert("输入内容不能为空!null undefined ")
            //将输入内容装进对象
            const item = {id:nanoid(),title:this.title,done:false};
            //调用添加方法
            this.$emit('addTodo',item)
            //清空输入框中内容
            this.title='';
        }
    },
} 
</script>

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

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

MyFooter.vue

<template>
  <div class="todo-footer" v-show="total">
        <label>
          <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
          <span>已完成{{completed}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
      </div>
</template>

<script>
export default {
    name:'MyFooter',
    props:['todos'],
    computed:{
        total(){
            return this.todos.length
        },
        completed(){
            //循环遍历(第一个参数上一次循环返回的值,第二个参数,当前循环的对象)
            return this.todos.reduce((pre,todo)=>{
                //如果done为真,就加一
                return pre + (todo.done ? 1 : 0);
            },0)
        },
        //计算属性的set/get方法
        isAll:{
            get(){
                //返回值布尔类型,判断是否全选
                return this.total === this.completed && this.total > 0;
            },
            set(value){
                //调用更改全部多选框的方法,将底部多选框的值传递进去
                this.$emit('checkedAll',value)
            }

        }
    },
    methods:{
        //调用App的清除方法
        clearAll(){
            this.$emit('clearDoneTodo')
        }
    }
    
}
</script>

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

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

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

.todo-footer button {
  float: right;
  margin-top: 5px;
}
</style>

消息订阅与发布

导入

import pubsub from ‘pubsub-js’

谁想接收消息,谁就订阅:

methods:{

​ demo(name,data){两个参数,第一个名字(pyy)第二个是传过来的数据

​ }

}

mounted(){

​ 订阅消息,返回值是一个id,可以在销毁之前根据这个id取消订阅

​ this.pid = pubsub.subscribe(‘pyy’,this.demo)

}

发布消息(提供数据) : pubsub.publish(‘pyy’,数据)

取消订阅

pubsub.unsubscribe(this.pid );

给todoList案例添加编辑功能

(只修改了App.vue,MyItem.vue,MyHeader.vue)

在MyHeader.vue中只修改了一行,添加了isEdit属性,默认值是false

 //将输入内容装进对象
const item = {id:nanoid(),title:this.title,done:false,isEdit:false};

App.vue

<template>
 <div >
  <div class="todo-container">
    <div class="todo-wrap">
     <MyHeader @addTodo="addTodo"></MyHeader>
     <MyList :todos="todos" ></MyList>
      <MyFooter :todos="todos" @checkedAll="checkedAll" @clearDoneTodo="clearDoneTodo"></MyFooter>
    </div>
  </div>
</div>
</template>

<script>

  import MyHeader from './components/MyHeader.vue'
  import MyFooter from './components/MyFooter.vue'
  import MyList from './components/MyList.vue'
  import pubsub from 'pubsub-js'
 
export default {
    name:'App',
    data() {
        return {
          //从本地读取数据
            todos: JSON.parse(localStorage.getItem('todos'))  || [],
        }
    },
    components:{
       MyHeader,MyFooter,MyList
    },
    methods:{
      //添加方法:参数就是输入框所输入内容所在的对象
      addTodo(todoObj){
        //添加到集合的头部 
        this.todos.unshift(todoObj);
      },
      //修改方法,根据id去修改todo对象的done 
      changeTodo(id){
        //遍历todos集合,找到与参数id一直的对象
        this.todos.forEach((todo)=>{
          //将该对象的done值取反
          if(todo.id === id) todo.done = !todo.done
        })
      },
      //删除方法,根据id删掉改对象
      deleteTodo(_,id){
        //过滤一下,将符合条件的留下,形成一个新的集合,并赋值给todos
        this.todos = this.todos.filter((todo)=>{
            return todo.id !== id;
        })
      },
      //全选/全不选方法,参数就是,底部多选框的值
      checkedAll(value){
        //将参数赋值给数组中的每一个对象的done值
        this.todos.forEach((todo)=>{
          todo.done = value;
        })
      },
      //过滤掉已经完成的
      clearDoneTodo(){
        this.todos =  this.todos.filter((todo)=>{
          return !todo.done
        })
      },
	  //修改方法
	  updateTodo(newTodo,value){
		 this.todos.forEach((todo)=>{
          if(todo.id === newTodo.id){
			todo.title = value;
			return;
		  }
        })
	  }
	 
    },
    watch:{
      todos:{
        deep:true,
        handler(value){
          //本地存储,(存储到硬盘)
          localStorage.setItem('todos',JSON.stringify(value))
        }
      }
    },
	mounted(){
		this.$bus.$on('changeTodo',this.changeTodo);
		this.$bus.$on('deleteTodo',this.deleteTodo);
		this.$bus.$on('updateTodo',this.updateTodo);
		//使用消息订阅
		this.pubsubDeleteId = pubsub.subscribe('deleteItem',this.deleteTodo)
	},
	//在销毁之前解绑,
	beforeDestroy(){
		this.$bus.$off('changeTodo');
		this.$bus.$off('deleteTodo');
		this.$bus.$off('updateTodo');
		//取消订阅
		pubsub.unsubscribe(this.pubsubDeleteId);
	}
   
   

}
</script>

<style>
/*base*/
body {
  background: #fff;
}

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

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

.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}
.btn-edit {
  color: #fff;
  background-color: skyblue;
  border: 1px solid rgb(63, 137, 167);
  margin-right: 4px;
}

.btn-edit:hover {
  color: #fff;
  background-color: rgb(155, 214, 237);
}

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}







</style>

MyItem.vue

<template>
   <li>
          <label>
            <input type="checkbox" :checked="todo.done" @change="changeDone(todo.id)"/>
            <span v-show="!todo.isEdit">{{todo.title}}</span>
            <input type="text" 
            v-show="todo.isEdit"
            :value="todo.title"
            @blur="editEnd(todo,$event)"   
            @keyup.enter="editEnd(todo,$event)"
            ref="editInput"
            >

          </label>
          <button class="btn btn-danger" @click="handleDelete(todo.id)" >删除</button>
          <button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑</button>
        </li>
</template>

<script>
  import pubsub from 'pubsub-js'

export default {
    name:'MyItem',
    data() {
        return {
            
        }
    },
    props:['todo'],
    methods:{
        //将修改的多选框对象的id作为参数
        changeDone(id){
            //使用全局事件总线
            // this.changeTodo(id);
            this.$bus.$emit('changeTodo',id);
        },
        //将删除的对象的id作为参数
        handleDelete(id){
            if(confirm('确定删除吗')){

                //this.deleteTodo(id);
                //this.$bus.$emit('deleteTodo',id);
                pubsub.publish('deleteItem',id);
                
            }

        },
        //编辑按钮触发方法
        handleEdit(todo){
          todo.isEdit = true;
          //nextTick  下一轮在执行里面的内容
          //自动获取焦点,如果不写这个,则第一轮都执行完了聚焦,再解析模板,那么聚焦就没有用了。
          //等一轮,在聚焦。
          this.$nextTick(function(){
            this.$refs.editInput.focus();
          })
        },
        //修改完毕 按回车或者失去焦点时触发
        editEnd(todo,e){
          todo.isEdit = false;
          //如果修改内容为空,则弹窗
          if(!e.target.value.trim()) return alert('not null');
          this.$bus.$emit('updateTodo',todo,e.target.value)

        }
       
    },
    
}
</script>

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

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

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

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

li:before {
  content: initial;
  display: inline-block;
}

li:last-child {
  border-bottom: none;
}
li:hover {
    background-color: #dddddd;
}
li:hover button{
    display: block;
}
</style>

动画效果

作用:操作DOM元素的时候,在合适的时机给元素添加样式

App.vue

<template>
 <div >
	<Test></Test>
</div>
</template>
<script>
	import Test from './components/Test.vue'
	import 'animate.css'
export default {
    name:'App',
    components:{
       Test
    },
}
</script>
<style>
</style>

Test.vue

<template>
  <div>
	<button @click="isShow = !isShow">显示/隐藏</button>
    <!-- 使用 transition 标签包裹要过渡的元素,,多个标签用transition-group 并且每个元素都要指定key值 -->
    <transition-group 
    appear
    name="animate__animated animate__bounce" 
    enter-active-class="animate__bounceIn"
    leave-active-class="animate__bounceOut"
    >
        <h1 key="1" v-show="isShow">hello</h1>
        <!-- <h1 key="2" v-show="!isShow">bye</h1> -->
    </transition-group>
  </div>
</template>
<script>
export default {
    name:'Test',
    data(){
        return{
            isShow:true
        }
    }
}
</script>

<style>
h1{
    background-color: pink;
}
</style>

配置代理

vue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false, /*关闭语法检查*/
 
  devServer:{
    proxy:{
      '/pyy':{//匹配/pyy开头的路径
        target:'http://localhost:5000',
        pathRewrite:{'^/pyy':''},//请求时真实路径没有这个/pyy,将他去掉
        changeOrigin:true//默认为true,是否开启 圆滑模式(狗头)
      },
      '/pj':{//匹配/pyy开头的路径
        target:'http://localhost:5001',
        pathRewrite:{'^/pj':''},//请求时真实路径没有这个/pyy,将他去掉
        changeOrigin:true//默认为true,是否开启 圆滑模式(狗头)
      }
    }
    
  }
})

App.vue

<template>
 <div >
    <button @click="getStudent">getStudentInfo</button>
    <button @click="getCar">getCarInfo</button>
	
</div>
</template>

<script>

	import axios from 'axios'
 
export default {
    name:'App',
   methods:{
	getStudent(){
		axios.get('http://localhost:8080/pyy/students').then(
			response=>{
				console.log('请求成功',response.data);
			},
			error=>{
				console.log('请求失败',error.message);

			},
		
		)
	},
	getCar(){
		axios.get('http://localhost:8080/pj/cars').then(
			response=>{
				console.log('请求成功',response.data);
			},
			error=>{
				console.log('请求失败',error.message);

			},
		
		)
	},
   }
}
</script>

github案例

输入信息,去搜索github上的用户。

main.js

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

Vue.config.productionTip = false

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

App.vue

<template>
   <div class="container">
    <Search></Search>
    <List></List>
    
  </div>
</template>

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

<style>


</style>

Search.vue

<template>
    <section class="jumbotron">
      <h3 class="jumbotron-heading">Search Github Users</h3>
      <div>
        <input type="text" placeholder="enter the name you search" v-model="message"/>&nbsp;<button @click="Search">Search</button>
      </div>
    </section>
</template>

<script>
import axios from 'axios'
export default {
    name:'Search',
    data(){
        return{
            message:''
        }
    },
    methods:{
        Search(){
            //将一个对象传递过去
            this.$bus.$emit('searchUsers',{users:[],errMsg:'',isFirst:false,isLoading:true})
            axios.get(`https://api.github.com/search/users?q=${this.message}`).then(
			response=>{
				//请求成功
            this.$bus.$emit('searchUsers',{users:response.data.items,isLoading:false})

			},
			error=>{
				//请求失败
            this.$bus.$emit('searchUsers',{users:[].data.items,errMsg:error.message,isLoading:false})

			},
		
		)
        }
    }
}
</script>

<style>

</style>

List.vue

<template>
  <div class="row">
    <!-- 此处的a标签的href属性和img标签的src属性一定要动态绑定 -->
      <div class="card" v-show="info.users.length" v-for="user in info.users" :key="user.login">
        <a :href="user.html_url" target="_blank">
          <img :src="user.avatar_url" style='width: 100px'/>
        </a>
        <p class="card-text">{{user.login}}</p>
      </div>

      <h2 v-show="info.isFirst">welcom use</h2>
      <h2 v-show="info.isLoading">loading......</h2>
      <h2 v-show="info.errMsg">{{info.errMsg}}</h2>
    </div>
</template>

<script>
export default {
    name:'List',
    data(){
        return{
            info:{
                users:[],
                isFirst:true,
                isLoading:false,
                errMsg:''
            }
        }
    },
    methods:{
        SearchUsers(infoObj){
            //合并同类项,,将两个名字相同的项第二个覆盖第一个,如果第二个中没有第一个的某一项,则它不变
            this.info = {...this.info,...infoObj}
        }
    },
    mounted(){
        this.$bus.$on('searchUsers',this.SearchUsers)
    },
    beforeDestroy(){
        this.$bus.$off('searchUsers')
    }
}
</script>

<style>
.album {
  min-height: 50rem; /* Can be removed; just added for demo purposes */
  padding-top: 3rem;
  padding-bottom: 3rem;
  background-color: #f7f7f7;
}

.card {
  float: left;
  width: 33.333%;
  padding: .75rem;
  margin-bottom: 2rem;
  border: 1px solid #efefef;
  text-align: center;
}

.card > img {
  margin-bottom: .75rem;
  border-radius: 100px;
}

.card-text {
  font-size: 85%;
}
</style>

插槽

默认插槽

App.vue

<template>
   <div class="container">
   <Category title="游戏">
    <!-- 组件标签里面的东西去填坑 -->
    <ul>
      <li v-for="(game,index) in games" :key="index">
        {{game}}
      </li>
    </ul>
   </Category>
   <Category title="手机">
    <img src="https://www.apple.com.cn/v/iphone-14-pro/c/images/overview/hero/hero_endframe__dtzvajyextyu_large.jpg" alt="">
   </Category>
   <Category title="电影">
    <video controls src="https://www.bilibili.com/video/BV1Ds4y157xB?t=2.4"></video>
   </Category>
    
  </div>
</template>

<script>
import Category from './components/Category.vue'
export default {
  name: "App",
  components:{
    Category
  },
  data(){
      return {
        games:['GTA5','20777','PUBG','SEKIO'],
        phones:['apple','xiuaomi','HUAWEI','OPPO'],
        movies:['肖申克的救赎','蜘蛛侠','蜘蛛侠2','蜘蛛侠3']

      }
    }
};
</script>

<style>

.container{
  display: flex;
  justify-content: space-around;

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

Category.vue

<template>

    <div class="category">
        <h3>{{title}}</h3>
        <!-- 默认插槽  在这个地方挖个坑 -->
        <slot>商品信息</slot>
    </div>  
</template>

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

<style>
.category{
    background-color: pink;
    width: 200px;
    height: 300px;
}
h3{
    text-align: center;
    background-color: skyblue;
}
</style>

具名插槽

App.vue

<template>
  <div class="container">
    <Category title="游戏">
      <!-- 组件标签里面的东西去填坑 -->
      <!-- slot用来和坑的名字对应,对应上了就去填坑 -->
      <ul slot="conter">
        <li v-for="(game, index) in games" :key="index">
          {{ game }}
        </li>
      </ul>
      <a slot="footer" href="https://www.wegame.com.cn/" target="_blank"
        >wegame商店</a
      >
    </Category>
    <Category title="手机">
      <img
        slot="conter"
        src="https://www.apple.com.cn/v/iphone-14-pro/c/images/overview/hero/hero_endframe__dtzvajyextyu_large.jpg"
        alt=""
      />
      <div class="foot" slot="footer">
        <a href="https://www.apple.com.cn/" target="_blank">apple</a>
        <a href="https://www.huawei.com/" target="_blank">HUAWEI</a>
      </div>
    </Category>
    <Category title="电影">
      <video
        slot="conter"
        controls
        src="https://www.bilibili.com/video/BV1Ds4y157xB?t=2.4"
      ></video>
      <!-- v-slot:footer  这个东西只适用于 template标签-->
      <template v-slot:footer >
        <div class="foot">

      <a  href="https://www.bilibili.com/video/BV1Ds4y157xB?t=2.4" target="_blank"
        >观看完整版</a
      >
       <a  href="https://space.bilibili.com/9064879/?spm_id_from=333.999.0.0" target="_blank"
        >进入up主页</a
      >
        </div>
      <h4>紫雨carol</h4>

      </template>

    </Category>
  </div>
</template>

<script>
import Category from "./components/Category.vue";
export default {
  name: "App",
  components: {
    Category,
  },
  data() {
    return {
      games: ["GTA5", "20777", "PUBG", "SEKIO"],
      phones: ["apple", "xiuaomi", "HUAWEI", "OPPO"],
      movies: ["肖申克的救赎", "蜘蛛侠", "蜘蛛侠2", "蜘蛛侠3"],
    };
  },
};
</script>

<style>
a {
  text-decoration: none;
  color: black;
}

.container,
.foot {
  display: flex;
  justify-content: space-around;
}
img {
  width: 100%;
}
video {
  width: 100%;
}
h4{
  text-align: center;
}
</style>

Category.vue

<template>

    <div class="category">
        <h3>{{title}}</h3>
        <!-- 具名插槽  在这个地方挖个坑,起个名   -->
        <slot name="conter">商品信息</slot>
        <slot name="footer">商品信息</slot>
    </div>  
</template>

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

<style>
.category{
    background-color: pink;
    width: 300px;
    height: 400px;
}
h3{
    text-align: center;
    background-color: skyblue;
}
</style>

作用域插槽

App.vue

<template>
  <div class="container">
    <Category title="游戏">
      <!-- 组件标签里面的东西去填坑 -->
      <!-- slot-scope="{games} 传过来的是一个对象  解构赋值,用来接收坑里的数据  -->
      <template slot-scope="{games}">
       <ul >
        <li v-for="(game, index) in games" :key="index">
          {{ game }}
        </li>
      </ul>
      </template>
      
    
    </Category>
    <Category title="游戏">
       <template slot-scope="{games}">
       <ol >
        <li v-for="(game, index) in games" :key="index">
          {{ game }}
        </li>
      </ol>
      </template>
    </Category>
    <Category title="游戏">
       <template slot-scope="{games}">
        <h4 v-for="(game, index) in games" :key="index">
          {{ game }}
        </h4>
      </template>
    </Category>
  </div>
</template>

<script>
import Category from "./components/Category.vue";
export default {
  name: "App",
  components: {
    Category,
  },
 
};
</script>

<style>
a {
  text-decoration: none;
  color: black;
}

.container,
.foot {
  display: flex;
  justify-content: space-around;
}
img {
  width: 100%;
}
video {
  width: 100%;
}
h4{
  text-align: center;
}
</style>

Category.vue

<template>

    <div class="category">
        <h3>{{title}}</h3>
        <!-- 作用域插槽  在这个地方挖个坑  数据也在这个坑里,谁填这个坑,用我的数据就行 -->
        <slot :games="games">商品信息</slot>
        
    </div>  
</template>

<script>
export default {
    name:'Category',
    props:['title'],
     data() {
    return {
      games: ["GTA5", "20777", "PUBG", "SEKIO"],
      phones: ["apple", "xiuaomi", "HUAWEI", "OPPO"],
      movies: ["肖申克的救赎", "蜘蛛侠", "蜘蛛侠2", "蜘蛛侠3"],
    };
  },
}
</script>

<style>
.category{
    background-color: pink;
    width: 300px;
    height: 400px;
}
h3{
    text-align: center;
    background-color: skyblue;
}
</style>

VueX

专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理,适用于任意组件间通信。

vuex环境搭建

main.js

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

index.js

import Vue from 'vue'
//引入插件
import Vuex from 'vuex'
//使用插件
Vue.use(Vuex)
//用于存储数据
const state ={}
//用于操作数据
const mutations ={}
//用户相应组件中的动作
const actions ={}
//创建并导出vuex
export default new Vuex.Store({
    state,mutations,actions
})

Vuex案例

App.vue

<template>
  <div class="container">
    <Count></Count>
  </div>
</template>

<script>
import Count from './components/Count.vue'
export default {
  name: "App",
  components: {
    Count,
  },
  mounted(){
    console.log(this)
  }
 
};
</script>

<style>

</style>

Count.vue

<template>
  <div>
    <!-- 获取到index.js中states里面的sum属性的值 -->
    <h2>当前的值是:{{$store.state.sum}}</h2>

    <select v-model.number="num">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="addition">加</button>
    <button @click="subtraction">减</button>
    <button @click="oddAdd">奇数加</button>
    <button @click="waitforHasband">等一下,我老公呢</button>
  </div>
</template>

<script>
export default {
  name: "Count",
  data() {
    return {
      num: 1,
    };
  },
  methods: {
    addition() {
        //可以通过commit直接调用mutations,略过actions
      this.$store.commit("ADD", this.num);
    },
    subtraction() {
      this.$store.commit("SUB", this.num);

    },
    oddAdd() {
        //通过dispatch调用Action,方法名为add 参数是n

        this.$store.dispatch("odd", this.num);
        
    },
    waitforHasband() {
        
      this.$store.dispatch("wait", this.num);

    },

  },
};
</script>

<style>
</style>

index.js


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

//用于存储数据
const state = {
    //声明一个属性,默认值为0
    sum: 0
}
//用户相应组件中的动作
const actions = {
    //接收dispatch的调用,接收两个参数,第一个是阉割版的stroe,第二个是传过来的值
    odd(context, value) {
        if (context.state.sum % 2) {
            context.commit('ADD', value);
        }
    },
    wait(context, value) {
        //调用commit,将数据传递给mutations
        setTimeout(() => {
            context.commit('ADD', value);
        }, 500)

    },

}
//用于操作数据
const mutations = {
    //用于接收commit传过来的值,第一个参数是对象,包含了states里面声明的属性,第二个参数是传递过来的值
    ADD(state, value) {
        //修改states里面的sum属性
        state.sum += value;
    },
    SUB(state, value) {
        state.sum -= value;

    },

}
//创建并导出vuex
//创建并导出vuex
export default new Vuex.Store({
    state, mutations, actions
})

当state中的数据需要进行加工时,可以使用getters

const getters = {
    bigSum(state){
        return state.sum;
    }
}
//创建并导出vuex
//创建并导出vuex
export default new Vuex.Store({
    state, mutations, actions,getters
})

在组件中读取数据时:

$store.getters.bigSum

mapState、mapGetters、mapActions、mapMutations

修改上方案例写法用这四个去写,只修改了一个文件

Count.vue

<template>
  <div>
    <!-- 获取到index.js中states里面的sum属性的值 -->
    <h2>当前的值是:{{sum}}</h2>
    <h2>当前的值的九倍是:{{bigSum}}</h2>
    <h2>我是谁?{{name}}</h2>
    <h2>我在那?{{address}}</h2>

    <select v-model.number="num">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="addition(num)">加</button>
    <button @click="subtraction(num)">减</button>
    <button @click="oddAdd(num)">奇数加</button>
    <button @click="waitforHasband(num)">等一下,我老公呢</button>
  </div>
</template>

<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
  name: "Count",
  data() {
    return {
      num: 1,
    };
  },
  methods: {
    // addition() {
    //     //可以通过commit直接调用mutations,略过actions
    //   this.$store.commit("ADD", this.num);
    // },
    // subtraction() {
    //   this.$store.commit("SUB", this.num);

    // },
     //借助mapMutations向mutations里面传递,要在组件里这么写addition(参数) 
     //参数为对象
    ...mapMutations({addition:'ADD',subtraction:'SUB'}),
     //参数为数组
    //...mapMutations(['ADD','SUB']),
    // oddAdd() {
    //     //通过dispatch调用Action,方法名为add 参数是n

    //     this.$store.dispatch("odd", this.num);
        
    // },
    // waitforHasband() {
        
    //   this.$store.dispatch("wait", this.num);

    // },
     //借助mapActions向actions里面传递,要在组件里这么写addition(参数) 参数为对象
    ...mapActions({oddAdd:'odd',waitforHasband:'wait'})
    //数组为参数的写法,前后名字相同才可以这么写
    //...mapActions(['odd','wait'])


  },
  computed:{
      //因为返回值是一个对象,computed也是一个对象,对象里不能有对象,所以用...将里面内容摊开。
    //借助mapState从state中获取数据,参数为对象
    //...mapState({sum:'sum',name:'name',address:'address'}),

    //借助mapState从state中获取数据,参数为数组(state里面的属性名和组件里面的属性名相同时才能这么写)
    ...mapState(['sum','name','address']),
    //借助mapGetters从state中获取数据,参数为对象
    //...mapGetters({bigSum:'bigSum'}),
     //借助mapGetters从state中获取数据,参数为对象
    ...mapGetters(['bigSum'])
  }
};
</script>

<style>
</style>

多组件共享数据

模块化+命名空间

index.js


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

//模块化,将数据管理分成一块一块
const CountStore = {
    //开启命名空间
    namespaced:true,
    state:{
        sum: 0,
        name:'pyy',
        address:'earth',
    },
    actions:{
         //接收dispatch的调用,接收两个参数,第一个是阉割版的stroe,第二个是传过来的值
    odd(context, value) {
        if (context.state.sum % 2) {
            context.commit('ADD', value);
        }
    },
    wait(context, value) {
        //调用commit,将数据传递给mutations
        setTimeout(() => {
            context.commit('ADD', value);
        }, 500)

    },
    },
    mutations:{
         //用于接收commit传过来的值,第一个参数是对象,包含了states里面声明的属性,第二个参数是传递过来的值
    ADD(state, value) {
        //修改states里面的sum属性
        state.sum += value;
    },
    SUB(state, value) {
        state.sum -= value;

    },
    },
    getters:{
        bigSum(state){
            return state.sum * 9;
        }
    },

}
const PersonStore = {
    namespaced:true,

    state:{
        dogs:[{id:'1001',name:'张三'},{id:'1002',name:'李四'}]
    },
    actions:{
        addPang(context,value){
            if(value.name.indexOf('庞') === 0){
                context.commit('ADD_DOG',value)
            }else{
                alert("你不姓庞,滚")
            }
        }
    },
    mutations:{
        ADD_DOG(state,value){
            state.dogs.unshift(value);
        }
    },
    getters:{
        firstNameP(state){
            return state.dogs[0].name
        }
    },

}





//创建并导出vuex
//创建并导出vuex
export default new Vuex.Store({
    //模块化引入方式,一定要写在moudules里面 
    modules:{
        countAbout:CountStore, 
        personAbout:PersonStore
    }
})

App.vue

<template>
  <div class="container">
    <Count></Count>
    <hr>

    <Person></Person>
  </div>
</template>

<script>
import Count from './components/Count.vue'
import Person from './components/Person.vue'
export default {
  name: "App",
  components: {
    Count,Person
  },
  mounted(){
    console.log(this)
  }
 
};
</script>

<style>

</style>

Count.vue

<template>
  <div>
    <!-- 获取到index.js中states里面的sum属性的值 -->
    <h2>当前的值是:{{sum}}</h2>
    <h2>当前的值的九倍是:{{bigSum}}</h2>
    <h2>我是谁?{{dogs[i].name}}</h2>
    <button @click="(i=(i+1)%dogs.length)">下一位</button>
    <h2>我在那?{{address}}</h2>

    <select v-model.number="num">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="addition(num)">加</button>
    <button @click="subtraction(num)">减</button>
    <button @click="oddAdd(num)">奇数加</button>
    <button @click="waitforHasband(num)">等一下,我老公呢</button>
  </div>
</template>

<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
  name: "Count",
  data() {
    return {
      num: 1,
      i:0
    };
  },
  methods: {
     //命名空间开启后第一个参数就是在配置moudles里的模块的名字
    ...mapMutations('countAbout',{addition:'ADD',subtraction:'SUB'}),
    ...mapActions('countAbout',{oddAdd:'odd',waitforHasband:'wait'})
  },
  computed:{
    ...mapState('countAbout',['sum','name','address']),
    ...mapState('personAbout',['dogs']),
    ...mapGetters('countAbout',['bigSum'])
  }
};
</script>
<style>
</style>

Person.vue

<template>
  <div>
    <h2>备胎列表</h2>
    <h4>Count组件求和:{{sum}}</h4>
    <h4>下一个舔狗是:{{firstNameP}}</h4>

    <input type="text" placeholder="请输入名字" v-model="dogName">
    <button @click="addDog">添加备胎</button>
    <button @click="addDogP">你姓庞吗?</button>
    <ul>
        <li v-for="dog in dogs" :key="dog.id">
            {{dog.name}}
        </li>
    </ul>
  </div>
</template>

<script>
import {mapState,mapActions,mapGetters,mapMutations} from 'vuex'
import {nanoid} from 'nanoid'
export default {
    name:'Person',
    data(){
        return{
            dogName:'',
        }
    },
    computed:{

        ...mapState('personAbout',['dogs']),
        ...mapState('countAbout',['sum']),
        ...mapGetters('personAbout',['firstNameP'])
    },
    methods:{
    addDog(){
        const dog = {id:nanoid(),name:this.dogName};
        this.$store.commit('personAbout/ADD_DOG',dog);
        this.dogName = '';
    },
    addDogP(){
        const dog = {id:nanoid(),name:this.dogName};
        this.$store.dispatch('personAbout/addPang',dog);
        this.dogName = '';
    }
    }
}
</script>

<style>

</style>

路由

路由的基本使用

通常情况下路由组件放在pages文件夹下,一般组件放在components文件夹下

main.js

//引入Vue
import Vue from 'vue'
import VueRouter from 'vue-router'
//引入App
import App from './App.vue'
import router from './router/index.js'
Vue.config.productionTip = false

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

index.js


import VueRouter from 'vue-router'

import Home from '../pages/Home.vue'
import About from '../pages/About.vue'

export default new VueRouter({
    routes:[
        {
            path:'/about',
            component:About
        },
        {
            path:'/home',
            component:Home
        }

    ]
})

App.vue

<template>
  <div>
    <div class="row">
      <Banner></Banner>
    </div>
    <div class="row">
      <div class="col-xs-2 col-xs-offset-2">
        <div class="list-group">
          <!-- <a class="list-group-item" href="./about.html">About</a>
          <a class="list-group-item active" href="./home.html">Home</a> -->
          <!-- 用 router-link标签 实现路由的切换,to 目标路由-->
          <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.vue'
export default {
  name: "App",
  components: {
    Banner
  },
 
 
};
</script>

<style>

</style>

About.vue

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

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

<style>

</style>

Home.vue

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

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

<style>

</style>

Banner.vue

<template>
  <div class="col-xs-offset-2 col-xs-8">
        <div class="page-header"><h2>Vue Router Demo</h2></div>
      </div>
</template>

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

<style>

</style>

嵌套路由和路由的query参数

export default new VueRouter({
    routes:[
        {
            path:'/about',
            component:About
        },
        {
            path:'/home',
            component:Home,
            //二级路由,一个路由下的子路由
            children:[
                {
                    //直接写名字就行,自动加/home/
                    path:'message',
                    component:Message,
                    children:[
                        {
                            path:'detail',
                            component:Detail
                        }
                    ]
                   
                },
                {
                    path:'news',
                    component:News
                },
            ]
        },
        

    ]
})

Message.vue

<template>
  <div>
        <ul>
          <li v-for="m in messages" :key="m.id">
            <!-- 传递参数,query内容就是参数 path是路径 -->
            <router-link :to="{
              path:'/home/message/detail',
              query:{
                id:m.id,
                title:m.title
              }

            }">{{m.title}}</router-link>&nbsp;&nbsp;
            </li>

        </ul>
        <hr>
        <router-view></router-view>
      </div>
</template>

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

<style>

</style>

Detail.vue

<template>
  <ul>
    <!-- 接收参数 -->
    <li>消息编号:{{$route.query.id}}</li>
    <li>消息标题:{{$route.query.title}}</li>
  </ul>
</template>

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

<style>

</style>

命名路由

简化跳转

{
                    //配置项名称
                    name:'pyy',
                    component:Message,
                    children:[
                        {
                            path:'detail',
                            component:Detail
                        }
                    ]
                   
                },
 <router-link :to="{
               //这里就可以直接写名称了不用再写    xx/xxx/xx
              name:'pyy',
              query:{
                id:m.id,
                title:m.title
              }

            }">{{m.title}}</router-link>

params参数

{
                    //配置项名称
                    name:'pyy',
                    component:Message,
                    children:[
                        {
                            name:'pj',
                            path:'detail/:id/:title',//使用占位符声明接收params参数
                            component:Detail
                        }
                    ]
                   
                },

两种写法

<router-link :to="/home/message/detail/${m.id}/${m.title}">{{m.title}}</router-link>
<router-link :to="{
               //使用params时,这里一定要写name属性,不能写path
              name:'pyy',
              params:{
                id:m.id,
                title:m.title
              }

            }">{{m.title}}</router-link>

接收参数

 <li>消息编号:{{$route.params.id}}</li>

props配置

让路由组件更加方便的收到参数。

 {
                    //直接写名字就行,自动加/home/
                    path:'message',
                    component:Message,
                    children:[
                        {
                            path:'detail',
                            component:Detail,
                            //第一种写法,对象写法,通过key-value的组合传递
                            //props:{a:'pyy'}
                            //第二种写法,布尔值,接收到所有params参数
                            //props:true,
                            //第三种写法,函数,通过$route 取到数据通过ey-value的组合传递
                            //这里的参数route,接收到的就是$route里面就有数据
                            props(route){
                                return{
                                    id:route.query.id,
                                    title:route.query.title
                                }
                            }
                        }
                    ]
                   
                },

编程式路由导航

不需要这个标签了

 methods: {
    //push的方法,浏览器会记录每一次访问的地址,可以依次退回查看
    pushShow(m) {
      console.log(m)
      this.$router.push({
        name: "ddd",
        query: {
          id:m.id,
          title:m.title,
        },
      });
    },
    //replace的方法,浏览器只会记录最近一次的访问地址,之前的都被覆盖了。
    replaceShow(m) {
      this.$router.replace({
        name: "ddd",
        query: {
          id: m.id,
          title: m.title,
        },
      });
    },
  },

浏览器前进后退

 methods:{
      //后退一步
      back(){
        this.$router.back();
      },
      //前进一步
      forward(){
        this.$router.forward();
      },
      //
      gogogo(x){
        //参数为数字,正数就是前进几步,负数就是后退几步。
        this.$router.go(x);
      }


    }

缓存路由组件

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

被这个标签包裹的就会保持活力。

include内容填写组件名,就是只对这个组件保持活力

<!--缓存一个-->
<keep-alive include="News">
        <router-view></router-view>
      </keep-alive>

<!--缓存多个-->
<keep-alive :include="['News','Message']">
      
        <router-view></router-view>
      </keep-alive>

路由的独有的声明周期

activated被激活时触发

deactivated 失活时触发

 activated(){
        this.timer = setInterval(()=>{
            console.log("闪")
            this.opacity-=0.01
            if(this.opacity<=0) this.opacity = 1

        },16)
    },
    deactivated(){
        clearInterval(this.timer)
    }

全局前置路由守卫


const router =  new VueRouter({
    routes:[
        {
            name:'gy',
            path:'/about',
            component:About
        },
        {
            name:'jia',
            path:'/home',
            component:Home,
            //二级路由,一个路由下的子路由
            children:[
                {
                    //直接写名字就行,自动加/home/
                    name:'xx',
                    path:'message',
                    component:Message,
                    meta:{isAuth:true},
                    children:[
                        {
                            name:'ddd',
                            path:'detail',
                            component:Detail,
                            //第一种写法,对象写法,通过key-value的组合传递
                            //props:{a:'pyy'}
                            //第二种写法,布尔值,接收到所有params参数
                            //props:true,
                            //第三种写法,函数,通过$route 取到数据通过ey-value的组合传递
                            //这里的参数route,接收到的就是$route里面就有数据
                            props(route){
                                return{
                                    id:route.query.id,
                                    title:route.query.title
                                }
                            }
                        }
                    ]
                   
                },
                {
                    name:'xw',
                    path:'news',
                    component:News,
                    meta:{isAuth:true},//配置自定义属性,在meta里面可以自定义属性,
                },
            ]
        },
        

    ]
})
//前置守卫(大树守卫),进去之前我先检查一下子。一开始调用一次 
//第一个参数,到哪里去
//第二个参数,从哪里来
//第三个参数,让不让过去
router.beforeEach((to,from,next)=>{
    //是否需要校验一下  name就是路由配置时的name属性
    if(to.meta.isAuth){
        if(localStorage.getItem('name') === 'pyy'){
            next()
        }else{
            alert('你的名字不对')
        }
    }
    //其他模块随便进
    else{
        next()
    }
})



export default router;

后置路由守卫

//后置守卫,进去之后触发
router.afterEach((to,from)=>{
    //进去之后改变网页标题
    document.title = to.meta.title || '庞杰'
})

独享路由守卫

{
                    name: 'xw',
                    path: 'news',
                    component: News,
                    meta: { isAuth: true, title: '新闻' },//配置自定义属性,在meta里面可以自定义属性,
                    //独享路由守卫,只在进入这个路由组件时触发
                    beforeEnter: (to, from, next) => {
                        //是否需要校验一下  name就是路由配置时的name属性
                        if (to.meta.isAuth) {
                            if (localStorage.getItem('name') === 'pyy') {
                                next()
                            } else {
                                alert('你的名字不对')
                            }
                        }
                        //其他模块随便进
                        else {
                            next()
                        }
                    }
                },

组件内路由守卫

在组件内写的方法。

export default {
    name:'About',
//进入守卫,通过路由规则进入该组件才会被调用
    beforeRouteEnter(to,from,next){

    },
//离开守卫,通过路由规则离开该组件才会被调用
    beforeRouteLeave (to, from, next) {
      // ...
    }
}

history模式hash模式

hash模式:带着#,不美观,兼容性好

history模式:地址干净,美观,兼容性略差,部署上线后需要后端配合解决404问题

Vue3

main.js

import { createApp } from 'vue'
import App from './App.vue'

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

setup(拉开序幕,vue3新的配置项)

所有组合API的表演舞台

组件中所用到的数据,方法等等,都要配置在setup中

若返回一个对象,则对象中的属性,方法,在模板中都可以直接用

setip不能是async函数,因为返回值不再是return的对象,而是Promise,模板看不到rerurn对象中的属性了!!!

但是如果需要返回一个Prommise实例时,可以配合Suspense和异步组件使用

初始vue3

App.vue

<template>
  <h1>vue3</h1>
  <h2>who am i? {{name}}</h2>
  <h2>i am old? {{age}}</h2>
  <button @click="sayHello" >speack</button>
</template>

<script>

export default {
  name: 'App',

  setup(){
    //声明属性
    let name = 'pyy';
    let age = 24;
    //函数
    function sayHello(){
      alert('HelloWord')
    }
    //将属性和函数返回出去,模板才能用
    return {name,age,sayHello}

 }
}
</script>

<style>

</style>

ref函数

作用:定义一个响应式数据

App.vue

<template>
  <h1>vue3</h1>
  <h2>who am i? {{name}}</h2>
  <h2>i am old? {{age}}</h2>
  <h2>my wife? {{wife.name}}   {{wife.age}}</h2>
  <button @click="sayHello" >speack</button>
  <button @click="changeWife" >change</button>
</template>

<script>

  import {ref} from 'vue'
export default {
  name: 'App',

  setup(){
    //声明属性  
    //使用ref函数声明属性时,会返回一个RefImpl对象,里面有个value属性,
    let name = ref('pyy');
    let age = ref(24);
    //使用ref函数声明对象时,会返回一个RefImpl对象,里面有个value属性,value属性为一个Proxy对象,该对象里包含的就是声明对象中的内容。
    let wife = ref({name:'zq',age:24});
    //函数
    function sayHello(){
      alert('HelloWord')
    }
    function changeWife(){
      //修改name属性值,需要修改其RefImpl对象内的value属性,才能使其打到响应式
      // name.value = 'pj';
      // console.log(name)

      //修改对象内部属性时,需要修改其RefImpl对象内的value属性(Proxy对象)下的内部属性,才能使其完成响应式修改
      wife.value.name = 'zqq'
      console.log(wife.value)
    }
    //将属性和函数返回出去,模板才能用
    return {name,age,sayHello,wife,changeWife}

 }
}
</script>

<style>

</style>

reactive函数

作用:定义一个对象类型的响应式数据(基本类型不要用这个,还是用ref)

他可以是深层次的,就是可以一层套一层。

App.vue

<template>
  <h1>vue3</h1>
  <h2>who am i? {{name}}</h2>
  <h2>i am old? {{age}}</h2>
  <h2>hobby {{hobby}}</h2>
  <h2>my wife? {{wife.name}}   {{wife.age}}</h2>
  <button @click="sayHello" >speack</button>
  <button @click="changeWife" >change</button>
</template>

<script>

  import {ref, reactive} from 'vue' 
  export default {
  name: 'App',

  setup(){
    //声明属性  
    //使用ref函数声明属性时,会返回一个RefImpl对象,里面有个value属性,
    let name = ref('pyy');
    let age = ref(24);
    //使用reactive函数声明对象时,直接是一个Proxy对象,该对象里包含的就是声明对象中的内容。
    let wife = reactive({name:'zq',age:24});

    let hobby = reactive(['eat','play','study']);
    //函数
    function sayHello(){
      alert('HelloWord')
    }
    function changeWife(){
      //直接修改其内部属性
      wife.name = 'zqq';
      console.log(wife);
      //直接通过数组索引修改值
      hobby[2] = 'watch'

    }
    //将属性和函数返回出去,模板才能用
    return {name,age,sayHello,wife,changeWife,hobby}

 }
}
</script>

<style>

</style>

vue3的响应式

原理:

通过Proxy(代理)拦截对象中任意属性的变化,包括:属性值的读写,属性的添加,删除

通过Reflect(反射):对被代理的属性进行操作。

setip注意点:

1,在beforecreate生命周期之前执行

2,两个参数(props,context)

props:值为对象,组件外部传递进来并且组件内部声明接收的属性

context:上下文对象其包含三个关键字:

(1):attrs 值为对象,包含组件传递过来的但是没有在props配置中声明的属性,就会被流放至此。

(2):slots:接收到的插槽内容。

(3):emit 分发自定义事件的函数,

Demo.vue

<template>
  <h2>who am i? {{ person.name }}</h2>
  <h2>i am old? {{ person.age }}</h2>
  <button @click="hi">hi</button>
</template>

<script>
import { reactive } from "vue";
export default {
  name: "Demo",
  //接收父类的值
  props: ["msg", "school"],
  emits:['hello'],
  //props参数是一个Proxy代理对象。里面就是父组件传递过来的值   Proxy(Object) {msg: 'pyy', school: 'zhonghuan'}
  //context上下文包含接收自定义事件的关键字 emit    兜底的  attrs    插槽 slots
  setup(props, context) {
    let person = reactive(
      {
        name: "张三",
        age: 44,
      },
    );
    //
      function hi() {
        context.emit("hello", 666);
      }
    console.log(props);
    console.log(context.attrs);
    console.log(context.emit);//自定义事件
    console.log(context.slots);//父组件的插槽

    return {
      person,
      hi,
    };
  },
};
</script>

<style>
</style>

App.vue

<template>
  
  <Demo @hello="say" msg='pyy' school='zhonghuan'>
    <template v-slot:qwer>
      <span >
        1233
      </span>
    </template>
  </Demo>
</template>

<script>

  import Demo from './components/Demo.vue'
  export default {
  name: 'App',
  components:{
    Demo
  },
  setup(){
    function say(value){
      alert(`good morning${value}`)
    }
    return {
      say
    }
  }
}
</script>

<style>

</style>

计算属性computed

与vue2中的计算属性功能一样

Demo.vue

<template>
  <h3>{{ person.firstName }}</h3>
  <h3>{{ person.lastName }}</h3>
  <input type="text" v-model="person.firstName" /><br />
  <br />
  <input type="text" v-model="person.lastName" /><br />
  <br />
  <input type="text" v-model="fullName" />
</template>

<script>
import { reactive, computed } from "vue";
export default {
  name: "Demo",

  setup() {
    let person = reactive({
      firstName: "张",
      lastName: "三",
    });
    //简写  只能读,不能改
    let fullName2 = computed(()=>{
        return person.firstName + "-" + person.lastName;
    })
    //完整版写法
    let fullName = computed({
      get() {
        return person.firstName + "-" + person.lastName;
      },
      set(value) {
        let name = value.split("-");
        person.firstName = name[0];
        person.lastName = name[1];
      },
    });
    return {
      person,
      fullName,
    };
  },
};
</script>

<style>
</style>

watch属性

和vue2的监视属性功能一样

监视recative定义的响应式数据时:oldValue无法正确获取,(deep配置失效)

监视recactive定义的响应式数据中某个属性时deep配置生效

<template>
  <h3>{{ person.firstName }}</h3>
  <h3>{{ person.lastName }}</h3>
  <h3>{{ age }}</h3>
  <button @click="age++">age++</button>
  <input type="text" v-model="person.firstName" /><br />
  <br />
  <input type="text" v-model="person.lastName" /><br />
  <br />
  <input type="text" v-model="fullName" />
</template>

<script>
import {ref, reactive, computed ,watch} from "vue";
export default {
  name: "Demo",

  setup() {

    let age = ref(12);

    let person = reactive({
      firstName: "张",
      lastName: "三",
    });
    //第一个参数,监视的属性名
    //第二个参数,回调函数,
    //第三个参数,配置项{immediate:true,deep:true}

    //第一种情况,监视ref
    watch(age,(newValue,oldValue)=>{
        console.log("年龄发生了变化",newValue,oldValue)
    })
    //第二种情况,监视reactive
    watch(person,(newValue,oldValue)=>{
        console.log("名字发生了变化",newValue,oldValue)
    })
    //第三种情况,同时监视数组中的元素
    watch([age,person],(newValue,oldValue)=>{
        console.log("名字或年龄发生了变化",newValue,oldValue)
    })
     //第四种情况,监视reactive响应式数据中的某个属性
    watch(()=>person.firstName,(newValue,oldValue)=>{
        console.log("姓发生了变化",newValue,oldValue)
    })
   
    return {
        age,
      person,
    };
  },
};
</script>

<style>
</style>

watchEffect函数

不用指定监视那个属性,在该回调中用到谁就监视谁。

watchEffect(()=>{
        const a1 = age.value;
        const s2 = person.firstName
        console.log("watchEffech监视到有属性变化了")
    })

生命周期

setup函数就代表了beforeCreate 和created这两个生命周期钩子

<template>
  <h3>{{ age }}</h3>
  <button @click="age++">age++</button>
  
</template>

<script>
import {ref, onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount, onUnmounted} from "vue";
export default {
  name: "Demo",

  setup() {
    let age = ref(24);
   //组合式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 {
       age
    };
  },
};
</script>

<style>
</style>

自定义hook函数

本质是一个函数,把setup里面用到的CompositionAPI(组合式API)进行封装

类似于Vue2中的mixin

优势:复用代码,让setup中的逻辑更清楚

toRef

作用:创建一个ref对象,其value值指向另一个对象中的某个属性。

语法:const name = toRef(person,‘name’)

应用:将响应式对象中的某个属性单独提供给外部使用时。

扩展:toRefs与toRef功能一致,但可以批量创建多个ref对象,toRefs(person)

<template>
  <h3>{{ person }}</h3>
  <h3>{{ name }}</h3>
  <h3>{{ age }}</h3>
  <h3>{{ job.j1.salary }}k</h3>
  <button @click="age++">age++</button>
  <button @click="job.j1.salary++">salary++</button>
  
</template>

<script>
import {reactive, toRef,toRefs} from "vue";
export default {
  setup() {
    let person = reactive( {
    name:'pyy',
    age:18,
    job:{
      j1:{
        salary:24
      }
    }
  })
   

    return {
       person,
       ...toRefs(person),
      //  name:toRef(person,'name'),
      //  age:toRef(person,'age'),
      //  salary:toRef(person.job.j1,'salary'),
    };
  },
};
</script>

<style>
</style>

其他Composition API

shallowReactive与shallRef

shallowReactive只处理对象最外层属性的响应式

shallRef值处理基本数据类型的响应式,不处理对象类型的响应式

readonly与shallReadonly

readonly:让一个响应式数据变为只读的(深只读)

shallReadonly:让一个响应式数据变为只读的(浅只读)

toRaw与markRaw

toRaw:将一个由reactive生成的响应式对象转为普通对象

使用场景:用于读取响应式对象对应的普通对象,对着普通对象的操作不会引起页面的更新

markRaw:标记一个对象,使其永远不会成为一个响应式对象

customRef

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制

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

<script>
import {customRef, reactive, toRef,toRefs} from "vue";
export default {
  name:'App',
  setup() {
    
      function myRef(value,delay){
        let timer;
        //track追踪value的变化,
        //trigger 通知vue去解析模板
        return customRef((track,trigger)=>{
          return{
            get(){
              track();
              return value;
            },
            set(newValue){
              //延时一下再去更新h3中的内容,防抖
              clearTimeout(timer)
              timer = setTimeout(() => {
                
                value = newValue;
                trigger();
              }, delay);
              
            }

          }

        })

      }
      let keyword = myRef('123',1000);//使用自定义的ref

    return {
      keyword

    };
  },
};
</script>

<style>
</style>

provide与inject

实现祖宗组件与 后代组件之间通信

实例:祖宗=App.vue 后代(隔代)= Sons.vue

App.vue

<template>
  <div class="app">
    <h4>app</h4>
    <h4>{{name}}</h4>
    <h4>{{salary}}</h4>
    <Child></Child>

  </div>
</template>

<script>
  import Child from './components/Child.vue'
  import {reactive,provide,toRefs} from 'vue'
  export default {
  name: 'App',
  components:{Child},
  setup() {
    
    let car = reactive({
      name:'bmw',
      salary:40
    })
    //使用che名字留给后代组件用定义的car对象
    provide('che',car);
    return {
      ...toRefs(car)
    }
  }

}
</script>

<style>
.app{
    background: gray;
    padding: 10px;
}
</style>

Child.vue

<template>
  <div class="child">
    <h4>child</h4>
    <Son></Son>
  </div>
</template>

<script>
import Son from './Sons.vue';
export default {
  name: "Child",
   components:{Son}
  
};
</script>

<style>
.child {
  background: green;
  padding: 10px;
}
</style>

Sons.vue

<template>
  <div class="son">
    <h4>Son</h4>
    <h4>{{name}}</h4>
    <h4>{{salary}}</h4>
  </div>
</template>

<script>
import {inject, toRefs} from 'vue'
export default {
    name:'Son',
    setup() {
        
       let car =  inject('che')
        return {
            ...toRefs(car)
        }
    }
}
</script>


<style>
.son{
    background: pink;
    padding: 10px;
}

</style>

响应式数据判断

isRef:是否为ref对象

isReacvice:是否为reactive创建的响应式代理

isReadonly:是否为readonly创建的只读代理

isProxy:是否为一个代理对象

新的组件

Fragment

根标签,在vue3中可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中

减少标签层级,减小内存占用

Teleport(TP传送)

用标签包裹的内容会被传送到to=“xxx”的位置

<template>
  <button @click="isShow = true">打开弹窗</button>
  <teleport to='body' >

    <div class="mask" v-if="isShow">
        <div class="dialog">
            <h4>弹窗</h4>
            <h4>弹窗</h4>
            <h4>弹窗</h4>
            <h4>弹窗</h4>
            <button @click="isShow = false">关闭弹窗</button>
        </div>

    </div>
  </teleport>
</template>

<script>
import {ref}  from 'vue'
export default {
    name:'Dialog',

    setup() {
        let isShow = ref(false)
        return{isShow}
    }
}
</script>

<style>
.mask{
    background: rgba(0, 0, 0, 50%);
    position: absolute;
    left: 0;top: 0;right: 0;bottom: 0;
}
.dialog{
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
    text-align: center;
    height: 300px;
    width: 300px;
    background: skyblue;

}
</style>

Suspense

异步加载组件

Vue CLI(vue脚手架)是一个官方提供的对Vue.js开发的脚手架工具,通过它我们可以快速初始化一个基于Vue.js的项目。下面是一个详细Vue CLI教程,包括如何安装、创建项目、常用命令等。 1. 安装Node.js:首先确保已经安装了Node.js,去Node.js官网下载安装包,按照指引完成安装。 2. 安装Vue CLI:打开命令行工具(如终端、命令提示符等),运行以下命令安装Vue CLI: ``` npm install -g @vue/cli ``` 3. 创建Vue项目:在命令行中进入你想要创建项目的目录,运行以下命令创建项目: ``` vue create project-name ``` 其中,`project-name`是你要创建的项目名称,可以根据需要自定义。 4. 选择项目配置:在创建项目的过程中,Vue CLI会让你选择一些项目配置,比如使用的包管理工具、需要的特性等,根据需要选择或者保持默认配置。 5. 运行项目:进入项目目录,运行以下命令启动项目: ``` cd project-name npm run serve ``` 6. 编写代码:打开项目目录,在`src`文件夹下可以看到`main.js`文件,这是项目的入口文件,可以在这里编写Vue代码。 7. 构建项目:完成代码编写后,运行以下命令构建项目: ``` npm run build ``` 这会生成一个可部署的静态网站文件放在`dist`目录下。 8. 常用命令: - `npm run serve`:启动开发服务器,实时编译和热重载。 - `npm run build`:构建生产环境的项目。 - `npm run lint`:检查和修复代码风格。 以上就是一个详细Vue CLI教程,希望能够帮到你入门Vue开发。如果想要进一步学习和掌握Vue CLI,建议查阅Vue CLI官方文档,了解更多详细信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值