Vue.js

136 阅读21分钟

Vue 3.0

Vue官网

初始Vue

  • Vue是一个框架,框架就是一个半成品的项目

    • 一个项目只能有一个框架
    • 一个项目可以有很多个库
  • 创建vue的应用实例

    1. 下载Vue.global.js文件

    2. 引入的Vue.global.js文件

      • 引用之后,在window中就存在了一个 Vue 的属性
      • Vue 属性中的 createApp 选项就是用来创建Vue实例的
    3. 创建一个Vue的实例,返回Vue的实例对象

      const app = window.Vue.createApp({ })
      
    4. 实例里面存在 data 选项,在data函数里面返回一个对象

      const app = window.Vue.createApp({
          // data选项存储数据
          data(){
              return {
                  // 这里写vue的数据
              }
          }
      })
      
    5. 将Vue实例挂载(渲染)到某一个标签里,此时该标签以及子元素就被Vue所管理了

      //语法1:挂载DOM对象
      	app.mount(document.getElementById("app"))
      //语法2:挂载CSS选择器
      	app.mount("#app")
      

🌰:

<div id="app"></div>
const app = window.Vue.createApp({
    data() {
        return {
            msg: "Hello World"
        }
    }
})
//挂载
app.mount(document.getElementById("app"))
##### Vue2创建Vue应用实例
1.引入文件
2.通过构造函数创建实例
3.挂载
	方式1:在el选项中挂载
    方式2:$mount("#app")进行挂载
    
🌰:
const vm = new Vue({
    // 挂载(方式1)
    // el:"#app",
    data(){
        return{
            msg:'hello world!'
        }
    }
})
// 挂载(方式2)
vm.$mount("#app")

