目录

1、认识Vue3

2、Vue指令

1、属性绑定指令

2、事件绑定指令

3、条件渲染指令

4、列表渲染指令

5、双向绑定指令

3、选项式API

1、计算属性

2、侦听器

4、Vue组件

1、组件的使用

2、组件通信

1、父子组件通信

2、兄弟组件通信

3、跨层级组件通信

3、插槽

1、匿名插槽

2、具名插槽

3、其它插槽

5、相关面试题

1、vue3相比vue2做了哪些升级

2、vue组件通信

3、computed和watch区别

4、v-model原理

5、v-if v-show的区别


1、认识Vue3

Vue3是一个灵活易用的用于构建用户界面的渐进式js框架,于2020年9月份正式发布,它在性能、开发体验和可维护性上都有巨大的改变

组合式API

Vue3组合式API就是使用setup函数,将同一功能相关的数据、方法、生命周期钩子组织在一起,对于大型项目,使得代码更易于复用和重构,它解决了vue2选项式API在复杂组件中逻辑关注点分离不清的问题

 <script setup>
    import { ref} from "vue"
    const count = ref(0)
    function increment() {
      count.value++
    }
  </script>

性能提升

vue3通过Tree-shaking,它的打包体积比vue2更小。vue3基于proxy的全新响应式系统,带来了更快的组件初始化、渲染和更新速度。它重写了虚拟dom的diff算法,在模板编译时进行了更多优化,减少了运行时开销。而Vue3从一开始就使用了ts重写,提供了完美的类型推断,提升了用户体验

全新的响应式系统

Vue3使用Proxy替代了Vue2的Obiect.defineProperty来实现响应式,可以直接监听对象和数组的动态新增或删除属性,默认支持Map、Set等集合类型,提供了更好的性能支持

新增内置组件

<Fragment>组件可以让Vue3拥有多个根节点,无需用一个额外的<div>包裹,<Teleport>组件可以将组件的一部分内容传送到dom的其它位置,<Suspense>组件提供了一种在组件树中协调嵌套异步依赖加载状态的方法

2、Vue指令

Vue指令是带有v-前缀的特殊属性,用于在模板中提供响应式的dom行为,从简单的dom的操作到复杂的交互行为,如条件渲染、循环、事件绑定等等

1、属性绑定指令

v-bind是属性绑定指令,可以动态的绑定属性,如id,class,style

v-bind动态绑定id

 <div id="app">
    <div v-bind:id="id">{{msg}}</div>
    <div :id="a"></div>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          msg: "hello",
          id: "title",
          a: "box"
        }
      },
    }).mount("#app") 
  </script>

v-bind动态绑定class

 <div id="app">
    <!-- 绑定一个字符串 -->
    <div v-bind:class="b">{{msg}}</div>
    <div :class="a"></div>
    <!--绑定一个对象 -->
    <div :class="classObj"></div>
    <!-- 绑定数组 -->
    <div :class="classArr"></div>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          msg: "hello",
          b: "title",
          a: "box",
          classObj: {
            active: true,
            active1: false
          },
          classArr: ["active2", "active3"]
        }
      },
    }).mount("#app") 
  </script>

v-bind动态绑定style

 <div id="app">
    <!-- 绑定到一个字符串 -->
    <div v-bind:style="title">{{msg}}</div>
    <!-- 绑定到一个对象 -->
    <div v-bind:style="styleObj"></div>
    <!-- 绑定到一个数组 -->
    <div v-bind:style="[styleObj1,styleObj2]">{{msg}}</div>
    <!-- 动态绑定属性名 -->
    <div v-bind:[key]="name"></div>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          msg: "hello",
          key: "class",
          name: "box",
          title: "width:200px;height:200px;background-color:red;text-align:center",
          styleObj: {
            width: "200px",
            height: "200px",
            backgroundColor: "green",
          },
          styleObj1: {
            width: "100px",
            height: "100px",
            backgroundColor: "yellow"
          },
          styleObj2: {
            textAlign: "center",
            lineHeight: "100px"
          }
        }
      },
    }).mount("#app") 
  </script>

