17 Vue组件相关

Vue组件为什么只能有一个根元素

vue3中没有问题

    Vue.createApp({
      components: {
        comp: {
          template: `
            <div>root1</div>
            <div>root2</div>
          `
        }
      }
    }).mount('#app')
  1. vue2中组件确实只能有一个根,但vue3中组件已经可以多根节点了。
  2. 之所以需要这样是因为vdom是一颗单根树形结构,patch方法在遍历的时候从根节点开始遍历,它要求只有一个根节点。组件也会转换为一个vdom
  3. vue3中之所以可以写多个根节点,是因为引入了Fragment的概念,这是一个抽象的节点,如果发现组件是多根的,就创建一个Fragment节点,把多个根节点作为它的children。将来patch的时候,如果发现是一个Fragment节点,则直接遍历children创建或更新

谈一谈对Vue组件化的理解

  • 组件化开发能大幅提高开发效率、测试性、复用性等
  • 常用的组件化技术:属性、自定义事件、插槽
  • 降低更新频率,只重新渲染变化的组件
  • 组件的特点:高内聚、低耦合、单向数据流

Vue组件渲染和更新过程

渲染组件时,会通过 Vue.extend 方法构建子组件的构造函数,并进行实例化。最终手动调用$mount() 进行挂载。更新组件时会进行 patchVnode 流程,核心就是diff算法

异步组件是什么?使用场景有哪些?

分析

因为异步路由的存在,我们使用异步组件的次数比较少,因此还是有必要两者的不同。

体验

大型应用中,我们需要分割应用为更小的块,并且在需要组件时再加载它们

    import { defineAsyncComponent } from 'vue'
    // defineAsyncComponent定义异步组件,返回一个包装组件。包装组件根据加载器的状态决定渲染什么内容
    const AsyncComp = defineAsyncComponent(() => {
      // 加载函数返回Promise
      return new Promise((resolve, reject) => {
        // ...可以从服务器加载组件
        resolve(/* loaded component */)
      })
    })
    // 借助打包工具实现ES模块动态导入
    const AsyncComp = defineAsyncComponent(() =>
      import('./components/MyComponent.vue')
    )

回答范例

  1. 在大型应用中,我们需要分割应用为更小的块,并且在需要组件时再加载它们。
  2. 我们不仅可以在路由切换时懒加载组件,还可以在页面组件中继续使用异步组件,从而实现更细的分割粒度。
  3. 使用异步组件最简单的方式是直接给defineAsyncComponent指定一个loader函数,结合ES模块动态导入函数import可以快速实现。我们甚至可以指定loadingComponenterrorComponent选项从而给用户一个很好的加载反馈。另外Vue3中还可以结合Suspense组件使用异步组件。
  4. 异步组件容易和路由懒加载混淆,实际上不是一个东西。异步组件不能被用于定义懒加载路由上,处理它的是vue框架,处理路由组件加载的是vue-router。但是可以在懒加载的路由组件中使用异步组件

为什么要使用异步组件

  1. 节省打包出的结果,异步组件分开打包,采用jsonp的方式进行加载,有效解决文件过大的问题。
  2. 核心就是包组件定义变成一个函数,依赖import() 语法,可以实现文件的分割加载。
    components:{ 
      AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([]) 
    }

原理

    export function ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void { 
        // async component 
        let asyncFactory 
        if (isUndef(Ctor.cid)) { 
            asyncFactory = Ctor 
            Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend 
            // 第二次渲染时Ctor不为undefined 
            if (Ctor === undefined) { 
                return createAsyncPlaceholder( // 渲染占位符 空虚拟节点 
                    asyncFactory, 
                    data, 
                    context, 
                    children, 
                    tag 
                ) 
            } 
        } 
    }
    function resolveAsyncComponent ( factory: Function, baseCtor: Class<Component> ): Class<Component> | void { 
        if (isDef(factory.resolved)) { 
            // 3.在次渲染时可以拿到获取的最新组件 
            return factory.resolved 
        }
        const resolve = once((res: Object | Class<Component>) => { 
            factory.resolved = ensureCtor(res, baseCtor) 
            if (!sync) { 
                forceRender(true) //2. 强制更新视图重新渲染 
            } else { 
                owners.length = 0 
            } 
        })
        const reject = once(reason => { 
            if (isDef(factory.errorComp)) { 
                factory.error = true forceRender(true) 
            } 
        })
        const res = factory(resolve, reject)// 1.将resolve方法和reject方法传入,用户调用 resolve方法后 
        sync = false 
        return factory.resolved 
    }

