Vue的原理

211 阅读16分钟

vue的基本原理

当vue的实例创建时,vue会遍历data中的属性,用Object.defineProperty(vue3.0使用的是proxy)将他们转换为setter/getter,并在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有响应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而使关联的组件更新。

image.png

双向绑定对的原理

Vue.js是采用数据劫持结合发布者-订阅者模式的方法,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据发生变动时发布消息给订阅者,触发响应的监听回调。

image.png

  • 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

  • 实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图。

  • 实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。

    // dep是消息订阅器Dep

使用 Object.defineProperty() 来进行数据劫持有什么缺点?

在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。

在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。

MVVM、MVC、MVP的区别

MVVM,MVC,MVP是三种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化代码效率。

  1. MVVM分为Model,View和ViewModel三部分
  • Model代表数据模型,数据和业务逻辑都在Model层中定义;
  • View代表UI视图,负责数据的展示;
  • ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

image.png

Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。 这种模式实现了 Model和View的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作DOM。

单项数据流

  1. 什么是单向数据流

单向数据流(Unidirectional data flow)方式使用一个上传数据流和一个下传数据流进行双向数据通信,两个数据流之间相互独立。单向数据流指只能从一个方向来修改状态。

image.png

2. Vue 中的单向数据流

对于 Vue 来说,组件之间的数据传递具有单向数据流这样的特性。

  1. 父组件总是通过 props 向子组件传递数据;
  2. 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定;
  3. 父级 prop 的更新会向下流动到子组件中,但是反过来则不行;
  4. 这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解;
  5. 每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值;
  6. 这意味着不应该在一个子组件内部改变 prop。如果这样做,Vue 会在浏览器的控制台中发出警告。

3. 单向数据流 - 优点

  1. 所有状态的改变可记录、可跟踪,源头易追溯;
  2. 所有的数据,具有唯一出口和入口,使得数据操作更直观更容易理解,可维护性强;
  3. 当数据变化时,页面会自动变化
  4. 当你需要修改状态,完全重新开始走一个修改的流程。这限制了状态修改的方式,让状态变得可预测,容易调试。

4. 单向数据流 - 缺点

  1. 页面渲染完成后,有新数据不能自动更新,需要手动整合新数据和模板重新渲染
  2. 代码量上升,数据流转过程变长,代码重复性变大
  3. 由于对应用状态独立管理的严格要求(单一的全局 store,如:Vuex),在处理局部状态较多的场景时(如用户输入交互较多的“富表单型”应用),会显得啰嗦及繁琐。

双向数据流

1. 什么是双向数据流?

在双向数据流中,Model(可以理解为状态的集合) 中可以修改自己或其他Model的状态, 用户的操作(如在输入框中输入内容)也可以修改状态。(双向数据流也可以叫双向数据绑定)


当我们在前端开发中采用 MV* 的模式时,M - model,指的是模型,也就是数据,V - view,指的是视图,也就是页面展现的部分。


将从服务器获取的数据进行“渲染”,展现到视图上。每当数据有变更时,我们会再次进行渲染,从而更新视图,使得视图与数据保持一致

image.png


页面也会通过用户的交互,产生状态、数据的变化,这个时候,我们则编写代码,将视图对数据的更新同步到数据

image.png


2. 双向数据流 - 优点
  1. 数据模型变化与更新,会自动同步到页面上,用户在页面的数据操作,也会自动同步到数据模型
  2. 无需进行和单向数据绑定的那些相关操作;
  3. 在表单交互较多的场景下,会简化大量业务无关的代码。
3. 双向数据流 - 缺点
  1. 无法追踪局部状态的变化;
  2. “暗箱操作”,增加了出错时 debug 的难度;
  3. 由于组件数据变化来源入口变得可能不止一个,数据流转方向易紊乱。
  4. 改变一个状态有可能会触发一连串的状态的变化,最后很难预测最终的状态是什么样的。使得代码变得很难调试

生命周期

Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。

  1. 生命周期包括beforeCreatecreatedbeforeMount,MountedbeforeUpdateUpdatedbeforeDestroydestroyed
  • beforeCreate(创建前) :数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。

  • created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。

  • beforeMount(挂载前) :在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。

  • mounted(挂载后) :在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。

  • beforeUpdate(更新前) :响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。

  • updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

  • beforeDestroy(销毁前) :实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。

  • destroyed(销毁后) :实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