2、事件绑定指令

  <div id="app">
  <div v-on:click="fn" v-on:mouseover="fn2"></div>
  <!-- 事件传参 -->
   //第一种写法
   <div v-on:click="(e)=>{fn3(20,e)}"></div>
   //第二种写法
   <div v-on:click="fn3(20,$event)"></div>
   <!-- 简写 -->
    <div @click="fn3"></div>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      methods: {
        fn(){
          console.log("鼠标点击了")
        },
        fn2(){
          console.log("鼠标滑过了")
        },
        fn3(m,e)j
          console.log(m,e)
        }
      },
    }).mount("#app") 
  </script>

点击按钮控制盒子的显示和隐藏

 <div id="app">
    <div :style="classObj"></div>
    <button @click="fn">按钮</button>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          flag: true,
          classObj: {
            width: "100px",
            height: "100px",
            backgroundColor: "red",
            display:"block"
          }
        }
      },
      methods: {
        fn() {
          this.classObj.display = this.classObj.display==="block"?"none":"block"
        }
      },
    }).mount("#app") 
  </script>

3、条件渲染指令

v-if:是真实的按条件渲染,它确保了在切换时,条件区块内的事件监听和子组件都会被销毁和重建,同时,v-if也是惰性的,如果在初次渲染时条件值为false,则不会做任何事,条件区块,条件区块只有当条件首次变为true时才被渲染

 <div id="app">
    <div v-if="grades>=90">优秀</div>
    <div v-else-if="grades>=80">良好</div>
    <div v-else-if="grades>=60">及格</div>
    <div v-else>不及格</div>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          grades: 88
        }
      },
    }).mount("#app") 
  </script>

v-if控制显示隐藏

 <div id="app">
    <template v-if="flag">
      <div>123</div>
      <div>123</div>
      <div>123</div>
    </template>
    <button @click="change">按钮</button>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          flag: true,
        }
      },
      methods: {
        change() {
          this.flag = !this.flag
        }
      },
    }).mount("#app") 
  </script>

注:template是一个容器元素,在最终渲染时不会被渲染为任何标签,不会影响结构

v-show:无论元素的初始条件如何,始终会被渲染,它通过控制css的display属性来控制元素的显示和隐藏

 <div id="app">
    <div v-show="flag">123</div>
    <button @click="change">按钮</button>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          flag: true,
        }
      },
      methods: {
        change() {
          this.flag = !this.flag
        }
      },
    }).mount("#app") 
  </script>

注:v-if有更高的切换消耗,v-show有更高的初始渲染消耗

4、列表渲染指令

v-for列表渲染指令,可以用来循环数组,对象等等

循环数组

  <div id="app">
    <ul>
      <li v-for="(item,index) in hobby">{{index+1}} {{item}}</li>
    </ul>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          flag: true,
          hobby: ["看电影", "跳舞", "唱歌"]
        }
      }
    }).mount("#app") 
  </script>

注:对于数组的变化,只要可以修改原数组的方法就可以触发页面更新,反之不可以,可以通过给数组重新赋值的方式触发页面更新

循环对象

 <div id="app">
    <ul>
      <!-- 循环对象 第一个参数是属性值,第二个参数是属性名,第三个参数是索引 -->
      <li v-for="(value,key,index) in obj">{{key}}:{{value}}</li>
    </ul>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          flag: true,
          obj: {
            name: "小明",
            age: 20,
            education: "本科"
          }
        }
      }
    }).mount("#app") 

循环整数

 <div id="app">
    <template v-for="item in 10">
      <div>123</div>
      <div>456</div>
    </template>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {}
      }
    }).mount("#app") 

5、双向绑定指令

v-model是用于实现数据双向绑定的,主要用于表单输入元素如input、textarea、select,也可以说使用v-model不仅可以实现数据驱动视图,还可以实现视图驱动数据

