Vue2

1.快速入门

  • 注意代码顺序,要求div在前,script在后,否则无法绑定数据
  • 先创建一个vue对象,构造器传入含有el和data属性的对象
  • el表示关联的html元素,可以使用多种选择器绑定html元素
  • data表示该vue对象的数据池,只有被vue对象绑定的html元素才能使用到数据池中的数据
  • 使用插值表达式(两个大括号)给html标签绑定数据
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue快速入门</title>
    <script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
    <!--
    这里的两个大括号嵌套是插入表达式,
    通过id到对应vue对象中的数据池中匹配数据
    -->
    <h1>欢迎{{message}}-{{name}}</h1>
</div>
<script type="text/javascript">
    //创建一个vue对象,构造器传入含有el和data属性的对象
    //el表示关联的html元素的id
    //data表示该vue对象的数据池
    let vue = new Vue({
        el:"#app",
        data:{//这里的data也是一个对象,data对象的属性保存着数据池数据
            message:"Hello,Vue",
            name:"杨逸"
        }
    });
    console.log("vue=",vue);
    //获取数据池中数据
    console.log(vue._data.message);
    console.log(vue._data.name);
    //直接获取
    console.log(vue.name);
    console.log(vue.message);

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

2.单向数据渲染(v-bind)

  • 单向数据渲染就是数据池data中数据的变化会影响到页面view展示的数据,而页面数据的变化不会影响到数据池的变化
  • 插值表达式是给标签绑定数据的,不能给标签的属性绑定数据
  • 给标签的属性绑定数据,需要使用单向数据绑定(v-bind)
  • 使用的方式是给属性加上"v-bind:"前缀,然后 在属性值的位置写上数据池中的属性
  • 单向数据绑定还可以简写成一个冒号加属性名
  • 应用实例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>单向数据渲染(v-bind)的使用</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <!--插值表达式是给标签绑定数据的-->
    <h1>{{message}}</h1>
    <!--单行数据渲染(v-bind)是给标签的属性绑定数据的-->
        <!--单向数据渲染就是数据池data中数据的变化会影响到页面view展示的数据,而页面数据的变化不会影响到数据池的变化-->
    <!--使用的方式是给属性加上"v-bind:"前缀,然后 在属性值的位置写上数据池中的属性-->
    <img v-bind:src="img_src" v-bind:width="img_width">
    <!--单向数据渲染还可以简写成一个冒号加属性名-->
    <img :src="img_src" :width="img_width">

</div>
<script>
    let vue = new Vue({
        el:"#app",
        data:{
            message:"单向数据绑定的使用",
            img_src:"../img/1.jpg",
            img_width:"200px"
        }
    })
</script>
</body>
</html>

3.双向数据渲染(v-model=)

  • 双向数据渲染就是数据池data中数据的变化会影响到页面view展示的数据,页面数据的变化也会影响到数据池中数据的变化

  • 使用方式是"v-model="后面直接写上数据池中的属性

  • 双向数据绑定没有简写

  • 修饰符

    1. number:将输入转换为数字,v-model.number="age"
    2. trim:去掉字符串开头和结尾的空格字符,v-model.trim="username"
    3. lazy:当失去焦点时,再更新数据池中的数据,v-model.lazy="remark"
  • 应用实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>双向数据渲染的使用</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <h1>{{message}}</h1>
    <!--使用双向数据渲染,给下面表单控件输入新的值时,会影响到数据池的hobby数据-->
    <input name="hobby" v-model="hobby"><br><br>
    <input name="hobby" :value="hobby">
    <p>你的爱好是{{hobby}}</p>
</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{
            message:"请输入你的爱好",
            hobby:"吃瓜"
        }
    });
</script>

</html>
  • 应用实例:收集表单的各种数据
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用双向数据绑定收集表单的各种控件的数据</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <form @submit.prevent="submit" id="app">
        用户名:<input type="text" v-model.trim="user.usernmae"><br><br>
        密码:<input type="password" v-model="user.password"><br><br>
        性别:<input type="radio" v-model="user.sex" value="男">男<input type="radio" v-model="user.sex" value="女">女<br><br>
        年龄:<input type="number" v-model="user.age"><br><br>
        爱好:<br>
        学习<input type="checkbox" v-model="user.hobby" value="学习">
        唱歌<input type="checkbox" v-model="user.hobby" value="唱歌">
        写代码<input type="checkbox" v-model="user.hobby" value="写代码">
        <br><br>
        城市:
        <select v-model="user.city">
            <option>选择城市</option>
            <option value="beijing">北京</option>
            <option value="shanghai">上海</option>
            <option value="shenzhen">深圳</option>
        </select><br><br>
        个人介绍:<br><br>
        <textarea v-model.lazy="user.introduce"></textarea><br><br>
        <input type="checkbox" v-model="user.agree">同意条款<a href="baidu.com">服务条款</a><br><br>
        <button>提交</button>
    </form>
    

</body>
<script>
    const vm = new Vue({
        el:'#app',
        data:{
            user:{
                usernmae:'',
                password:'',
                age:0,
                sex:'',
                hobby:[],
                city:'',
                introduce:'',
                agree:''
            }
        },
        methods:{
            submit(){
                const user = JSON.stringify(this.user);
                console.log(user);
            }
        }
    });
</script>
</html>

4.事件绑定

  • 使用v-on:时间名="vue对象方法池中的方法"绑定事件
  • 使用methods属性定义vue对象的方法池
  • 如果浏览器支持,事件绑定可以简写,当绑定的方法没有参数时,可以省略形参列表(小括号)
  • 如果浏览器支持.v-on:可以使用@符号代替
  • 如果绑定的方法只有一条语句,可以将语句直接写到事件绑定的地方,如<button @click="count += 2">次数+2</button>
  • 应用案例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件绑定</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <h1>vue的事件绑定</h1>
    <!--在vue中,时间绑定使用的是"v-on:事件名='方法池中的绑定的方法'"-->
    <button v-on:click="sayHi()">点击事件一</button>
    <button v-on:click="sayOk()">点击事件二</button>
    <!--事件绑定的简写,如果方法没有参数可以省略小括号(形参列表)-->
    <!--可以使用"@"符号代替"v-on:",需要浏览器支持-->
    <button v-on:click="sayHi">点击事件三</button>
    <button @click="sayOk">点击事件四</button>
</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{

        },
        methods:{//vue对象的方法池,事件调用的方法都在这里定义
            sayHi(){
                console.log("hi,银角大王");
            },
            sayOk(){
                console.log("ok,金角大王");
            }
        }
    });
</script>
</html>

5.修饰符

  • 修饰符(Modifiers)是以()指明的后缀,指出某个指令以特殊方式绑定。
  • 例如,.prevent修饰符告诉v-on指令对于触发的事件调用event.preventDefault()即阻止事件原本的默认行为
  • 使用方式,在对应事件后加上要使用的修饰符
  • 应用实例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件修饰符的基本使用</title>
    <script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
    <!--这里使用".prevent"事件修饰符,阻止事件的默认机制,使用我们自定义的方法进行处理-->
    <form action="http://www.baidu.com/" @submit.prevent="mySubmit">
        <h1>事件修饰符的基本使用</h1>
        妖怪的名字:<input type="text" v-model="monster.name"><br>
        <input type="submit" value="提交">
    </form>
</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{
            monster:{
            }
        },
        methods:{
            mySubmit(){
                console.log("自定义的提交方法被调用");
                if (this._data.monster.name){
                    console.log("表单提交,表单的信息",this.monster);
                }else {
                    console.log("妖怪名不能为空");
                }
            }
        }
    });
</script>
</html>

事件修饰符

  • .stop,阻止事件冒泡,阻止单击事件继续传播
  • .prevent,阻止事件的默认处理,提交事件不再重载页面
  • .capture,先执行事件的默认操作,再执行回调函数;内部元素触发的事件先在此处理,然后才交由内部元素进行处理
  • .self,只当在 event.target 是当前元素自身时触发处理函数
  • .once,事件将只会触发一次
  • .passive,不阻止默认事件的发生
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

按键修饰符

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

更多细节在Vue的官方文档

6.计算属性(computed)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算属性</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        姓:<input type="text" v-model='firstName'><br>
        名:<input type="text" v-model='lastName'><br>
        全名:{{fullName}}
    </div>
</body>
<script>
    const vm = new Vue({
        el:'#app',
        data:{
            firstName:'张',
            lastName:'三'
        },
        computed:{
            // 通过计算得到完整的姓名,get函数用于返回计算后的属性,set用于响应该属性变化的回调函数
            // fullName:{
            //     get(){
            //         return this.firstName.slice(0,2) + '-' + this.lastName.slice(0,2);
            //     },
            //     set(val){

            //     }
            // }

            //简写,如果只需要获取属性而不需要修改
            fullName(){
                return this.firstName.slice(0,2) + '-' + this.lastName.slice(0,2);
            }
        }
    });
</script>
</html>

