一、组件的定义

组件: 封装了自定义内容与逻辑,独立的、可重用的显示组合体,它包括逻辑 (Script)、模板 (template)或样式 (style)。

(一)组件的两种形态

组件的书写有两种形态,JavaScript对象形态与vue单文件形态

1、 JavaScript对象形态

JavaScript对象形态:组件用js对象的形式表达,内容与逻辑等都是以对象的属性表示 { /* 选项 */ }
组件的对象形态非常的自由,对象的属性除了用于表达组件内容与逻辑的组件内属性外,还可放置非组件属性,可以像操作普通对象一样自由操作对象内的组件属性也非组件属性。
对象形态的组件在components属性或component函数注册时,能利用组件属性编译为组件,非组件属性将被舍弃,编译后的组件与原js对象属性结构会有不同。
对象形态组件定义如下:

const ttt="我是外部数据"
const XXX={
    props: ['ccc'],  //接收传参
    data(){return{count:0,ttt}}, //定义响应对象
    methods:{tt:function (){this.count++}},//定义函数
    mounted(){tt()},//周期勾子
    template: `<button @click="tt">{{ttt}}点了 {{ count }} 次!</button><app dd="子组件"/>`,//模版
    components: {'app':{props: ['dd'],template: `<button>我是{{dd}}</button>`}}//定义子组件
}
a、对象形态组件书写规范

对象形态组件只有组件属性才能在注册时编译为组件,组件属性有规范的属性名及规范的属性值。

  • 模板属性: 模板属性名为template,值为<html标签>。注:如果是多个html标签,要用反单引号括起。
  • 组件属性: 按功能不同,有如下属性。
    放置响应数据的data(){}函数
    放置自定义函数的methods:{}属性
    各种生命周期函数如 mounted(){}函数
    放置父参的 props:[]属性
    放置子组件的components:{}属性。
    组件属性可以引用对象里非组件属性。
    组件属性也能引用对象外的数据。
  • 样式部属性: 没有单独的样式部属性,样式是放在模板部的html标签里,且只能使用内联样式和外部样式表,不能使用内部样式表(template:不能放<style>标签)。
b、js文件导入