函数式组件优势和原理

函数组件的特点

  1. 函数式组件需要在声明组件是指定 functional:true
  2. 不需要实例化,所以没有this,this通过render函数的第二个参数context来代替
  3. 没有生命周期钩子函数,不能使用计算属性,watch
  4. 不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
  5. 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
  6. 函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)

优点

  1. 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件
  2. 函数式组件结构比较简单,代码结构更清晰

使用场景:

  • 一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件
  • “高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件

例子

    Vue.component('functional',{ // 构造函数产生虚拟节点的
        functional:true, // 函数式组件 // data={attrs:{}}
        render(h){
            return h('div','test')
        }
    })
    const vm = new Vue({
        el: '#app'
    })

源码相关

    // functional component
    if (isTrue(Ctor.options.functional)) { // 带有functional的属性的就是函数式组件
      return createFunctionalComponent(Ctor, propsData, data, context, children)
    }
    
    // extract listeners, since these needs to be treated as
    // child component listeners instead of DOM listeners
    const listeners = data.on // 处理事件
    // replace with listeners with .native modifier
    // so it gets processed during parent component patch.
    data.on = data.nativeOn // 处理原生事件
    
    // install component management hooks onto the placeholder node
    installComponentHooks(data) // 安装组件相关钩子 (函数式组件没有调用此方法,从而性能高于普通组件)

Vue组件之间通信方式有哪些

Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。Vue 组件间通信只要指以下 3 类通信父子组件通信隔代组件通信兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信

组件传参的各种方式

