初识Vue(三)

310 阅读7分钟

Vue组件

组件基础

组件最大的优势就是可复用性 当使用构建步骤时,一般会将Vue组件定义在一个单独的vue文件中,这叫做单文件组件(简称SFC) template: 必须,写Html标签 script: 可选,写JavaScript逻辑 style: 可选,写Css样式 组件的引用 第一步 import 组件名 from '组件地址' 第二步 components中注册该主键 第三步 在template中使用 大驼峰或者蛇形字符串用-连接

<template>
    <h3>{{message}}</h3>
</template>

<script>
    export default {
        name: "ComponentComposition",
        data() {
            return {
                message: "组件组成"
            }
        }
    }
</script>

<style scoped>

</style>

组件嵌套关系

组件允许将UI划分为独立的,可复用的部分,并且可以对每个部分单独的思考. 在实际应用中,组件常常被组织成层层嵌套的树状结构.

<!--ComponentNestingRelationship-->
<template>
    <h3>{{message}}</h3>
    <ComponentNestingRelationshipHeader />
    <ComponentNestingRelationshipMain />
</template><script>
    import ComponentNestingRelationshipHeader from './ComponentNestingRelationshipHeader.vue'
    import ComponentNestingRelationshipMain from './ComponentNestingRelationshipMain.vue'
​
    export default {
        name: "ComponentNestingRelationship",
        components: {
            ComponentNestingRelationshipHeader,
            ComponentNestingRelationshipMain
        },
        data(){
            return{
                message: '组件嵌套关系'
            }
        }
    }
</script><style scoped></style>
<!--ComponentNestingRelationshipHeader-->
<template>
    <div>ComponentNestingRelationshipHeader</div>
</template><script>
    export default {
        name: "ComponentNestingRelationshipHeader"
    }
</script><style scoped></style>
<!--ComponentNestingRelationshipMain-->
<template>
    <div>ComponentNestingRelationshipMain</div>
</template><script>
    export default {
        name: "ComponentNestingRelationshipMain"
    }
</script><style scoped></style>

组件注册方式

一个Vue组件在使用前需要被祖册,这样Vue才能在渲染模板时找到其对应的实现 组件的组成方式有两种全局注册和局部注册 全局注册存在的问题

  1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除(也叫"tree-shaking").如果全局注册了一个组件,即使它没有被使用,它仍然会出现在打包后的js文件中
  2. 全局注册在大型项目中使项目关系变得不那么明确.在父组件中使用子组件时,不太容易定位子组件的实现.和使用过多的全局变量一样,这可能会影响应用长期的可维护性
// 全局注册
import {createApp} from 'vue'
import App from './App.vue'
import ComponentRegistrationMethod from './examples-vue3/ComponentRegistrationMethod.vue'const app = createApp(App);
app.component('ComponentRegistrationMethod', ComponentRegistrationMethod);
app.mount('#app');

局部组件,参见【组件嵌套关系】ComponentNestingRelationship.vue

组件传递数据

组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的 传递数据的解决方案就是Props

Prop传递参数其实是没有类型限制的,不仅可以传递字符串类型数据,还可以是其他类型,例如: 数字,对象,数组等

实际上任何类型的值都可以作为Props的值被传递

Vue组件可以更细致地声明对传入的Props的校验要求

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,   commentIds: Array,
  author: Object,
  callback: Function
}

数据类型为数组或者对象的时候,默认值是需要返回工厂模式

<ComponentPassesData data="传递到了吗?" />
<template>
    <h3>{{message}}</h3>
    <div>Props接受到的数据: {{data}}</div>
</template><script>
    export default {
        name: "ComponentPassesData",
        props: {
            data: {
                type: String,
                default: 'props数据'
            }
        },
        data() {
            return {
                message: '组件传递数据'
            }
        }
    }
</script><style scoped></style>

自定义事件组件交互