7.监视属性(watch)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>监听属性</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
      <h3>今天天气{{weather}}</h3>
        <button @click="isHot=!isHot">切换天气</button>
    </div>
</body>
<script>
    const vm = new Vue({
        el:'#app',
        data:{
            isHot:true
        },
        computed:{
            weather(){
                return this.isHot ? '炎热' : '凉爽';
            }
        },
        watch:{
            isHot:{
                //初始化时是否调用一下
                immediate:true,
                //是否开启生成监听
                deep:true,
                //当isHot属性发生变化的时候调用
                handler(newValue,oldValue){
                    console.log('属性发生变化',oldValue,'-->',newValue);

                }
            }
        }
    
    });
</script>
</html>

8.条件渲染

  • v-if,当表达式为true时创建组件,为false时不创建
  • v-show,当表达式为true时,显示组件,组件默认创建,只是可能不显示
  • 条件渲染的参数都是到数据池中匹配的,并不是简单的字符串
  • v-if的应用案例
  • v-if是通过组件的创建和销毁实现显示控制的,如果频繁切换对性能的影响比较大
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue的条件渲染v-if</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <!--使用双向渲染关联到数据池中的数据-->
    <!--checked属性默认是true-->
    <input type="checkbox" v-model="sel">是否同意条款[使用v-if实现]
    <!--"v-if"的参数,实际上是到数据池中去匹配-->
    <!--"v-if"是通过组件的创建和销毁实现显示控制的,如果频繁切换对性能的影响比较大-->
    <h2 v-if="sel">你同意条款</h2>
    <h2 v-else>你不同意条款</h2>

</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{
            sel:false
        }
    });

</script>
</html>
  • v-show应用实例
  • v-show实际上是通过控制display属性进行显示的切换的,没有子组件的创建和销毁
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>使用v-show实现显示控制</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <!--使用双向渲染关联到数据池中的数据-->
    <!--checked属性默认是true-->
    <input type="checkbox" v-model="sel">是否同意条款[使用v-show实现]
    <!--"v-show"的参数,实际上是到数据池中去匹配-->
    <!--"v-show"实际上是通过控制display属性进行显示的切换的,没有子组件的创建和销毁-->
    <h2 v-show="sel">你同意条款</h2>
    <h2 v-show="!sel">你不同意条款</h2>

</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{
            sel:false
        }
    });

</script>
</html>
  • v-else-if的使用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>条件渲染作业</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <h1>演示条件渲染</h1>
    请输入你的成绩:<input type="text" v-model="score" @blur="judge">
    <p>你当前的成绩是:{{score}}</p>
    <p v-if="score >= 90">优秀</p>
    <p v-else-if="score >= 70">良好</p>
    <p v-else-if="score >= 60">及格</p>
    <p v-else>不及格</p>

</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{
            score:0
        },
        methods:{
            judge(){
                if (this.score > 100){
                    this.score = 100;
                }else if (this.score < 0){
                    this.score = 0;
                }
            }
        }
    });
</script>
</html>

9.列表渲染

  • 可以取出数组里的值,也可以取出数组的值的索引
  • 可以取出对象属性的值,也可以取出对象属性的名称,还可以取出对象属性的索引
  • 取值的循序是有要求的,先取值,然后取属性名,最后索引
  • v-for的应用实例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表渲染</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
     <ul>
        <!--渲染的目标可以是一个数字,默认从1渲染到指定的数字-->
        <li v-for="value in 3">{{value}}</li>
    </ul>
    <ul>
        <!--渲染列表的值-->
        <li v-for="x in list1">{{x}}</li>
    </ul>
    <ul>
        <!--渲染列表的值的索引-->
        <li v-for="(value,index) in list1">{{index}}:{{value}}</li>
    </ul>
    <ul>
        <!--渲染对象的属性值,属性名,属性索引-->
        <!--使用列表渲染,取数据的循序是固定的,先值,然后属性名,最后索引,如果没有的就跳过-->
        <li v-for="(value,name,index) in monster">{{index}}:{{name}}:{{value}}</li>
    </ul>

</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{
            list1:[1,2,3,4,5],
            monster:{
                name:"牛魔王",
                age:12,
                skill:["牛啊","牛逼"]
            }
        }
    });
</script>
</html>
  • v-for列表循环作业
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表渲染作业</title>
  <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <table border="1px">
        <tr>
            <th>id</th>
            <th>name</th>
            <th>age</th>
            <th>score</th>
        </tr>
        <tr  v-if="x.score >= 60" v-for="x in student" >
            <td>{{x.id}}</td>
            <td>{{x.name}}</td>
            <td>{{x.age}}</td>
            <td>{{x.score}}</td>
        </tr>
    </table>
</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{
            student:[
                {id:1,name:"jack",age:20,score:90},
                {id:2,name:"tom",age:20,score:30},
                {id:3,name:"kk",age:20,score:60}]
        }
    });
</script>
</html>

10.组件化编程

  • 组件的创建需要创建vue对象之前,否则vue对象关联不到挂组件

全局组件

  • 调用Vue.component()方法创建组件,第一个参数是组件名,第二个参数是组件对象,该组件是属于所有vue对象
  • 创建的组件也是一个vue对象
  • 组件的template属性表示的是组件的具体html内容
  • 组件的数据池与常规的vue对象的数据池是不一样的
  • 组件里的数据池是一个方式,方法里根据组件里的数据名,返回一个数据对象
  • 使用vue创建的组件,通过标签的形式使用,标签名就是组件的名称
  • 每使用一个组件标签,就创建一个新的vue对象,它们之间的数据是独立的
  • 全局组件应用实例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>全局组件</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <h1>全局组件化编程</h1>
    <!--
    使用vue创建的组件,通过标签的形式使用,标签名就是组件的名称
    每使用一个标签,就创建一个新的vue对象,它们之间的数据是独立的
    -->
    <counter></counter><br>
    <counter></counter><br>
    <counter></counter>
</div>

</body>
<script type="text/javascript">
    //使用组件化编程,调用Vue.component()方法创建组件,第一个参数是组件名,第二个参数是组件对象
    //创建的组件也是一个vue对象
    Vue.component("counter",{
        //template属性表示的是组件的具体html内容
        template:`<button @click="click">点击次数{{count}}[全局组件化编程]</button>`,
        //组件里的数据池是一个方式,方法里根据组件里的数据名,返回一个数据对象
        //这里的数据池与常规的vue对象的数据池是不一样的
        data(){
            return {
                count:0
            }
        },
        methods:{
            click(){
                this.count++;
            }
        }
    });
    let vue = new Vue({
        el:"#app"
    });
</script>
</html>

局部组件

  • 在vue对象挂载的元素中使用组件池中的组建,这种组件称为局部组件

  • 先创建组件,然后在vue对象的组件池中加入创建好的组件

  • vue组件池里的组件只有在vue对象挂载的元素才能使用,其他位置不能使用

  • 也可以导入外部js文件里的组件,然后再加入组件池中

  • 局部组件应用实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="../js/vue.js"></script>
    <title>非单文件组件</title>
</head>
<body>
    <div id="app">
        <!-- 第三步:使用组件 -->
        <school></school>
    </div>
</body>
<script>
    // 第一步:创建组件
    const school = Vue.extend({
        name:'myComponent',
        //组件的结构
        template:`
        <div>
            <p>学校名称:{{name}}</p>
            <p>学校地址:{{address}}</p>
        </div>`,
        //组件的数据池,必须使用函数的形式
        data(){
            return {
                name:'广科',
                address:'珠海'
            }
        }
    });

    // 全局注册组件,参数一是组件的名称,参数二是组件实例对象
    Vue.component('school',school);

    const vm = new Vue({
        el:'#app',
        //第二步:注册组件(局部组件)
        components:{
            school
        }
    });
</script>
</html>
  • 局部组件应用实例

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>局部组件</title>
        <script type="text/javascript" src="../script/vue.js"></script>
    
    </head>
    <body>
    <div id="app">
        <h1>局部组件的使用</h1>
        <!--在vue对象挂载的元素中使用组件池中的组建,这种组件称为局部组件-->
        <my_counter></my_counter>
        <my_counter></my_counter>
    </div>
    </body>
    <script type="text/javascript">
        //创建一个组件对象,组件对象里的属性是固定的
        let counter = {
            //template属性表示的是组件的具体html内容
            template:`<button @click="click">点击次数{{count}}[局部组件化编程]</button>`,
            //组件里的数据池是一个方式,方法里根据组件里的数据名,返回一个数据对象
            //这里的数据池与常规的vue对象的数据池是不一样的
            data(){
                return {
                    count:0
                }
            },
            methods:{
                click(){
                    this.count++;
                }
            }
        };
    
        //创建一个vue对象,并挂载
        let vue = new Vue({
            el:"#app",
            components:{//在vue对象的组件池中加入创建好的组件
                //vue组件池里的组件只有在vue对象挂载的元素才能使用,其他位置不能使用
                my_counter:counter
            }
    
        });
    </script>
    </html>
    

单文件组件

  • 组件一
