Vue3全家桶

认识Vue

  • 是一套用于构建用户界面的渐进式JavaScript框架
  • 渐进式框架:表示我们可以在项目中一点点来引入和使用vue,而不一定需要全部使用vue来开发整个项目

如何使用Vue?

  • Vue的本质,就是一个JavaScript的库:
    • 我们可以把它理解成一个已经帮我们封装好的库
    • 在项目中可以引入并且使用它
  • 使用方式:
    • 在页面通过CDN方式引入
    • 下载Vue的JS文件,并且自己手动引入
    • 通过npm包管理工具安装使用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="https://cdn.jsdelivr.net/npm/vue@3.3.11/dist/vue.global.min.js"></script>
    <div id="app"></div>

    <script>
      const app = Vue.createApp({
        template:"<h2>hello world</h2>"
      })
      app.mount("#app")
    </script>
</body>
</html>

案例一:动态数据

const app = Vue.createApp({
        // 插值语法:{{}}
        template:"<h2>{{title}}</h2>",
        data:function(){
            return {
                title:'hello world',
                message:"你好,Vue3"
            }
        }
      })
      app.mount("#app")

案例二:列表数据

 const app = Vue.createApp({
        template:`
            <h2>电影列表</h2>
            <ul>
                <li v-for="item in movies">{{item}}</li>
            </ul>
        `,
        data:function(){
            return {
                title:'hello world',
                movies:['大话西游',"星际穿越","盗梦空间","少年派"]
            }
        }
      })
      app.mount("#app")

案例三:计数器

 const app = Vue.createApp({
            template: `
            <h2>当前计数:{{counter}}</h2>
           <button @click="a">+1</button>
           <button @click="b">-1</button>
        `,
            data: function () {
                return {
                    counter: 0
                }
            },
            methods: {
                a: function () {
                    this.counter++;
                },
                b: function () {
                    this.counter--;
                }
            }
        })
        app.mount("#app")

声明式编程

  • Vue和React一样都是声明式编程
  • 命令式编程关注的是“how to do”,自己完成整个how的过程
  • 声明式编程关注的是“what to do”,由框架通过低层帮助我们完成"how"的过程

MVVM模型

  • MVC和MVVM都是一种软件的体系结构
    • MVC是model-view-controller的简称
    • MVVM是model-view-viewmodel的简称
  • 通常情况下,我们也称Vue是一个MVVM模型
    • 但是Vue官方说,Vue并没有完全遵守MVVM的模型,但是整个设计是受到它的启发的

options选项

data属性

  • data属性时传入一个函数,并且该函数需要返回一个对象:
    • 在Vue2的时候,也可以传入一个对象
    • 在Vue3的时候,必须传入一个函数
  • data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理
    • 所以我们在template或者app中通过{{counter}}访问counter,可以从对象中获取到数据
    • 所以我们修改counter的值时,app中的{{counter}}也会发生变化
<script src="vue.js"></script>
    <div id="app">
        <h2>{{message}}</h2>
        <button @click="change">改变message</button>
    </div>

    <script>
        const app = Vue.createApp({
           data:function() {
            return {
                message:"hello world"
            }
           },
           methods: {
            change:function(){
                this.message = "helo data"
            }
           }
        })
        app.mount("#app")
    </script>

methods属性

  • methods属性是一个对象,通常我们会在这个对象中定义很多的方法:
    • 这些方法可以被绑定在模版中
    • 在该方法中,我们可以使用this关键字来直接访问到data中返回的对象中的属性

问题一:不能使用箭头函数?

  • 官方解释:
    • 箭头函数绑定了父级作用域的上下文,所以this将不会按照期望指向组件实例,this.counter将会是undefined
  • 使用箭头函数,this会指向window

问题二:不使用箭头函数的情况下,this到底指向的是什么?

  • 事实上Vue的源码当中就是对methods中的所有函数进行了遍历,并且通过bind绑定了this

模版语法

  • React的开发模式:
    • React使用的jsx,所以对应的代码都是编写的类似于js的一种语法
    • 之后通过Babel将jsx编译成React.createElement函数调用
  • Vue也支持jsx的开发模式
    • 但是大多数情况下,使用基于HML的模版语法
    • 在模版中,允许开发者以声明式的方式将DOM和底层组件实例的数据绑定在一起
    • 在底层的实现中,Vue将模版编译成虚拟DOM渲染函数

插值语法

  • 插值语法也就是双大括号语法将数据显示到模版中
  • 插值语法不仅仅可以是data中的属性,也可以是一个JS的表达式或者methos中的函数

v-once指令

  • v-once用于指定元素或者组件只渲染一次
    • 当数据发生变化时,元素或者组件以及所有的子元素将被视为静态元素并且跳过
    • 该指令用于性能优化
    • 子元素也会被渲染一次

v-text指令

  • 用于更新元素的textContent
  • 类似插值语法的作用

v-html

  • 默认情况下,如果我们展示的内容本身是html的,那么vue并不会对齐进行特殊的解析
    • 如果我们希望这个内容可以被Vue解析出来,那么可以使用v-html来展示
    <script src="vue.js"></script>
    <div id="app">
        <h2>{{message}}</h2>
        <h2 v-html="message"></h2>
    </div>

    <script>
        const app = Vue.createApp({
           data:function() {
            return {
                message:`<span>helo v-html</span>`
            }
           },
        })
        app.mount("#app")
    </script>

v-pre

  • v-pre用于跳过元素和它的子元素的编译过程,显示原始的插值语法标签
    • 跳过不需要编译的节点,加快编译的速度

v-cloak

  • 这个指令保持在元素上直到关联组件实例结束编译
    • 和CSS一起使用时,可以隐藏未编译的插值语法标签直到组件实例准备完毕
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    [v-cloak] {
        display: none;
    }
</style>
<body>
    <script src="vue.js"></script>
    <div id="app">
        <h2 v-cloak>{{message}}</h2>
    </div>

    <script>
       setTimeout(()=> {
        const app = Vue.createApp({
           data:function() {
            return {
                message:`helo world`
            }
           },
        })
        app.mount("#app")
       },500)
    </script>
</body>

</html>

v-memo

  • 类似于React的memo函数,用于性能优化
<!-- 只有name发生改变的时候才重新渲染 -->
    <div v-memo="[name]">
        <h2>{{name}}</h2>
        <h2>{{age}}</h2>
        <h2>{{height}}</h2>
    </div>

v-bind绑定属性

  • 某些属性我们也希望动态来绑定
    • 比如动态绑定a元素的href伤心
    • 比如动态绑定img元素的src属性