如果是单个复选框,绑定到的是布尔值

 <div id="app">
    <input type="checkbox" v-model="val">hello
    <h1>{{val}}</h1>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          val: false,
          reg: /^\d{6}$/
        }
      },
      methods: {
        check() {
          if (this.reg.test(this.val)) {
            alert("验证通过")
          } else {
            alert("格式有误")
          }
        }
      },
    }).mount("#app") 
  </script>

注:针对单个复选框,可以手动更该值得类型,不一定要是布尔值,可以是true-value或false-value

如果是多个复选框,绑定到数组,存的是选中项的value

 <div id="app">
    <input type="checkbox" v-model="val" value="吃饭">吃饭
    <input type="checkbox" v-model="val" value="睡觉">睡觉
    <h1>{{val}}</h1>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          val: [],
          reg: /^\d{6}$/
        }
      },
      methods: {
        check() {
          if (this.reg.test(this.val)) {
            alert("验证通过")
          } else {
            alert("验证失败")
          }
        }
      },
    }).mount("#app") 
  </script>

如果是单选框,绑定到的是选中项的value

 <div id="app">
    <input type="radio" v-model="val" value="吃饭">吃饭
    <input type="radio" v-model="val" value="睡觉">睡觉
    <h1>{{val}}</h1>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          val: "",
          reg: /^\d{6}$/
        }
      },
      methods: {
        check() {
          if (this.reg.test(this.val)) {
            alert("验证通过")
          } else {
            alert("验证失败")
          }
        }
      },
    }).mount("#app") 
  </script>

如果是下拉框,绑定到的是选中项的option的value

 <div id="app">
    <select v-model="val">
      <option>河南省</option>
      <option>湖南省</option>
      <option>湖北省</option>
    </select>
    <h1>{{val}}</h1>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          val: "河南省",
          reg: /^\d{6}$/
        }
      },
      methods: {
        check() {
          if (this.reg.test(this.val)) {
            alert("验证通过")
          } else {
            alert("验证失败")
          }
        }
      },
    }).mount("#app") 
  </script>

3、选项式API

选项式API是Vue2中默认的一种组织代码的方式,它把组件的不同功能,如数据,方法,计算属性等等分别放在不同的选项里,使代码的结构更清晰

注:vue中的选项式API,有data,computed,methods,watch

1、计算属性

computed计算属性的值是根据其它数据动态计算得来的。它具有自动缓存的特点,只要计算时依赖的数据没有变化,computed就不会重新计算,而是直接返回缓存的结果。计算属性返回的是一个值,在模板或代码里,我们可以直接把它当作一个普通的data属性来使用,不需要加括号调用

 <div id="app">
    <p>{{fn}}</p>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          orderStatus: 3//1未发货,2已发货,3派送中,4已签收
        }
      },
      computed: {
        fn() {
          if (this.orderStatus === 1) {
            return "未发货"
          } else if (this.orderStatus === 2) {
            return "已发货"
          } else if (this.orderStatus === 3) {
            return "派送中"
          } else {
            return "已签收"
          }
        }
      }
    }).mount("#app") 
  </script>

methods和computed都能实现类似功能,所以我们经常会将两者搞混淆,两者的主要区别就是缓存,计算属性是有缓存的,依赖项不变时,多次访问会直接返回缓存结果,不会重复执行函数,方法是无缓存的,每次访问都会重新执行一遍函数,如果我们是基于响应式数据进行复杂计算,并且利用缓存来提升性能,我们就可以使用computed

 <div id="app">
    <button @click="minus">-</button>
    <span>{{count}}</span>
    <button @click="add">+</button>
    <p>{{fn}}</p>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          count: 1,
          price: 10
        }
      },
      methods: {
        minus() {
          if (this.count > 0) {
            this.count--
          }
        },
        add() {
          this.count++
        }
      },
      computed: {
        //只要price和count发生变化,fn就会自动更新
        fn() {
          return this.count * this.price
        }
      }
    }).mount("#app") 
  </script>

注:计算属性是一个运算结果,是只读属性,不能更改的,其实,计算属性的只读属性就是我们只定义了一个函数,就是计算属性的getter,它负责计算并返回一个值