keep-alive

keep-alive是 Vue 提供的一个内置组件,用来对组件进行缓存——在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

keep-alivekeep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。

如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。

父子组件嵌套下的生命周期

父组件created -> 子组件created -> 子组件mounted ->父组件mounted

常用的修饰符

  • .stop
  • .once
  • .prevent
  • sync (属性间的双向绑定)
表单修饰符
  • .number
  • .trim

watch和computed的区别

watch如何实现首次加载就触发监听
计算属性里面可不可以有异步逻辑

不可以 计算属性里面有return 所以不能有异步 immdeterly deep

vue项目实现首屏加载优化

  • 服务器端渲染
  • 骨架屏
  • 图片懒加载
  • 异步组件
  • 路由懒加载
  • webpack配置
  • 减少http请求
  • css合并
动态组件

根据某些条件渲染组件 component占位符 vue的内置组件component,是一个占位符的作用,用v-bing:is动态绑定组件名

vueX

1.页面刷新后store中的数据是否存在

页面刷新后strore中的数据会丢失。原因是在页面刷新时,vue的实例会重新加载,因此导致store也会重置。store是用来存储状态的,而不是用来存储数据的。

2.如何持久化
  • 监听页面是否刷新,如果页面刷新了,将store对象存储到localStorage中。页面打开后,判断localStorage中是否存在store,有的话则直接赋值使用,没有的话则表示是第一次进入,取store中的默认值。
    <template> 
        <div id="app">
            <router-view /> 
        </div> 
     </template> 
     <script> 
         export default { 
             name: 'App', 
             created() { 
                 // 在页面加载时读取sessionStorage里的状态信息 
                 if (sessionStorage.getItem('store')) { 
                    this.$store.replaceState( Object.assign( 
                    {}, this.$store.state,JSON.parse(sessionStorage.getItem('store')) ) ) 
                  } 
                  // 页面刷新时,将vuex里的信息保存到sessionStorage 
                 // 在页面刷新时先触发beforeunload事件 
                 window.addEventListener('beforeunload', () =>{sessionStorage.setItem('store',JSON.stringify(this.$store.state)) }) }, 
          } 
    </script>

  • 使用插件vuex-persistedstate
    import Vue from 'vue' 
    import Vuex from 'vuex' 
    import createPersistedState from 'vuex-persistedstate'
    Vue.use(Vuex)
    const store = new Vuex.Store({ 
    // 定义状态 
    state: { 
        isopen:'', 
        isThirdAuthDev:false, 
        id: '', 
        nickname: '',
        fieldsbind:[], 
        bindList:{} 
    },
    mutations: { 
        // 相当于编写set方法 
        setUser (state, data) {
            state.nickname = data 
        }, 
        setUserId (state, data) { 
            state.id = data 
        },
    },
    actions: { 
        // 写一些异步的操作 
        // setUserAysnc (context, LoginUser) { 
        // context.commit('setUser', LoginUser) // } 
     }, 
     modules: {},
     plugins: [ 
        createPersistedState({ 
        storage: window.localStorage, 
        reducer (state) { 
            return { id: state.id, // 持久化 
                nickname: state.nickname, 
                username: state.username, 
            } 
        } 
       }) 
     ] 
   })

vuex包括哪些方法
  • state:统一定义管理公共数据 ($store.state.userInfo.name) MapState
  • getters:相当于计算属性($store.getters.realName) MapGetters
  • mutations:修改数据 ($store.commit('addAge',{nuber:1}))
  • actions:里面写一些异步方法 ($store.dispatch('userInfo'))
  • model:拆分模块