绑定基本属性

  • v-bind用于绑定一个或多个属性值,或者向另一个组件传递prop值
  • 语法糖写法:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="vue.js"></script>
    <div id="app">
       <img v-bind:src="imgURL" alt="">
       <a v-bind:href="href">百度</a>
    </div>

    <script>
        const app = Vue.createApp({
           data:function() {
            return {
                message:`helo world`,
                imgURL:"https://img1.baidu.com/it/u=1546227440,2897989905&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
                href:"http://www.baidu.com"
            }
           },
        })
        app.mount("#app")
    </script>
</body>

</html>

绑定class

  • 在开发中,有时候我们的元素class也是动态的,比如:
    • 当数据为某个状态时,字体为红色
    • 当数据为另一个状态时,字体显示黑色
  • 动态绑定的class可以和普通class同时使用
  • 对象语法:我们可以传给:class一个对象,以动态的切换class
  • 数组语法:我们可以把一个数组传给:class,以应用一个class列表
<style>
    .active{
        color: red;
    }
</style>
<body>
    <script src="vue.js"></script>
    <div id="app">
        <h2 :class="classes">hello world</h2>

        <button :class="isActive?'active':''" @click="btn">按钮</button>
        <!-- 对象语法 -->
        <button :class="{active:isActive}" @click="btn">按钮</button>
        <!-- 数组语法 -->
        <div :class="['a',{active:isActive}]">哈哈哈</div>
    </div>

    <script>
        const app = Vue.createApp({
           data:function() {
            return {
                message:`helo world`,
                classes:"abc cba",
                isActive:false
            }
           },
           methods: {
            btn:function(){
                this.isActive = !this.isActive
            }
           }
        })
        app.mount("#app")
    </script>
</body>

绑定style属性

  • 我们可以使用v-bind:style来绑定一些CSS内联样式:
    • 因为某些样式我们需要根据数据动态来决定
  • CSS属性名使用驼峰式或哦者短横线分隔(记得用引号括起来)来命名
      <!-- 动态绑定style,后面跟上一个对象类型 -->
        <h2 :style="{color:fontColor,fontSize:'30px'}">hhh</h2>

动态绑定属性

  • 某些情况下,我们需要动态绑定属性名
    • 如果属性名是不固定的,我们可以使用:[属性名]="值"的格式来定义
  <!-- 属性的名称是动态的 -->
        <div :[name]="value">{{message}}</div>

v-on绑定事件

  • 前端开发中,需要经常和用户进行各种各样的交互
    • 需要监听用户发生的事件,比如点击、拖拽、键盘事件等
    • 在Vue中使用v-on指令监听事件
   <!-- 完整的写法 -->
        <div class="box" v-on:click="btn"></div>
        <!-- 语法糖写法 -->
        <div class="box" @click="btn"></div>
        <!-- 元素绑定多个方法 -->
        <div v-on="{click:btn, mousemove:divMousemove}"></div>

v-on 传递参数

  <!--默认会传递event对象 -->
        <button @click="btn"></button>
        <!-- 只有自己的参数 -->
        <button @click="btn("why")"></button>
        <!-- 传递自己的参数时,还需要event对象,使用$event -->
        <button @click="btn("why",$event)"></button>

v-on修饰符

  • v-on支持修饰符,修饰符相当于对事件进行了一些特殊的处理
    • .stop 调用event.stopPropagation()
    • .prevent 调用event.preventDefault()
    • .capture 添加事件侦听器使用capture模式
    • .self 只当事件是从侦听器绑定的元素本身触发时才触发回调
    • .{keyAlias} 仅当事件是从特定键触发时才触发回调
    • .once 只触发一次回调
    • .left 只当点击鼠标左键触发
    • .right 只当点击鼠标右键时触发
    • .middle 只当点击鼠标中键时触发
    • .passive {passive:true}模式添加侦听器
<button @click.stop="btn">button</button>

条件渲染

  • Vue提供了下面的指令进行条件判断
    • v-if
    • v-else
    • v-else-if
    • v-show
<body>
    <script src="vue.js"></script>
    <div id="app">
        <div v-if="Object.keys(info).length">
            <h2>个人信息</h2>
            <ul>
                <li>姓名:{{info.name}}</li>
                <li>年龄:{{info.age}}</li>
            </ul>
        </div>

        <div v-else>
            <h2>没有信息</h2>
        </div>


        <h2 v-if="score > 90">1</h2>
        <h2 v-else-if="score > 80">2</h2>
        <h2 v-else>3</h2>
    </div>

    <script>
        const app = Vue.createApp({
            data: function () {
                return {
                    message: `helo world`,
                    info: {
                        name: "gpl",
                        age: 18
                    },
                    score: 0
                }
            }
        })
        app.mount("#app")
    </script>
</body>

template元素

  • 因为v-if是一个指令,所以必须将其添加到一个元素上:
    • 此时我们渲染div,但是我们并不希望div这种元素被渲染
    • 这个时候,我们可以选择template
  • template元素可以被当作不可见的包裹元素

v-show

  • 类似v-if,也是根据一个条件决定是否显示元素或者组件
<template id="my-app>
	<h2 v-show="isShow">hhhh</h2>
</template>
  • 和v-if的区别
    • 不支持template,因为template没有display样式来切换
    • 不可以和v-else一起使用
  • 本质区别
    • v-show元素无论是否显示到浏览器上,它的DOM实际都是存在的,只是同多CSS的display来切换
    • v-if当条件为false时,其对应的原生根本不会被渲染到DOM中
  • 开发中选择
    • 如果我们的元素需要在显示和隐藏之间频繁切换,使用v-show
    • 否则使用v-if

v-for 列表渲染

  • 遍历数组
    • 格式:“(item,index) in 数组”
  • 遍历对象
    • 格式:v-for=“(value,key,index) in obj”
  • 遍历字符串
    • v-for = “item in message”
  • 遍历数字
    • v-for = “item in 10”
  • 最外层可以使用template元素

数组更新检测

  • Vue将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新
  • 这些被包裹的方法包括:
  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
  • 不修改原数组的方法不被侦听

v-for中的key是什么作用?

  • 在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性
<li v-for="item in letters" :key="item">{{item}}</li>
  • 这个key属性有什么作用呢?
    • 官方解释: key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes
    • 不使用key,Vue会使用一种最大限度减少动态元素并且尽可能尝试就地修改/复用相同类型元素的算法
    • 而使用key采用另一种算法,会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素

认识VNode

  • VNode全称是Virtual Node,也就是虚拟节点
  • 无论是组件还是元素,在Vue中表现出来的都是一个个VNode
  • VNode本质是一个JS对象
<div class="title" style="font-size:30px; color: red;">helo world</div>
const vnode = {
            type:"div",
            props:{
                class:"title",
                style:{
                    class:"title",
                    style:{
                        "font-size":"30px",
                        color:"red"
                    }
                }
            },
            children:"hahaha"
        }