<template>
    <!-- 组件的结构 -->
    <div>
        <p>学校名称:{{name}}</p>
        <p>学校地址:{{address}}</p>
    </div>
</template>

<script>
// 组件的交互
// 导出组件
export default Vue.extend({
    name:'School',
    data(){
        return {
            name:'广科',
            address:'珠海'
        }
    }
});
</script>

<style>
/* 组件的样式 */
</style>
  • 组件二
<template>
    <div>
        <p>学生姓名:{{ name }}</p>
        <p>学生年龄{{ age }}</p>
    </div>
</template>

<script>
export default {
    name:'Student',
    data(){
        return {
            name:'张三',
            age:20
        }
    }
};
</script>

  • 组件整合
<template>
    <div>
        <School></School>
        <Student></Student>
    </div>
</template>

<script>
import School from './School.vue'
import Student from './Student.vue'

export default{
    name:'App',
    components:{
        School,
        Student
    }
}
</script>
  • 使用组件和程序的入口
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>单文件组件入口</title>
    <script src="../js/vue.js"></script>
    <script type="module" src="./main.js"></script>
</head>
<body>
    <div id="app">
        <App></App>
    </div>
</body>
</html>
import App from './App.vue'

new Vue({
    el:'#app',
    components:{
        App
    }
});

通过prop配置项给组件传递数据

  • 定义组件
  • 组件接收传递的参数需要配置props指定需要传递的参数
  • props中的数据是只读的,不可修改
<template>
    <div>
        <h1>{{ msg }}</h1>
        <p>学生姓名:{{ name }}</p>
        <!-- 传递的值不能直接修改,可以通过数据池里的中间变量进行修改 -->
        <p>学生年龄:{{ myAge+1 }}</p>
        <p>学生性别:{{ sex }}</p>
        <button @click="myAge++">点我年龄加1</button>
    </div>
</template>

<script>
export default {
    name:'Student',
    data(){
        return {
            msg:'学生信息',
            myAge:this.age
        }
    },
    //需要接收参数要在组件定义时配置
    // 简单的配置
    // props:['name','age','sex']

    //限制接受数据类型的配置
    // props:{
    //     name:String,
    //     sex:String,
    //     age:Number
    // }

    // 限制数据类型,限制数据必要性,能设置数据默认值的配置方式
    //type限制数据类型,required限制传参的必要性,default设置默认值
    props:{
        name:{
            // 限制数据的类型
            type:String,
            // 必须传递该参数
            required:true
        },
        sex:{
            // 限制数据的类型
            type:String,
            // 必须传递该参数
            required:true
        },
        age:{
            type:Number,
            // 设置默认值
            default:99
        }
    }
};
</script>

  • 使用组件
  • 向组件传递数据直接在组件标签上写对应的属性即可
<template>
  <div id="app">
    <School></School>
    <!-- 使用v-bind简写的方式向组件传递数值 -->
    <Student name="张三" sex="男" :age="18"></Student>
  </div>
</template>

<script>

import School from './components/School.vue'
import Student from './components/Student.vue'

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


通过mixin配置项复用相同的配置

  • mixin配置对象和组件对象同时具有的配置以组件配置的为准,但生命周期的钩子函数例外,两个地方的配置都生效

  • 创建mixin混合配置对象

// 创建一个混合配置对象,使用混合对象复用相同的配置
export const mixin = {
    // vue中的配置对象都可以在这里配置,
    methods:{
        showName(){
            alert(this.name);
        }
    }
};
  • 导入mixin混合配置对象并使用
<template>
    <div>
        <h1>{{ msg }}</h1>
        <p @click="showName">学生姓名:{{ name }}</p>
        <!-- 传递的值不能直接修改,可以通过数据池里的中间变量进行修改 -->
        <p>学生年龄:{{ myAge+1 }}</p>
        <p>学生性别:{{ sex }}</p>
        <button @click="myAge++">点我年龄加1</button>
    </div>
</template>

<script>
// 引入混合配置对象
import {mixin} from '@/utils/mixin.js'

export default {
    name:'Student',
    data(){
        return {
            msg:'学生信息',
            myAge:this.age
        }
    },
    // 使用mixin混合配置对象,复用相同的配置
    mixins:[mixin]
};
</script>

<template>
    <!-- 组件的结构 -->
    <div>
        <p @click="showName">学校名称:{{name}}</p>
        <p>学校地址:{{address}}</p>
    </div>
</template>

<script>
import {mixin} from '@/utils/mixin'

// 组件的交互
// 导出组件
export default {
    name:'School',
    data(){
        return {
            name:'广科',
            address:'珠海'
        }
    },
    mixins:[mixin]
};
</script>

<style>
/* 组件的样式 */
</style>

vue的plugs插件

  • 编写插件
// 编写vue的插件
export default {
    // 插件的入口,必需有这个install函数
    // 第一个参数是Vue的原型对象,剩下的参数是使用插件时传入的自定义参数
    install(Vue){
        // 获取到Vue的原型对象,我们就可以在Vue的原型上添加额外的功能
        // 这个函数由在创建Vue时调用
        console.log(Vue);
    }
}
  • 使用插件
import Vue from 'vue'
import App from './App.vue'
// 导入插件
import plugs from '@/utils/plugs'

Vue.config.productionTip = false;
// 使用插件
Vue.use(plugs);

new Vue({
  // 渲染vue的template
  render: h => h(App),
}).$mount('#app');

组件的自定义事件

  • 定义和绑定自定义事件
    1. 直接在组件上绑定组件自定义事件
    2. 通过组件对象的$on函数绑定组件自定义事件
    3. 绑定原生事件需要使用.native修饰符
<template>
  <div id="app">
    <!-- 第一张方式:给组件绑定myEvent自定义事件 -->
    <School @myEvent="getSchoolName" ref="demo"></School>

    <!-- 给组件绑定原生的事件需要使用.native修饰符 -->
    <Student name="张三" sex="男" :age="18" @click.native="nativeClickEvent"></Student>

  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
import School from './components/School.vue'
import Student from './components/Student.vue'

export default {
  name: 'App',
  components: {
    HelloWorld,
    School,
    Student
  },
  methods:{
    getSchoolName(name){
      console.log('App接受到数据:',name);
    },
    nativeClickEvent(){
      alert('原始的click事件被触发');
    }
  },
  mounted(){
    // 第二种方式:通过ref给组件绑定自定义事件
    // this.$refs.demo.$on('myEvent',this.getSchoolName);
    // 绑定只触发一次的自定义事件
    // this.$refs.demo.$once('myEvent',this.getSchoolName);
  }
}
</script>


  • 触发自定义事件
    • 触发事件需要在组件对象中调用$emit函数使用组件自定义事件
<template>
    <!-- 组件的结构 -->
    <div>
        <h1>{{ msg }}</h1>
        <p @click="showName">学校名称:{{name}}</p>
        <p>学校地址:{{address}}</p>
        <button @click="diyHanlder">点我触发组件的自定义事件</button>
        <button @click="deathHanlder">点我解绑组件的自定义事件</button>
    </div>
</template>

<script>
import {mixin} from '@/utils/mixin'

// 组件的交互
// 导出组件
export default {
    name:'School',
    data(){
        return {
            name:'广科',
            address:'珠海',
            msg:'学校信息'
        }
    },
    methods:{
        diyHanlder(){
            // 手动触发组件的自定义事件
            this.$emit('myEvent',this.name);
        },
        deathHanlder(){
            // 解绑组件一个自定义事件
            this.$off('myEvent');

            // 解绑多个组件自定义事件,使用数组的形式传参
            // this.$off(['myEvent','myEvent2']);

            // 解绑所有组件自定义事件
            // this.$off();
            
        }
    },
    mixins:[mixin]
};
</script>

<style>
/* 组件的样式 */
</style>
  • 解除自定义事件
deathHanlder(){
    // 解绑组件一个自定义事件
    this.$off('myEvent');

    // 解绑多个组件自定义事件,使用数组的形式传参
    // this.$off(['myEvent','myEvent2']);

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

}

11.生命周期

  • 生命周期流程图

  1. beforeCreate()
    • 组件创建遇到的第一个生命周期函数,这个阶段data和methods以及dom结构都未被初始化,也就是获取不到data的值,不能调用methods中的函数
  2. created()
    • 这个阶段组件的data和methods中的方法已初始化结束,可以访问,但是dom结构未初始化,页面未渲染
    • 在这个阶段经常发起ajax请求
  3. beforeMount()
    • 当模板在内存中编译完成,此时内存中的模板结构还未渲染至页面上,看不到真实的数据
  4. mounted()
    • 此时,页面渲染好,用户看到的是真实的页面数据,生命周期创建阶段完毕,进入到了运行中的阶段
  5. beforeUpdate()
    • 当执行此函数,数据池的数据新的,但是页面是旧的
  6. updated()
    • 页面已经完成了更新,此时,data数据和页面的数据都是新的
  7. beforeUnmount()
    • 当执行此函数时,组件即将被销毁,但是还没有真正开始销毁,比时组件的data、methods数据或方法还可以被使用
  8. unmounted()
    • 组件已经完成了销毁
  • 生命周期函数(钩子函数)使用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>生命周期函数(钩子函数)的使用</title>
    <script type="text/javascript" src="../script/vue.js"></script>

