Vue3(四)非父子组件通信、生命周期、$ref、Mixin混入

362 阅读2分钟

一. 非父子组件的通信

1.1. Provide/Inject

Provide/Inject用于非父子组件之间共享数据:

  1. 比如有一些深度嵌套的组件,子组件想要获取父组件的部分内容;
  2. 在这种情况下,如果我们仍然将props沿着组件链逐级传递下 去,就会非常的麻烦;

image.png 对于这种情况下,我们可以使用 Provide 和 Inject :

  1. 无论层级结构有多深,父组件都可以作为其所有子组件的依赖提供者;
  2. 父组件有一个 provide 选项来提供数据;
  3. 子组件有一个 inject 选项来开始使用这些数据;

实际上,你可以将依赖注入看作是“long range props”,除了:

  1. 父组件不需要知道哪些子组件使用它 provide 的 property
  2. 子组件不需要知道 inject 的 property 来自哪里
  • 基本使用
//祖先组件使用provide
export default {
components:{
    Home,
},
provide:{
    name:"why",
    age:18,
},
}
//孙子组件使用inject使用这些数据
<template>
    <div>bannner{{name}}+{{ age }}</div>
</template>
<script>
export default {
    inject: ["name", "age"]//inject告诉当前需要引用数组的key
}
  • Provide和Inject函数写法
//祖先组件使用provide()
export default {
    components: {
        Home,
    },
    data() {
        return {
            message: "Hello App"
        }
    },
    //provide一般写成函数形式
    provide() {
        return {
            name: "woooy",
            age: 18,
            mesage: this.message//此时的this绑定的provide的this
        }
    }

}
//孙子组件
<template>
    <div>bannner{{ name }}+{{ age }}</div>
    <h2>{{ message }}</h2>
</template>
<script>
export default {
    // 注入name,age,message
    inject: ["name", "age", 'message']//inject告诉当前需要引用数组的key
}
  • 数据的响应式
    • computed

image.png

1.2. 事件总线hy-event-store

  • 在event-bus.js中创建eventBus对象
  • 监听事件:
    • eventBus.on()
  • 发出事件:
    • eventBus.emit()

image.png

二. 组件化-额外知识补充

2.1. 生命周期函数

生命周期函数:

  1. 生命周期函数是一些钩子函数(回调函数),在某个时间会被Vue源码内部进行回调;
  2. 通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段;
  3. 那么我们就可以在该生命周期中编写属于自己的逻辑代码了;

生命周期过程分析: App/Home/Banner/showmessage
创建组件之前回调:beforeCreate

  1. 创建组件实例 created(重要:1.发送网络请求 2. 事件监听 3. this.$watch()-数据的监听)
  2. template模板编译
    挂载到虚拟DOM 之前回调:beforeMount
  3. 挂载到虚拟DOM -虚拟DOM->真实DOM-->界面看到h2/div mounted(重要:元素已经被挂载,获取DOM,使用DOM)
  4. 数据更新:message改变 数据更新之前,执行beforeUpate。根据最新的数据生成VNode,真实的DOM。
    更新完成回调updataed
  5. 如:不再使用v-if ="false"
    移除之前回调:beforeUnmount
    将之前挂载在虚拟DOM中的Vnode从虚拟Dom移除,并将真实的DOM进行更新
    unmounted(相对重要:回收操作,取消事件监听)
    将组件实例销毁 生命周期的历程.png
<template>
    <div>{{ message }}</div>
    <button @click="btnClick">按钮</button>
</template>
<script>
export default {
    data() {
        return {
            message: "message"
        }
    },
    methods: {
        btnClick() {
            this.message = '数据改变'
        }
    },
    // 1.组件被创建之前
    beforeCreate() {
        console.log("beforeCreated");
    },
    // 2.组件创建完成
    created() {
        console.log("created-组件创建完成:");
        console.log("1.发送网络请求");
        console.log("2.监听eventBus事件");
        console.log("3.监听watch数据");
    },
    // 3.组件template准备挂载
    beforeMount() {
        console.log("beforeMount");
    },
    // 4.组件中的template被挂载,虚拟DOM->真实DOM
    mounted() {
        console.log("mounted-组件被挂载");
        console.log("1.获取Dom,2.使用DOM");
    },
    // 5.数据发生改变,界面还未更新。
    // 5.1准备更新DOM
    beforeUpdate() {
        console.log("beforeUpdate");
    },
    // 5.2更新DOM
    updated() {
        console.log("updated-已经获取了最新的DOM:updated");
    },
    // 6.卸载对应的Vnode-->再卸载Dom的元素
    // 6.1卸载之前
    beforeUnmount() {
        console.log("beforeUnmounted");
    },
    // 6.2DOM元素卸载完成
    unmounted() {
        console.log("ummounted");
    }
}
</script>
<style scoped></style>