在这里插入图片描述

虚拟DOM

  • 多个VNode,会形成一个VNode Tree
  • 虚拟DOM的作用
    • 使用diff算法
    • 实现跨平台
      在这里插入图片描述

key在列表元素中间插入新元素的作用

  • 在Vue中,对于相同父元素的子元素节点并不会重新渲染整个列表
  • 因为对于列表中a、b、c它们都是没有变化的
  • 在操作真实DOM时,只需要插入一个f的li即可
  • 有key时,vue调用patchKeyedChildren方法
  • 没有key时,使用patchUnkeyedChildren方法
    在这里插入图片描述

Vue的compouted

复杂data的处理方式

  • 我们可以通过插值语法显示一些data中的数据
    • 在模版中放入太多的逻辑会让模版过重和难以维护
    • 并且如果多个地方都使用到,会有大量重复的代码
  • 我们有什么办法可以将逻辑抽离出去呢?
    • 将逻辑抽离到method中
    • 使用计算属性computed

认识计算属性computed

  • 什么是计算属性?
    • 对于任何包含响应式数据的复杂逻辑,都应该使用计算属性
  • 计算属性将被混入到组件实例中
    • 所有getter和setter的this上下文自动地绑定为组件实例(即可以使用this访问数据)
 <div id="app">
        <h2>{{fullname}}</h2>
    </div>
    <script>
        const app = Vue.createApp({
            data: function () {
                return {
                    a: "a",
                    b: "b",
                }
            },
            computed: {
                // 1.计算属性默认对应的是一个函数
                fullname() {
                    return this.a + this.b
                }
            }
        })
        app.mount("#app")

    </script>

computed vs methods

  • 计算属性是有缓存的
    • 测试:如果在函数中添加console.log,会发现methods会多次执行,而计算属性只执行一次,这是vue做的性能优化
    • 计算属性会基于它们的依赖关系进行缓存
    • 在数据不发生变化时,计算属性时不需要重新计算的
    • 但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算

计算属性的set和get写法

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>

</style>

<body>
    <script src="vue.js"></script>
    <div id="app">
        <h2>{{fullname}}</h2>
        <button @click="setFullname">button</button>
    </div>

    <script>
        const app = Vue.createApp({
            data: function () {
                return {
                    a: "a",
                    b: "b",
                }
            },
            computed: {
                // 语法糖写法
                // fullname(){
                //     return this.a + this.b
                // }
                // 完整写法
                fullname: {
                    get: function () {
                        return this.a + this.b
                    },
                    set: function (value) {
                        const names = value.split(" ")
                        this.a = names[0]
                        this.b = names[1]
                    }
                }
            },
            methods: {
                setFullname() {
                    this.fullname = "helo world"
                }
            },
        })
        app.mount("#app")

    </script>
</body>

</html>

认识侦听器watch

  • data返回的对象中定义了数据,这个数据通过插值语法绑定到template中
  • 当数据变化时,template会自动进行更新
  • 某些情况下,我们希望在代码逻辑中监听某个数据的变化,就需要watch来实现
  <div id="app">
        <h2>{{message}}</h2>
        <button @click="change">button</button>
    </div>

    <script>
        const app = Vue.createApp({
            data: function () {
                return {
                    message:"hello world"
                }
            },
            methods: {
               change(){
                this.message = "hello watch"
               }
            },
            watch:{
                message(){
                    console.log('message发生了变化');
                }
            }
        })
        app.mount("#app")

    </script>

v-model

  • 表单提交是开发中非常常见的功能
  • 要求我们可以在代码逻辑中获取到用户提交的数据,通常使用v-model指令来完成
    • v-model指令可以在表单input、textarea以及select中创建双向数据绑定
    • 会根据控件类型自动选取正确的方法来更新元素
    • 本质上不是语法糖,负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊操作
 <div id="app">
        <h2>{{message}}</h2>
        <input type="text" v-model="message">
    </div>

v-model的原理

  • 官方解释:v-model的原理其实就是背后两个操作
    • v-bind绑定value属性的值
    • v-on绑定input事件监听到函数,函数会获取最新的值赋值到绑定的属性中
<input v-model = "searchText" />
<input :value="searchText" @input="searchText = $event.target.value"/>

v-model修饰符-lazy

  • 加上lazy修饰符,那么会将绑定的事件切换成change事件,只有在提交时(回车)才会触发

v-model修饰符-number

  • v-model绑定后的值总是string类型
  • 我们希望转换成数字类型,那么可以使用.number修饰符

v-model修饰符-trim

  • 去掉首尾的空格

Vue组件化开发基础

Vue组件化开发思想

  • 一个页面拆分成一个个小的功能块,每个功能块完成自己的独立的功能,那么之后整个页面的管理和维护就变得非常容易
  • 我们需要通过组件化的思想来思考整个应用程序
    • 一个完整的页面拆分成多个组件
    • 每个组件实现一个功能块
    • 每个组件又进行细分
    • 组件本身可以复用

注册Vue的全局组件

  • 全局组件:在任何其他的组件中都可以使用的组件
  • 注册方式:
    • 需要使用我们全局创建的app来注册组件
    • 通过component方法传入组件名称、组件对象即可注册一个全局组件了
    • 之后,我们可以在APP组件的template中直接使用这个全局组件
  • 使用app.component注册第一个组件时,第一个参数是组件的名称,定义组件名的方式有两种
    • 方式一:使用短横线分割符
    • 方式二:使用大驼峰标识
 <script src="vue.js"></script>
    <div id="app">
        <h2>{{message}}</h2>
        <input type="text" v-model="message">
        <product-item></product-item>

    </div>
    <script>
        // 1.组件:App组件(根组件)
        const App = {
            data: function () {
                return {
                    message: "hello world"
                }
            }
        }
        // 2.开发product-item的组件
        const productItem = {
            template: `<div class="product">
                        <h2>我是商品</h2>
                        </div>`

        }
        const app = Vue.createApp(App)
        // 使用app注册一个全局组件
        app.component("product-item", productItem)

        app.mount("#app")

    </script>