</head>
<body>
<div id="app">
    <span id="num">{{name}}被点赞{{count}}次</span>
    <button @click="count++">点赞</button>
</div>
</body>
<script type="text/javascript">
    let vue = new Vue({
        el:"#app",
        data:{
            count:0,
            name:"杨逸"
        },
        methods:{
            show(){
                console.log("show函数被调用",this.count,"  ",this.name);
            }
        },
        beforeCreate(){//生命周期函数
            console.log("=====beforeCreate=====创建前");
            console.log("data数据池的数据能否被使用?[no]",this.count," ",this.name);
            // console.log("methods方法池的方法能否被使用?[no]",this.show());
            console.log("用户dom页面是否被加载?[no]",document.getElementById("num"));
            console.log("用户dom页面是否被渲染?[no]",document.getElementById("num").innerText);
        },
        created(){
            console.log("=====create=====创建后");
            console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
            console.log("methods方法池的方法能否被使用?[yes]",this.show());
            console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
            console.log("用户dom页面是否被渲染?[no]",document.getElementById("num").innerText);
        },
        beforeMount(){
            console.log("=====beforeMount=====挂载前");
            console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
            console.log("methods方法池的方法能否被使用?[yes]",this.show());
            console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
            console.log("用户dom页面是否被渲染?[no]",document.getElementById("num").innerText);
        },
        mounted(){
            console.log("=====mounted=====挂载后");
            console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
            console.log("methods方法池的方法能否被使用?[yes]",this.show());
            console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
            console.log("用户dom页面是否被渲染?[yes]",document.getElementById("num").innerText);
        },
        beforeUpdate(){
            console.log("=====beforeUpdate=====创数据更新前");
            console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
            console.log("methods方法池的方法能否被使用?[yes]",this.show());
            console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
            console.log("用户dom页面是否被渲染成新数据?[no]",document.getElementById("num").innerText);
        },
        updated(){
            console.log("=====updated=====数据更新后");
            console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
            console.log("methods方法池的方法能否被使用?[yes]",this.show());
            console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
            console.log("用户dom页面是否被渲染成新数据?[yes]",document.getElementById("num").innerText);
        }

    });
</script>
</html>
  • 生命周期函数对比
生命周期函数data数据池的数据能否使用methods方法池的方法能否使用用户dom页面是否加载用户dom页面是否被渲染
beforeCreatenononono
createdyesyesyesno
beforeMountyesyesyesno
mountedyesyesyesyes
beforeUpdateyesyesyesno
updatedyesyesyesyes

12.自定义指令

  • 在vue实例中的directives中配置自定义指令
  • 自定义指令生命周期有三个回调函数
    1. bind
    2. inserted
    3. update
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自定义指令</title>
    <script src="../js/vue.js"></script>

</head>
<body>
    <div id="app">
        <p>n的值:<span v-text="n"></span></p>
        <!-- 使用自定义指令 -->
        <p>n放大十倍的值:<span v-big="n"></span></p>
        <button @click="n++">点我n加一</button><br><br>
        <!-- 自定义指令获取自动焦点事件 -->
        <input type="text" v-fbind="n">
    </div>
</body>
<script>
    const vm = new Vue({
        el:'#app',
        data:{
            n:1
        },
        // 自定义指令
        // 自定义指令的this是Window
        directives:{
            // 参数一:dom对象,参数二表达式对象,在vue模板解析时会调用该函数
            //简写
            big(elemnet,binding){
                elemnet.innerText = binding.value*10;
                // console.log(elemnet,binding);
            },
            // 完整写法
            fbind:{
                //与dom绑定的回调函数
                bind(elemnet,binding){
                    elemnet.value = binding.value;
                },
                // 渲染插入dom的回调函数
                inserted(elemnet,binding){
                    // 在插入dom后获取焦点事件
                    elemnet.focus();
                },
                // 模板重新渲染的回调函数
                update(elemnet,binding){
                    elemnet.value = binding.value;
                }
            }
        }
    });
</script>
</html>

13.Vue中的动画和过渡

动画

  • 使用transition标签包裹需要的动画的元素
    1. 定义一个关键帧动画
    2. hello-enter-active样式写上进入的动画,使用定义好的关键帧动画
    3. hello-leave-active样式写上离开的动画,使用定义好的关键帧动画
<template>
  <div>
    <button @click="isShow=!isShow">显示/隐藏</button>
    <!-- 使用vue中的动画标签 ,appear属性表示第一次加载页面时也播放动画-->
    <transition name="hello" appear>
      <h1 v-show="isShow">关键帧动画的案例</h1>
    </transition>
  </div>
</template>

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

}
</script>

<style>
/* 使用关键帧动画 */
.hello-enter-active{
  animation: test 1s;
}
.hello-leave-active{
  animation: test 1s reverse;
}
/* 定义一个关键帧 */
@keyframes test{
    from{
      transform: translateX(-100%);
      }
    to{
      transform: translateX(0);
    }
}

</style>

过渡

  • 使用transition标签包裹需要的动画的元素

  • 进入的过渡动画

    1. v-enter:进入的起点
    2. v-enter-active:进入的过渡动画
    3. v-enter-to:进入的终点
  • 离开的过渡动画

    1. v-leave:离开的起点
    2. v-leave-active:离开的过渡动画
    3. v-leave-to:离开的终点
<template>
  <div>
    <button @click="isShow=!isShow">显示/隐藏</button>
    <!-- 使用vue中的动画标签 ,appear属性表示第一次加载页面时也播放动画,多个标签需要动画时使用transition-group,并加上key属性-->
    <transition-group name="hello" appear>
      <h1 v-show="isShow" key="1">过渡动画的案例1</h1>
      <h1 v-show="!isShow" key="2">过渡动画的案例2</h1>
    </transition-group>
  </div>
</template>

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

}
</script>

<style>
h1{
  background-color: orange;
}
/* 使用过渡动画 */

/* 起点和终点是同一个地方,我们可以简写 */
/* 进入的起点,离开的终点 */
.hello-enter,.hello-leave-to{
  transfrom:translateX(-100%);
}

/* 进入过程的动画,离开过程的动画 */
.hello-enter-active,.hello-leave-active{
  transition: 0.5s linear;
}

/* 进入的终点,离开的起点 */
.hello-enter-to,.hello-leave{
  transfrom:translateX(0);
}
</style>

多个元素需要过渡和动画

  • 使用transition-group包裹需要动画的多个元素,并给每个元素带上不同值的key属性
    <!-- 使用vue中的动画标签 ,appear属性表示第一次加载页面时也播放动画,多个标签需要动画时使用transition-group,并加上key属性-->
    <transition-group name="hello" appear>
      <h1 v-show="isShow" key="1">过渡动画的案例1</h1>
      <h1 v-show="!isShow" key="2">过渡动画的案例2</h1>
    </transition-group>