2.2. refs引入元素/组件

2.2.1.$refs的使用

某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例:

  1. 在Vue开发中我们是不推荐进行DOM操作的;
  2. 这个时候,我们可以给元素或者组件绑定一个ref的attribute属性;

组件实例有一个$refs属性: 它一个对象Object,持有注册过 ref attribute 的所有 DOM 元素和组件实例。

  • 在元素中添加 ref 属性
 <div class="app">
        <!-- vue提供的ref属性,可以自己赋值 -->
        <h2 ref="title" class="title" :style="{color:titleColor}">{{message}}</h2>
        <button ref="btn" @click="btnChange">修改title</button>
    </div>
    
    
    methods: {
        btnChange(){
            // this.message="vue",
            // this.titleColor="green"

            // 获取h2/button元素
            console.log(this.$refs.title);
            console.log(this.$refs.btn);
        }
    }
    

image.png

  • 在组件中添加 ref 属性
  • this.$refs.属性
 <button ref="btn" @click="btnChange">修改title</button>
 <banner ref="banner"></banner>
        
    methods: {
        btnChange() {
            // 2.获取h2/button元素
            console.log(this.$refs.title);
            console.log(this.$refs.btn);
            // 3..获取banner组件,组件实例
            console.log(this.$refs.banner);
            // 3.1在父组件中可以主动调用子组件的对象方法
            this.$refs.banner.bannerClick();
            // 3.2获取banner组件实例,获取banner中的元素
            console.log(this.$refs.banner.$el);
            // 3.3如果banner template是多个根,拿到的是第一个node节点
              console.log(this.$refs.banner.$el.nextElementSibling);
            //   4.组价实例还要两个属性
            console.log(this.$parent);
            console.log(this.$root);
        }
    }

image.png

2.3. 动态组件的使用

2.3.1.v-if显示不同的组件,实现案例

 <div class="app">
        <div class="tab">
            <template v-for="(item, index) in tabs" :key="index">
                <button :class="{ active: currentIndex == index }" @click="btnClick(index)">{{ item }}</button>
            </template>
        </div>
        <div class="views">
            <!-- 1.第一种做法:v-if判断逻辑,决定显示的组件 -->
            <template v-if="currentIndex === 0">
                <home></home>
            </template>
            <template v-else-if="currentIndex === 1">
                <about></about>
            </template>
            <template v-else-if="currentIndex === 2">
                <Category></Category>
            </template>
        </div>

router.gif

2.3.2.动态组件的使用实现案例

动态组件是使用 component 组件,通过一个特殊的attribute is 来实现:

<!-- 2. 动态组件 -->
            <!-- is中的组件需要来自两个地方,1.全局注册的组价,2.局部注册的组件 -->
            <component @homeClick="btnClick" 
            :is="tabs[currentIndex]" 
            name="wzb" :age="30"></component>
            

2.4. keep-alive

2.4.1. 内置组件:keep-alive

在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉,这个时候我们就可以使用一个内置组件:keep-alive。

   <div class="views">
            <!-- 外层包裹一个Keep-alive ,将组件缓存起来-->
            <keep-alive>
                <component :is="tabs[currentIndex]"></component>
            </keep-alive>
        </div>

2.5.2. keep-alive属性

  • include - string | RegExp | Array。只有名称匹配的组件会被缓存;

image.png

image.png

  • exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存;
  • max - number | string。最多可以缓存多少组件实例,一旦达到这个数 字,那么缓存组件中最近没有被访问的实例会被销毁;

2.5.3. include 和 exclude prop 允许组件有条件地缓存:

  • 二者都可以用逗号分隔字符串、正则表达式或一个数组来表示;
  • 匹配首先检查组件自身的 name 选项;

2.5.4. 存活生命周期函数:

对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数的:

  • 但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件;
  • 这个时候我们可以使用activated 和 deactivated 这两个生命周期钩子函数来监听;
 // 对应保持keep-alive组件,监听没有进行切换
    activated() {//进入活跃状态
        console.log("home-activated");
    },
    deactivated() {
        console.log("home-deactivated");
    }