在组件的模板表达式中,可以直接使用emit方法触发自定义事件触发自定义事件的目的是组件之间传递数据自定义事件可以在组件中反向传递数据,prop可以将数据从父组件传递到子组件,那么反向如何操作呢,就可以利用自定义事件实现emit方法触发自定义事件 触发自定义事件的目的是组件之间传递数据 自定义事件可以在组件中反向传递数据, prop 可以将数据从父组件传递到子组件,那么反向如何操作呢,就可以利用自定义事件实现 emit

<!--app.vue-->
<script setup>
    import CustomEventComponentInteraction from './examples-vue3/CustomEventComponentInteraction.vue'
    
    const getData = (data) => {
        console.log(data)
    }
</script>
<template>
    <CustomEventComponentInteraction @data="getData"/>
</template>
<!--CustomEventComponentInteraction.vue-->
<template>
    <h3>{{message}}</h3>
    <button @click="sendHandle">发送数据</button>
</template><script>
    export default {
        name: "CustomEventComponentInteraction",
        data() {
            return {
                message: '自定义事件组件交互'
            }
        },
        methods: {
            sendHandle() {
                this.$emit("data", this.message)
            }
        }
    }
</script><style scoped></style>

透传属性

透传属性指的是传递给一个组件,却没有被该组件声明为props或emits的属性或者v-on事件监听器 最常见的例子就是class,style和id 当一个组件以单个元素为根作渲染时,穿透的属性会被自动添加到根元素上 必须只有一个根元素生效

<TransparentProperties class="test-transparent-properties" />
<template>
    <h3>{{message}}</h3>
</template><script>
    export default {
        name: "TransparentProperties",
        data() {
            return {
                message: '透传属性'
            }
        }
    }
</script><style scoped>
    .test-transparent-properties{
        color: red;
    }
</style>

插槽

  1. 在某些场景中,可能需要为子组件传递一些片段,让子组件在它们的组件中渲染这些片段 slot元素是一个插槽出口(slot outlet),标示了父元素提供的插槽内容(slot content)在哪里被渲染
  2. 插槽的渲染作用域 插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的 v-slot有对应的简写#,因此<template v-slot:name>可以简写为<template #name> 其意思就是将这部分模板片段传入子组件的name插槽中
  3. 插槽中的数据传递 在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据. 要做到这一点需要一种方法来让子组件在渲染时将一部分数据提供给插槽 可以像对组件传递props那样,向插槽的出口上传递属性 子组件将自己的参数传递给父组件,父组件通过v-slot="slotProps"或者#name="slotProps"接收,父组件通过slotProps.子组件传递过来的参数名称传递到插槽
<script setup>
const slotContent = "slot";
</script>
<template>  
    <Slot>
        <template v-slot:name="slotProps">
            <div>{{slotContent}}+{{slotProps.data}}</div>
        </template>
    </Slot>
</template>
<template>
    <h3>{{message}}</h3>
    <slot name="name" :data="data">default</slot>
</template><script>
    export default {
        name: "Slot",
        data() {
            return {
                message: '插槽',
                data: '自己的数据'
            }
        }
    }
</script><style scoped></style>

组件生命周期

每个组件在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。 同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会 为了方便记忆,我们可以将他们分类: 创建时:beforeCreate 、created 渲染时:beforeMount 、mounted 更新时:beforeUpdate 、updated 卸载时:beforeUnmount 、unmounted

<template>
    <h3>{{message}}</h3>
    <button @click="updateMessage">更新数据</button>
</template><script>
    export default {
        name: "ComponentLifeCycle",
        data() {
            return {
                message: '组件生命周期'
            }
        },
        methods: {
            updateMessage(){
                this.message = '组件生命周期-更新'
            }
        },
        beforeCreate(){
            console.log('创建之前');
        },
        created(){
            console.log('创建之后');
        },
        beforeMount(){
            console.log('渲染之前');
        },
        mounted(){
            console.log('渲染之后');
        },
        beforeUpdate(){
            console.log('更新之前');
        },
        updated(){
            console.log('更新之后');
        },
        beforeUnmount(){
            console.log('销毁之前');
        },
        unmounted(){
            console.log('销毁之后');
        }
    }
</script><style scoped></style>

动态组件

有些场景会需要再两个或者多个组件之间来回切换,例如Tab界面