注册Vue的局部组件

  • 局部组件:只有在注册的组件中才能使用的组件
  • 全局组件往往在应用程序一开始就会全局组件完成,那么就意味如果某些组件我们没有用到,也会一起被打包
    • 比如我们注册了三个全局组件:ComponentA ComponentB ConponentC
    • 在开发中我们只使用A和B,没有用到C但是我们依然在全局进行了注册,那么webpack也会对其进行打包
    • 会增加用户下载对应的JS是的包大小
  • 所以开发中通常注册的是局部组件
    • 局部注册由我们需要使用到的组件中,通过components属性选项来进行注册
  <script src="vue.js"></script>
    <div id="app">
        <h2>{{message}}</h2>
        <Home-Nav></Home-Nav>
        <product-item>
            <!-- 不显示HomeNav组件 -->
            <!-- <HomeNav></HomeNav> -->
        </product-item>
    </div>

    <script>
        // 1.组件:App组件(根组件)
        const App = {
            components:{
                productItem:{
                    template: `<div class="product">
                        <h2>我是商品</h2>
                        </div>`
                },
                HomeNav:{
                    template: `<div class="product">
                        <h2>HomeNav</h2>
                        </div>`
                }
            },
            data: function () {
                return {
                    message: "hello world"
                }
            }
        }

        const app = Vue.createApp(App)

        app.mount("#app")

    </script>
</body>

Vue的开发模式解析

  • 目前的开发模式存在很多问题
  • 真实开发中,我们可以通过后缀名为.vue的单文件组件来解决问题,并且通过打包工具对其进行处理

  • 单文件的特点
    • ES6、CommonJS的模块化能力
    • 组件作用域的CSS
    • 可以使用预处理器来构建更加丰富的组件,比如TS、Babel、Less等
<template>
	<h2></h2>