计算属性的高级用法

计算属性的高级用法就是给计算属性设置一个getter和setter,让它从一个只读属性变成一个既能读取又能修改的属性,get()就是当访问这个计算属性时,它会被调用,set(value)就是当修改这个计算属性是,它会被调用,并且赋给它的值会作为参数value传入

 <div id="app">
    <p>{{fullName}}</p>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          firstName: "李",
          lastName: "四"
        }
      },
      computed: {
        //读取时触发
        fullName: {
          get() {
            return this.firstName + "" + this.lastName
          },
          //修改时触发
          set(value) {
            const names = value.split(" ")
            this.firstName = names[0]
            this.lastName = names[1]
          }
        }
      }
    }).mount("#app") 
  </script>

注:computed的高级用法特别适合处理一些复杂的表单或需要双向绑定的数据

2、侦听器

watch是一个监听器,可以监听data中的数据或计算属性,一旦监听的数据或计算属性发生变化,就会立刻执行watch中的函数

监听data中的普通数据

<div id="app">
    <p>{{a}}</p>
    <button @click="fn">按钮</button>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          a: 1
        }
      },
      methods: {
        fn() {
          this.a = 100
        }
      },
      watch: {
        //方法名就是监听的数据名
        a() {
          console.log("a变了")
        }
      }
    }).mount("#app") 
  </script>

注:watch中的方法名就是监听的数据名

监听data中的对象或数组

watch默认是浅层监听,当要监听一个对象或数组时,watch默认只监听它的引用地址是否发生变化,如果只是修改了对象的某个属性或数组的某个元素,watch是不会触发的,我们可以开启深度监听,或整体赋值的方式

深度监听

 <div id="app">
    <p>{{obj}}</p>
    <p>{{arr}}</p>
    <button @click="fn">按钮</button>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          obj: {
            name: "小红",
            age: 10
          },
          arr: [1, 2, 3]
        }
      },
      methods: {
        fn() {
          this.obj.name = "小明"
          this.arr[2] = 4
        }
      },
      watch: {
        //a b变化了监听器也会执行
        obj: {
          handler() {
            console.log("object改变了")
          },
          deep: true
        },
        arr: {
          handler() {
            console.log("arr改变了")
          },
          deep: true
        }
      }
    }).mount("#app") 
  </script>

注:深度监听很消耗性能,不推荐使用

整体赋值

  <div id="app">
    <p>{{obj}}</p>
    <p>{{arr}}</p>
    <button @click="fn">按钮</button>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          obj: {
            name: "小红",
            age: 10
          },
          arr: [1, 2, 3]
        }
      },
      methods: {
        fn() {
          this.obj = { name: "小明", age: 10 }
          this.arr = [1, 2, 4]
        }
      },
      watch: {
        //a b变化了监听器也会执行
        obj() {
          console.log("object改变了")
        },
        arr() {
          console.log("arr改变了")
        }
      }
    }).mount("#app") 
  </script>

监听计算属性

<div id="app">
    <p>{{a}}</p>
    <button @click="fn">按钮</button>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data() {
        return {
          a: 1,
          b: 10
        }
      },
      methods: {
        fn() {
          this.a = 100
        }
      },
      computed: {
        add() {
          return this.a + this.b
        }
      },
      watch: {
        //a b变化了监听器也会执行
        add() {
          console.log("add变化了")
        }
      }
    }).mount("#app") 
  </script>

4、Vue组件

Vue组件就是我们可以将UI拆分为独立的,可复用的代码片段,每个组件都有自己的逻辑和样式,可以嵌套和组合用来构建复杂的应用程序

1、组件的使用

我们在使用组件的时候,组件一定要进行注册,同时,组件的写法有大驼峰如<MyCom/>,连字符如<my-com/>两种,推荐使用大驼峰

 <div id="app">
    <MyChild />
  </div>
  <script>
    const { createApp } = Vue
    const MyChild = {
      template: `<h1>我是子组件</h1>`,
      data() {
        return {
          info: "我是子组件"
        }
      }
    }
    createApp({
      //局部注册组件
      components: {
        Child
      }
    }).mount("#app") 
  </script>