对象形态组件可以存在于其它的js文件中,可通过完整引入或模块化引入(副本引入)(详见:vue3个人心得---概论(HTML、JS 和 VUE初解)。如果是import(副本引入),js文件要用export导出对象形态组件。

c、对象组件的使用环境

对象组件一般在非构建环境使用,在vite构建的开发环境,需要开启template属性支持。
开启支持的方法:根目录vite.config.js文件加入如下语句(Vue3+vite架构)

import { defineConfig } from 'vite'
import path from "path";
export default defineConfig({
 resolve: {
 alias: {'vue': path.resolve(__dirname,"node_modules/vue/dist/vue.esm-bundler.js")},}})

注意地址的准确性,否则不生效。
非构建开发环境不能从node_modules包中的vue导入ref、reactive、computed、watch等函数,如:import {ref} from 'vue'。但导入vue.js文件会导入一个名为Vue的对象,可用解构的方法解构出,如:const {ref} = Vue。注意Vue对象首字母大写。

2、vue单文件形态组件

在vite构建的开发环节,有一种后缀为.vue的组件文件,这种组件采用html标签的形式表达。在<template>标签里直接书html标签、在<script>标签的export default里书写组件逻辑属性、还允许使用</style>样式标签。import导入vue单文件时vite会将其转换为js组件对象。
vue组件只能在vite构建环境下使用,在非构建环境导入运行会跳出(禁止运行非js模块)警告。vue单文件形态组件如下:

<script>
import { ref } from 'vue'
export default {
// 放置响应数据的data(){}函数
// 放置自定义函数的methods:{}属性
// 各种生命周期函数如 mounted(){}函数
// 放置父参的 props:[]属性
// 放置子组件的components:{}属性。
 data() {const tr=ref(0);function pt(){tr.value++};return {count:0,tr,pt}},
 components:{} ,       
 mounted(){},
 methods:{increment(){this.count++}},
 props:[]}
</script>
<template>
  <button @click="increment">{{ count }}</button>
  <button  @click="pt">{{tr}}</button>
</template>
<style scoped>.logo {height: 6em}</style>
a、vue组件书写规范

vue组件通过标签来描述组件,vite规范了vue组件可用的标签及标签内容的书写形式,必须遵循vite书写规范才能被解释为组件内容。

  • template标签: 所有的html标签都可放在template标签内(除<script>、<style>标签外),import导入时vite会自动将其编译为组件对象的render函数。
    在一个vue文件中只能有一个<template>标签,非<template>标签内的html标签不会被解释为html显示。
  • script标签: script标签内可放置副作用代码和export default代码,export default内可放置组件属性与非组件属性,组件属性会被template标签调用,import导入时vite会将所有export default内的属性并入到组件对象内。
    在一个vue文件中只能使用一个<script>标签,除非有一个<script setup>语法糖才可以使用第二个<script>
  • 组件属性: 与对象组件的组件属性一样,包括data(){}、methods:{}、生命周期函数(如mounted(){})、props:[]、components:{}等。
    组件属性须经export default暴露才能被template标签使用。
    组件属性可以引用export default里非组件属性。
    组件属性也能引用副作用代码的数据
  • <style>标签: 在一个vue文件中可以有多个<style>标签,标签加上scoped属性表明样式只作用于本vue或子vue,防止污染上家。
b、vue文件导入

vue文件只能在vite构建环境下使用。js文件需手动用export原样导出组件对象,而vue文件不需要export,vite会自动将template标签编译为组件对象的render函数,并将script标签export default内的属性并入到编译的组件对象里一并导出。

(二)setup函数及<script setup>语法糖

1、setup函数

从vue3开始,无论是对象组件还是单文件组件,都可以使用setup函数,使用了setup函数的组件称为组合式 API,未采用setup函数的组件称为选项式 API

  • 组合式API使用setup将多个属性选项(data、computed、methods、watch、beforeCreate、created)合并成一个选项。详见setup函数在vue中的使用
  • setup(a,b)函数有两个形参,第一个形参是父传过来的props,第二个形参是父传过来的attrs及emit。
  • 并不是所有的属性选项都并入到setup,比如components:{}属性、props:[]属性、template属性(对象组件)、生命函数(除beforeCreate、created外)。
  • setup函数是个生命函数,位于beforeCreate、created之间,在组件挂载前需执行的代码(beforeCreate、created)都可以放在setup函数内。
  • setup函数是个返回函数,组合进来的属性、只有通过return返回才能被template使用,通过return能实现。
    对象组件里使用setup函数如下:。

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
    <script src="https://cdn.staticfile.net/vue/3.2.36/vue.global.min.js"></script>
    </head>
    <body>
    <div id="hello-vue" class="demo"></div>
    <script >
    const {ref} = Vue
    const HelloVueApp = {
    setup(a,b){
        console.log(a,b)
        const count=ref(1)
      tt()
      function tt() {count.value++}
        return{count,tt}},
    template: `<button @click="tt">点了 {{ count }} 次!</button><app ccc="子组件"/>`,
    components: {'app':{props: ['ccc'],template: `<button>我是{{ccc}}</button>`}}
    }
    Vue.createApp(HelloVueApp).mount('#hello-vue')
    </script>
    </body>
    </html>

2、<script setup>语法糖

setup函数在vue组件上有一种语法糖的形式——即在<script>标签内加上setup。

  • 采用语法糖的组件,不需要手动用return返回setup函数内的属性值,不需要手动用export default暴露setup函数及其它属性值,就能供<template>使用。
  • 采用语法糖的组件,子组件不需要components属性定义就能直接供<template>使用。
  • <script setup>内的代码(非生命函数内的代码)会在每次组件实例被刷新的时候执行,而setup函数及普通<script>内的代码只在组件初始化时执行一次。
  • 有些配置项在script setup中不适用(如:在vue3.3版以下,禁止自动接收透传inheritAttrs: false不能写在script setup中,需另开一个<script>书写),有些代码只希望初始化时执行一次,也可以结合普通script实现。
  • 当script setup与普通script标签在一起时,script代码会并入script setup代码且script代码排在前面。因此在普通script标签定义的对象不需要再使用export default暴露(如是属性,还是需要export default暴露)。
    采用语法糖如下:

    <script setup>
    import { ref } from 'vue'
          const Eee={template:`<button>我是子组件</button>`}
          const count = ref(0)
          function increment(){count.value++}
    </script>
    <template>
    <button @click="increment">{{count}}</button>
    <Eee /><br>
    <component :is="Eee" />
    </template>
    <style scoped>.logo {height: 6em}</style>

二、全局组件、局部子组件、动态子组件

在app创建阶段可以通过app的component函数创建全局组件。
在当前组件,可以通过components:{}属性注册局部子组件。

1、全局组件

定义的全局子组件,在所有的组件中可以直接使用,不必import引入,也不需在当前组件的components:{}属性里再注册为子组件。

a、component函数书写规范

app.component('组件名', 组件)
组件名: 组件在模版上使用的标签名,组件名要加引号。
组件: 组件可以是外部的组件对象名(js组件对象名或vue组件对象名),也可以是js组件对象实体。
实例如下:

import App from './App.vue'
import zivue from './zivue.vue'
const ziapp={setup(){},template:'<h1>对象子组件</h1>',}
const app=createApp(App)
app.component('zivue', zivue)
app.component('ziapp', ziapp)
app.component('aHoo', {setup(){},template: '<h1>对象子组件</h1>',})
app.mount("#app")
b、component函数链式调用

多次调用component()函数时,可以被链式调用:

app.component('ComponentA', ComponentA)
  .component('ComponentB', ComponentB)
  .component('ComponentC', ComponentC)
c、全局组件注册时机

全局组件注册必须在挂载之前,即
app.component(‘a’,b)函数须在app.mount("#app")之前。
如果采用的是vite构建,参数2只能使用vue组件名,对象组件名和对象组件实体必须开启对象组件规范才能使用。

2、局部子组件

组件外的组件要在当前组件使用,需在当前组件的components:{}属性里注册为局部子组件才能被当前组件使用(全局子组件不用)。如:
export default{components:{子组件名:组件}}
注意:全局子组件注册是调用函数,用的是调用函数的()小括号,子组件名是参数,要用引号。局部子组件的注册是属性的方式,用的是属性的:{}帽号加大括号,子组件名为属性名,不需要加引号。
如果用了<script setup>语法糖,组件可以直接作为子组件使用,不须在当前组件的components:{}属性里注册。

3、动态子组件

组件只有经全局注册或局部注册才可以在<template>作为组件标签使用。
动态子组件:组件不须注册,用绑定<component/>标签is属性的方式将<component/>标签渲染为组件标签。如:
<component :is="组件" />动态子组件标签。

三、非组件标签

vue默认会将任何非原生的 HTML 标签优先当作 Vue组件标签处理,如果是个自定义标签(非组件标签),会在开发时导致 Vue 抛出一个“解析组件失败”的警告。
可通过判定函数赋给isCustomElement来对自定义标签进行标注。

  • 非构建开发时:app.config.compilerOptions.isCustomElement属性用于自定义标签标注,如:app.config.compilerOptions.isCustomElement = (tag){return tag.includes('rrr')},须在.mount('#app')挂载函数前对isCustomElement属性赋值。
  • Vite 构件开发时:在根目录下找到配置文件vite.config.js,在里面添加如下语句:

    import vue from '@vitejs/plugin-vue'
    export default {
    plugins: [
      vue({
        template: {
          compilerOptions: {isCustomElement: (tag){return tag.includes('-')}}}})]}

四、标签内的内容处理

1、挂载点标签内的内容处理

挂载点标签内有内容时,如果子组件的template属性有内容时会覆盖挂载点标签内的内容。如下:

<div id="hello-vue">
  <button >我是挂载1会被覆盖吗</button>
</div>
<div id="hello">
  <button >我是挂载2</button>
</div>
<script>
const App = {template: `<button @click="">你消失了</button>` }
const HelloVueApp = {template:``}
Vue.createApp(App).mount('#hello-vue')
Vue.createApp(HelloVueApp).mount('#hello')
</script>

当子组件是单文件组件时 :无论组件有没有<template>标签都会清空挂载标签内的内容。
特例:以上限制只适用于 createApp 方法创建的app。如果是createSSRApp创建的app,不会清空挂载点内的标签。详见SSR
2、子组件标签内的内容处理
子组件标签内的内容vue默认会将其归为 隐式具名插槽 ,其显示与否取决于子组件是否有插槽出口标签<slot></slot>

递归组件???命名空间组件????顶层 await????


百分之一百零八
15 声望3 粉丝