14.Vue中配置请求代理

  • 修改vue.config.js文件
  • 简单代理
  // 简单的代理配置
   devServer:{
     proxy:'http:localhost:8080'
   }
  • 完整代理
  // 完整的代理配置
  devServer:{
    proxy:{
      // 匹配所以以/api为前缀的请求
      '/api1':{
          // 代理目标的基础路径
          target:'http://localhost:8080',
          // 是否支持webSctock,默认为true
          ws:true,
          // 是否在转发时修改host,true即修改为目标主机的host,false即保持原host
          changeOrigin:true,
          // 配置路径重写
          pathRewrite:{
            '^/api1':''
          }
      },
      // 可以配置多个代理
      '/api2':{
          target:'http://localhost:8081',
          ws:true,
          changeOrigin:true,
          pathRewrite:{
            '^/api2':''
      }
    }
  • 完整vue.config.js文件
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false, /*关闭语法检查*/
  // 简单的代理配置
  // devServer:{
  //   proxy:'http:localhost:8080'
  // }

  // 完整的代理配置
  devServer:{
    proxy:{
      // 匹配所以以/api为前缀的请求
      '/api1':{
          // 代理目标的基础路径
          target:'http://localhost:8080',
          // 是否支持webSctock,默认为true
          ws:true,
          // 是否在转发时修改host,true即修改为目标主机的host,false即保持原host
          changeOrigin:true,
          // 配置路径重写
          pathRewrite:{
            '^/api1':''
          }
      },
      // 可以配置多个代理
      '/api2':{
          target:'http://localhost:8081',
          ws:true,
          changeOrigin:true,
          pathRewrite:{
            '^/api2':''
      }
    }
  }
})

15.vue组件的插槽机制

  • 默认插槽
<template>
  <div>
    <h1>{{title}}分类</h1>
    <ul>
        <li v-for="(data,index) of slotData" :key="index">{{data}}</li>
    </ul>
    <!-- 默认插槽,没有名称 -->
    <!-- 使用组件时,放到组件标签内的元素会替换到Slot标签 -->
    <slot>没有使用插槽时,会显示的默认信息</slot>
  </div>
</template>

<script>
export default {
    name:'Slot1',
    props:[
        'slotData',
        'title']

}
</script>

<style scoped>
div{
    width: 30%;
}
</style>
  • 具名插槽
<template>
  <div>
    <!-- 具名插槽 -->
    <!-- 使用组件时,放到组件标签内的元素会替换到Slot标签 -->
    <!-- 程序员可以在插槽使用具体的元素进行替换 -->
    <slot name="content">没有使用插槽时,会显示的默认信息</slot>
    <slot name="foot">没有使用插槽时,会显示的默认信息</slot>

  </div>
</template>

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

<style scoped>
div{
    width: 30%;
}
</style>
  • 作用域插槽
<template>
  <div>
    <!-- 作用域插槽,数据放在组件中,结构和样式放在使用者身上 -->
    <!-- 使用作用域插槽,向组件使用者传递组件内部的数据 -->
    <slot name="content" :SlotData="SlotData">没有使用插槽时,会显示的默认信息</slot>
    <slot name="foot">没有使用插槽时,会显示的默认信息</slot>

  </div>
</template>

<script>
export default {
    name:'Slot3',
    data(){
      return{
        SlotData:['牛肉面','叉烧饭','泡面']
      }
    }
}
</script>

<style scoped>
div{
    width: 30%;
}
</style>
  • 使用插槽
<template>
  <div>

    <!-- 默认插槽 -->
    <div class="defaultSlot">
      <Slot1 :slotData="foods" title="美食">
        <h3>使用默认插槽,可以由使用者自定义内容</h3>
      </Slot1>
      <Slot1 :slotData="games" title="游戏">
        <h3>使用默认插槽,游戏分类的插槽使用</h3>
      </Slot1>
       <Slot1 :slotData="films" title="电影">
        <h3>使用默认插槽,电影分类的插槽使用</h3>
      </Slot1>
    </div>

    <!-- 具名插槽 -->

    <div class="hasName">
        <Slot2>
          <template slot="content">
                <h1>电影分类</h1>
                <ul>
                    <li v-for="(data,index) of films" :key="index">{{data}}</li>
                </ul>
          </template>
          <template v-slot:foot>
              <h3>使用具名插槽,电影分类的插槽使用</h3>
          </template>
        </Slot2>
        <Slot2>
          <template slot="content">
                <h1>美食分类</h1>
                <ul>
                    <li v-for="(data,index) of foods" :key="index">{{data}}</li>
                </ul>
          </template>
          <template v-slot:foot>
              <h3>使用具名插槽,美食分类的插槽使用</h3>
          </template>
        </Slot2>
        <Slot2>
          <template slot="content">
                <h1>游戏分类</h1>
                <ul>
                    <li v-for="(data,index) of games" :key="index">{{data}}</li>
                </ul>
          </template>
          <template v-slot:foot>
              <h3>使用具名插槽,游戏分类的插槽使用</h3>
          </template>
        </Slot2>
    </div>

    <!-- 作用域插槽 -->
    <div class="scopeSlot">
      <Slot3>
        <template slot="content" scope="ComponentData">
           <h1>美食分类</h1>
                <ul>
                    <!-- 使用组件内部的数据,数据使用组件内部的,结构和样式在组件的使用处定义 -->
                    <li v-for="(data,index) of ComponentData.SlotData" :key="index">{{data}}</li>
                </ul>
        </template>
        <template v-slot:foot>
          <h3>使用作用域插槽,美食分类的插槽使用</h3>
        </template>
      </Slot3>
      <Slot3>
        <!-- 使用对象解构的语法获取组件内部的数据 -->
          <template slot="content" scope="{SlotData}">
           <h1>美食分类</h1>
                <ol>
                    <!-- 使用组件内部的数据,数据使用组件内部的,结构和样式在组件的使用处定义 -->
                    <li v-for="(data,index) of SlotData" :key="index">{{data}}</li>
                </ol>
        </template>
        <template v-slot:foot>
          <h3>使用作用域插槽,美食分类的插槽使用</h3>
        </template>
      </Slot3>
      <Slot3>
        <template slot="content" scope="{SlotData}">
           <h1>美食分类</h1>
                    <!-- 使用组件内部的数据,数据使用组件内部的,结构和样式在组件的使用处定义 -->
                    <h4 v-for="(data,index) of SlotData" :key="index">{{data}}</h4>
        </template>
        <template v-slot:foot>
          <h3>使用作用域插槽,美食分类的插槽使用</h3>
        </template>
      </Slot3>
    </div>

  </div>
</template>

<script>
import Slot1 from '@/components/Slot1.vue'
import Slot2 from '@/components/Slot2.vue'
import Slot3 from '@/components/Slot3.vue'
export default {
    name:'MySlot',
    components:{
      Slot1,
      Slot2,
      Slot3
    },
    data(){
      return{
        foods:['牛肉面','叉烧饭','泡面'],
        games:['王者荣耀','荒野大镖客','GTA5'],
        films:['流浪地球','唐人街探案','杀手不太冷']
      }
    }
}
</script>

<style>
div.defaultSlot,.hasName,.scopeSlot{
  display: flex;
  justify-content: space-around;
}

</style>

16.Vuex插件

创建Vuex实例并使用

  • 导入Vue和Vuex,并在Vue使用Vuex
  • 创建Vuex.store实例
    1. actions:存放store.dispatch的回调函数
    2. mutations:存放store.commit的回调函数
    3. state:存放共享的数据
    4. getters:存放由state中数据计算得来的计算数据
import Vue from 'vue'
import Vuex from 'vuex'

// 创建Vuex实例前在Vue中使用Vuex
Vue.use(Vuex);

// 创建Vuex的store实例,并导出
export default new Vuex.Store({
    // Vuex.$store.dispatch()的回调函数池
    actions:{
        // 第一个参数是Vuex的上下文精简版的store,第二个参数开始是程序员的传参
        incrementOdd(context,value){
            if(context.state.sum%2){
                // 调用commit函数进行具体数据处理
                context.commit('increment',value);
            }
        },
        incrementWait(context,value){
            setTimeout(()=>{
                context.commit('increment',value);
            },500);
        }
    },
    // Vuex.$store.commit()的回调函数池
    mutations:{
        // 第一个参数是当前Vuex实例的state数据池,第二个参数开始是程序员的传参
        increment(state,value){
            // console.log(state);
            state.sum += value;
        },
        decrement(state,value){
            state.sum -= value;
        }
    },
    // 类似于Vue实例的data数据池
    state:{
        sum:0
    },
    // 类似与Vue实例中的computed数据
    getters:{
        // 参数是Vuex.store.state数据池
        bigSum(state){
            // console.log(state);
            return state.sum*10;
        }
    }
});

在组件中使用Vuex.store实例共享数据

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

<script>
export default {
    name:'Count',
    data(){
        return{
            value:1,
        }
    },
    methods:{
        increment(){
            // console.log(this.value);
            //没有其他的复杂逻辑,直接调用Vuex.mutations中的回调函数
            // 第一个参数是actions回调函数池中回调函数的名称
            this.$store.commit('increment',this.value)
        },
        decrement(){
            this.$store.commit('decrement',this.value)
        },
        incrementOdd(){
            // 第一个参数是mutations回调函数池中毁掉函数的名称
            this.$store.dispatch('incrementOdd',this.value);
        },
        incrementWait(){
            this.$store.dispatch('incrementWait',this.value);

        }
    }
}
</script>

<style>
button{
    margin: 10px;
}
</style>

使用简写方式

  • 导入Vuex的简写依赖import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
使用mapState对获取State数据进行简写
  • 需要给State中的数据起别名时使用对象的简写方式
  • 使用与State中数据的名称时使用数组的简写方式
computed:{
    // 对象的方式
    // ...mapState({sum:'sum'})
    //数组的方式
    ...mapState(['sum'])
}
使用mapGetters对获取store.getters中的计算数据进行简写
computed:{
    // ...mapGetters({bigSum:'bigSum'})
    ...mapGetters(['bigSum'])
}
使用mapActions对dispatch的回调函数调用进行简写
// dispatch简写
// ...mapActions({incrementOdd:'incrementOdd',incrementWait:'incrementWait'})
...mapActions(['incrementOdd','incrementWait'])
使用mapMutations对commit回调函数调用进行简写
// commit简写
// ...mapMutations({increment:'increment',decrement:'decrement'}),
...mapMutations(['increment','decrement'])
完整简写
<template>
  <div>
    <h1>当前的和为:{{sum}}</h1>
    <h3>放大十倍:{{bigSum}}</h3>
    <select v-model.number="value">
        <option :value="1">1</option>
        <option :value="2">2</option>
        <option :value="3">3</option>
    </select>
    <button @click="increment(value)">+</button>
    <button @click="decrement(value)">-</button>
    <button @click="incrementOdd(value)">奇数时+</button>
    <button @click="incrementWait(value)">等一等再加</button>
  </div>
</template>

<script>
// 导入Vuex中map映射的依赖
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
    name:'Count',
    data(){
        return{
            value:1,
        }
    },
    methods:{
        // increment(){
        //     // console.log(this.value);
        //     //没有其他的复杂逻辑,直接调用Vuex.mutations中的回调函数
        //     // 第一个参数是actions回调函数池中回调函数的名称
        //     this.$store.commit('increment',this.value)
        // },
        // decrement(){
        //     this.$store.commit('decrement',this.value)
        // },

        // commit简写
        // ...mapMutations({increment:'increment',decrement:'decrement'}),
        ...mapMutations(['increment','decrement']),

        // incrementOdd(){
        //     // 第一个参数是mutations回调函数池中毁掉函数的名称
        //     this.$store.dispatch('incrementOdd',this.value);
        // },
        // incrementWait(){
        //     this.$store.dispatch('incrementWait',this.value);

        // }

        // dispatch简写
        // ...mapActions({incrementOdd:'incrementOdd',incrementWait:'incrementWait'})
        ...mapActions(['incrementOdd','incrementWait'])

    },
    computed:{
        // 对象的方式
        // ...mapState({sum:'sum'})
        //数组的方式
        ...mapState(['sum']),
        // ...mapGetters({bigSum:'bigSum'})
        ...mapGetters(['bigSum'])
    }
}
</script>