组件嵌套

 <div id="app">
    <Child />
  </div>
  <script>
    const { createApp } = Vue
    const Grand = {
      template: `<h1>{{info1}}</h1>`,
      data() {
        return {
          info1: "我是孙子组件"
        }
      }
    }
    const Child = {
      template: `<div>
      <h1>1111</h1>
      <Grand/>
      </div>`,
      components: {
        Grand
      },
      data() {
        return {
          info: "我是子组件"
        }
      },
    }
    createApp({
      components: {
        Child
      },
      data() {
        return {
          msg: "123"
        }
      },
    }).mount("#app") 
  </script>

2、组件通信

1、父子组件通信

父传子

父组件通过props传递数据给子组件,父组件在子组件的标签上绑定属性,子组件通过props选项接收

 <div id="app">
    <div>
      <p>{{msg}}</p>
      <!-- 在父组件中的子组件的标签上绑定属性 -->
      <Child :msg1="msg1" />
    </div>
  </div>
  <script>
    const { createApp } = Vue
    const Child = {
      template: `<div>
      <h1>{{info}}</h1>
      <h1>{{msg1}}</h1>
       </div>`,
      data() {
        return {
          info: "我是子组件"
        }
      },
      // 子组件通过props接收
      props: ["msg1"]
    }
    createApp({
      data() {
        return {
          msg: "123",
          //传递给子组件的数据
          msg1: "12345"
        }
      },
      components: {
        Child
      }
    }).mount("#app") 
  </script>

子传父

在父组件中子组件的标签上声明一个自定义事件,对应的事件处理函数为父组件里的一个方法,在父组件中定义方法,将参数赋值给父组件自己的数据,然后在子组件中定义方法,方法中要通过$emit去主动触发,我们定义的自定义事件,有两个参数,第一个参数为自定义事件,第二个参数为子组件要传递的数据

 <div id="app">
    <div>
      <p>{{msg}}</p>
      <Child @myfn="handler" />
    </div>
  </div>
  <script>
    const { createApp } = Vue
    const Child = {
      template: `<div>
      <h1 @click="fn">{{info}}</h1>
       </div>`,
      data() {
        return {
          info: "我是子组件",
          //传递给父组件的数据
          childInfo: "我是子组件的数据"
        }
      },
      methods: {
        fn() {
          //自定义事件
          this.$emit("myfn", this.childInfo)
        }
      },
    }
    createApp({
      data() {
        return {
          msg: "123",
        }
      },
      methods: {
        handler(data) {
          //将子组件传递的数据赋值给父组件自己的数据
          this.msg = data
        }
      },
      components: {
        Child
      }
    }).mount("#app") 
  </script>

子父传值也可以使用v-model

父子组件通信一般会有父组件要使用子组件表单标签的数据,vue就加强了v-model,可以作用在子组件上,将父组件中data里的某个数据进行双向绑定,子组件使用固定的props属性value接受以及固定的自定义事件input也可以在子组件上进行使用,达到优化父子通信代码的目的

父组件的子组件标签上使用v-model将需要绑定的数据写在子组件上

 //Father.vue
//searchText是父组件data中的数据
<Child v-model="searchText"></Child>

子组件使用value来接受父组件传递的数据

//Child.vue
export default {
   props:['value']
   }

子组件通过input自定义事件来将数据重新赋给v-model绑定的数据。如果是计算属性,则需要保证计算属性有set

 <input type="text" ref="searchInput"> <button @click="search">搜索</button>
 <script>
    export default {
         methods: {
            search(){
              let text = this.$refs.searchInput.value;
               //触发固定的自定义事件
                 this.$emit("input",text);
                }
            },
        }
</script>

注:使用v-model可以节省父子表单数据通信的代码,但一个子组件只能用一个v-model,我们可以用v-model绑定一个对象,然后再子组件内部对属性进行操作

2、兄弟组件通信