选项

  • data :存储数据

  • methods:函数

  • computed:计算属性

  • watch:侦听器

  • app.component:全局组件

    • template:模板
    • components:局部组件
  • props:父传子

  • emits:子传父,手动触发事件

  • inheritAttrs:false:阻止属性透传

  • 依赖注入:

    • provide:提供数据
    • inject:获取数据
  • 生命周期钩子:

    • beforeCreate:创建前
    • created:创建完
    • beforeMount:挂载前
    • mounted:挂载完
    • beforeUpdate:更新前
    • updated:更新完
    • beforeDestroy :销毁前(vue2
    • destroyed :销毁完(vue2
    • beforeUnmount:卸载前(vue3
    • unmounted:卸载完(vue3

模板语法

  • 语法:{{ }}
  • 作用:用来渲染 data 里面的数据的
  • 注意: 渲染的标签必须是被Vue所管理的标签
  • 解决{{}}闪现方法:
    • 方法1:使用 v-text 代替
    • 方法2:根标签中加上指令 v-cloak,在样式中写上 [v-cloak] { display: none; }
  • 模板语法可以写:字符串表达式变量
  • 拓展:表达式 就是可以有返回值的,如:msg.toUpperCase()

指令

表单绑定(双向数据绑定)

  • 指令: v-model

  • 语法:v-model="msg"

    • 第一个向: 将data里面的数据渲染到页面
    • 第二个向: 当页面的 input 框的内容发生改变之后,数据也会跟着改变
  • 搭配: 一般情况下和 input 搭配使用

  • v-model是一种语法糖

    • 文本框、密码框、文本域中

      v-model <=相当于=> :value + @input
      例:
      <input type="text" v-model="msg">
      👆相当于👇
      <input type="text" :value="msg" @input="msg = $event.target.value">
      
    • 单选按钮、多选框中

      v-model <=相当于=> :checked + @change
      
      * 选中某一个单选按钮或多选框时,其实就是将`value`赋值给`v-model`绑定的数据里
      * 单选:当`v-model`绑定的数据与`value`的数据一样时,对应的按钮将被选中
      * 多选:当`v-model`绑定的数据(数组)包含了对应多选的`value`值时,`value`对应的按钮将被选中
      
    • 下拉列表中

      v-model <=相当于=> :value + @change
      
  • v-model 的修饰符

    • v-model.lazy:将 input 事件修改成 change 事件
    • v-model.number:将 v-model 收集到的数据修改成 number 类型,能改则改,不能改就不改
    • v-model.trim:将 v-model 收集的数据去除首尾空格

文本渲染

  • v-html
    • 语法:v-html="btn"
    • 作用: 渲染的 data 里的数据,可以渲染标签
  • v-text
    • 语法:v-text="msg"
    • 作用: 渲染的 data 里的数据,当纯文本渲染

安全说明:

​ 在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值

  • v-pre
    • 语法:<p v-pre>{{msg}}</p>
    • 作用: 不使用Vue语法进行解析,不渲染

动态属性

  • 指令:v-bind

  • 简写::

    • 语法1:v-bind: / :

      <p v-bind:id="i1">你好</p>
      <p :abc="i1">世界</p>
      
      data() {
          return {
              i1: "abc"
          }
      }
      
    • 语法2:v-bind="obj"

      <p v-bind="student"></p>
      <!-- 展开后等价于<p name="张三" age="18"></p> -->
      
      data() {
          return {
              student: {
                  name: "张三",
                  age: 18,
              }
          }
      }
      
  • 动态class

    • 语法1::class='表达式'

      <h1 :class='`${msg} box`'>hello world</h1>
      
    • 语法2::class='[表达式1, ...]'

      <h1 :class="['box','active']">hello world</h1>
      <h1 :class="['box',{active:flag}]">hello world</h1>
      
    • 语法3:<div :class='{类名: 类名是否显示, ...}'

      <h1 :class="{active:flag}">hello world</h1>
      
  • 动态style

    • 语法1::style='{css属性:css属性值}'

      <p :style="{color:red,fontSize:`${msgFs}px`,[msgMar]:`${msg}px`}">hello world</p>
      
    • 语法2::style='[css属性:css属性值]'

      <p :style="['color:red',{fontSize:`${msgFs}px`}]">hello world</p>
      
  • 普通属性重复问题

    <h1 id="box1" :id="box2">hello world</h1>
    <h1 class="red" :class="{blue:'ture'}">hello world</h1>
    <h1 style="coler:red" :style="['backgroundColor:blue']">hello world</h1>
    
##### Vue2Vue3普通属性重复问题中的区别
对于动态绑定普通属性来说,如果出现属性重复的情况:
	Vue2是后面的属性会覆盖前面的属性(后者生效)
	Vue3是前面的属性会覆盖掉后面的属性(前者生效)
对于class类和style样式来说:
	Vue做了特殊的处理,没有覆盖操作,而是合并操作
	操作Vue里面class和style,可以使用对象或者数组来进行操作

事件绑定

  • 指令:v-on:

  • 简写:@

  • 语法1:无参 / 简写

    • 语法:v-on:click = fn / @click = fn

      v-on : 	指令
      click: 	事件类型
      fn : 	执行函数
      
    • 事件对象:在函数里接收一个 event 的形参

  • 语法2:有参

    • 语法:@click = fn(10)
  • 语法3:有参及事件对象

    • 语法:@click = fn('你好',$event)

列表渲染

  • 指令:v-for

  • v-for 可以遍历 数组对象数字

  • 遍历数组

    • 语法1:v-for="item in list"

      v-for: 	指令
      item:	遍历的每一项
      list:	遍历的数组
      
    • 语法2:v-for="(item,index) in list

      v-for: 	指令
      item:	遍历的每一项
      index:	每一项的下标
      list:	遍历的数组
      
  • 遍历对象

    • 语法:v-for="(value,key,index) in obj"

      v-for: 	指令
      value:	遍历每一项的键值
      key:	遍历每一项的键
      index:	每一项键值的下标
      obj:	遍历的对象
      
  • 遍历数字

    • 语法:v-for="(item,index) in 10"

      相当于遍历 [1,2,3,4,5,6,7,8,9,10]
      

条件控制

  • 指令:v-if v-else-if v-else

    <p v-if="score>=80">优秀</p>
    <p v-else-if="score>=70">良好</p>
    <p v-else-if="score>=60">及格</p>
    <p v-else>不及格</p>
    
  • 指令:v-show

    <p v-show="score>=80">优秀</p>
    <p v-show="score>=70">良好</p>
    <p v-show="score>=60">及格</p>
    <p v-show="score<60">不及格</p>
    
  • v-ifv-show 的区别(面试题)

    • v-if 是真实的条件控制语句,当满足条件时,显示元素;当不满足条件时,不显示(没有元素)
    • v-show 也可以控制元素显示与隐藏,当满足条件时,显示元素;当不满足条件时,使用display:none 来隐藏元素
    • v-if 有一套 v-ifv-else-ifv-else 条件控制指令
    • v-if 可以搭配 Template 标签进行使用,而 v-show 不行
    • v-if 不显示的标签其实是不存在的,而 v-show 是存在的
    • 当我们频繁的切换标签的时候,使用 v-show,减少创建DOM时间,以空间换时间(牺牲内存)
    • 当我们使用少次的时候,使用 v-if,减少内存占用,以时间换空间(牺牲时间)

v-if与v-for的优先级

##### Vue2Vue3中,v-if与v-for的优先级
v-if和v-for可以一起使用,但是官方不推荐,因为这样二者的优先级不明显
Vue2中,'v-for'的优先级高于'v-if'
Vue3中,'v-if'的优先级高于'v-for'

自定义指令

  • 全局自定义指令:

    • 语法:app.directive("指令",{ updated(el){ 操作} })

      app.directive("red",{
          updated(el){
              el.style.color = "red"
          }
      })
      
  • 局部自定义指令:

    • 语法:directives:{ 指令(el){ 操作 } }

      directives:{
          blue(el){
              el.style.color = "blue"
          }
      }
      
  • 使用:v-指令

    <div v-blue></div>
    

this指向

  • 在Vue中,methods中的函数 this => 组件的实例对象
  • 在函数中(非箭头函数),this.num => data里面的数据(num)

修饰符

  • 事件修饰符
    • .stop:阻止事件的冒泡
    • .prevent:阻止默认行为
    • .self:事件目标和触发事件对象一致时触发函数
    • .capture:触发捕获(写在外部)
    • .once:只触发一次函数
  • 按键修饰符
    • .enter:回车键
    • .tab:Tab键
    • .delete:(捕获“Delete”和“Backspace”两个按键)
    • .esc:Esc键
    • .space:按空格键触发
    • .up:上键
    • .down:下键
    • .left:左键
    • .right:右键
    • .ctrl:Ctrl键
    • .alt:Alt键
    • .shift:Shift键
    • .meta:Win键
  • exact 修饰符
    • .exact:按键必须一致,不允许按下其他未绑定的按键
  • 可以链式使用
##### Vue2Vue3按键修饰符的区别
在Vue2中,按键修饰符,可以使用键码
在Vue2中,按键修饰符,不可以使用键码,将键码废弃了

Template

  • Template 是一个空的标签,在元素当中不体现出来

key

  • ( 面试题 )
    1. Vue里面的 key 是一个特殊的属性,在元素当中是不体现出来
    2. 在解析成虚拟DOM时,如果没有写key值,那么这个key就类似于下标 0 , 1 , 2 , 3....
    3. 使用列表渲染的时候,key必须是唯一的,key值是一定不会也不能重复
    4. key值使用尽量不要使用下标

虚拟DOM、diff算法

  • 虚拟DOM:本质是一个JS对象,Vue会将DOM解析成一个JS文件

  • 比如:(没有key,diff算法内部混乱)

    • 真实DOM

      <div>
          <span>张三</span>
          <span>李四</span>
      </div>
      
    • 虚拟DOM

      {
          key:1,
          type:'div',
          attr:'',
          children:[
              {
                  key:2, // 假设对应的值为:001
                  type:'span',
                  attr:'',
                  children:"张三"
              },
              {
                  key:3, // 假设对应的值为:002
                  type:'span',
                  attr:'',
                  children:"李四"
              },
          ]
      }
      
    • 当我们将数据修改的时候,Vue又会重新生成一个虚拟DOM文件

    • 真实DOM

      <div>
          <span>王五</span>
          <span>张三</span>
          <span>李四</span>
      </div>
      
    • 虚拟DOM

      {
          key:1,
          type:'div',
          attr:'',
          children:[
              {
                  key:2, // 此时对应的值为:001
                  type:'span',
                  attr:'',
                  children:"王五"
              },
              {
                  key:3, // 此时对应的值为:002
                  type:'span',
                  attr:'',
                  children:"张三"
              },
              {
                  key:4,
                  type:'span',
                  attr:'',
                  children:"李四"
              },
          ]
      }
      
  • 比如:(有key)

    • 真实DOM

      <div>
          <span>张三</span>
          <span>李四</span>
      </div>
      
    • 虚拟DOM

      {
          key:1,
          type:'div',
          attr:'',
          children:[
              {
                  key:2, // 假设对应的值为:001
                  type:'span',
                  attr:'',
                  children:"张三"
              },
              {
                  key:3, // 假设对应的值为:002
                  type:'span',
                  attr:'',
                  children:"李四"
              },
          ]
      }
      
    • 当我们将数据修改的时候,Vue又会重新生成一个虚拟DOM文件

    • 真实DOM

      <div>
          <span>王五</span>
          <span>张三</span>
          <span>李四</span>
      </div>
      
    • 虚拟DOM

      {
          key:1,
          type:'div',
          attr:'',
          children:[
              {
                  key:4,
                  type:'span',
                  attr:'',
                  children:"王五"
              },
              {
                  key:2, // 此时对应的值为:001
                  type:'span',
                  attr:'',
                  children:"张三"
              },
              {
                  key:3, // 此时对应的值为:002
                  type:'span',
                  attr:'',
                  children:"李四"
              },
          ]
      }
      

diff算法

  • 其实就是一个比较算法

  • 从外部一层一层的往内部进行比较

  • 先比较key值

  • 当key一样时,再比较内容,如果内容一致则使用之前的内容,如果内容不一致则修改不同点

  • 当找不到相同key时,添加

计算属性

  • 语法1:(函数写法)

    computed:{
        msgCompute(){
            console.log("计算属性被调用");
            return this.msg.split("").reverse().join("");
        }
    }
    
  • 语法2:(对象写法)

    computed:{
        fullname:{
            // 获取
            get(){
                return this.firstname + '-' + this.lastname
            },
            // 设置
            set(val){
                console.log("修改计算属性",val);
                this.firstname = val.split("-")[0]
                this.lastname = val.split("-")[1]
            }
        }
    }
    
  • 写一个 computed 选项

  • computed 里是一个函数,不要用箭头函数,箭头函数没有 this;或者 computed 里是一个对象(具有 gettersetter

  • 计算属性必须要有返回值,返回计算出来的结果

  • 计算属性依赖于某一些数据

  • 模板中可以使用直接计算属性

  • 计算属性的结果不能被修改

  • 计算属性可以在插件(dev-tools)中显示

  • 计算属性有缓存,函数没有缓存

  • 计算属性中不要使用 data 里面的变量名

  • 有且仅有当计算属性作用域内的声明式变量发生变化时,这个计算属性才会重新计算

  • 计算属性一般对带有 __ob__ 的变量(声明式变量)进行计算

侦听器

  • watch 选项:侦听器,也叫监听器

  • 作用:当被监听的数据发生变化时,触发监听的钩子

  • 注意:

    • 可以监听:data声明式变量、computed计算属性
    • 监听的是 变化,当监听的变量没有发生变化时,监听器钩子不执行
  • 语法:

    • key(函数名)有两种写法:

      1. 直接被监视的数据

        watch: {
            a(newVal,oldVal){ }
        }
        
      2. 写一个字符串(精确监听)

        watch: {
            "b.c.d"(newVal, oldVal) {
            }
        }
        
    • value的写法:

      • 形参:
        • 参数1:新数据(newVal)

        • 参数2:老数据(oldVal)

      1. 一个函数,不要写箭头函数

        watch: {
            a(newVal,oldVal){
                console.log(newVal,oldVal);
            }
        }
        
      2. 一个对象,对象中写一个 handler 函数(固定语法)

        • 可选属性
          • deep:true:深度监听
          • immediate:true:立即执行( 比mounted 要快)
        watch: {
            a:{
                handler(newVal,oldVal){
        
                },
                deep:true,// 深度监听
                immediate:true, // 立即执行
            }
        }
        
      3. 字符串的写法

        • 字符串里面写一个函数,这个函数需要在methods定义
        watch: {
            a:"fn"
        },
        methods:{
            fn(newVal, oldVal) {
                console.log(newVal, oldVal);
            }
        }
        
      4. 数组写法

        • a:[监视1,监视2]
        • 可以执行多个函数
        watch: {
            a:[
                "fn",
                function(newVal,oldVal){
                    console.log(newVal,oldVal);
                }
            ]
        }
        

生命周期

  • 创建阶段
    • beforeCreate:创建之前(有 this ,没有 data
    • created:创建完成(有 this ,有 data
  • 挂载阶段
    • beforeMount:挂载之前(Vue解析之前的 DOM
    • mounted:挂载完成(Vue解析之后的 DOM
  • 更新阶段(不要在此阶段更新数据,易死循环)
    • beforeUpdate:更新之前(更新之前的数据)
    • updated:更新完成(更新之后的数据)
  • 销毁(卸载)阶段
    • beforeDestroy / beforeUnmount:销毁(卸载)之前
    • destroyed / unmounted:销毁(卸载)完毕
##### Vue2Vue3生命周期中的区别
销毁/卸载阶段:
	Vue2:beforeDestroy,destroyed
    Vue3:beforeUnmount,unmounted

template模板

  • template 模板选项

  • 语法1:直接写在模板选项中

    template:`<h1>{{msg}}</h1>`,
    data(){
        return{
            msg:"hello world"
        }
    }
    
  • 语法2:使用 template 标签

    <template id="box">
        <h1>{{msg}}</h1>
    </template>
    
    template:'#box',
    data(){
        return{
            msg:"hello world"
        }
    }
    
##### Vue2Vue3模板选项中的区别
	Vue2:直接替换'#app'模板,template模板可以写在'#app'里面或外面
    vue3:使用innerHTML的方式替换'#app'里面的内容,只能写在'#app'外面

模板引用

  • Vue的声明性渲染模型抽象了大部分对DOM的直接操作,但在某些情况下,仍然需要直接访问底层DOM元素。可以使用特殊的 ref 属性。

  • 语法

    • 语法1:

      • 在模板中:ref="xxx";在Vue中:this.$refs.xxx
      <input type="text" ref="inp">
      
      console.log(this.$refs.inp);
      console.log(this.$refs.inp.value);
      
    • 语法2:refv-for 的使用

      • 在列表渲染上面绑定 ref,使用this.$refs获取得到一个数组
      <div v-for="(item,index) in list" :key="index" ref="aaa">{{item}}</div>
      
      console.log(this.$refs.aaa)
      
    • 语法3:

      • 在组件中使用 ref,暂时没有学到组件,所以无法演示
    • 语法4:函数模板引用

      • 将 el 赋值给一个数据属性或 ref 变量
      <input :ref="(el) => { dom = el }">
      
      data(){
          return{
              dom:""
          }
      },
      methods:{
      	getElement(){
          	console.log(this.dom);
              console.log(this.$refs.inp.value);
          }
      }
      

实例与根实例

  • 得到Vue的实例对象

    const app1 = Vue.createApp({
        data() {
            return {}
        }
    })
    console.log(app1)
    
  • 得到Vue的根 ( Root ) 实例对象(根组件)

    const app2 = Vue.createApp({
        data() {
            return {
                msg: 1
            }
        }
    }).mount('#app')
    console.log(app2)
    

组件

  • 组件:将(页面UI模板)拆分成若干部分,这个部分就是组件
  • 组件的优点:
    • 独立性、复用性
  • 组件化开发:
    • 在开发项目的过程当中,时刻思考这将组件的拆分,这个项目就是一个组件化开发项目
  • 模块化开发:
    • 如:nodejs

全局组件

  • 步骤

    • 定义注册

      app.component("组件的名字",{
          template:`<h1>组件模板</h1>`
          data(){}
          computed:{}
          ...
      })
      
    • 使用组件

      • 将组件的名字当做标签来使用
  • 注册全局组件

    app.component('zujain1', {
        template: `<h1>全局组件1</h1>`
    })
    app.component('zujain2', {
        template: `<h1>全局组件2</h1>`
    })
    
    <!-- 使用 -->
    <zujain1></zujain1>
    <zujain2></zujain2>
    
  • 可以链式调用

    app.component('zujain1', {
        template: `<h1>全局组件1</h1>`
    }).component('zujain2', {
        template: `<h1>全局组件2</h1>`
    })
    
##### Vue2Vue3中,全局组件注册的区别
vue2使用: Vue.component
vue3使用: app.component

### Vue2Vue3组件的使用注意事项:
Vue2的组件注册必须要在'new Vue'之前
Vue3的组件注册必须要写在'app'挂载之前

局部组件

  • 语法:

    components:{
        '局部组件名':{
            template:`<h1>局部组件模板</h1>`
        }
    }
    
  • 注册局部组件

    app.component("zujian1",{
        template:`<h1>全局组件模板</h1>`,
        
        //局部组件注册
        components:{
            'zujain2':{
                template:`<h2>局部组件模板</h2>`
            }
        }
    })
    

全局组件与局部组件的区别

  • 全局组件 在页面的任何地方都可以使用
  • 局部组件 只能在该当前组件的父组件里使用,不能越级使用

组件中template其他使用方式

<div id='app'>
    <zujian1></zujian1>
</div>
<template id="box1">
    <h1>全局组件</h1>
    <zujian2></zujian2>
</template>
<template id="box2">
    <h1>局部组件</h1>
</template>
app.component("zujian1",{
    template:"#box1",
    components:{
        "zujian2":{
            template:"#box2"
        }
    }
})
  • 上述方式有代码高亮,但是看起来比较乱

组件的作用域

  • 父组件的 数据函数 是给父组件自己使用的
  • 子组件的 数据函数 是给子组件自己使用的
  • 两个组件之间没有直接的关系

ref和组件的使用

<div id='app'>
    <button @click="fn">获取ref</button>
    <h1 ref="xxx1">我是app组件</h1>
    <zujian ref="xxx2"></zujian>
</div>
const app = Vue.createApp({
    methods:{
        fn(){
            console.log(this.$refs.xxx1); // 可获取到<h1>的DOM
            console.log(this.$refs.xxx2.count); // 可获取到count的值
            console.log(this.$refs);
        }   
    }
})

//注册组件
app.component("wenwen",{
    template:`
		<h1>子组件</h1>
		{{count}}
		<button @click="count++">自增</button>
	`,
    data(){
        return{
            count:1
        }
    }
})
app.mount('#app')

Vue2中data的写法

  • Vue2 data 是可以使用 对象写法函数写法

  • 对象写法

    data:{
        num:1,
    }
    
  • 函数写法:

    data(){
        return{
            num:1,
        }
    }
    
#####
由于 组件自有的声明式数据 会被复用,所以 `data` 需要使用工厂函数`data(){}`来保证复用时他们的data相互独立,互不影响

父子组件的生命周期

  • 初始化
    • beforeCreate
    • created
    • beforeMount
      • --child beforeCreate
      • --child created
      • --child beforeMount
      • --child mounted
    • mounted
  • 更新
    • beforeUpdate
      • --child beforeUpdate
      • --child updated
    • updated
  • 死亡
    • beforeDestroy
      • --child beforeDestroy
      • --child destroyed
    • destroyed

父子组件通信

  • props => 用来父组件向子组件传递数据
    • 父组件发送ajax请求,获取到数据,将数据使用props传递给子组件

父传子

  • 使用 props 来接收父组件传来的数据

  • props 的使用

    1. 子组件名当做标签使用,将数据写在该子组件的标签的身上
    2. 子组件中写一个 props 选项
    3. props 就是父组件传递过来的内容,子组件可以将这个内容当做 data 数据来使用
  • props只读性

  • 父组件将数据传给子组件时,这个 props 里的数据是不能被修改的

  • 🌰:

    <div id='app'>
        <zujian :message="student"></zujian>
        <hr>
        <zujian :message="person"></zujian>
    </div>
    
    const app = Vue.createApp({
        data() {
            return {
                student: [
                    { id: 1, name: "张三", gender: "男" },
                    { id: 3, name: "李四", gender: "女" }
                ],
                person: [
                    { id: 1, name: "王五", gender: "女" },
                    { id: 3, name: "赵六", gender: "男" }
                ]
            }
        }
    })
    
    //子组件
    app.component("wenwen", {
        template: `
    		<ul>
                <li v-for="item in message" :key="item.id">{{item.name}}~~{{item.gender}}</li>    
            </ul>
    	`,
        props: ["message"]
    })
    app.mount('#app')
    
  • props 的两种书写形式

    • 数组:(没有校验)

      • 语法:props:["参数1","参数2"...]
    • 对象:(有校验规则)

      • 语法:

        props:{
            参数1:{ 校验规则 },
            参数2:{ 校验规则 },
            ...
        }
        
      • 校验规则

        • type: String => 校验数据类型

          • String、 Number、 Function、Object...
        • type:[String, Number] => 校验多种可能的类型

        • required: true => 必传,否则**警告**

        • default: 100 => 默认值,未传则使用默认值

        • 对象类型的默认值

          • 对象或者数组应当用工厂函数返回
          • 工厂函数会收到组件所接收的原始 props 作为参数
          message: {
              type: Object,
              default(rawProps) {
          		// rawProps 原始数据,没啥用
                  return { message: 'hello' }
              }
          }
          
        • 自定义类型校验

          message: {
              validator(value) {
                  // value为props接收的数据,该value值必须与以下其中一个值匹配
                  // 可使用正则
                  return ['小学生', '中学生', '大学生'].includes(value)
              }
          }
          

父子组件通信时的数据冲突

当父组件传入的数据与子组件的数据有相同的时候
那么子组件会`优先使用自己的数据`
如果自己没有这个数据,那么就使用父组件`props`传来的数据

子传父

  • 使用 $emit 向父组件通信

  • 方式一:

    • 子组件:例:this.$emit("事件类型","参数")
      • 注意: 参数只能写一个,如果有多个参数,那么将参数转换成数组或者是对象
    • 父组件:例:<zujian @sjlx="fn"></zujian>
      • 子组件将 this.$emit("事件类型") 执行了之后,那么父组件(@sjlx="fn")就会执行 fn 这个函数
    <div id='app'>
        <zujian @dj="fatherFn"></zujian>
    </div>
    
    const app = Vue.createApp({
        methods:{
            fatherFn(){
                console.log("子组件点击的,触发这个函数");
            }
        }
    })
    // 子组件
    app.component("zujian",{
        template:`
            <div>
                <h1>子组件</h1>    
                <button @click="sonFn">$emit事件</button>
            </div>
    	`,
        methods:{
            sonFn(){
                //触发事件 => 触发到爹的身上
                this.$emit("dj")
            }
        }
    })
    
  • 方式二:

    • 子组件:例:this.$emit("dj","参数")
    • 父组件:例:<zujian@wy="msg = $event"></zujian>
      • $event 就是参数
    <div id='app'>
        <zujian @dj="msg=$event"></zujian>
    </div>
    
    const app = Vue.createApp({
        data(){
            return{
                msg:1
            }
        }
    })
    // 子组件
    app.component("zujian",{
        template:`
            <div>
                <h1>子组件</h1>    
                <button @click="sonFn">$emit事件</button>
            </div>
    	`,
        methods:{
            sonFn(){
                this.$emit("dj",'2')
            }
        }
    })
    

透传属性 attrs

  • 在组件的属性传递的过程中,子组件会继承该组件标签身上的属性

  • 继承需要子组件有根元素

  • 如果组件标签身上写的属性与组件根标签身上的属性相同时会被替换(组件标签替换组件里的根标签)

  • 特殊属性: classstyle 会合并,注意: 如果 key 一样,value 也一样,那么会使用标签身上

  • 事件相同,会合并 (事件在元素中不体现出来)

  • 阻止属性透传

    • inheritAttrs:false
    • 写在与data平级的位置
  • 属性 attrs

    • 当属性写入 props 里面,函数写入 emits 里面后,在 attrs 里面就看不到了

      ##### Vue2Vue3
      Vue2中没有`emits`这个属性
      Vue2中,$attrs只有非函数属性,函数在$listeners中
      Vue3中,不管属性还是函数,都在$attrs里
      
    • 注意:被 propsemits 声明过的属性或函数不会包含在 attrs 中,因为已经被“消费”了

<div id="app">
    <zujian name="张三" age="18" gender="男" class="red" style="font-size:20px" @click="father"></zujian>
</div>
const app = Vue.createApp({
    methods: {
        father() {
            console.log('父组件事件');
        }
    }
})
app.component('zujian', {
    template: `
        <div name="张乐" sex="男" class="blue" style="color:red">
        	{{name}}
        	<button @click="son">子组件点击按钮</button>  
        </div>
    `,
    props: ["name"],
    emits: ["click"],
    inheritAttrs: false, //阻止属性透传
    mothods: {
        son() {
            this.$emit('click')
        }
    }
})
  • 深层次组件使用 attrs 中的属性和函数:
    • v-bind="$attrs"
<div id='app'>
    <father name="张三" age="18" gender="男"></father>
</div>
// 父组件 
const app = Vue.createApp({
})

// 子组件
app.component("father",{
    template:`
    	<div>{{name}}</div>
    	<son v-bind="$attrs"></son>
    `,// 
    props:["name"]
})


//孙组件
app.component("son",{
    template:`
        <div>孙组件</div>
        {{age}}
    `,
    props:["age"]
})

子传父(Vue3组合式api)

* 在子组件中调用父组件中的方法
  • 父组件

    <Fuzujian @fun="fn" />
    <Fu :list=listdata>
    
  • 子组件接收方法,并调用父组件中的方法

    // 接收父传子的方法
    let emits = defineEmits(['fun'])
    // 调用父组件中的方法
    emits('fun', '传递的参数')
    
    // 子组件接收父组件的数据
    let props = defineProps({
        list: {
            type: Array
        }
    })
    

组件间的通信v-model

  • v-model 父子组件的双向数据通信

  • **v-model ** 在Vue3组件当中 :modelValue + @update:modelValue

  • 方式一:v-model="msg"

    • 拆开写法::modelValue="msg" @Update:modelValue="fn"
      • 简写形式:modelvalueupdate:modelvalue (小写)
      • 拆开形式:modelValueupdate:modelValue (大写)
      • 注意大小写,建议使用 vue-devtools 工具
    <!-- <zujian v-model="msg"></zujian> -->
    <zujian :modelValue="msg" @Update:modelValue="fn"></zujian>
    
    const app = Vue.createApp({
        data() {
            return {
                msg: '🏀'
            }
        },
        methods: {
            fn(params) {
                this.msg = params
            }
        }
    })
    app.component('zujian', {
        template: `
            <div>
                {{modelvalue}}
                <button @click="btn('⚽')">点击</button>
            </div>
        `,
        props: ["modelvalue"],
        emits: ["update:modelvalue"],
        methods: {
            btn(params) {
                this.$emit('update:modelvalue', params)
            }
        }
    })
    
  • 方式二:v-model:msg="msg"

    • 拆开写法::msg="msg" @update:msg="msg = $event"
    <!-- <zujian v-model:msg="msg"></zujian> -->
    <zujian :msg="msg" @update:msg="msg = $event"></zujian>
    
    const app = Vue.createApp({
        data() {
            return {
                msg: '🏀'
            }
        },
    })
    
    app.component('zujian', {
        template: `
        	{{msg}}
        	<button @click="btn('⚽')">点击</button>
        `,
        props: ["msg"],
        emits: ["update:msg"],
        methods: {
            btn(params) {
                this.$emit("update:msg", params)
            }
        }
    })
    
###### Vue2中的组件通信
  • 语法:v-model="msg"

    • 拆开写法::value="msg" @input="msg=$event"
    <!-- <zujian v-model="msg"></zujian> -->
    <zujian :value="msg" @input="msg=$event"></zujian>
    
    Vue.component('zujian', {
        template: `
            <div>
            	{{value}}
            	<button @click="btn('⚽')">点击</button>
            </div>
        `,
        props: ["value"],
        // Vue2中没有emits
        methods: {
            btn(params) {
                this.$emit('input', params)
            }
        }
    })
    
    new Vue({
        el: '#app',
        data() {
            return {
                msg: '🏀'
            }
        },
    })
    

emits的数据格式校验

  • 语法:

    • 数组写法:emits:[] (无校验)
    • 对象写法:emits:{}
      • 校验结果需要返回一个 boolean 值,返回为 true 则没有警告,返回 false 则给出警告
    emits:{
        btn(params) {
            // params:传递的参数
            /* 
                校验数据的方式有哪些?
                    1. typeof
                    2. instanceof
                    3. constructor
                    4. Object.prototype.toString.call()
                    5. Array.isArray()
            */
            return Object.prototype.toString.call(params) === '[object Object]'
        }
    }
    

插槽

  • 插槽:也是组件之间一种通信方式, 可以传递标签

  • 语法:

    • 父组件:

      • 写在组件标签内
      • template 标签中使用指令 v-slot:插槽name值
      • 简写:v-slot => #
      <!-- 匿名(默认)插槽 -->
      <template v-slot:default>
          <h1>s1</h1>
      </template>
      <!-- 匿名插槽 -->
      <zujian>
          <h1>匿名插槽</h1>
      </zujian>
      <!-- 具名插槽 -->
      <template v-slot:s1>
          <h1>s1</h1>
      </template>
      <!-- 简写形式 -->
      <template #s1>
          <h1>s1</h1>
      </template>
      
    • 子组件:

      • 使用 <slot></slot>

        <slot></slot>
        <slot name='s1'></slot>
        
  • 插槽的分类:

    • 匿名插槽:没有名字的插槽(其实是有名字的,叫 default
    • 具名插槽:具有名字的插槽

匿名插槽

  • 父组件:

    • 直接在组件标签内写内容
  • 子组件:

    • 使用 slot 标签
  • 匿名插槽其实是有名字的,叫 default,可以省略不写

    • <template v-slot=default></template>
<div id="app">
    <zujian>
        <h2>插槽</h2>
    </zujian>
</div>
app.component('zujian', {
    template: `
        <div>
        	<h1>子组件</h1>
        	<slot></slot>
        </div>
	`
})

具名插槽

  • 父组件:
    • template 标签中使用指令 v-slot:插槽name值
    • 简写:v-slot => #
  • 子组件:
    • slot 标签中添加 name
<div id="app">
    <zujian>
        <template v-slot:s1>
            <h1>s1</h1><hr>
        </template>
        <template #s2>
            <hr><h1>s2</h1>
        </template>
    </zujian>
</div>
app.component('zujian', {
    template: `
        <div>
        	<slot name="s1"></slot>
        	<h1>子组件</h1>
        	<slot name="s2"></slot>
        </div>
    `,
})

插槽作用域

  • 数据在哪里写,就使用哪里的数据
    • 父组件使用的是父组件里面的内容(数据+函数)
    • 子组件使用的是子组件里面的内容(数据+函数)
<div id="app">
    <zujian>
        <template #s1>
            <h1>{{msg1}}</h1>	<!-- 插槽1 -->
            <hr>
        </template>
        <template #s2>
            <hr>
            <h1>{{msg2}}</h1>	<!-- 插槽2 -->
        </template>
    </zujian>
</div>
const app = Vue.createApp({
    data() {
        return {
            msg1: '插槽1',
            msg2: '插槽2',
        }
    },
})

app.component('zujian', {
    template: `
        <div>
            <slot name="s1"></slot>
            	<h1>子组件{{msg1}}</h1>	<!-- 子组件 -->
            <slot name="s2"></slot>
        </div>
    `,
    data() {
        return {
            msg1: '子组件'
        }
    }
})

Vue2插槽的用法

##### Vue2插槽的用法
  • Vue 2.6之前的版本 => slot="插槽name值"
  • Vue 2.6之后的版本 => v-slot:插槽name值
  • 高版本向下兼容低版本
<!-- Vue2.6之前的版本的语法 -->
<zujian>
    <template slot="s1">
        <button>我是2.6之前的版本</button>
    </template>
</zujian>

<!-- Vue2.6之后的版本的语法,Vue3使用的也是此语法 -->
<zujian>
    <template v-slot:s1>
        <button>我是2.6之后的版本</button>
    </template>
</zujian>
Vue.component("zujian", {
    template: `
        <div>
        	<slot name="s1"></slot>    
        </div>
    `
})

作用域插槽

  • 作用域插槽:父组件期望子组件返回过来的内容

  • 语法:

    • 父组件:

      <zujain>
          <template v-slot:s1="obj"></template>
          <!-- obj就是期望子组件返回过来的内容 -->
      </zujain>
      
    • 子组件:

      <slot name="s1" a='1' b='2' c='3'></slot>
      <!-- 将a,b,c返回,会被父组件的 obj 所接收 -->
      

Vue2的作用域插槽

##### Vue2中的作用域插槽
<!-- Vue2.6之前的版本 -->
<template slot="s1" slot-scope="{a,b,c}">{{a}}--{{b}}--{{c}}</template> 
<!-- vue2.6之后的版本 -->
<template v-slot:s1="{a,b,c}">{{a}}--{{b}}--{{c}}</template> 
Vue.component("zujian",{
    template:`
        <div>
        	<slot name="s1" a='1' b='2' c='3'></slot>
        </div> 
    `,
})

深层组件通信(依赖注入)

  • 深层组件通信:使用 provideinject

一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖

  • 语法1:

    • 提供数据:(对象写法)

      provide:{
          key1:value1,
          key2:value2
      }
      
    • 获取数据:

      inject:["key1"]
      
  • 语法2:

    • 提供数据:(函数写法)

      • 传递动态数据时,this要指向的Vue,将对象的写法改成函数写法
      provide:{
          return{
              key1:value1,
          	key2:value2
          }
      }
      
    • 获取数据:

      • 可以取别名,有默认值
      inject:{
          别名:{ // 别名(小名)
              from:key1, // 数据来源
              default:"默认值" // 默认值
          }
      }
      
  • 使用:和data同一级别的使用

<div id='app'>
    <h4>app组件</h4>
    <father></father>
</div>
const app = Vue.createApp({
    data() {
        return {
            name: '张三',
            age: 18,
            gender: '男'
        }
    },
    // 提供数据
    provide() {
        return {
            name: this.name,
            age: this.age,
            gender: this.gender
        }
    }
})

// 父组件
app.component('father', {
    template: `
        <div>
            <h4>父组件</h4>
            	{{name}}--{{age}}
            <son></son>
        </div>
    `,
    inject: ["name", "age"]
})
// 子组件
app.component('son', {
    template: `
        <div>
            <h4>子组件</h4>
            	{{name}}--{{gender}}
            <grandson></grandson>
        </div>
    `,
    inject: ["name", "gender"]
})
// 孙组件
app.component('grandson', {
    template: `
        <div>
            <h4>孙组件</h4>
            {{sex}}
        </div>
    `,
    inject: {
        sex: { // 取别名
            from: "gender",
            default: '女'    //默认值
        },
    }
})

解决数据不响应

  • 响应式:当数据发生改变时,页面要随之改变

  • provide 不是一个响应式钩子

  • 为保证注入方(inject)和供给端(provide)的响应性链接,需要使用 computed() 函数提供一个计算属性

    provide() {
        return {
            // 将非响应式的数据修改成响应式的数据
            name: Vue.computed(() => { //箭头函数可以保证this不乱
                return this.name;
            }),
        }
    }
    
    ...
    app.config.unwrapInjectedRef = true,
    app.mount('#app')
    

    临时配置要求

    上面的用例需要设置 app.config.unwrapInjectedRef = true 以保证注入会自动解包这个计算属性。这将会在 Vue 3.3 后成为一个默认行为,而我们暂时在此告知此项配置以避免后续升级对代码的破坏性。在 3.3 后就不需要这样做了。

事件总线

  • 回顾:Vue组件与Vue实例之间的关系

    const vm = new Vue()
    console.log(vvv.__proto__);//Vue的原型对象
    Vue.component("zujian",{
        template:`
            <div>
            	<h1>组件</h1>    
            </div>
        `,
        mounted(){
            //VueComponent => Vue的组件
            console.log(this);// 组件的实例
            console.log(this.__proto__); //组件的原型对象
            console.log(this.__proto__.__proto__);//Vue的原型的对象
            console.log(vm.__proto__ === this.__proto__.__proto__);
        }
    })
    
    new Vue({
        el:"#app"
    })
    

Vue2事件总线

  • 创建一辆公交车

    • const eventBus = new Vue()
  • 触发事件 $emit

    eventBus.$emit("自定义事件名", "携带参数传给自定义事件")
    
  • 监听事件:$on

    • 当该 该自定义事件 被触发后执行函数
    eventBus.$on("自定义事件名", (params) => {
        console.log("收到参数:", params);
    })
    
  • 🌰:(此案例:组件1=>组件2=>son)

    ##### Vue2案例
    //搭建一辆公交车
    const eventBus = new Vue()
    
    // 组件1
    Vue.component("zujianone", {
        template: `
        <div>
        	<h1>组件1</h1>    
            	<button @click="btn">组件1按钮</button>
            <son></son>
        </div>
        `,
        methods: {
            btn() {
                //触发一个事件
                eventBus.$emit("aaa", "组件1呼叫组件2")
            }
        }
    })
    
    // 组件1的子组件
    Vue.component("son", {
        template: `
        	<h1>son组件</h1>
        `,
        mounted() {
            eventBus.$on("bbb", (params) => {
                console.log("son组件收到:", params);
            })
        }
    })
    
    // 组件2
    Vue.component("zujiantwo", {
        template: `
            <div>
            	<h1>我是组件2</h1>    
            	<button @click="btn">组件2按钮</button>
            </div>
        `,
        methods: {
            btn() {
                //触发一个事件
                eventBus.$emit("bbb", "组件2呼叫son组件")
            }
        },
        mounted() {
            //监听事件aaa
            eventBus.$on("aaa", (params) => {
                console.log("组件2收到:", params);
            })
        }
    })
    
    new Vue({
        el: '#app',
        data() {
            return {}
        }
    })
    

Vue3事件总线

  • 引入 mitt 文件(npm / 标签 / 下载)

    • npmnpm install --save mitt
    • <script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
    • https://unpkg.com/mitt/dist/mitt.umd.js
  • 启动 mitt 文件

    const eventBus = mitt();
    
  • 监听事件

  • eventBus.on("自定义事件名",函数)

  • 触发事件

  • eventBus.emit("自定义事件名",参数)

  • 🌰:

    const eventBus = mitt()	// 下载引入,启动mitt文件
    const app = Vue.createApp({
    })
    // 组件1
    app.component('zujianone', {
        template: `
            <h1>组件一</h1>
            <button @click="btn">呼叫组件2</button>
            <son></son>
        `,
        methods: {
            btn() {
                eventBus.emit('aaa', '组件1呼叫组件2')
            }
        },
        mounted() {
        }
    })
    // 组件2
    app.component('zujiantwo', {
        template: `
            <h1>组件二</h1>
            <button @click="btn">呼叫组件son</button>
        `,
        methods: {
            btn() {
                eventBus.emit('bbb', '组件2呼叫son')
            }
        },
        mounted() {
            eventBus.on('aaa', (params) => {
                console.log('组件2收到:', params);
            })
        }
    })
    // son组件
    app.component('son', {
        template: `
        	<h1>son组件</h1>
        `,
        mounted() {
            eventBus.on('bbb', (params) => {
                console.log('son收到:', params);
            })
        }
    })
    app.mount('#app')
    

Vue3脚手架中事件总线

  • 下载 mitt

    • 指令:npm i mitt -S
  • mitt 挂载到 config > globalProperties

    • mian.js 中(全局挂载):

      // 导入mitt
      import mitt from 'mitt'
      // 挂载
      app.config.globalProperties.$mitt = mitt()
      
  • 使用

    • emit:触发事件

      • this.$mitt.emit("自定义事件名",参数)
      this.$mitt.emit("xxx",'参数')
      
    • on:监听事件

      • this.$mitt.on("自定义事件名",函数)
      this.$mitt.on("xxx",(params)=>{
          console.log(params)
      })
      

动态组件

  • 动态组件可以灵活切换要显示哪个组件

  • 使用 <component></component> 标签

  • 语法:

    • 语法1:写组件名

      <component is="组件名"></component>
      
    • 语法2:写vue选项

      <component :is="{template:`<div>我是组件{{count}}<button @click='count++'>点我</button></div>`,data(){return{count:1}}}"></component>
      

KeepAlive

  • KeepAlive 是一个内置组件

  • 作用:在多个组件间动态切换时,缓存被移除的组件

  • 在切换组件时,如果不想让该组件被销毁(卸载),需要使用 KeepAlive 标签将组件给包裹起来

  • 使用:

    • ⭐在DOM模板当中,需要使用 <keep-alive> 标签进行包裹组件
    <keep-alive>
        <component is="zujianone"></component>
    </keep-alive>
    
  • KeepAlive的属性

    • include:包含(白名单,免死令牌)

      <!-- 数组写法 -->
      <keep-alive :include="['zujianone','zujiantwo']">
          <component :is="msg"></component>
      </keep-alive>
      
      <!-- 字符串写法 -->
      <!-- ⭐不可以有空格 -->
      <keep-alive include="coma,comb">
          <component :is="msg"></component>
      </keep-alive>
      
    • exclude:排除(黑名单,必死)

      <keep-alive :exclude="['zujianone','zujiantwo']">
          <component :is="msg"></component>
      </keep-alive>
      
    • max:最大可以缓存组件数组件(包括当前显示的组件),缓存最后显示的n-1个组件

注意:
    1.一般情况下,三个属性单独使用
    2.在每个组件里面需要添加一个`name`3.数组写法 (需使用 `v-bind`)

KeepAlive的生命周期

  • 激活
    • activated
      • 在首次挂载时触发
      • 每次从缓存中被重新插入时触发
  • 失活
    • deactivated
      • 从DOM上移除、进入缓存时触发
      • 组件卸载时触发
  • 扩展:
    • 激活 钩子里面的定时器,需要在 失活 里面进行清除

Transition

  • Transition:是一个内置组件

  • 作用:用来制作过度动画

  • 进入动画:

    • v-enter-from:进入动画起始状态
    • v-enter-active:进入动画生效状态
    • v-enter-to:进入动画结束状态
  • 离开动画:

    • v-leave-from:离开动画起始状态
    • v-leave-active:离开动画生效状态
    • v-leave-to:离开动画结束状态
  • 使用:

    • 将添加动画的 标签使用 <Transition></Transition> 包裹起来
    • 书写CSS样式
  • 🌰:

    /* 进入动画的起始,离开动画的结束 */
    .v-enter-from,.v-leave-to{
        opacity:0
    }
    /* 动画的生效过程中 */
    .v-enter-active,.v-leave-active{
        transition: opacity 5s ease;
    } 
    
    <div id='app'>
        <button @click="show = !show">Toggle</button>
        <Transition>
            <p v-if="show">过度动画</p>
        </Transition>
    </div>
    
  • 属性:appear

    • appear 属性:页面打开时,执行一次动画效果
    <Transition appear>
        <p v-if="show">过度动画</p>
    </Transition>
    

过度动画命名

  • Transition 添加 name 属性之后,会有将之前的 v 换成 name 属性值
  • 对于一个有名字的过渡效果,对它起作用的过渡class会以其名字而不是 v 作为前缀
/* 进入动画的起始,离开动画的结束 */
.dh-enter-from,.v-leave-to{
    opacity:0
}
/* 动画的生效过程中 */
.dh-enter-active,.v-leave-active{
    transition: opacity 5s ease;
} 
<div id='app'>
    <button @click="show = !show">Toggle</button>
    <Transition name="dh">
        <p v-if="show">过度动画</p>
    </Transition>
</div>

animation

/* 原生写法 */
.bounce-enter-active {
    animation: bounce-in 0.5s;
}
.bounce-leave-active {
    animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
    0% {
        transform: scale(0);
    }
    50% {
        transform: scale(1.25);
    }
    100% {
        transform: scale(1);
    }
}
<link rel="stylesheet" href="./css/animate.css">
<div id='app'>
    <button @click="show = !show">Toggle</button>
    <Transition name="custom-classes" 
                enter-active-class="animate__animated animate__slideInRight"
                leave-active-class="animate__animated animate__zoomOutRight">
        <p v-if="show">hello</p>
    </Transition>
</div>

TransitionGroup

  • <TransitionGroup> 是一个内置组件

  • 作用:用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果

  • 注意:当在 DOM模板 中使用时,组件名需要写为 <transition-group>

$el

  • $el:该组件实例管理的DOM根节点

  • $el 直到组件 挂载完成 (mounted) 之前都是 undefined

  • 对于单一根元素的组件,$el 将会指向该根元素

  • 对于单一根元素的组件,$el 将会指向该根元素

* 小窍门
	为保持一致性,官网推荐使用`模板引用`来直接访问元素而不是依赖`$el`

$nextTick()

  • $nextTick():绑定在实例上的 nextTick() 函数

  • 等待下一次 DOM 更新刷新的工具方法

  • $nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成

  • 可以传递一个回调函数作为参数,或者 await 返回的 Promise

$set

##### Vue2独有
  • 可以将非响应式数据修改成响应式数据

  • 语法:this.$set(对象或者数组,key或下标,新数据)

    this.$set(vm.person,"name","张三")
    
  • **补充:**Vue2 将被侦听的 数组 的变更方法进行了包裹,所以它们也将会触发视图更新,包括:

    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()

响应式api

  • ref

    • 作用:将基本数据类型数据变成响应式数据

    • 语法:

      let 响应式数据 = ref(默认值)
      
  • reactive

    • 作用:将复杂数据类型数据变成响应式数据

    • 语法:

      let data = reactive({处理的数据})
      
    • 特点:

      1.使用proxy代理,将对象第一层的属性编程响应式数据,不用递归,提升性能
      2.proxy代理的数据是对象,如果对象的属性有层次,他会实现懒代理
      	懒代理:只有在用户使用了该数据,才会将该数据变成响应式数据
      
  • toRefs

    • 作用:将 reactive 代理对象中的属性,值变成ref处理的数据

    • 语法:

      let {name,age} = toRefs(ractive响应式数据)
      
  • readonly

    • 作用:数据只能读取不能修改,如:父传子的数据

    • 语法:

      let 数据 = readonly(默认数据)
      
  • isxxx

    • isRef
    • isReactive
    • isReadonly
    • 作用:判断该数据是否是被该方法(ref、reactive、readonly)处理
    • 返回值:true、false
  • toRef

    • 作用:将reactive代理的数据(对象)中的某个属性变成ref

    • 语法

      let ref值 = toRef(reactive对象,属性)
      
  • shallowReactive、shallowRef、shallowReadonly

    作用:浅层,数据的第一层
    	shallowReactive:第一层数据是响应式
        shallowRef:第一层数据是响应式
    	shallowReadonly:第一层数据是只读的
    
  • watchEffect

    • 特点:

      1.默认立即执行
      2.监听的数据直接写到处理函数中,并且可以监听多个数据
      3.监听数据的变化,获取到最新的数据,没有旧的数据
      
    • 语法

      watchEffect(()=>{})
      
  • watch

    • 作用:

      时刻监听数据改变(响应式数据)
      
    • 语法:

      watch(监听数据,处理函数,配置项)
      
    • 监听ref数据

      watch(age,()=>{})
      
    • 监听reactive数据

      watch(()=>state.name,()=>{})
      
    • 配置项

      wacth(
          ()=>state.like,
          ()=>{},
          {
      		immediate:true, // 立即执行
      		deep:true // 深度监听
      	}
      )
      
    • 监听多个数据

      watch([age,()=>state.name],([newA,newN],[oldA,oldN])=>{})
      
##### watch和watchEffect的想同点与不同点
相同点:都可以时时刻刻监听某个数据的改变,都可以处理异步问题,都可以蒋婷多个数据
不同点:
	watch默认不会立即执行
    watch可以获取到新旧数据

环境搭建

  • nodejs安装
    • 版本:16+(npm 8+)
    • 在node官网下载v16(.msi)双击安装
    • 在命令行中node -vnpm -v / node --version 查询版本号
  • 包管理器
    • npm
    • yarn
      • 安装Yarn: npm install yarn -g
    • cnpm:淘宝镜像
      • 安装淘宝镜像:npm install cnpm -g --registry=https://registry.npmmirror.com

脚手架

  • 安装脚手架(vite)
    • 指令:npm init vue@3 或者 npm init vue@latest
    • vue2脚手架:cnpm i @vue/cli -g
# 项目名称
? Project name:
# 是否添加TypeScript
? Add TypeScript? » No / Yes
# 是否支持JSX
? Add JSX Support? » No / Yes
# 是否为单页应用程序开发添加Vue路由器
? Add Vue Router for Single Page Application development? » No / Yes
# 是否添加Pinia进行状态管理
? Add Pinia for state management? » No / Yes
# 是否添加Vitest进行单元测试
? Add Vitest for Unit Testing? » No / Yes
# 是否添加Cypress来进行单元测试和端到端测试
? Add Cypress for both Unit and End-to-End testing? » No / Yes
# 是否添加ESLint来进行代码质量检查
? Add ESLint for code quality? » No / Yes
# 是否添加更漂亮的代码格式
? Add Prettier for code formatting? » No / Yes
...
创建Vue项目成功
...
指令:
    cd vue-project:	切换路径
    npm install:	复活依赖包
    npm run lint:	代码规范检测
    npm run dev:	项目启动
  • 项目启动:npm run dev

目录分析

  • _vscode 当项目以服务器启动,生成一个文件夹

    • extensions.json: 叫我们安装插件
  • README.md:项目的说明

  • node_modules:项目所依赖的包

  • public:本地资源目录

  • src:代码目录

  • _eslintrc.cjs:代码检测的规范

  • .gitignore:上传代码( git add / git commit / git push) 时,要忽略哪些文件或目录

  • .prettierrc.json:漂亮的代码的规范

  • index.html:单页面

  • package-lock.json:package锁文件

  • package.json:项目的配置文件

  • vite.config.js:脚手架(vite)的配置

  • **.vue**文件:单文件组件,由视图模块、Vue组件选项、样式这三个部分组成

  • package.json => script

    • dev:本地启动的项目 => 指令:npm run dev(项目启动)
    • build:项目写完之后,打包,发布 => 指令:npm run build(项目打包)
      • 注意: 打包会生成一个 dist 文件夹,后期将dist文件夹放到云端,别人就可以访问到了
    • preview:预览,启动 dist 文件 => 指令:npm run preview
    • lint:检测代码是否符合规范 => 指令:npm run lint
  • 扩展:

    • **index**文件中的全局引入尽量使用绝对地址,出了src目录尽量使用绝对地址,/ 会自动从根目录开始找

webpack创建项目

  • 指令:

    vue create 项目名称
    
  • 选择vue版本和预设功能

    # Please pick a preset: (Use arrow keys)
    > Default ([Vue 3] babel, eslint)
      Default ([Vue 2] babel, eslint)
      Manually select features(手动选择功能)
    
  • 手动选择功能

    # Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
    >(*) Babel
     (*) TypeScript
     ( ) Progressive Web App (PWA) Support
     ( ) Router
     ( ) Vuex
     ( ) CSS Pre-processors
     ( ) Linter / Formatter
     ( ) Unit Testing
     ( ) E2E Testing
    
  • 选择要启动的vue版本

    # Choose a version of Vue.js that you want to start the project with (Use arrow keys)
    > 3.x
      2.x
    
  • 希望将Babel、ESLint等的配置放在哪

    # Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
    > In dedicated config files(专用配置文件中)
      In package.json (package.json中)
    
  • 保存为未来项目的预设

    # Save this as a preset for future projects? (y/N) n
    

目录结构

node_modules: 项目依赖
public: 静态资源目录
src: 核心代码
	assets: 公共方法
	components: 公共组件
	App.vue: 根组件
    main.js: 入口文件
.browserslistrc: 对浏览器支持
.gitignore: Git要忽略的文件目录
babel.config: ES6=>ES5
package.json: 项目配置文件
vue.config.js:webpack 配置文件

scoped作用域

  • **.vue**文件由 <script/><tamplate/><style/> 组成

  • <style> 标签上的 scoped 属性,是一个样式作用域

    <script></script>
    <template></template>
    <style scoped>
    </style>
    
  • scoped

    • 可以让当前组件独享这个样式(局部作用域)
    • 对子组件的根元素样式同样有效果(根元素有继承)
    • 对子组件的内部没有效果
  • 原理:

    `scoped`修饰`style`的时候
    会对当前组件里面的元素(包括子组件的根元素)的标签上添加一个属性:`data-v-xxx`
    `scoped`只对带有`data-v-xxx`属性的标签起作用
    

父组件中修改子组件内部样式

  • 方案1:在写一个不带 scoped 属性的 style 标签

  • 方案2:深度选择器

    • 语法::deep(子组件的类名)
    :deep(.box){
      background-color: springgreen;
    }
    

请求、响应拦截器

  • axios 的二次封装

    • 下包:npm i axios

    • 导入:

      import axios from 'axios'
      
    • 创建 axios 实例

      • baseURL:基准地址
      • timeout:超时时间(ms)
      const server = axios.create({
          baseURL: 'http://localhost:3000/',
          timeout: 2000
      })
      
    • 添加请求拦截器

      server.interceptors.request.use(function (config) {
      
          // 在发送请求之前做些什么
          
          return config; // 放行
      }, function (error) {
          
          // 对请求错误做些什么
          
          return Promise.reject(error);
      });
      
    • 添加响应拦截器

      server.interceptors.response.use(function (response) {
      
          // 2xx 范围内的状态码都会触发该函数。
          // 对响应数据做点什么
          
          return response; // 放行
      }, function (error) {
          
          // 超出 2xx 范围的状态码都会触发该函数。
          // 对响应错误做点什么
          
          return Promise.reject(error);
      });
      
    • 暴露(导出)

      export default server
      
    • 全局挂载

      • main.js 文件中

        // 导入
        import axios from './utils/request.js'
        // 挂载到config.globelPropertis
        app.config.globalProperties.$axios = axios;
        
    • 使用

      this.$axios({
          url: "messageList",
          method: "get"
      })
      

nprogress进度条

  • 下包:npm i nprogress

  • 导包:导入 js,导入 css

    import nprogress from 'nprogress'
    import 'nprogress/nprogress.css'
    
  • 使用

    • 添加进度条

      nprogress.start();
      
    • 停止进度条

      nprogress.done();