<style>
button{
    margin: 10px;
}
</style>

模块化+名称空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

  3. 在每个模块都开启命名空间

  4. 使用模块化方式创建Vuex.Store实例

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  5. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
  6. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  7. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  8. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    

17.Router插件

  • 安装路由依赖npm install vue-router
  • 路由的切换会导致组件的销毁和创建,我们也可以使用<keep-alive></keep-alive>标签指定需要缓存的组件,避免路由切换导致的数据丢失

配置vue-router路由插件

  • 导入vue和vue-router
  • 在Vue原型上使用vue-router插件
  • 导入需要使用的组件
  • 创建路由实例
import VueRouter from 'vue-router'
import Vue from 'vue'

// 引入组件
import Home from '@/pages/Home.vue'
import About from '@/pages/About.vue'
import News from '@/pages/News.vue'
import Message from '@/pages/Message.vue'
// 使用路由插件
Vue.use(VueRouter)

// 创建VueRouter实例
export default new VueRouter({
    routes:[
        // 一级路由
        {
            // 路由的名称
            name:'home',
            // 路由的路径
            path:'/home',
            // 路由使用的组件
            component:Home,
            // 多级路由
            children:[
            	//二级路由    
                {
                    name:'New',
                    path:'news',
                    component:News
                },
                {
                    name:'Message',
                    path:'Messages',
                    component:Message
                }
            ]
        },
        {
            name:'about',
            path:'/about',
            component:About
        }
    ]
});

使用路由

  • 使用一级路由
<template>
<div>
    <Banner></Banner>
    <div class="row">
        <div class="col-xs-2 col-xs-offset-2">
            <div class="list-group">
                <!-- 路由跳转标签,to属性表示跳转的目标路由,active-class属性控制选中菜单的样式 -->
                <router-link class="list-group-item" active-class="active" to="/about">About</router-link><br>
                <router-link class="list-group-item" to="/home">Home</router-link>
            </div>
        </div>
    </div>
    <div class="row">
        <!-- 路由组件的占位符 -->
        <router-view></router-view>
    </div>
</div>
</template>

<script>
import Banner from '@/components/Banner.vue'
export default {
    name:'MyRouter',
    components:{
        Banner
    }
}
</script>

<style>

</style>
  • 使用二级路由
<template>
         <div class="col-xs-6">
        <div class="panel">
          <div class="panel-body">
            <div>
              <h2>Home组件内容</h2>
              <div>
                <ul class="nav nav-tabs">
                  <li>
                    <!-- 二级路由,to属性需要带上完整路由路径 -->
                    <router-link class="list-group-item" active-calss="active" to="/home/News">News</router-link>
                  </li>
                  <li>
                    <router-link class="list-group-item" active-calss="active" to="/home/Messages">Message</router-link>
                  </li>
                </ul>
              </div>
            </div>
            <!-- 路由组件占位符 -->
            <router-view></router-view>
          </div>
        </div>
      </div>
</template>

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

<style>

</style>

使用路由传参

  • query式传参不需要在路由中进行配置
  • params传参需要在路由中进行额外配置
  • 传参组件
<template>
<div>
    <ul>
        <li v-for="m of messageList" :key="m.id">
            <!-- query参数 -->
            <!-- 路由传参第一种写法,字符串写法 -->
            <!-- <router-link :to="`/home/messages/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->

            <!-- 对象写法 -->
            <!-- <router-link :to="{
                path:'/home/messages/detail',
                query:{
                    id:m.id,
                    title:m.title
                }
            }">{{m.title}}</router-link> -->

            <!-- params参数,需要在路由中配置路径参数的名称 -->
            <!-- 字符串写法,路径参数 -->
            <!-- <router-link :to="`/home/messages/detail/${m.id}/${m.title}`">{{m.title}}</router-link> -->

            <!-- 对象写法 -->
            <!-- 路由使用params式对象传参,必须使用路由的名称,不能使用路由的路径 -->
            <router-link :to="{
                name:'Detail',
                params:{
                    id:m.id,
                    title:m.title
                }
            }">{{m.title}}</router-link>

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

<script>
export default {
    name:'Message',
    data(){
        return{
            messageList:[
                {id:'001',title:'message1'},
                {id:'002',title:'message2'},
                {id:'003',title:'message3'},
            ]
        }
    }
}
</script>

<style>

</style>
  • params传参的路由配置
import VueRouter from 'vue-router'
import Vue from 'vue'

// 引入组件
import Home from '@/pages/Home.vue'
import About from '@/pages/About.vue'
import News from '@/pages/News.vue'
import Message from '@/pages/Message.vue'
import Detail from '@/pages/Detail.vue'
// 使用路由插件
Vue.use(VueRouter)

// 创建VueRouter实例
export default new VueRouter({
    routes:[
        // 一级路由
        {
            // 路由的名称
            name:'home',
            // 路由的路径
            path:'/home',
            // 路由使用的组件
            component:Home,
            // 多级路由
            children:[
                // 二级路由
                {
                    name:'New',
                    path:'news',
                    component:News
                },
                {
                    name:'Message',
                    path:'Messages',
                    component:Message,
                    children:[
                        // 三级路由
                        {
                            name:'Detail',
                            //params传参的路由配置
                            path:'detail/:id/:title',
                            component:Detail
                        }
                    ]
                }
            ]
        },
        {
            name:'about',
            path:'/about',
            component:About
        }
    ]
});
  • 接受参数组件
  • 通过$route对象可以获取路由的传递的参数
<template>
  <div>
    <h3>信息id:{{id}}</h3>
    <h3>信息标题:{{title}}</h3>
  </div>
</template>

<script>
export default {
    name:'Detail',
    data(){
        return{
            // $route对象中存放着路由的传参
            id:this.$route.query.id||this.$route.params.id,
            title:this.$route.query.id||this.$route.params.title
        }
    },
    mounted(){
        // 获取当前路由信息
        console.log(this.$route);
    }

}
</script>

<style>

</style>

路由使用props传参

  • 路由需要声明传递的参数
  • 组建需要使用props进行参数接收
import VueRouter from 'vue-router'
import Vue from 'vue'

// 引入组件
import Home from '@/pages/Home.vue'
import About from '@/pages/About.vue'
import News from '@/pages/News.vue'
import Message from '@/pages/Message.vue'
import Detail from '@/pages/Detail.vue'
// 使用路由插件
Vue.use(VueRouter)

// 创建VueRouter实例
export default new VueRouter({
    routes:[
        // 一级路由
        {
            // 路由的名称
            name:'home',
            // 路由的路径
            path:'/home',
            // 路由使用的组件
            component:Home,
            // 多级路由
            children:[
                // 二级路由
                {
                    name:'New',
                    path:'news',
                    component:News
                },
                {
                    name:'Message',
                    path:'Messages',
                    component:Message,
                    children:[
                        // 三级路由
                        {
                            name:'Detail',
                            path:'detail/:id/:title',
                            component:Detail,
                            // 路由使用props传递参数
                            // 第一种方式,对象写法
                            // props:{id_prop:'001',title_prop:'通过路由prop传递的参数'}

                            // 第二种布尔值的写法,收集路由所有通过params传递的参数,进行prop进行传递
                            // props:true

                            // 第三种写法,函数的写法,该回调函数会将$route对象作为参数,函数的返回值作为传递的数据
                            props($route){
                                return{
                                    id_prop:$route.params.id,
                                    title_prop:$route.params.title
                                }
                            }
                            
                        }
                    ]
                }
            ]
        },
        {
            name:'about',
            path:'/about',
            component:About
        }
    ]
});
  • 组件声明接受路由通过prop传递的数据