</template>
<script>
	module.exports = {
		data:function () {
			return {
			
		}
	}
</script>
<style scoped>
	p{
		font-size:2em;
	}
</style>

Vue项目脚手架生成

  • 方式一:安装Vue CLI 使用vuecli 生成项目(基于webpack)
vue create app
  • 方式二: 安装并执行 create-vue,它是 Vue 官方的项目脚手架工具(基于vite)
 npm create vue@latest
  • 其中jsconfig.json的作用:
    • 给VSCode来进行读取,VSCode在读取到其中的内容时,给我们的代码更加友好的提示
<!-- <script>
export default {
 data() {
   return {
     title: "hello world"
   };
 }
};
</script> -->
<!-- 使用<script setup>时,不再需要像传统的Vue组件那样显式声明data属性。<script setup>是一种简化的语法,Vue.js 3.0中引入的特性,它会自动将模板中使用的变量注入到组件中。 -->
<script setup>
 const title = "hello world"
</script>

<template>
 <h2>{{ title }}</h2>
</template>

<style scoped>
/* 在这里可以添加组件的样式 */
</style>
import './assets/main.css'

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

const app = createApp(App)

app.use(router)

app.mount('#app')

组件间通信

父子组件之间通信的方式

  • 父子组件之间如何进行通信呢?
    • 父传子:通过props属性
    • 子传父:通过$emit触发事件

父传子

  • 什么是props
    • 是你可以在组件上注册的一些自定义的attribute
    • 子组件通过attribute的名称获取到对应的值
  • props有两种常见的用法:
    • 方式一:字符串数,数组中的字符串就是attribute的名称
    • 方式二:对象类型,我们可以指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等

<script>
import Showinfo from './components/Showinfo.vue';
export default {
  components:{
    // 'show-info': Showinfo,
    Showinfo
  },
  data() {
    return {
      title: "hello world"
    };
  }
};
</script>
<template>
  <showinfo name="a" age="18" height="1.88"></showinfo>
  <showinfo name="b" age="28" height="1.90"></showinfo>
</template>

<style scoped>
/* 在这里可以添加组件的样式 */
</style>
<template>
    <div class="infos">
        <h2>姓名:{{ name }}</h2>
        <h2>年龄:{{ age }}</h2>
        <h2>身高:{{ height }}</h2>
    </div>
</template>
<script>
    export default {
        // 接受父组件传递过来的值
       // props:["name","age","height"]
		 props:{
            name:{
                type:String,
                required:true,
                default:"default"
            },
            age:{
                type:Number,
                default:0
            },
            height:{
                type:Number,
                default:2
            }
        }
    }
</script>
<style scoped>
</style>
props对象类型的细节
  • 如果默认值是对象或者数组必须从一个工厂函数获取
props:{
	 friend:{
                type:Object,
                default:() => {{name:"james"}}
            },
      friend2:{
                type:Array,
                default:() => {["a","b"]}
            }
}

子传父

  • 什么情况下子组件需要传递内容给父组件?
    • 当子组件有一些事件发生时,父组件需要切换内容
    • 子组件有一些内容想要传递给父组件的时候
  • 如何传递
    • 现在子组件中定义好在某些情况下触发的事件名称
    • 在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中
    • 在子组件发生某个事件的时候,根据事件名称触发对应的事件
<template>
    <div class="add">
        <button @click="btnClick(1)">+1</button>
        <button @click="btnClick(5)">+5</button>
        <button @click="btnClick(10)">+10</button>
    </div>
</template>
<script>
export default {
    methods: {
        btnClick(count) {
            // 让子组件发出去一个自定义事件
            // 第一个参数是我们的自定义名称
            // 第二个参数是我们传递的参数
            this.$emit("add", count)
        }
    }
}
</script>
<template>
    <div class="sub">
        <button @click="btnClick(-1)">-1</button>
        <button @click="btnClick(-5)">-5</button>
        <button @click="btnClick(-10)">-10</button>
    </div>
</template>
<script>
export default {
    methods: {
        btnClick(count) {
            this.$emit("sub", count)
        }
    }
}
</script>
<script>
import AddCounter from './components/AddCounter.vue';
import SubCounter from './components/SubCounter.vue';
export default {
  components: {
    AddCounter,
    SubCounter
  },
  data() {
    return {
      counter: 0
    };
  },
  methods: {
    addBtnClick(count) {
      this.counter += count
    },
    subBtnClick(count) {
      console.log(count);
      this.counter += count
    }
  }
};
</script>
<template>
  <div class="app">
    <h2>当前计数:{{ counter }}</h2>
    <add-counter v-on:add="addBtnClick"></add-counter>
    <sub-counter @sub="subBtnClick"></sub-counter>
  </div>
</template>

<style scoped>
/* 在这里可以添加组件的样式 */
</style>

非父子组件通信

Provide/Inject
  • Provide/Inject用于非父子组件之间共享数据:
    • 比如有一些深度嵌套的组件,子组件想要获取父组件的部分内容
    • 在这种情况下,如果我们仍然将props沿着组件链逐级传递下去,就会非常麻烦
  • 父组件有一个provide选项来提供数据
  • 子组件有一个inject选项来开始使用这些数据
<template>
    <div>
        <h2>Home</h2>
        <home-banner></home-banner>
    </div>
</template>

<script>
import HomeBanner from './HomeBanner.vue';
export default {
    components: {
        HomeBanner
    }
}
</script>

<style scoped></style>
<template>
    <h2>
        HomeBanner:{{ name }} {{ age }}
    </h2>
</template>

<script>
export default {
    inject: ["name", "age"]
}
</script>

<style scoped></style>
<script>
import Home from './components/Home.vue';
import { computed } from 'vue'; // 需要导入computed函数
export default {
  components: {
    Home
  },
  data() {
    return {
      massage: "helo app"
    }
  },
  provide() {
    return {
      name: "why",
      age: 18,
      massage: this.message,
      // 下面这种写法,可以实现子组件中更改对应的值,
      // 返回一个ref对象,需要取出其中的value来使用
      message2: computed(() => this.message)
    }
  }
};
</script>
<template>
  <div class="app">
    <home></home>
  </div>
</template>

<style scoped>
/* 在这里可以添加组件的样式 */
</style>
全局事件总线
  • Vue3中移除了 o n 、 on、 onoff和$once方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库:
    • Vue3官方有推荐一些库,例如mitt和tiny-emitter
    • 这里使用hy-event-store

  1. npm install hy-event-store
  2. utiles文件夹的event-bus.js
import {HYEventBus} from "hy-event-store"

const eventBus = new HYEventBus()

export default eventBus
  1. components文件夹中的HomeBanner.vue
<template>
    <h2>HomeBanner</h2>
    <button @click="bannerBtnClick">banner</button>
</template>

<script>
import eventBus from '../utils/event-bus'
export default {
   methods:{
    bannerBtnClick(){
        eventBus.emit("why", 18)
    }
   }
}
</script>

<style scoped>
</style>
  1. App.vue
<script>
import Home from './components/Home.vue';
import eventBus from './utils/event-bus'
export default {
  components: {
    Home
  },
  data() {
    return {
      massage: "helo app"
    }
  },
  methods: {
    whyEvent(args) {
      console.log("app中监听", args)
    }
  },
  created() {
    // 事件监听
    eventBus.on("why", this.whyEvent)
    // 在组件卸载的时候,取消事件
    eventBus.off("why", this.whyEvent)
  }
};
</script>
<template>
  <div class="app">
    <home></home>
  </div>
</template>

<style scoped>
/* 在这里可以添加组件的样式 */
</style>

插槽Slot

插槽Slot的作用

  • 在开发中,我们经常封装一个个可复用的组件:
    • 为了让这个组件更加具有通用性,我们不能将组件中的内容限制为固定的div、span等元素
    • 比如某些情况下我们使用组件,希望组件显示是一个按钮或者一张图片
    • 我们希望让使用者来决定某一块区域到底存放什么内容和元素
  • 这个时候我们就可以来定义插槽Slot
    • 插槽的使用过程其实是抽取共性、预留不同
    • 我们会将共同的元素、内容依然在组件内进行封装
    • 同时会将不同的元素作为占位,让外部决定到底显示什么样的元素
  • 如何使用插槽slot?
    • Vue中将元素作为承载分发内容的出口
    • 在封装组件中,使用特殊的元素就可以作为封装组件开启一个插槽
    • 该插槽插入什么内容取决于父组件如何使用

插槽Slot的基本使用

<template>
    <h1>{{ title }}</h1>
    <div class="content">
        <slot>
            <p>插槽默认内容</p>
        </slot>
    </div>
</template>
<script>
export default {
    props: {
        title: {
            type: String,
            default: " "
        },
        content: {
            type: String,
            default: " "
        }
    }
}
</script>
<style scoped>
</style>
<script>
import ShowMessage from './components/ShowMessage.vue';
export default {
  components: {
    ShowMessage
  },
  data() {
    return {
      massage: "helo app"
    }
  },
};
</script>
<template>
  <div class="app">
    <show-message title="slot" content="插槽">
      <button>我是按钮元素</button>
    </show-message>
    <show-message title="slot2" content="插槽2">
      <a href="http://www.baiud.com">百度一下</a>
    </show-message>
    <show-message title="slot3" content="插槽3">
    </show-message>
  </div>
</template>

<style scoped>
/* 在这里可以添加组件的样式 */
</style>

具名插槽Slot使用

  • 当有多个插槽时,我们希望插槽有对应的显式,这个时候我们就可以使用具名插槽
    • 给插槽起一个名字,元素有一个特殊的attribute:name
    • 一个不带name的slot,会带有隐含的名字default
  • 动态插槽名
    • 我们可以通过v-slot:[name]方式动态绑定一个名称
<template>
    <div class="nav-bar">
        <div class="left">
            <slot name="left">left</slot>
        </div>
        <div class="center">
            <slot name="center">center</slot>
        </div>
        <div class="right">
            <slot name="right">right</slot>
        </div>
    </div>
</template>
<template>
  <div class="app">
    <show-message title="slot" content="插槽">
      <!-- 插入具名插槽 -->
      <template v-slot:left>
        <button>返回</button>
      </template>
      <template v-slot:center>
        <span>内容</span>
      </template>
      <template v-slot:right>
        <a href="#">登录</a>
      </template>
    </show-message>
  </div>
</template>

作用域插槽Slot使用

  • 渲染作用域:
    • 无论是父组件自己的tamplate或者是子组件插槽的tamplate,只要是在父级模版中,那么都是在父级作用域中编译的
    • 子模版中的所有内容都是在子作用域中编译的
  • 作用域slot:
    • 即在父组件自定义插槽时,可以使用子组件插槽标签中传递过来的数据
  <div>
        <slot :item="item" >
            <span>{{ item }}</span>
        </slot>
    </div>
 <show-message title="slot" content="插槽">
      <template #default="props">
          <button>{{ props.item }}</button>
      </template>
    </show-message>

  • 如果我们的插槽是默认插槽default
    • 那么在使用的时候v-slot:default="slotProps"可以简写成v-slot=“slotProps”
    • 组件的标签可以被当作插槽的模版来使用,即可以省略template,直接将v-slot直接用在组件上

组件化的额外知识补充

组件的生命周期

  • 什么是生命周期?
    • 每个组件都可能经历从创建、挂载、更新、卸载等一系列过程
    • 在这个过程中的某一个阶段,我们可能会想要添加一些属于自己的代码逻辑(比如组件创建完就请求一些服务器数据)
  • 生命周期函数:
    • 生命周期函数是一些钩子函数(回调函数),在某个时间被Vue源码内部进行回调
    • 通过对生命周期的回调,我们可以知道目前组件正在经历什么阶段
    • 也可以在该生命周期函数中编写属于自己的逻辑代码
      在这里插入图片描述

组件中的ref引用($refs)

  • 某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例
    • 在Vue开发中是不推荐进行原生DOM操作的
    • 这个时候,我们可以给元素或者组件绑定一个ref的attribute属性
  • 组件实例也有一个$refs属性:
    • 它一个对象Object,持有注册过ref attribute的所有DOM元素和组件实例
<template>
  <div class="app">
    <h2 ref="title">hello world</h2>
    <button ref="btn" @click="change">修改title</button>
    <banner ref="banner"></banner>
  </div>
</template>

<script>
import Banner from './components/Banner.vue';
export default {
  methods: {
    change() {
      // 获取h2/button元素
      console.log(this.$refs.title, this.$refs.btn)
      // 获取组件实例
      // 可以在父组件中调用子组件的对象方法
      console.log(this.$refs.banner)

      // 获取banner组件中的元素
      console.log(this.$refs.banner.$el)
    }
  },
  components: {
    Banner
  }
}
</script>
<style scoped></style>

动态组件的使用

  • 比如我们想实现一个功能:
    • 点击一个tab-bar,切换不同的组件显示
  • 有两种不同的实现思路来实现
    • 方式一:通过v-if来判断,显示不同的组件
    • 方式二:动态组件的方式

  • 动态组件使用component组件,通过一个特殊的attribute is来实现
<div class="app">

    <!-- 方式一: -->
    <template v-if="index == 0">
      <banner>1</banner>
    </template>
    <template v-else="index==2">
      <banner>2</banner>
    </template>

    <!-- 方式二 -->
    <!-- is中的组件需要来自全局注册的组件或者局部注册的组件 -->
    <!-- tabs是包含需要显示的组件的数组 -->
    <component :is="tabs[index]"></component>
  </div>

keep-alive的使用

  • 当我们切换组件时,为了避免组件频繁的创建和卸载,可以使用keep-alive
  • 对于缓存的组件来说,再次进入时,我们呢不会执行created或者mounted等生命周期的
    • 但是我们希望监听到何时重新进入到了组件,何时离开了组件
    • 这个时候可以使用activated和deactivated这两个生命周期钩子函数来监听
  • keep-alive有一些属性:
    • include: 只有名称匹配的组件会被缓存
    • exclude:任何名称匹配的组件都不会被缓存
    • max:最多可以缓存多少组件实例,一旦到达这个数字,那么缓存组件中没有被访问的实例会被销毁
 <!-- 使用keep-alive包裹让组件不用频繁创建和卸载 -->
    <!-- include:组件的名称来自于组件定义的name选项 -->
    <keep-alive include="home,about">
      <component :is="tabs[index]"></component>
    </keep-alive>

异步组件的使用

  • 默认的webpack打包过程:
    • 默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将组件模块打包到一起(比如一个app.js文件中)
    • 这个时候随着项目不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢
  • 所以需要在打包时对代码进行分包
    • 对于一些不需要立即使用的组件,单独对他们进行拆分,拆分成一些小的代码块chunk.js
    • 这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容

  • 使用import函数进行分包处理
// import函数可以让webpack对导入文件进行分包处理
import("./utils/math").then(res => {
    res.sum(20,30)
})
  • 实现异步组件
import { defineAsyncComponent } from 'vue';
// import Banner from './components/Banner.vue';
// 对组件进行分包处理
const AsyncBanner = defineAsyncComponent(() => import("./components/Banner.vue"))

组件的v-model

  • 在input中可以使用v-model来完成双向绑定
    • v-model默认帮助我们完成了两件事:v-bind:value的数据绑定和@input的数据监听
  • 组件上使用v-model
<my-input v-model="message"/>
<my-input :model-value="message" @update:model-value="messag = $event"></my-input>

组件的混入Mixin

  • 目前组件化开发中,组件和组件有时候存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取
  • Vue2和Vue3中都支持的一种方式就是使用Mixin来完成
    • Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能
    • 一个Mixin对象可以包含任何组件选项
    • 当组件使用Mixin对象时,所有Mixin对象的选项将被混入进入组件本身的选项中
  • Mixin的合并规则
  • 情况一:data函数的返回对象
    • 返回值对象默认情况下会进行合并
    • 如果data返回值对象的属性发生了冲突,那么会保留组件自身的数据
  • 情况二:生命周期钩子函数
    • 会被合并到数组中,都会被调用
  • 情况三:值为对象的选项,例如methos、components和directives,将被合并为同一个对象
    • 比如都有methods选项,并且都定义了方法,那么它们都会生效
    • 但是如果对象的key相同,那么会取组件对象的键值对

  • 全局混入
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
// import router from './router'
// import函数可以让webpack对导入文件进行分包处理

const app = createApp(App)

// app.use(router)
// 全局混入
app.mixin({
    created() {
        console.log("mixin created");
    }
})
app.mount('#app')
  • 局部混入
export default {
    data () {
        return {
            message:"hello world"
        }
    },
    created() {
        console.log("message",this.message)
    }
}
<template>
    <h2>Home</h2>
</template>

<script>
import messageMixin from "../mixins/message-mixin"
    export default {
        // data() {
        //     return {
        //         message:"hello world"
        //     }
        // }
        mixins:[messageMixin]
    }
</script>

<style scoped>
</style>

Vue3 Composition API

认识CompositionAPI

  • 在Vue2中,编写组件的方式是Options API
    • 特点是在对应的属性中编写对应的功能模块
    • 比如data定义数据、methods定义方法、computed定义计算属性、watch中监听属性变化
  • Options API的弊端
    • 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中
    • 当我们组件变得更大、更复杂时,同一个功能的逻辑会被拆分的很分散
  • Composition API可以帮助我们把同一个逻辑关注点相关的代码收集在一起
  • 在组件中,编写Composition API的位置是setup函数

Setup函数的基本使用

  • setup函数的参数
    • 第一个参数:props
    • 第二个参数:context
  • props就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取:
    • 对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义
    • 并且在template中正常去使用props中的属性
    • 如果我们在setup函数中想要获取props,不可以通过this去获取
    • 因为props有直接作为参数传递到setup函数中,我们可以直接通过参数来使用即可
  • context,包含三个属性
    • attrs:所有非prop的attribute
    • slots:父组件传递过来的插槽
    • emit:当我们组件内部需要发出事件时会用到emit(因为不能访问this,所以不能通过this.$emit发出事件)
<template>
  <div class="app">
    <h2>当前计数:{{ counter }}</h2>
    <button @click="addClick">+1</button>
    <button @click="subClick">-1</button>
  </div>
</template>

<script>
import { ref } from "vue"
export default {
  setup() {
    // 1.定义counter的内容
    // 默认定义的数据都不是响应式数据,所以需要使用ref
    let counter = ref(100)
    const addClick = () => {
      counter.value++
    }
    const subClick = () => {
      counter.value--
    }

    return {
      counter,
      addClick,
      subClick
    }
  }
}
</script>
<style scoped></style>

Setup中数据的响应式

  • 如果想在setup中定义的数据提供响应式的特性,可以使用reactive的函数
    • reactive函数处理我们的数据之后,数据再次被使用就会进行依赖收集
    • 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面)
    • 我们之前编写的data选项,也是在内部交给reactive函数将其变成响应式对象的