兄弟组件通信通常使用事件总线,事件总线是一种以vue实例对象为核心的一种组件之间通信的编程技巧,事件总线理论上可以实现任何两个组件之间的通信,不用考虑层级关系

事件总线主要以一个新的vue实例对象为核心,通过vue对象里的$on和$emit来实现一个自定义事件的创建和监听,可以在需要通信的两个组件中分别引入该vue对象,让接收数据的组件定义自定义事件,发送数据的组件调用该自定义事件,并把数据作为事件的参数完成组件通信

$on:vue实例上的方法,用于创建一个自定义事件并监听,需要$emit来触发

$emit:vue实例上的方法,用于触发该vue实列上创建的自定义事件

事件总线实现兄弟组件的通信

在util文件夹中新建一个bus.js,里面引入vue生成vue对象并暴露

    import Vue from "vue";
    const bus = new Vue();
    export default bus;

需要通信的两个组件都引入该bus.js

    import bus from "./bus.js"

待接受数据的兄弟组件创建自定义事件

    //页面一打开就会执行
    created() {
        bus.$on('getData',(res)=>{
            //res就是接受的数据,名称可以自己改
        })
      },

发送数据的兄弟组件2触发该自定义事件,完成数据传递

    <button @click="send">发送数据</button>
    methods: {
        send(){
            bus.$emit('getData',this.message)
        }
    }

注:事件总线可以实现任何两个组件之间的通信,但一个自定义事件是单向通信,如果要双方都能通信,则需要每个组件都定义一个自定义事件。并且如果要和第三个或更多的组件完成通信则需要定义更多的自定义事件,比较麻烦

3、跨层级组件通信

跨层级组件的通信方式就是实现祖先组件到孙子组件的通信,根据组件间传递的数据是否具有响应式,实现的方式也不同

如果组件间传递的数据是非响应式的,可以通过provide和inject来实现,provide和inject是vue组件对象上的两个属性,其中provide主要用于声明要传递的数据,它是一个函数,由其返回值来提供。inject是一个数组,一般是接受数据的组件进行设置,用于声明要接受的数据

祖先组件

    export default {
        data(){},
        provide(){
            //返回值就是要传递的数据
            return {
                name:'张三',
                age:12
            }
        }
    }

孙子组件

    export default {
        data(){},
        inject:["name",'age']
    }

注:provide/inject只能用于祖先组件传递数据给后代组件,后代组件不能使用provide/inject传递数据给祖先组件

如果组件传递的是响应式的数据,可以使用$attrs/$listeners,它们两个也是vue组件实例对象的两个属性,都是一个对象,$attrs存放的是祖先组件通过自定义属性传递的数据,$listeners里面就包含了从祖先组件开始到当前组件为止,声明的所有自定义事件。

祖先组件将需要传递的数据以及自定义事件声明在子组件上

   //GrandParent.vue组件
 <Father :userInfo="userInfo" :phone='phone' @changeUser='changeUser'>
     <script>
     export default{
         data(){
             return{
                 userInfo:{},
                 phone:"12315"
             }
         },
         methods:{
             changeUser(){
             }
         }
     }   
     </script>

后代组件使用props接受自己想要的数据,剩余未接受的数据就通过$attrs,继续向下传递

    <Child v-bind="$attrs" v-on="$listeners" @changeAddress='changeAddress'></Child>
    <script>
    export default{
        props:['userInfo'],
        data(){
            return{
                address:'河南'
            }
        },
        methods:{
            changeAddress(){
            }
        }
    }
   

注:vue组件通信还可以使用状态机如vuex、pinia,后面的文章我会详细讲解

3、插槽

插槽就是组件上的一个占位符,它可以让我们在使用组件时,可以给组件插入任意内容,这个内容可以是一段html代码或数据,让组件变得更灵活,可复用。根据插槽的命名可以将插槽分为匿名插槽,具名插槽和作用域插槽

1、匿名插槽