当使用来在多个组件之间切换时,被切换的组件会被卸载掉.

可以通过组件来强制内切换的组件仍保持存活

<template>
    <keep-alive>
        <component :is="currentCompontent">123</component>
    </keep-alive>
    <button @click="switchCompentent">切换组件</button>
</template><script setup>
    import TemplateSyntax from './examples-vue3/TemplateSyntax.vue'
    import ComponentLifeCycle from './examples-vue3/ComponentLifeCycle.vue'
    import {defineComponent, ref} from 'vue'
​
    const currentCompontent = ref('TemplateSyntax');
​
    const switchCompentent = () => {
        currentCompontent.value = currentCompontent.value === 'TemplateSyntax' ? 'ComponentLifeCycle' : 'TemplateSyntax';
    }
</script><style scoped></style>

异步组件

在大型项目中,可能需要拆分应用为更小的块,并仅在需要的时候从服务器加载相关组件.

vue提供了defineAsyncComponent方法来实现此功能.

<template>
    <keep-alive>
        <component :is="currentCompontent">123</component>
    </keep-alive>
    <button @click="switchCompentent">切换组件</button>
</template><script setup>
    import TemplateSyntax from './examples-vue3/TemplateSyntax.vue'
    import {defineAsyncComponent, ref} from 'vue'
    
    const ComponentLifeCycle = defineAsyncComponent(()=>{
        import('./examples-vue3/ComponentLifeCycle.vue');
    });
    const currentCompontent = ref('TemplateSyntax');
​
    const switchCompentent = () => {
        currentCompontent.value = currentCompontent.value === 'TemplateSyntax' ? 'ComponentLifeCycle' : 'TemplateSyntax';
    }
</script><style scoped></style>

依赖注入

通常情况下,当需要父组件向子组件传递数据时,会使用props. 有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据 在这种情况下,如果仅使用props则必须将其沿着组件链逐渐传递下去,这将会很麻烦 这一问题被称为prop逐级透传 provide和inject可以解决这个问题 一个父组件相对于其所有的后代组件,会作为依赖提供者. 任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖 这种方式只能从上往下传,不能从下往上传

<!--DependencyInjection.vue-->
<template>
    <div class="DependencyInjection">
        <h3>{{message}}</h3>
        <DependencyInjectionChild1 />
    </div>
</template><script>
    import DependencyInjectionChild1 from './DependencyInjectionChild1.vue'
​
    export default {
        name: "DependencyInjection",
        data(){
            return {
                message: '依赖注入',
                data: '这个是数据我要传递给子组件的子组件'
            }
        },
        provide() {
            return {
                data: this.data
            }
        },
        components: {
            DependencyInjectionChild1
        }
    }
</script><style scoped>
    .DependencyInjection{
        border: 1px solid red;
        padding: 10px;
    }
</style>
<!--DependencyInjectionChild1.vue-->
<template>
    <div class="DependencyInjectionChild1">
        <h3>{{message}}</h3>
        <DependencyInjectionChild1Child2 />
    </div>
</template><script>
    import DependencyInjectionChild1Child2 from './DependencyInjectionChild1Child2.vue'
​
    export default {
        name: "DependencyInjectionChild1",
        data(){
            return {
                message: '我是依赖注入的子组件'
            }
        },
        components: {
            DependencyInjectionChild1Child2
        }
    }
</script><style scoped>
    .DependencyInjectionChild1{
        border: 1px solid green;
        padding: 10px;
    }
</style>
<!--DependencyInjectionChild1Child2.vue-->
<template>
    <div class="DependencyInjectionChild1Child2">
        <h3>{{message}}</h3>
        <p>接收数据: {{data}}</p>
        <p>接收数据: {{data_}}</p>
    </div>
</template><script>
    export default {
        name: "DependencyInjectionChild1Child2",
        data(){
            return {
                message: '我是依赖注入子组件的子组件',
                data_: this.data
            }
        },
        inject: ['data']
    }
</script><style scoped>
    .DependencyInjectionChild1Child2{
        border: 1px solid purple;
        padding: 10px;
    }
</style>