<template>
  <div class="app">
    <h2>message:{{ message }}</h2>
    <h2>账号:{{ account.username }}</h2>
    <h2>密码:{{ account.password }}</h2>
    <button @click="changeAccount">修改账号</button>
  </div>
</template>
<script>
import { ref, reactive } from "vue"
export default {
  setup() {
    // 定义普通的数据:可以正常的被使用
    // 缺点:数据不是响应式的
    const message = "hello world"

    // 定义响应式数据
    // 使用reactive函数:定义复杂类型的数据
    const account = reactive({
      username: "codewhy",
      password: "123456"
    })
    function changeAccount() {
      account.username = "big"
    }
    return {
      message,
      account,
      changeAccount
    }
  }
}
</script>
<style scoped></style>

  • Reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型
    • 如果我们传入一个基本数据类型会报警告
  • 这个时候我们可以使用ref API
    • ref会返回一个可变的响应式对象,该对象作为一个响应式的引用维护者它内部的值,这就是ref名称的来源
    • 它内部的值是在ref的value属性中被维护的
    • 其中在模版中使用ref的值时,Vue会自动帮助我们进行解包操作(自动提取value值)
    • 但是在setup函数内部,依然是一个ref引用,需要使用ref.value的方式

Vue中单向数据流的规范和做法

  • 单向数据流(规范)
    • 子组件拿到数据后只能 使用,不能修改
    • 如果确实要修改,那么应该将事件传递出去,由父组件统一进行修改
  • readonly
    • Vue3为我们提供了readonly方法
    • readonly会返回原生对象的只读代理