组件通信常用方式有以下几种

  • props / $emit 适用 父子组件通信
    • 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
  • ref$parent / $children(vue3废弃) 适用 父子组件通信
    • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
    • $parent / $children:访问访问父组件的属性或方法 / 访问子组件的属性或方法
  • EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信
    • 这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件
  • $attrs / $listeners(vue3废弃) 适用于 隔代组件通信
    • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( classstyle 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( classstyle 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用,多余的属性不会被解析到标签上
    • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
  • provide / inject 适用于 隔代组件通信
    • 祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态 ,跨级组件间建立了一种主动提供与依赖注入的关系
  • $root 适用于 隔代组件通信 访问根组件中的属性或方法,是根组件,不是父组件。$root只对根组件有用
  • Vuex 适用于 父子、隔代、兄弟组件通信
    • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

根据组件之间关系讨论组件通信最为清晰有效

  • 父子组件:props/$emit/$parent/ref
  • 兄弟组件:$parent/eventbus/vuex
  • 跨层级关系:eventbus/vuex/provide+inject/$attrs + $listeners/$root

下面演示组件之间通讯三种情况: 父传子、子传父、兄弟组件之间的通讯

1. 父子组件通信

使用props,父组件可以使用props向子组件传递数据。

父组件vue模板father.vue:

    <template>
      <child :msg="message"></child>
    </template>
    
    <script>
    import child from './child.vue';
    export default {
      components: {
        child
      },
      data () {
        return {
          message: 'father message';
        }
      }
    }
    </script>

子组件vue模板child.vue:

    <template>
        <div>{{msg}}</div>
    </template>
    
    <script>
    export default {
      props: {
        msg: {
          type: String,
          required: true
        }
      }
    }
    </script>

回调函数(callBack)

父传子:将父组件里定义的method作为props传入子组件

    // 父组件Parent.vue:
    <Child :changeMsgFn="changeMessage">
    methods: {
    	changeMessage(){
    		this.message = 'test'
    	}
    }
    // 子组件Child.vue:
    <button @click="changeMsgFn">
    props:['changeMsgFn']

子组件向父组件通信

父组件向子组件传递事件方法,子组件通过$emit触发事件,回调给父组件

父组件vue模板father.vue:

    <template>
        <child @msgFunc="func"></child>
    </template>
    
    <script>
    import child from './child.vue';
    export default {
        components: {
            child
        },
        methods: {
            func (msg) {
                console.log(msg);
            }
        }
    }
    </script>

子组件vue模板child.vue:

    <template>
        <button @click="handleClick">点我</button>
    </template>
    
    <script>
    export default {
        props: {
            msg: {
                type: String,
                required: true
            }
        },
        methods () {
            handleClick () {
              //........
              this.$emit('msgFunc');
            }
        }
    }
    </script>

2. provide / inject 跨级访问祖先组件的数据

父组件通过使用provide(){return{}}提供需要传递的数据

    export default {
      data() {
        return {
          title: '我是父组件',
          name: 'poetry'
        }
      },
      methods: {
        say() {
          alert(1)
        }
      },
      // provide属性 能够为后面的后代组件/嵌套的组件提供所需要的变量和方法
      provide() {
        return {
          message: '我是祖先组件提供的数据',
          name: this.name, // 传递属性
          say: this.say
        }
      }
    }

子组件通过使用inject:[“参数1”,”参数2”,…]接收父组件传递的参数

    <template>
      <p>曾孙组件</p>
      <p>{{message}}</p>
    </template>
    <script>
    export default {
      // inject 注入/接收祖先组件传递的所需要的数据即可 
      //接收到的数据 变量 跟data里面的变量一样 可以直接绑定到页面 {}
      inject: [ "message","say"],
      mounted() {
        this.say();
      },
    };
    </script>

3. $parent + $children 获取父组件实例和子组件实例的集合

  • this.$parent 可以直接访问该组件的父实例或组件
  • 父组件也可以通过 this.$children 访问它所有的子组件;需要注意 $children 并不保证顺序,也不是响应式的
    <!-- parent.vue -->
    <template>
    <div>
      <child1></child1>   
      <child2></child2> 
      <button @click="clickChild">$children方式获取子组件值</button>
    </div>
    </template>
    <script>
    import child1 from './child1'
    import child2 from './child2'
    export default {
      data(){
        return {
          total: 108
        }
      },
      components: {
        child1,
        child2  
      },
      methods: {
        funa(e){
          console.log("index",e)
        },
        clickChild(){
          console.log(this.$children[0].msg);
          console.log(this.$children[1].msg);
        }
      }
    }
    </script>
    <!-- child1.vue -->
    <template>
      <div>
        <button @click="parentClick">点击访问父组件</button>
      </div>
    </template>
    <script>
    export default {
      data(){
        return {
          msg:"child1"
        }
      },
      methods: {
        // 访问父组件数据
        parentClick(){
          this.$parent.funa("xx")
          console.log(this.$parent.total);
        }
      }
    }
    </script>
    <!-- child2.vue -->
    <template>
      <div>
        child2
      </div>
    </template>
    <script>
    export default {
      data(){
        return {
         msg: 'child2'
        }
      }
    }
    </script>

4. $attrs + $listeners多级组件通信

$attrs 包含了从父组件传过来的所有props属性

    // 父组件Parent.vue:
    <Child :name="name" :age="age"/>
        
    // 子组件Child.vue:
    <GrandChild v-bind="$attrs" />
    
    // 孙子组件GrandChild
    <p>姓名:{{$attrs.name}}</p>
    <p>年龄:{{$attrs.age}}</p>

$listeners包含了父组件监听的所有事件

    // 父组件Parent.vue:
    <Child :name="name" :age="age" @changeNameFn="changeName"/>
        
    // 子组件Child.vue:
    <button @click="$listeners.changeNameFn"></button>

5. ref 父子组件通信

    // 父组件Parent.vue:
    <Child ref="childComp"/>
    <button @click="changeName"></button>
    changeName(){
    	console.log(this.$refs.childComp.age);
    	this.$refs.childComp.changeAge()
    }
    
    // 子组件Child.vue:
    data(){
    	return{
    		age:20
    	}
    },
    methods(){
    	changeAge(){
    		this.age=15
      }
    }

6. 非父子, 兄弟组件之间通信

vue2中废弃了broadcast广播和分发事件的方法。父子组件中可以用props$emit()。如何实现非父子组件间的通信,可以通过实例一个vue实例Bus作为媒介,要相互通信的兄弟组件之中,都引入Bus,然后通过分别调用Bus事件触发和监听来实现通信和参数传递。Bus.js可以是这样:

    // Bus.js
    
    // 创建一个中央时间总线类  
    class Bus {  
      constructor() {  
        this.callbacks = {};   // 存放事件的名字  
      }  
      $on(name, fn) {  
        this.callbacks[name] = this.callbacks[name] || [];  
        this.callbacks[name].push(fn);  
      }  
      $emit(name, args) {  
        if (this.callbacks[name]) {  
          this.callbacks[name].forEach((cb) => cb(args));  
        }  
      }  
    }  
      
    // main.js  
    Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  
    // 另一种方式  
    Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能  
    <template>
    	<button @click="toBus">子组件传给兄弟组件</button>
    </template>
    
    <script>
    export default{
    	methods: {
        toBus () {
          this.$bus.$emit('foo', '来自兄弟组件')
        }
      }
    }
    </script>

另一个组件也在钩子函数中监听on事件

    export default {
      data() {
        return {
          message: ''
        }
      },
      mounted() {
        this.$bus.$on('foo', (msg) => {
          this.message = msg
        })
      }
    }

7. $root 访问根组件中的属性或方法

  • 作用:访问根组件中的属性或方法
  • 注意:是根组件,不是父组件。$root只对根组件有用
    var vm = new Vue({
      el: "#app",
      data() {
        return {
          rootInfo:"我是根元素的属性"
        }
      },
      methods: {
        alerts() {
          alert(111)
        }
      },
      components: {
        com1: {
          data() {
            return {
              info: "组件1"
            }
          },
          template: "<p>{{ info }} <com2></com2></p>",
          components: {
            com2: {
              template: "<p>我是组件1的子组件</p>",
              created() {
                this.$root.alerts()// 根组件方法
                console.log(this.$root.rootInfo)// 我是根元素的属性
              }
            }
          }
        }
      }
    });

8. vuex

  • 适用场景: 复杂关系的组件数据传递
  • Vuex作用相当于一个用来存储共享变量的容器

  • state用来存放共享变量的地方
  • getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值
  • mutations用来存放修改state的方法。
  • actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作

小结

  • 父子关系的组件数据传递选择 props$emit进行传递,也可选择ref
  • 兄弟关系的组件数据传递可选择$bus,其次可以选择$parent进行传递
  • 祖先与后代组件数据传递可选择attrslisteners或者 ProvideInject
  • 复杂关系的组件数据传递可以通过vuex存放共享的变量

组件中写name属性的好处

可以标识组件的具体名称方便调试和查找对应属性

    // 源码位置 src/core/global-api/extend.js
    
    // enable recursive self-lookup
    if (name) { 
        Sub.options.components[name] = Sub // 记录自己 在组件中递归自己  -> jsx
    }

Vue.extend 作用和原理

官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并

  • extend是构造一个组件的语法器。然后这个组件你可以作用到Vue.component这个全局注册方法里还可以在任意vue模板里使用组件。 也可以作用到vue实例或者某个组件中的components属性中并在内部使用apple组件。
  • Vue.component你可以创建 ,也可以取组件。

相关代码如下

    export default function initExtend(Vue) {
      let cid = 0; //组件的唯一标识
      // 创建子类继承Vue父类 便于属性扩展
      Vue.extend = function (extendOptions) {
        // 创建子类的构造函数 并且调用初始化方法
        const Sub = function VueComponent(options) {
          this._init(options); //调用Vue初始化方法
        };
        Sub.cid = cid++;
        Sub.prototype = Object.create(this.prototype); // 子类原型指向父类
        Sub.prototype.constructor = Sub; //constructor指向自己
        Sub.options = mergeOptions(this.options, extendOptions); //合并自己的options和父类的options
        return Sub;
      };
    }

Vue中如何扩展一个组件

此题属于实践题,考察大家对vue常用api使用熟练度,答题时不仅要列出这些解决方案,同时最好说出他们异同

答题思路:

  • 按照逻辑扩展和内容扩展来列举
    • 逻辑扩展有:mixinsextendscomposition api
    • 内容扩展有slots
  • 分别说出他们使用方法、场景差异和问题。
  • 作为扩展,还可以说说vue3中新引入的composition api带来的变化

回答范例:

  1. 常见的组件扩展方法有:mixinsslotsextends
  2. 混入mixins是分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项
    // 复用代码:它是一个配置对象,选项和组件里面一样
    const mymixin = {
       methods: {
          dosomething(){}
       }
    }
    // 全局混入:将混入对象传入
    Vue.mixin(mymixin)
    
    // 局部混入:做数组项设置到mixins选项,仅作用于当前组件
    const Comp = {
       mixins: [mymixin]
    }
  1. 插槽主要用于vue组件中的内容分发,也可以用于组件扩展

子组件Child

    <div>
      <slot>这个内容会被父组件传递的内容替换</slot>
    </div>

父组件Parent

    <div>
       <Child>来自父组件内容</Child>
    </div>

如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用域插槽

  1. 组件选项中还有一个不太常用的选项extends,也可以起到扩展组件的目的
    // 扩展对象
    const myextends = {
       methods: {
          dosomething(){}
       }
    }
    // 组件扩展:做数组项设置到extends选项,仅作用于当前组件
    // 跟混入的不同是它只能扩展单个对象
    // 另外如果和混入发生冲突,该选项优先级较高,优先起作用
    const Comp = {
       extends: myextends
    }
  1. 混入的数据和方法不能明确判断来源且可能和当前组件内变量产生命名冲突,vue3中引入的composition api,可以很好解决这些问题,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应式的数据,然后在setup选项中组合使用,增强代码的可读性和维护性。例如
    // 复用逻辑1
    function useXX() {}
    // 复用逻辑2
    function useYY() {}
    // 逻辑组合
    const Comp = {
       setup() {
          const {xx} = useXX()
          const {yy} = useYY()
          return {xx, yy}
       }
    }

子组件可以直接改变父组件的数据么,说明原因

这是一个实践知识点,组件化开发过程中有个单项数据流原则,不在子组件中修改父组件是个常识问题

思路

  • 讲讲单项数据流原则,表明为何不能这么做
  • 举几个常见场景的例子说说解决方案
  • 结合实践讲讲如果需要修改父组件状态应该如何做

回答范例

  1. 所有的 prop 都使得其父子之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器控制台中发出警告
    const props = defineProps(['foo'])
    // ❌ 下面行为会被警告, props是只读的!
    props.foo = 'bar'
  1. 实际开发过程中有两个场景会想要修改一个属性:

这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data,并将这个 prop 用作其初始值:

    const props = defineProps(['initialCounter'])
    const counter = ref(props.initialCounter)

这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

    const props = defineProps(['size'])
    // prop变化,计算属性自动更新
    const normalizedSize = computed(() => props.size.trim().toLowerCase())
  1. 实践中如果确实想要改变父组件属性应该emit一个事件让父组件去做这个变更。注意虽然我们不能直接修改一个传入的对象或者数组类型的prop,但是我们还是能够直接改内嵌的对象或属性

什么是递归组件?举个例子说明下?

分析

递归组件我们用的比较少,但是在TreeMenu这类组件中会被用到。

体验

组件通过组件名称引用它自己,这种情况就是递归组件

    <template>
      <li>
        <div> {{ model.name }}</div>
        <ul v-show="isOpen" v-if="isFolder">
          <!-- 注意这里:组件递归渲染了它自己 -->
          <TreeItem
            class="item"
            v-for="model in model.children"
            :model="model">
          </TreeItem>
        </ul>
      </li>
    <script>
    export default {
      name: 'TreeItem',
      // ...
    }
    </script>

回答范例

  1. 如果某个组件通过组件名称引用它自己,这种情况就是递归组件。
  2. 实际开发中类似TreeMenu这类组件,它们的节点往往包含子节点,子节点结构和父节点往往是相同的。这类组件的数据往往也是树形结构,这种都是使用递归组件的典型场景。
  3. 使用递归组件时,由于我们并未也不能在组件内部导入它自己,所以设置组件name属性,用来查找组件定义,如果使用SFC,则可以通过SFC文件名推断。组件内部通常也要有递归结束条件,比如model.children这样的判断。
  4. 查看生成渲染函数可知,递归组件查找时会传递一个布尔值给resolveComponent,这样实际获取的组件就是当前组件本身

原理

递归组件编译结果中,获取组件时会传递一个标识符 _resolveComponent("Comp", true)

    const _component_Comp = _resolveComponent("Comp", true)

就是在传递maybeSelfReference

    export function resolveComponent(
      name: string,
      maybeSelfReference?: boolean
    ): ConcreteComponent | string {
      return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
    }

resolveAsset中最终返回的是组件自身:

    if (!res && maybeSelfReference) {
        // fallback to implicit self-reference
        return Component
    }

Vue中组件和插件有什么区别

1. 组件是什么

组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件

组件的优势

  • 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现
  • 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
  • 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级

2. 插件是什么

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

  • 添加全局方法或者属性。如: vue-custom-element
  • 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  • 通过全局混入来添加一些组件选项。如vue-router
  • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如vue-router

3. 两者的区别

两者的区别主要表现在以下几个方面:

  • 编写形式
  • 注册形式
  • 使用场景

3.1 编写形式

编写组件

编写一个组件,可以有很多方式,我们最常见的就是vue单文件的这种格式,每一个.vue文件我们都可以看成是一个组件

vue文件标准格式

    <template>
    </template>
    <script>
    export default{ 
        ...
    }
    </script>
    <style>
    </style>

我们还可以通过template属性来编写一个组件,如果组件内容多,我们可以在外部定义template组件内容,如果组件内容并不多,我们可直接写在template属性上

    <template id="testComponent">     // 组件显示的内容
        <div>component!</div>   
    </template>
    
    Vue.component('componentA',{ 
        template: '#testComponent'  
        template: `<div>component</div>`  // 组件内容少可以通过这种形式
    })

编写插件

vue插件的实现应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象

    MyPlugin.install = function (Vue, options) {
      // 1. 添加全局方法或 property
      Vue.myGlobalMethod = function () {
        // 逻辑...
      }
    
      // 2. 添加全局资源
      Vue.directive('my-directive', {
        bind (el, binding, vnode, oldVnode) {
          // 逻辑...
        }
        ...
      })
    
      // 3. 注入组件选项
      Vue.mixin({
        created: function () {
          // 逻辑...
        }
        ...
      })
    
      // 4. 添加实例方法
      Vue.prototype.$myMethod = function (methodOptions) {
        // 逻辑...
      }
    }

3.2 注册形式

组件注册

vue组件注册主要分为全局注册局部注册

全局注册通过Vue.component方法,第一个参数为组件的名称,第二个参数为传入的配置项

    Vue.component('my-component-name', { /* ... */ })

局部注册只需在用到的地方通过components属性注册一个组件

    const component1 = {...} // 定义一个组件
    
    export default {
    	components:{
    		component1   // 局部注册
    	}
    }

插件注册

插件的注册通过Vue.use()的方式进行注册(安装),第一个参数为插件的名字,第二个参数是可选择的配置项

    Vue.use(插件名字,{ /* ... */} )

注意的是:

注册插件的时候,需要在调用 new Vue() 启动应用之前完成

Vue.use会自动阻止多次注册相同插件,只会注册一次

4. 使用场景

  • 组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue
  • 插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身

简单来说,插件就是指对Vue的功能的增强或补充

Last Updated:
Contributors: leeguooooo