匿名插槽也叫默认插槽,在子组件内部使用<slot>标签定义插槽,在父组件中使用子组件时,直接在子组件标签内写内容

 <div id="app">
    <div>1111</div>
    <Child>
      <h1>我在父组件中</h1>
    </Child>
  </div>
  <script>
    const { createApp } = Vue
    //子组件
    const Child = {
      template: `<h1>我是子组件数据</h1>
               <slot></slot>`,
    }
    createApp({
      components: {
        Child
      }
    }).mount("#app") 
  </script>

注:匿名插槽的name属性默认为default

2、具名插槽

具名插槽就是当一个组件需要多个插槽时,在子组件中用name属性给每个插槽起个名字,在父组件中使用v-slot指令指定内容要插入哪个插槽

 <div id="app">
    <div>1111</div>
    <Child>
      <template v-slot:before>
        <h1>我在子组件前面</h1>
      </template>
      <template v-slot:after>
        <div>
          我在子组件后面
        </div>
      </template>
    </Child>
  </div>
  <script>
    const { createApp } = Vue
    //子组件
    const Child = {
      template: `
      <slot name="before"></slot>
      <h1>我是子组件数据</h1>
      <slot name="after"></slot>`,
    }
    createApp({
      components: {
        Child
      }
    }).mount("#app") 
  </script>

注:v-slot插槽的名字不能直接作用于标签,外面要套template模板标签

3、其它插槽

条件插槽

条件插槽就是根据特定条件来决定是否渲染某个插槽的内容,或者渲染哪个插槽的内容,我们可以使用$slots来访问父组件传递给子组件的插槽内容

 <div id="app">
    <div>1111</div>
    <Child>
      <template v-slot:after>
        <div>
          我在子组件后面
        </div>
      </template>
    </Child>
  </div>
  <script>
    const { createApp } = Vue
    //子组件
    const Child = {
      template: `
      <h1>我是子组件数据</h1>
    //条件插槽 
      <div v-if="$slots.after">
        <slot name="after"></slot>
        </div>
      `,
    }
    createApp({
      components: {
        Child
      }
    }).mount("#app") 
  </script>

注:$slots是vue实例上的一个特殊属性,它是一个对象,可以用来访问父组件传递给子组件的插槽内容,它可以访问具名插槽,默认插槽,还可以通过检查$slots.name属性是否存在,来判断父组件是否提供了该插槽

动态插槽

动态插槽就是插槽的名字可以是一个变量,而不是一个写死的字符串,这样我们就可以在父组件中根据不同的条件,动态的决定给子组件的哪个插槽传递内容

 <div id="app">
    <div>1111</div>
    <Child>
   //动态插槽
      <template v-slot:[before]>
        <h1>我在子组件前面</h1>
      </template>
    </Child>
  </div>
  <script>
    const { createApp } = Vue
    //子组件
    const Child = {
      template: `
      <slot name="before"></slot>
      <h1>我是子组件数据</h1>
      `,
      }
    createApp({
      data() {
        return {
          before: "before"
        }
      },
      components: {
        Child
      }
    }).mount("#app") 
  </script>

作用域插槽

作用域插槽就是子组件可以把数据传给父组件,父组件在填充内容时能使用这些数据。在子组件的<slot>上绑定属性,这些属性会成为一个对象,传给父组件,父组件会用v-slot:default="slotProps"接收子组件传来的数据

 <div id="app">
    <div>1111</div>
    <Child>
     //作用域插槽
      <template v-slot:before="slotProps">
        <h1>我在子组件前面{{slotProps.info}}</h1>
      </template>
    </Child>
  </div>
  <script>
    const { createApp } = Vue
    //子组件
    const Child = {
      template: `
      <slot name="before" :info="info"></slot>
      <h1>我是子组件数据</h1>
      `,
      data(){
        return{
          info:"我是子组件数据呀"
        }
      }
    }
    createApp({
      components: {
        Child
      }
    }).mount("#app") 
  </script>

注:v-slot可以简写为#,如#before

5、相关面试题

1、vue3相比vue2做了哪些升级

(1)diff算法:vue3引用了一个静态标记patchflag,去标记更新的类型,下次更新是同种类型会跳过之前没有必要的对比过程,从根节点开始向下对比