<template>
  <div>
    <h3>信息id:{{id}}</h3>
    <h3>信息标题:{{title}}</h3>
     <h3>prop传递的id:{{id_prop}}</h3>
    <h3>prop传递的标题:{{title_prop}}</h3>
  </div>
</template>

<script>
export default {
    name:'Detail',
    // 接受路由通过prop传递的数据
    props:['id_prop','title_prop'],
    data(){
        return{
            // $route对象中存放着路由的传参
            id:this.$route.query.id||this.$route.params.id,
            title:this.$route.query.id||this.$route.params.title
        }
    },
    mounted(){
        // 获取当前路由信息
        console.log(this.$route);
    }
}
</script>

<style>

</style>

route-link标签的replace属性

  • 通过给route-link标签添加replace属性,可以让vue-router跳转时使用新的路由地址替换历史路由栈栈顶的记录
  • 默认是使用push向历史路由栈压入一条新的路由记录

编程式路由

  • 通过$router路由器对象的push或replace函数向路由历史记录栈中添加一条路由记录实现路由的跳转
<template>
<div>
    <ul>
        <li v-for="m of messageList" :key="m.id">
            <!-- query参数 -->
            <!-- 路由传参第一种写法,字符串写法 -->
            <!-- <router-link :to="`/home/messages/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->

            <!-- 对象写法 -->
            <!-- <router-link :to="{
                path:'/home/messages/detail',
                query:{
                    id:m.id,
                    title:m.title
                }
            }">{{m.title}}</router-link> -->

            <!-- params参数,需要在路由中配置路径参数的名称 -->
            <!-- 字符串写法,路径参数 -->
            <!-- <router-link :to="`/home/messages/detail/${m.id}/${m.title}`">{{m.title}}</router-link> -->

            <!-- 对象写法 -->
            <!-- 路由使用params式对象传参,必须使用路由的名称,不能使用路由的路径 -->
            <router-link :to="{
                name:'Detail',
                params:{
                    id:m.id,
                    title:m.title
                }
            }">{{m.title}}</router-link>
            <!-- 编程式路由 -->
            <button @click="pushShow(m)">push实现路由跳转</button><br>
            <button @click="replaceShow(m)">replace实现路由跳转</button>
        </li>
    </ul>
    <div>
        <router-view></router-view>
    </div>
</div>
</template>

<script>
export default {
    name:'Message',
    data(){
        return{
            messageList:[
                {id:'001',title:'message1'},
                {id:'002',title:'message2'},
                {id:'003',title:'message3'},
            ]
        }
    },
    methods:{
        pushShow(m){
            // 使用路由器的$route.push()跳转路由
            this.$router.push({
                // 指定路由的名称
                name:'Detail',
                params:{
                    id:m.id,
                    title:m.id
                }
            });
        },
        replaceShow(m){
            // 使用路由器的$router.replace()跳转路由
            this.$router.replace({
                // 指定路由的名称
                name:'Detail',
                params:{
                    id:m.id,
                    title:m.id
                }
            });
        }
    }
}
</script>
  • 其他路由器函数,实现编程式路由
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退

路由中的新生命周期函数

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

全局(前置/后置)路由守卫

  • 全局前置路由守卫beforeEach((to,from,next)=>{})在进入路由前调用
  • 全局后置路由守卫afterEach(to,from)在进入路由后调用
// 全局前置路由守卫
router.beforeEach((to,from,next)=>{
    // to是目标路由对象,from当前路由对象
    console.log(to,from);
    // 通过to和from对象,我们可以在这里进行权限校验等逻辑操作
    // next是放行函数,不调用则不会切换路由
    next();

});

//全局后置路由守卫
router.afterEach((to,from)=>{
    // to是目标路由对象,from当前路由对象
    console.log(to,from);
    // 后置路由守卫没有next函数,因为已经切换路由,不需要进行放行
    // 我们可以在这里进行一些后续的善后操作,比如设置切换路由后网页的标题
});

独享路由守卫

  • 在路由中配置beforeEnter()函数
{
            name:'about',
            path:'/about',
            component:About,
            // 我们可以在路由meta中设置程序员需要的自定义数据
            meta:{
                // 自定义一个字段,标识需要进行鉴权
                isAthu:true
            },
            // 在路由中配置独享路由守卫,参数与全局路由守卫一致
            beforeEnter(to,from,next){
                // 我们也可以自定义鉴权等逻辑操作
                if(to.meta.isAthu){
                    // 需要鉴权则不允许访问
                    return;
                }
                next();
            }
        }

组件内路由守卫

<template>
<div class="col-xs-6">
    <div class="panel">
        <div class="panel-body">
            <h2>我是About的内容</h2>
        </div>
    </div>
</div>
</template>

<script>
export default {
    name:'About',
    // 组件内进入路由守卫
    beforeRouteEnter(to,from,next){
        //方向
		next();
    },
    // 组件内离开路由守卫
    beforeRouteLeave(to,from,next){
		//方向
        next();
    }
}
</script>

<style>

</style>
  • 完整路由配置
import VueRouter from 'vue-router'
import Vue from 'vue'

// 引入组件
import Home from '@/pages/Home.vue'
import About from '@/pages/About.vue'
import News from '@/pages/News.vue'
import Message from '@/pages/Message.vue'
import Detail from '@/pages/Detail.vue'
// 使用路由插件
Vue.use(VueRouter)

// 创建VueRouter实例
const router =  new VueRouter({
    routes:[
        // 一级路由
        {
            // 路由的名称
            name:'home',
            // 路由的路径
            path:'/home',
            // 路由使用的组件
            component:Home,
            // 多级路由
            children:[
                // 二级路由
                {
                    name:'New',
                    path:'news',
                    component:News
                },
                {
                    name:'Message',
                    path:'Messages',
                    component:Message,
                    children:[
                        // 三级路由
                        {
                            name:'Detail',
                            path:'detail/:id/:title',
                            component:Detail,
                            // 路由使用props传递参数
                            // 第一种方式,对象写法
                            // props:{id_prop:'001',title_prop:'通过路由prop传递的参数'}

                            // 第二种布尔值的写法,收集路由所有通过params传递的参数,进行prop进行传递
                            // props:true

                            // 第三种写法,函数的写法,该回调函数会将$route对象作为参数,函数的返回值作为传递的数据
                            props($route){
                                return{
                                    id_prop:$route.params.id,
                                    title_prop:$route.params.title
                                }
                            }
                        }
                    ]
                }
            ]
        },
        {
            name:'about',
            path:'/about',
            component:About,
            // 我们可以在路由meta中设置程序员需要的自定义数据
            meta:{
                // 自定义一个字段,标识需要进行鉴权
                isAthu:true
            },
            // 在路由中配置独享路由守卫,参数与全局路由守卫一致
            beforeEnter(to,from,next){
                // 我们也可以自定义鉴权等逻辑操作
                if(to.meta.isAthu){
                    // 需要鉴权则不允许访问
                    return;
                }
                next();
            }
        }
    ]
});

// 全局前置路由守卫
router.beforeEach((to,from,next)=>{
    // to是目标路由对象,from当前路由对象
    console.log(to,from);
    // 通过to和from对象,我们可以在这里进行权限校验等逻辑操作
    // next是放行函数,不调用则不会切换路由
    next();

});

//全局后置路由守卫
router.afterEach((to,from)=>{
    // to是目标路由对象,from当前路由对象
    console.log(to,from);
    // 后置路由守卫没有next函数,因为已经切换路由,不需要进行放行
    // 我们可以在这里进行一些后续的善后操作,比如设置切换路由后网页的标题
});

export default router;

路由的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
  3. hash模式:
    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history模式:
    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
  • 配置路由工作模式
// 创建VueRouter实例
const router =  new VueRouter({
    //路由工作模式
    mode:'hash',//hash或者history
    routes:[
        //路由配置
        {},
        {}
    ]
})

Vue3

1.main.js入口文件变化

  • 创建实例使用createApp函数,而不是使用new Vue()的方式
// 引入vue工厂函数
import { createApp } from 'vue'
// 引入组件
import App from './App.vue'

// 使用vue工厂函数创建组件并挂载
createApp(App).mount('#app')

2.setup()配置项

  • vue3添加配置vue实例的方式
  • 我们可以在其中配置vue2中几乎所有的数据配置项,包括data数据池,methods方法池等等