setup中禁用this

  • 官方关于this的描述:
    • 表达的含义是this并没有指向当前组件实例
    • 并且在setup被调用之前,data、computed、methos等都没有被解析
    • 所以无法在setup中获取this

setup中computed函数使用

<template>
  <h2>{{ fullname }}</h2>
</template>
<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    // 1.定义数据
    const names = reactive({
      firstname: "kobe",
      lastname: "bryant"
    })
    const fullname = computed(() => {
      return names.firstname + " " + names.lastname
    })
    return {
      names,
      fullname
    }
  }
}
</script>

setup中生命周期函数

  • 在生命周期函数前面加上on

script setup语法糖

<script setup>
	console.log('hello world")
</script>
  • 里面的代码会被编译成组件setup()函数的内容
    • 这意味着与普通的

Vue-Router

认识前端路由

  • 路由其实是网络工程中的一个术语
    • 在架构一个网络时,非常重要的两个设备就是路由器和** 交换机**
    • 事实上,路由器主要维护的是一个映射表
    • 映射表会决定数据的流向
    • 多个电脑通过路由器连接网络,路由器给每台设备分配一个ip地址(私网ip),每一个ip地址对应电脑独一无二的mac地址
  • 路由的概念在软件工程中出现,最早是在后端路由中实现的,原因是web的发展主要经历了这样一些阶段:
    • 后端路由阶段:后端根据url渲染出完整网页,再根据服务器返回到前端显示
    • 前后端分离阶段:属于后端路由,但是一些数据根据axios网络请求动态变化
    • 单页面富应用(SPA):==url地址改变,渲染不同的组件,url映射组件而不是整个页面 ==
      • 在前后端分离的基础上加了一层前端路由

URL的hash

  • 前端路由是如何做到URL和内容进行映射的?监听URL的改变
  • URL的hash
    • URL的hash也就是描点(#),本质上是改变window.location的href属性
    • 我们可以通过直接赋值location.hash来改变href,但是页面不发生刷新
    • 本质就是对location.hash的监听事件
<div id="app">
	<a href="#/home">home</a>
	<a href="#/about">about</a>
	<div class="router-view"></div>
</div>
// 1.获取router-view
const routerViewEl = document.querySelector(".router-view")

// 2.监听hashchange
window.addEventListener("hashchange", () => {
	switch(location.hash) {
		case "#/home":
			routerViewEl.innerHTML = "home";
			break;
		case "#/about":
			routerViewEl.innerHTML = "about"
			break;
		default:
			routerViewEl.innerHTML = "default"
	}
})

HTML5的History

  • history接口是HTML5新增的,它有六种模式改变URL而不刷新页面:
    • replaceState:替换原来的路径
    • pushState:使用新的路径
    • popState:路径的回退
    • go:向前或向后改变路径
    • forward:向前改变路径
    • back:向后改变路径
// 1.获取router-view
const routerViewEl = document.querySelector(".router-view")

// 2.监听所有的a元素
const aEls = document.getElementsByTagName("a");
for (let aEl of aEls){
	aEl.addEventListener("click", (e) => {
		e.preventDefault();
		const href = aEl.getAttribute("href");
		history.pushState({},"",href);
		historyChange()
	})
}

// 3.监听popstate和go操作
window.addEventListener("popstate",historyChange);
window.addEventListener("go",historyChange);

// 4.执行设置页面操作
function historyChange() {
	switch (location.pathname) {
		case "/home":
			routerViewEl.innerHTML = "home";
			break;
		case "/about";
			routerViewEl.innerHTML = "about"
			break;
		default:
			routerViewEl.innerHTML = "default"
	}
}

Vue-Router基本使用

  • 安装vue-router
npm install vue-router
  • src目录下新建router文件夹,其中创建index.js文件,创建路由对象
import { createRouter, createWebHashHistory } from 'vue-router'

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

// 创建一个路由:映射关系
const router = createRouter({
  // 指定采用的模式:hash
  history: createWebHashHistory(),
  // 映射关系
  routes: [
    {
      path: '/home',
      component: Home
    },
    {
      path: '/about',
      component: About
    }
  ]
})

export default router
  • 让路由对象生效,app.use(router)
import './assets/main.css'
import router from './router'
import { createApp } from 'vue'
import App from './App.vue'

// createApp(App).mount('#app')
const app = createApp(App)
app.use(router)
app.mount('#app')
  • router-view的占位,router-link进行路由切换
<script setup></script>

<template>
  <div class="app">
    <h2>App Content</h2>
    <div class="nav">
      <router-link to="/home">首页</router-link>
      <router-link to="/about">关于</router-link>
    </div>
    <router-view></router-view>
  </div>
</template>

<style scoped></style>

路由的默认路径

  // 映射关系
  routes: [
    {
      path: '/',
      redirect: '/home'
    },
    {
      path: '/home',
      component: Home
    },
    {
      path: '/about',
      component: About
    }
  ]

router-link

  • to属性
    • 是一个字符串,或者是一个对象
  • replace属性:
    • 设置replace属性的话,当点击时,会调用router.replace(),而不是router.push()
  • active-class属性:
    • 设置激活a元素后应用的class,默认是router-link-active
  • exact-active-class属性:
    • 链接精准激活时,应用于渲染的的class,默认是router-link-exact-active

路由懒加载分包处理

  • 当打包构建应用时,JS包会变得非常大,影响页面加载
    • 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更高效
    • 也可以提高首屏的渲染效率
// 路由的懒加载
const Home = () => import('../Views/Home.vue')
const About = () => import('../Views/About.vue')

路由的其他属性

  • name属性:路由记录独一无二的名称
  • meta属性:自定义的数据
    {
      name: 'home',
      path: '/home',
      component: Home,
      meta: {
        name: 'name',
        age: 18
      }
    },

动态路由和路由嵌套

动态路由基本匹配

  • 很多时候我们需要将给定匹配模式的路由映射到同一个组件
    • 例如我们有一个User组件,应该对所有用户进行渲染,但是用户的ID是不同的
    • 在Vue Router中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数
    {
      path: '/user/:id',
      component: () => import('../Views/User.vue')
    }
  • 在router-link中进行如下跳转
  <router-link to="/user/123">用户123</router-link>
  <router-link to="/user/456">用户456</router-link>
  • 获取动态路由的值
<script setup>
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
// 第一次获取
const route = useRoute()
console.log(route.params.id)

onBeforeRouteUpdate((to, from) => {
 console.log('from', from.params.id)
 console.log('to', to.params.id)
})
</script>

<template>
 <!-- 在模版中获取到id -->
 <div>User:{{ $route.params.id }}</div>
</template>

<style scoped></style>

NotFound

  • 对于那些没有匹配到的路由,我们通常会匹配到固定的某个页面
    • 比如NotFound的错误页面中,这个时候我们可以编写一个动态路由用于匹配所有的页面
{
      path: '/:pathMatch(.*)',
      component: () => import('../Views/NotFound.vue')
    }
  • 我们可以获取到传入的参数:
  <div>{{ $route.params.pathMatch }}-----NotFound</div>

路由的嵌套

<script setup></script>

<template>
  <div>home</div>
  <router-link to="/home/recommend">推荐</router-link>
  <router-link to="/home/ranking">排行</router-link>
  <router-view></router-view>
</template>

<style scoped></style>
    {
      name: 'home',
      path: '/home',
      component: () => import('../Views/Home.vue'),
      meta: {
        name: 'name',
        age: 18
      },
      children: [
        {
          path: 'recommend',
          component: () => import('../Views/HomeRecommend.vue')
        },
        {
          path: 'ranking',
          component: () => import('../Views/HomeRanking.vue')
        }
      ]
    },

路由的编程式导航

  • 在vue2中
homeClick(){
	this.$router.push('/home')
}
  • 我们也可以传入一个对象
homeClick(){
	this.$router.push({
	path:'/home'
})
}
  • 在setup中
import { useRouter } from 'vue-router'
const router = useRouter()
function homeClick() {
  // 写法一:
  // router.push('/home')
  // 写法二:
  router.push({
    path: '/home',
    // 传递参数
    query: {
      name: 'name'
    }
  })
}
function aboutClick() {
  router.push('/about')
}

动态添加路由

  • 某些情况下我们需要动态的来添加路由:
    • 比如根据用户不同的权限,注册不同的路由
    • 这个时候我们可以使用一个方法addRoute
// 动态添加路由
let isAdmin = true
if (isAdmin) {
  router.addRoute({
    path: '/admin',
    component: () => import('../Views/Admin.vue')
  })

  // 添加home页面下的二级路由
  router.addRoute('home', {
    path: 'vip',
    component: () => import('../Views/HomeVip.vue')
  })
}
  • 删除路由有以下三种方式:
    • 方式一:添加一个name相同的路由
    • 方式二:通过removeRoute方法,传入路由的名称
    • 方式三:通过addRoute方法的返回值回调

路由导航守卫

  • vue-router提供的导航守卫主要用来通过跳转或取消的方式守卫导航
  • 全局的前置守卫beforeEach是在导航触发时会被回调的
  • 它有两个参数:
    • to:即将进去的路由Route对象
    • from:即将离开的路由Route对象
  • 它有返回值:
    • false:取消当前导航
    • 不返回或者undefined:进行默认导航
    • 返回一个路由地址:
      • 可以是一个string类型的路径
      • 可以是一个对象,对象包含path、query、params等信息
  • 可选的第三个参数:next(不推荐)
    • 在vue2中我们是通过next函数来决定如何进行跳转的
    • 但是在Vue3中我们是通过返回值来控制的,不再推荐使用next函数,这是因为开发中很容易调用多次next
<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

function loginClick() {
  console.log('点击了登录')
  //   向服务器发送请求,服务器返回token
  localStorage.setItem('token', 'token')
  router.push('/order')
}
</script>

<template>
  <div>
    <h2>登录页面</h2>
    <button @click="loginClick">登录</button>
  </div>
</template>

<style scoped></style>
// 路由导航守卫
// 进行任何的路由跳转之前,传入的beforeEach中的函数都会被调用
// 需求:进入订单页面时,判断用户是否登录(isLogin => localStorage保存token)
// 情况一:用户没有登录,那么跳转到登录页面,进行登录操作
// 情况二:用户已经登录,那么直接进入订单页面
router.beforeEach((to, from) => {
  //1.进入到任何别的页面时,都跳转到login页面
  // if (to.path !== '/login') {
  //   return '/login'
  // }

  // 2.进入到订单页面时,判断用户是否登录
  const token = localStorage.getItem('token')
  if (!token && to.path === '/order') {
    return '/login'
  }
})

Vuex状态管理

认识应用状态管理

  • 开发中,应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序的某一个位置,对于这些数据的管理我们就称之为状态管理
  • 之前是如何管理自己的状态的?
    • 在Vue开发中,我们使用组件化 的开发方式
    • 而在组件中我们定义data或者setup中返回使用的数据,这些数据我们称之为state
    • 模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View
    • 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能修改state,这些行为事件我们称之为actions

  • 需要管理的数据越来越复杂
    • JS需要管理的状态越来越多,越来越复杂
    • 这些状态包括服务器返回的状态、缓存数据、用户操作产生的数据等等
    • 也包括一些UI状态,比如某些元素是否被选中,是否显示加载动效,当前分页
  • 当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏
    • 多个视图依赖于同一状态
    • 来自不同视图的行为需要变更同一状态
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值