vue2和vue3的区别

  1. 生命周期 对于生命周期来说,整体上变化不大,只是大部分的生命周期函数名称加上"on",功能上是类似的。vue3在组合式API(Composition API)中使用生命周期钩子时需要钩子时需要先引入,而Vue2在选项API(Option API)中可以直接调用钩子函数
    // vue3 
    <script setup> 
    import { onMounted } from 'vue'; 
    // 使用前需引入生命周期钩子 
    onMounted(() => { // ... }); // 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不会被覆盖 
    onMounted(() => { // ... }); 
    </script> 
    // vue2 
    <script> 
        export default { mounted() { // 直接调用生命周期钩子 // ... }, } 
    </script>
  1. 多根节点

在vue2中,模板中只能有一个根节点,但是vue3中支持多节点,也就是fragment。

    // vue2中在template里存在多个根节点会报错 
    <template> 
        <header></header> 
        <main></main> 
        <footer></footer> 
    </template> 
    // 只能存在一个根节点,需要用一个<div>来包裹着 
    <template> 
        <div> 
            <header></header> 
            <main></main> 
            <footer></footer> 
        </div> 
    </template>
  1. Composition API

Vue2 是选项API(Options API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。

Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。

  1. 异步组件(Suspense)

Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。

<tempalte> 
    <suspense> 
        <template #default> 
            <List /> 
        </template> 
        <template #fallback>
            <div> Loading... </div>
        </template> 
    </suspense> 
</template>

在 List 组件(有可能是异步组件,也有可能是组件内部处理逻辑或查找操作过多导致加载过慢等)未加载完成前,显示 Loading...(即 fallback 插槽内容),加载完成时显示自身(即 default 插槽内容)。

  1. 响应式原理

Vue2 响应式原理基础是 Object.defineProperty;Vue3 响应式原理基础是 Proxy。

  • 为什么vue3更改响应原理呢:vue2无法监听数组或对象新增删除的元素。

    Vue2 相应解决方案:针对常用数组原型方法push、pop、shift、unshift、splice、sort、reverse进行了hack处理;提供Vue.set监听对象/数组新增属性。对象的新增/删除响应,还可以new个新对象,新增则合并新属性和旧对象;删除则将删除属性后的对象深拷贝给新对象。

    Proxy 是 ES6 新特性,通过第2个参数 handler 拦截目标对象的行为。相较于 Object.defineProperty 提供语言全范围的响应能力,消除了局限性。 6.优化底层diff算法

vue2怎么实现深度监听
  • 增加属性时使用set的方式this.set的方式 **this.set(this.data,”key”,value')**
    let obj = {name:'amy'}
    let arr = ['joy']
    obj.age = 19 
    arr [1] = 'fff' //此时监听不到相应

    this.$set(this.obj,'age',19);
    this.$set(this.arr,1,'fff')
  • watch增加deep的属性
    watch:{
        name:{
            handler(newVuale,oldValue){
                console.log(newVuale)
            }
        },
        deep:true
    }
组合式api的优势
  • 逻辑统一写在一起,便于开发维护
vue2的v-modle在vue3中如何实现

blog.csdn.net/qq554244261…

  • vue2中的v-modle是一个语法糖,实现数据双向绑定。vue中提供:mode属性来改变默认绑定的props和event。
    <input v-modle="val">
    //等价于
    <input :value="val" @input="val=$event.target.value">
    //在自定义组件中
    如果作为子组件使用,父组件如何实现双向绑定?
    可以在父组件调用子组件的v-model重写
    //父组件
    <component v-model="val"> 
    <component :value="val" @input="val = arguments[0]"
    //子组件
    <input type="text" :value="val" @input="$emit('input',$event.target.value)">

    model: {
      prop: 'val',
      event: 'input'
    }
    props: {
      val: String
    }

   //**model的作用就是将父子组件之间,传入的值和触发的事件一一对应起来(所以prop要和props里面获取的值一样)。**
   **但是我们也可以发现一个问题,由于传入的值和model中的prop必须是对应的,所以一次只能绑定一个v-model。**
  • Vue3,重写了v-model,可以绑定多个v-model
    移除了model  
    prop默认值改为了modelValue  
    event默认值改为了update:modelValue  
    **总结一下,就是可以通过v-model:xxx的方式,然后通过抛出的方法update:xxx来实现多个v-model的绑定     **
    //父组件
    <template>
      <div>
        <Child v-model:text="message" />
        <div>绑定的值:{{message}}</div>
      </div>
    </template>

    //子组件
    <template>
      <div>
        <input 
          type="text" 
          :value="text" 
          @input="$emit('update:text', $event.target.value)"
        >
      </div>
    </template>
    <script>
        export default {
          //3.x 接收v-model冒号后面的值,相应的触发的方法改为update:text
          props:['text']
        }
    </script>

 //父组件
<template>
  <div>
    <Child 
      v-model="message1"
      v-model:text="message2" 
    />
    <div>绑定的值1:{{message1}}</div>
    <div>绑定的值2:{{message2}}</div>
  </div>
</template>

//子组件
<template>
  <div>
    <input 
      type="text" 
      :value="modelValue" 
      @input="$emit('update:modelValue', $event.target.value)"
    >
    <input 
      type="text" 
      :value="text" 
      @input="$emit('update:text', $event.target.value)"
    >
  </div>
</template>
<script>
export default {
  //v-model冒号后面不写值,默认就是modelValue
  props:['modelValue','text']
}
</script>
父子组件之间如何通信
  • props/$emit 父组件通过props将数据流向子组件,子组件触发函数通知父组件
  • bus.bus.emit A组件通过emit发送数据 B组件通过bus.bus.on来接收数据 ($once移除事件)
  • vuex

$refs在通信时有什么弊端

  • 必须在模板渲染之后,不是响应式的,时不时配合$nextTick
自定义组件的v-model是做什么的

是实现父子组件数据双向绑定的

vue3

1.ref和reactive的区别
  • reactive参数一般接受对象或数组,是深层次的响应式。ref参数一般接收简单数据类型,若ref接收对象为参数,本质上会转变为reactive方法
  • 在JS中访问ref的值需要手动添加.value,访问reactive不需要
  • 响应式的底层原理都是Proxy

vue2

  1. 如何获取事件对象 $event
  2. 父子组件之间的通信 props+emitvuexemit vuex refs
  3. $refs在通信时的弊端
 必须在模板渲染之后,不是响应式的,时不时配合`$nextTick`
  1. vuex页面刷新state是否存在
  2. vuex持久化怎么实现
  3. .sync修饰符的作用 :实现属性的双向绑定
    money.sync ="total"
    等价于
    :money = "total" v-on:update:money="total =$event"

7. 自定义组件中的v-modle?v-bind和v-on的结合快乐老家 8. vue3中的v-modle怎么实现 9. 嵌套组件的生命周期的顺序 10. 首屏的优化 11. diff算法 双端算法和简单算法 12. qiankun框架 主应用和子应用之间合同通信(登录信息如何同步) 13. webpack 性能问题 14. 组件懒加载如何实现

  • 普通组件的加载
 <template>
  <div class="hello">
    <One-com></One-com>
  </div>
</template>

<script>
import One from './one'
export default {
  components:{
    "One-com":One
  },
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>
  • ES 提出的import方法
<template>
  <div class="hello">
    <One-com></One-com>
  </div>
</template>

<script>
export default {
  components:{
    "One-com": ()=>import("./one");
  },
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

  • 异步方法
  1. 路由懒加载
  • 普通路由加载
<template>
  <div class="hello">
    <One-com></One-com>
  </div>
</template>

<script>
export default {
  components:{
    "One-com":resolve=>(['./one'],resolve)
  },
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

    import Vue from 'vue' 
    import Router from 'vue-router' 
    import HelloWorld from '@/components/HelloWorld' 
    Vue.use(Router) 
    export default new Router({ 
        routes: [ { 
            path: '/', 
            name: 'HelloWorld',
            component:HelloWorld 
        } ] 
    })
vue异步组件实现懒加载
  • component:resolve=>(require(['需要加载的路由的地址']),resolve)
import Vue from 'vue' 
import Router from 'vue-router' 
/* 此处省去之前导入的HelloWorld模块 */ 
Vue.use(Router) 
export default new Router({ 
    routes: [ { 
        path: '/', 
        name: 'HelloWorld', 
        component: resolve=>(require(["@/components/HelloWorld"],resolve))
     }] 
 })
ES 提出的import方法(------最常用------)
  • 方法如下:const HelloWorld = ()=>import('需要加载的模块地址') (不加 { } ,表示直接return)
    import Vue from 'vue' 
    import Router from 'vue-router' 
    Vue.use(Router) 
    export default new Router({ 
        routes: [ { 
            path: '/', 
            name: 'HelloWorld', 
            component: ()=>import("@/components/HelloWorld") 
         } ] 
     })
webpack提供的require.ensure()
    { 
        path: '/home', 
        name: 'Home', 
        component: r => require.ensure([],() => r(require('@/components/HelloWorld')), 'home') 
    }
  1. vue2中的minix和vue3中的hooks

项目中的问题

  1. 如何实现登录
  2. 怎么判断用户登录 路由拦截 判断token是否失效
  3. 代码管理 git 分支管理流程
  4. 如何实现用户的鉴权
  5. 如何保证多端显示效果的一致性
  6. 封装组件 如何实现

路由传参的两种方式

  • query

    query 是通过 URL 中的查询参数传递数据的,比如 /user?id=123&name=张三,这里的 id 和 name 就是查询参数。在路由中,可以通过 this.$route.query 来获取当前路由的查询参数,例如:

// 跳转到 /user 页面,并传递查询参数
this.$router.push({ path: '/user', query: { id: '123', name: '张三' } })

// 在 /user 页面中获取查询参数
console.log(this.$route.query.id) // '123'
console.log(this.$route.query.name) // '张三'
  • params

params 是通过 URL 中的占位符传递数据的,比如 /user/123,这里的 123 就是占位符。在路由中,可以通过 this.$route.params 来获取当前路由的占位符参数,例如:

// 定义带有占位符的路由
{
  path: '/user/:id',
  component: User
}

// 在 User 组件中获取占位符参数
console.log(this.$route.params.id) // '123'

需要注意的是,query 传递的数据会被编码到 URL 中,因此适合传递一些简单的数据,比如查询条件;而 params 传递的数据则不会被编码,因此适合传递一些复杂的数据,比如对象或数组。

  • props

另外,params 传递的数据还可以使用 props 属性来进行传递,这种方式需要在路由配置中设置 props: true,并在组件中定义 props 属性来接收参数。例如:

// 定义带有占位符的路由,并开启 props 属性
{
  path: '/user/:id',
  component: User,
  props: true
}

// 在 User 组件中定义 props 属性
props: ['id']

插槽

插槽是父组件向子组件传递代码片段

  • 默认插槽

    当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。

  • 具名插槽

    带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件可以出现多个具名插槽。

  • 作用域插槽

    默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。

实现原理:当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

// 父组件
<template>
    <div id="father">
        <son>
            <!-- 下面的节点如果没有使用插槽,将不会显示 -->
            
            // 旧版本中的写法
            <!-- 默认插槽 -->
            <div>0000000000</div>
            <!-- 具名插槽 -->
            <div slot="a">11111111111</div>
            <div slot="b">2222222222</div>
            <!-- 作用域插槽:获取子组件中内部定义一个数据`msg` -->
            <div slot="c" slot-scope="slotProps">
                {{slotProps.msg}}
            </div>
            
            // 新版本中的写法:v-slot:插槽名 或 #插槽名
            <template v-slot:c="slotProps"> 
                {{slotProps.msg}} 
            </template> 
            <!-- 也可以缩写为 #插槽名 -->  
            <template #c="slotProps">   // slotProps是绑定在 <slot> 元素上的 attribute 被称为插槽 prop
                {{slotProps.msg}} 
            </template>
        </son>
    </div>
</template>

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

// 子组件
<template>
    <div id="son">
        <!-- 插槽,允许父组件里面的元素在此处插入并显示 -->
        <!-- 默认插槽 -->
        <slot></slot>
        <!-- 具名插槽 -->
        <slot name="a"></slot>
        <slot name="b"></slot>
        <!-- 作用域插槽 -->
        <slot name="c" :msg="msg"></slot>   
    </div>
</template>
<script>
export default {
    name: "son",
    data () {
        return {
            msg: "子组件的数据"
        }
    }
};
</script>

  • slot的作用
  1. 扩展组件能力,提高组件的复用性;
  2. 使用插槽可以将一些比较复杂的父传子的通信去掉,直接在父组件中完成后利用插槽显示到子组件中(这是由于父组件模板的内容在父组件作用域内编译,子组件模板的内容在子组件作用域内编译)。