(2)多根节点:vue2中不支持多根节点,有多根节点的话会报错,因为vdom是一个单根树形结构描述当前视图结构,patch方法在遍历时,从根节点开始遍历,它要求只有一个根节点,组件也会转化为vdom,所以必须要满足单根节点的要求。vue3支持多根节点,因为vue3引入了fragment这一抽象节点的概念,如果发现组件是多根的会自动创建一个fragment节点,把多根节点视为自己的children,在patch时,如果发现是一个fragment节点,会自动创建更新

(3)API:vue2是选项式API,data、props、computed、watch等这些逻辑会散乱在文件的不同位置,导致代码的可读性变差。vue3使用的是组合式API,可将同一逻辑内容写到一起,增强代码的可读性,更利于逻辑封装

(4)异步组件:vue3提供了suspense组件,允许程序在等待异步组件加载完成前渲染兜底内容,使用户体验更平滑

(5)Teleport:vue3提供了Teleport组件,可将部分dom移动到vue app之外的位置,如项目中常见的dialog弹窗

(6)事件缓存:vue3的catcheHandler可在第一次渲染后缓存事件,相比vue2无需每次渲染都传递一个新函数

(7)ts支持:vue3由ts重写,相对于vue2有更好的ts支持,vue2的选项式api是一个简单的对象,而ts是一种类型系统,面向对象的语法不是特别匹配

2、vue组件通信

(1)父子通信:父组件通过props向子组件传递数据,子组件通过$emit向父组件发送数据,这种传值方式具有单向数据流的特点。父组件还可以通过$children拿到子组件实例,子组件还可以通过$parent拿到父组件实例。除此之外,父组件可以通过$refs拿到子组件的数据,这种传值方式是非响应式的

(2)跨层级通信:如果传递的数据是非响应式的,父组件可以通过provide提供变量,子组件通过inject注入变量。如果传递的是响应式的数据,,可以使用$attrs/$listeners,$attrs包含父组件传入,且未被子组件声明的props属性,$listeners里面就包含了从祖先组件开始到当前组件为止,声明的所有自定义事件

(3)兄弟组件通信:通常使用事件总线,基于一个空的vue实例,利用$on注册事件和$emit触发事件。还可以使用状态机vuex,利用单一状态树通过$store提交mutation

面试还可能会问全局事件总线的原理:

事件总线是一种设计模式,发布订阅模式,发布订阅模式有两种方法$on和$emit

3、computed和watch区别

computed是计算属性,它依赖于其它属性值,并且computed属性值具有缓存效果,只有它依赖的属性值发生改变,下次获取的computed的值才会重新计算

watch是侦听器,更多的是观察的作用,无缓存,类似于某些数据的监听回调,每当监听的数据发生变化时,都会执行回调进行后续操作

4、v-model原理

v-model是给标签设置了@input事件或@change事件再添加动态属性:value实现了双向绑定,v-model是两者结合构成的快捷方式,也称v-model为语法糖

5、v-if v-show的区别

在 vue 中 v-show 与 v-if 的作用效果是相同的,都能控制元素的显示隐藏。v-show底层使用css中的display属性来实现元素显示或隐藏的,dom元素依然存在,它有更高的初始渲染消耗,而v-if控制元素的显示隐藏是将dom元素整个添加或删除,有更高的切换消耗。v-show不管返回的值是true还是false都会渲染,而v-if 如果返回的是true会渲染标签,但如果返回false不会渲染标签。v-if切换有一个局部编译或卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件,而v-show只是简单的基于css切换

面试中还可能问v-if和v-for可以同时使用吗

在vue中,v-if和v-for不能同时在同一个元素上使用,在vue2中,v-for的优先级比v-if高,而在vue3中,v-if的优先级比v-for高,如果两个指令同时使用的话,每次渲染都会先执行循环,然后再判断条件,即使我们只需要渲染一部分元素,也会遍历整个数组,比较消耗性能,同时,两个指令放在同一个元素上会使代码难以阅读和理解

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