// vue3的新配置项
    setup(){
        // 简单的数据使用ref()函数创建,返回的是refImpl对象
        let name = ref('杨逸');
        let age = ref('20');
        // console.log(name);

        // 使用ref()函数定义对象,底层会去调用reactive()函数帮助创建,返回的是Proxy对象
        const user = ref({
            name:'李四',
            job:'安卓开发'
        });

        // console.log(user);

        // 不仅可以定义数据,还可以定义函数

        function showInfo(){
            console.log('定义在setup中的函数被调用');
        };

        // 修改定义的普通数据
        function update(){
            // 因为创建数据时使用ref()函数返回的是refImpl对象,所以修改时需要使用.value的形式进行修改
            name.value = '修改后的名字';
            age.value = 200;
            console.log('修改ref创建的普通数据');
        };

        //修改对象类型的数据
        function updateObject(){
            // 因为创建对象类型数据使用的reactive()函数,使用修改时只需要使用一层.value就可以拿到对象的数据进行修改
            user.value.name = '对象修改后的name';
            user.value.job = '对象修改后的job';
            console.log('修改reactive创建的对象数据');
        }

        // 最后返回将数据暴露出去
        return{
            name,
            age,
            user,
            showInfo,
            update,
            updateObject
        };

        //第二种返回值,返回reader函数渲染的结果,使用这种方式会替代组件模板中的内容
        // return h => h('渲染内容');
    }
  • vue2中的data和methods配置项同时也可以使用,没有被废除
data(){
        return{
            job:'java开发工程师',
            salary:20000
        }
    },
    methods:{
        // 这里是可以通过this访问到setup中的数据,而setup不能访问到外面的数据
        showMessage(){
            console.log('methods()中的函数被调用');
        }
    }
  • 完整代码
<template>
  <div>
    <h1>Vue3的测试页面</h1>
    <h3>setup()中的普通数据{{ name }}</h3>
    <h3>setup()中的普通数据{{ age }}</h3>
    <h3>setup()中的对象数据{{ user.name }}-{{ user.job }}</h3>
    <button @click="update">修改setup中的普通数据</button><br><br>
    <button @click="updateObject">修改setup中的对象数据</button><br><br>
    <button @click="showInfo">setup()中的函数</button>
    <hr>
    <h3>data()中的数据{{ job }}</h3>
    <h3>data()中的数据{{ salary }}</h3>
    <button @click="showMessage">methods()中的函数</button>
</div>
</template>

<script>
// 导入reader的渲染函数h
// import {h} from 'vue'
//导入ref()函数
import {ref,reactive} from 'vue'
export default {
    name:'Test',
    data(){
        return{
            job:'java开发工程师',
            salary:20000
        }
    },
    methods:{
        // 这里是可以通过this访问到setup中的数据,而setup不能访问到外面的数据
        showMessage(){
            console.log('methods()中的函数被调用');
        }
    },
    // vue3的新配置项
    setup(){
        // 简单的数据使用ref()函数创建,返回的是refImpl对象
        let name = ref('杨逸');
        let age = ref('20');
        // console.log(name);

        // 使用ref()函数定义对象,底层会去调用reactive()函数帮助创建,返回的是包含Proxy对象的refImpl对象
        // const user = ref({
        //     name:'李四',
        //     job:'安卓开发'
        // });

        // 使用reactive()函数直接创建,返回的是Proxy对象
        const user = reactive({
            name:'李四',
            job:'安卓开发'
        });

        console.log(user);

        // 不仅可以定义数据,还可以定义函数

        function showInfo(){
            console.log('定义在setup中的函数被调用');
        };

        // 修改定义的普通数据
        function update(){
            // 因为创建数据时使用ref()函数返回的是refImpl对象,所以修改时需要使用.value的形式进行修改
            name.value = '修改后的名字';
            age.value = 200;
            
            console.log('修改ref创建的普通数据');
        };

        //修改对象类型的数据
        function updateObject(){
            // 因为创建对象类型数据使用的reactive()函数,使用修改时只需要使用一层.value就可以拿到对象的数据进行修改
            // user.value.name = '对象修改后的name';
            // user.value.job = '对象修改后的job';

            //使用reactive()创建的对象是Proxy对象,可以直接使用对应的属性
            user.name = '对象修改后的name';
            user.job = '对象修改后的job';
            console.log('修改reactive创建的对象数据');
        }

        // 最后返回将数据暴露出去
        return{
            name,
            age,
            user,
            showInfo,
            update,
            updateObject
        };

        //第二种返回值,返回reader函数渲染的结果,使用这种方式会替代组件模板中的内容
        // return h => h('渲染内容');
    }

}
</script>

<style>

</style>

ref()函数和refImpl对象

  • 导入ref()函数
import {ref} from 'vue'
  • ref()通过数据劫持实现响应式数据
  • 在vue3使用ref()函数定义普通响应式数据
 // 简单的数据使用ref()函数创建,返回的是refImpl对象
 let name = ref('杨逸');
 let age = ref('20');

// 使用ref()函数定义对象,底层会去调用reactive()函数帮助创建,返回的是包含Proxy对象的refImpl对象
 const user = ref({
     name:'李四',
     job:'安卓开发'
 });

reactive()函数和Proxy对象

  • 导入reactive()函数
import {reactive} from 'vue'
  • reactive()通过代理和反射实现响应式数据
  • 在vue3中使用reactive()函数定义对象(数组)响应式数据
// 使用reactive()函数直接创建,返回的是Proxy对象
const user = reactive({
    name:'李四',
    job:'安卓开发'
});

3.Vue3.0中的响应式原理

vue2.x的响应式

  • 实现原理:

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

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

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

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。

Vue3.0的响应式

4.reactive对比ref

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

5.setup的两个注意点

  • setup执行的时机

    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
      • slots: 收到的插槽内容, 相当于 this.$slots
      • emit: 分发自定义事件的函数, 相当于 this.$emit

6.计算属性与监视

1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    import {computed} from 'vue'
    
    setup(){
        ...
    	//计算属性——简写
        let fullName = computed(()=>{
            return person.firstName + '-' + person.lastName
        })
        //计算属性——完整
        let fullName = computed({
            get(){
                return person.firstName + '-' + person.lastName
            },
            set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })
    }
    

2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。
    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
    	console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
    	console.log('sum或msg变化了',newValue,oldValue)
    }) 
    
    /* 情况三:监视reactive定义的响应式数据
    			若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
    			若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
    */
    watch(person,(newValue,oldValue)=>{
    	console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效
    
    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
    

3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.age
        console.log('watchEffect配置的回调执行了')
    })
    

7.生命周期

vue2.x的生命周期lifecycle_2
vue3.0的生命周期lifecycle_2

1

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
    • beforeDestroy改名为 beforeUnmount
    • destroyed改名为 unmounted
  • Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
    • beforeCreate===>setup()
    • created=======>setup()
    • beforeMount ===>onBeforeMount
    • mounted=======>onMounted
    • beforeUpdate===>onBeforeUpdate
    • updated =======>onUpdated
    • beforeUnmount ==>onBeforeUnmount
    • unmounted =====>onUnmounted

8.自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。

  • 类似于vue2.x中的mixin。

  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

9.toRef

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

  • 语法:const name = toRef(person,'name')

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

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

三、其它 Composition API

1.shallowReactive 与 shallowRef

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

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

  • 什么时候使用?

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

2.readonly 与 shallowReadonly

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

3.toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

4.customRef

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

  • 实现防抖效果:

    <template>
    	<input type="text" v-model="keyword">
    	<h3>{{keyword}}</h3>
    </template>
    
    <script>
    	import {ref,customRef} from 'vue'
    	export default {
    		name:'Demo',
    		setup(){
    			// let keyword = ref('hello') //使用Vue准备好的内置ref
    			//自定义一个myRef
    			function myRef(value,delay){
    				let timer
    				//通过customRef去实现自定义
    				return customRef((track,trigger)=>{
    					return{
    						get(){
    							track() //告诉Vue这个value值是需要被“追踪”的
    							return value
    						},
    						set(newValue){
    							clearTimeout(timer)
    							timer = setTimeout(()=>{
    								value = newValue
    								trigger() //告诉Vue去更新界面
    							},delay)
    						}
    					}
    				})
    			}
    			let keyword = myRef('hello',500) //使用程序员自定义的ref
    			return {
    				keyword
    			}
    		}
    	}
    </script>
    

5.provide 与 inject

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

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

  • 具体写法:

    1. 祖组件中:

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

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

6.响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

四、Composition API 的优势

1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

2.Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

五、新的组件

1.Fragment

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

2.Teleport

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

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

3.Suspense

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

  • 使用步骤:

    • 异步引入组件

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

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

六、其他

1.全局API的转移

  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。

      //注册全局组件
      Vue.component('MyButton', {
        data: () => ({
          count: 0
        }),
        template: '<button @click="count++">Clicked {{ count }} times.</button>'
      })
      
      //注册全局指令
      Vue.directive('focus', {
        inserted: el => el.focus()
      }
      
  • Vue3.0中对这些API做出了调整:

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

      2.x 全局 API(Vue3.x 实例 API (app)
      Vue.config.xxxxapp.config.xxxx
      Vue.config.productionTip移除
      Vue.componentapp.component
      Vue.directiveapp.directive
      Vue.mixinapp.mixin
      Vue.useapp.use
      Vue.prototypeapp.config.globalProperties

2.其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

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

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

  • 移除v-on.native修饰符

    • 父组件中绑定事件

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

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

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。