2.5. 异步组件的使用

2.5.1. webpack分包处理

  1. npm run build生成的文件夹分析

image.png

  • 默认的打包过程:
    • 默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将组 件模块打包到一起(比如一个app.js文件中);
    • 这个时候随着项目的不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢;
  • 打包时,代码的分包:
    • 所以,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js;
    • 这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容;
  1. 那么webpack中如何可以对代码进行分包呢?

使用import()函数

//  异步导入
//import函数导入可以让webpack对导入的文件进行分包处理
import("./utils/math").then(res => {
    res.sum(20, 30)
})

image.png

2.5.2.Vue中实现异步组件

  1. 如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那么Vue中给我 们提供了一个函数:defineAsyncComponent。
  2. defineAsyncComponent接受两种类型的参数:
    1. 类型一:工厂函数,该工厂函数需要返回一个Promise对象;
    2. 类型二:接受一个对象类型,对异步函数进行配置;
//defineAsyncComponent(() => import(""))
// defineAsyncComponent方法用来定义异步组件
import { defineAsyncComponent } from 'vue'
// 箭头函数返回一个promise对象
const AsyncCategory = defineAsyncComponent(() => import("./views/category.vue"))
export default {
    components: {
        Home,
        Category: AsyncCategory,//异步组件在打包时会进行分包的处理
        about
    },

image.png

2.5.3 异步组件的写法二(了解)

image.png

2.6. v-model

2.6.1. v-model在元素上

 <!--1. input中的v-model -->
        <input type="text" v-model="message">
        <!--  v-model="message"相当于 v-bind:value="message" @input="message = $event.target.value"-->
         <input type="text" :value="message" @input="message = $event.target.value">

2.6.2. v-model在组件上


 <!-- 2.组件中的v-model -->
        <counter v-model="appCounter"></counter>
        <!--组件的v-model相当于做了下面两件事
             1.将appCounte绑定到modelValue上面
             2. 自动监听update:modelVlue事件,将传过来的值,通过$event获取 -->
        <counter v-bind:modelValue="appCounter" v-on:update:modelValue="appCounter=$event"></counter>

counter组件

<template>
    <div>
        <h2>counter:{{ modelValue }}</h2>
        <button @click="changeClick">修改counter</button>
    </div>
</template>
<script>
export default {
    props: {
        modelValue: {
            type: Number,
            default: 0,
        }
    },
    methods: {
        changeClick() {
            this.$emit("update:modelValue", 999);
        }
    },

}
</script>
<style scoped></style>

2.6.3.v-model绑定多个属性

 <!-- 3.组件的v-model:自定义名称counter ,监听的事件也变成@uodate:counter-->
        <counter2 v-model:counter="appCounter" v-model:fy="num1"></counter2>

v-model:counter相当于做了两件事。

  1. 绑定counter属性
  2. 监听了@update:counter事件 image.png

2.7. 混入Mixin

2.7.1. mixin使用场景

目前我们是使用组件化的方式在开发整个Vue的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同 的代码逻辑进行抽取。

2.7.2. 在Vue2和Vue3中都支持的一种方式就是使用Mixin来完成:

  • Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能;
  • 一个Mixin对象可以包含任何组件选项;
  • 当组件使用Mixin对象时,所有Mixin对象的选项将被 混合 进入该组件本身的选项中;

在组件通过mixins: []

image.png

image.png

2.7.3. Mixin的合并规则

如果Mixin对象中的选项和组件对象中的选项发生了冲突,那么Vue会如何操作呢?

这里分成不同的情况来进行处理;

  • 情况一:如果是data函数的返回值对象
    • 返回值对象默认情况下会进行合并;
    • 如果data返回值对象的属性发生了冲突,那么会保留组件自身的数据;
  • 情况二:如何生命周期钩子函数
    • 生命周期的钩子函数会被合并到数组中,都会被调用;
  • 情况三:值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。
    • 比如都有methods选项,并且都定义了方法,那么它们都会生效;
    • 但是如果对象的key相同,那么会取组件对象的键值对;

2.7.4. 全局混入: app.mixin({})

  • 全局的Mixin可以使用 应用app的方法 mixin 来完成注册;
  • 一旦注册,那么全局混入的选项将会影响每一个组件;

在mian.js中

const app = createApp(App);
app.mixin({
    created(){
        console.log("混入的created:");
    }
})
app.mount('#app')