codewhy的Vue3学习笔记-组件化开发


父子组件间的通信


  1. 父组件 => 子组件 :通过props属性
  2. 子组件 => 父组件: 通过$emit触发事件


父传子


我们可以通过 props 来完成组件之间的通信

props是什么?

  1. props 是你可以在组件上注册一些自定义的 attribute
  2. 父组件可以给这些 attribute 赋值,子组件通过 attribute 的名称获取到对应的值

props有两种常见的用法:

  1. 方式一:字符串数组,数组中的字符串就是 attribute 的名称
  2. 方拾二:对象类型;对象类型我们可以在指定 props 名称的同时,指定它需要传递的类型,是否是必须的,默认值

props的数组用法

//子组件-showMessage
<template>
<div>组件展示的title:{{title}}</div>
<div>组件展示的content:{{content}}</div>
</template>
<script>
 export default {
props:["title", "content"]
}
</script>
// 父组件
<template>
<div>
   <show-message title="heihei" content="this is a boring boy">
   </show-message>
 </div>
</template>

props的对象用法

数组用法只能传入 attribute 的名称,并不能对形式做限制和传入默认值

  1. 指定传入的 attribute 的类型
  2. 指定传入的 attribute 是否是必传的
  3. 指定没有传入时,attribute 的默认值
export default {
props:{
//指定类型
title: String,
//指定类型 同时指定是否必选,默认值
content: {
type: String,
require: true,
default:"哈哈哈"
}
}
}

对象类型的其他写法

props: {
msg: String,
// 基础类型检查(null 和 undefined 会通过任何类型检测)
propA: [String, Number],
//可能多个类型
propB: {type: String, require: true}
//必填
propC:{type: Number, default:100 }
// 带有默认值的数字
propD:{
type: Object,
default() { return { message:'hello' } }
}
}
//带有默认值的对象
propE:{
validator(value) {
//这个值必须 匹配下面字符串中的一个
return ['success', 'denger', 'error'].includes(value)
}
}
// 自定义验证函数
propF:{
type:Function,
// 与对象或函数默认值不同,这不是一个工厂函数
default() {
return 'Default Function'
}
}

非Prop的Attribute

当我们传递给一个组件某个属性但是该属性没有定义对应的props或者emits时,就称之为非props的Attribute

例如常见的:class , style , id

Attribute继承

当组件有单个根节点时,非 Prop 的 Attribute 将自动添加到根节点的 Attribute 中

禁用Attribute继承和多根节点

如果我们不希望组件的根元素继承 attribute,可以在组件中设置 inheritAttrs : false

我们可以通过 $attrs 来访问所有的 非 props 的 attribute


子传父


前置知识

  1. emits

emits可以是数组或者对象,从组件触发自定义事件,对象可以进行参数验证

如何实现

  1. 首先,我们需要在子组件中定义好在某些情况下触发的事件名称
  2. 其次,在父组件中以 v-on 的方式传入要监听的事件名称,并绑定到对应的方法中
  3. 最后在子组件中发生某个事件的时候,根据事件名称触发对应的事件
// 父组件
<h2>当前计数: {{counter}}</h2>
<counter-operation @add="addOne"></counter-operation>
<script>
 data() {
 return {
   counter: 0
}
},
methods: {
 addOne() {
   this.counter++
}
}
// 子组件
<button @click="increment">+1</button>
emits: {
add: null,
},
data() {
 return {
   num: 0
}
},
methods: {
 increment() {
   console.log("+1");
   this.$emit("add");
},
 incrementN() {
   const flag = this.$emit('addN', this.num, "why", 18);
   console.log(flag,'flag');
}
}
</script>

自定义事件

自定义事件我理解的是:子组件传值给父组件的时候,父组件可以自定义事件名称例如 @customName 然后子组件就可以通过 emits 来接收到这个自定义事件并调用它,通过 this.$emits("customName",..args)args 是父组件中的这个自定义事件所需要的参数,可以传多个


非父子组件的通信


在我们实际开发中还有很多非父子组件的通信

主要有两种方式:

  1. Provide/Inject
  2. Mitt 全局事件总线

Provide 和 Inject

  1. 比如有一些深度嵌套的组件子组件想要获取父组件的部分内容
  2. 如果我们任然将 props 沿着组件链逐级传递下去,就会非常的麻烦;
  3. 对于这种情况,我们可以使用 Provide 和 Inject:
  4. 无论层级结构有多深,父组件都可以作为其所有子组件的依赖提供者
  5. 父组件有一个 provider 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据
  6. 实际上,你可以将依赖注入看成 'lang range props' 除了:父组件不需要知道哪些子组件使用它 provide 的property,子组件不需要知道 inject 的 property 来自哪里

<script>
 // 父组件
   provide() {
     return {
       name: "why",
       age: 18,
       length: computed(() => this.names.length) // ref对象 .value
    }
  },
</script>
<script>
 // 子组件 :在子组件中写入后 就可以把它当做data中的数据进行使用
 export default {
   inject: ["name", "age", "length"],
}
</script>

全局事件总线mitt库

Vue3 从实例中移除了 $on 、$off 和 $once 方法,如果我们希望继续使用全局事件总线 Vue3 推荐使用 mitt 库

不是很理解,不知道实际使用场景


插槽Slot


使用场景:

在开发中,我们会经常封装一个个可复用的组件:

  1. 前面我们会通过props传递给组件一些数据,让组件进行展示
  2. 但是为了让组件有更强的通用性,我们不能将组件的内容限制为固定的div,span等等元素
  3. 比如某种情况下我们使用组件,希望组件显示的是一个按钮,换一种情况组件显示的说一张图片,
  4. 我们应该让使用者可以决定莫一块区域到底存放什么内容和元素

举个栗子:假如我们定制一个通用的导航组件 -NavBar

  1. 这个组件分为三个区域,左边 - 中间 - 右边
  2. 但是你看,左边可能显示一个菜单图标,也可能显示一个返回按钮,可能还是什么都不显示;中间和右边同理

使用插槽slot

重要提醒:

插槽的使用其实就是抽取共性,预留不同;我们将共同的元素,内容依然组件内进行封装;同时将不同的元素使用slot 作为占位符,让使用这个组件的实例来定义 slot 内的内容

vue 中将 <slot> 元素作为承载分发内容的出口;在封装组件中,使用特殊的元素 <slot> 就可以为封装组件开启一个插槽,插槽内的内容完全取决于其实例组件

插槽的默认内容

有时候我们希望在使用插槽时,如果没有对应的内容,那么我们需要显示一个默认的内容,这个默认的内容只会在没有插入内容时候才会显示

//使用插槽的组件
<template>
 <div>
   <MySlotCpn>
  111                 // 这里的内容就会放到定义好的插槽中,  
</MySlotCpn>
 </div>
</template>
<script>
 import MySlotCpn from './MySlotCpn.vue';
 export default {
   components: {
     MySlotCpn
  }
}
</script>
// 插槽组件
<template>
 <div>
   <slot>
     <i>我是默认的i元素</i> //当使用这个插槽组件但是没有往插槽中插入任何东西就会显示默认的,否则显示的就是父组件的插入元素
   </slot>
 </div>
</template>

多个插槽效果

如果一个组件中含有多个插槽,但是元素我们插入多个内容,我们会发现每个插槽都会插入所有的内容,为了让内容插到对应的插槽就有了具名插槽

具名插槽就是给插槽起一个名字 <slot> 元素就有了一个特殊的 attribute:name , 一个不带 name 的 slot 会含有隐式的名字 default , 它的效果是:

//使用插槽的元素
<template v-slot:left>
this is left
</template>
// 插槽 这里slot加载到页面中是没有slot元素的,它只是一个占位符
<slot name="left"></slot>

动态插槽名

  1. 目前我们使用的插槽名称都是固定的;例如:v-slot:left, 这里这个 left 就是写死的,我们可以通过 v-slot:[dynamicSlotName],
<template v-slot:[name]>//这个name就是data中定义的动态数据
</template>

具名插槽的缩写:即把v-slot:替换为#


渲染作用域


在vue中有渲染作用域的概念:

  1. 父模板中的所有内容都是在父级作用域中编译;子模板也是一样;其实很好理解;例如我们无法在父模板中访问或者使用子模板的data变量或者方法


作用域插槽


从上面可知,父子作用域独立,但是有时候我们希望插槽可以访问到子组件中的内容,这个是很常见的需求

  1. 当一个组件被用来渲染一个数组元素时,我们使用插槽,并且希望插槽中没有显示每项的内容,这时候就需要作用域插槽

如果还有其他的具名插槽, 那么默认插槽也必须使用 template 来编写

独占默认插槽的缩写

如果我们的插槽是默认插槽default,那么在使用的时候 v-slot:default="slotProps" 可以简写为 v-slot="slotProps"

默认插槽和具名插槽混合使用:使用 v-slot:why(一个名称其他也行)来定义具名插槽,使用 v-slot="slotProps"来使用默认插槽


动态组件的使用


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

<template>
<button v-for="item in tabs" :key="item"
           @click="itemClick(item)"
           :class="{active: currentTab === item}">
     {{item}}
   </button>
<!-- 2.动态组件 -->
   <keep-alive include="home,about,category" max='2'>
     <component :is="currentTab"
                name="coderwhy"
                :age="18"
                @pageClick="pageClick">
     </component>
   </keep-alive>
</template>
<script>
 data() {
  return {
    tabs: ["home", "about", "category"],
    currentTab: "home"
  }
},
methods() {
  itemClick(item) {
    this.currentTab = item;
  },
  pageClick() {
    console.log("page内部发生了点击");
  }
}
</script>


认识 keep-alive


产生背景

在默认情况下,我们在切换组件时候,about组件会被销毁掉,再次回来时会重新创建组件,但是我们在开发中我们希望继续保持组件的状态,而不是销毁掉,这个时候我们可以使用一个内置组件:keep-alive

keep-alive属性

  1. include : string | RegExp | Array。只有在名称匹配的组件会被缓存
  2. exclude : string | RegExp | Array。任何匹配的组件都不会被缓存
  3. max : number | string。最多可以缓存多少组实例,一旦达到这个数字,那么缓存中最近没有被访问的实例被销毁
 <!-- 2.动态组件 -->
   <keep-alive include="home,about,category" max='2'>
     <component :is="currentTab"
                name="coderwhy"
                :age="18"
                @pageClick="pageClick">
     </component>
   </keep-alive>


缓存组件的生命周期


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

  1. 但是我们有时候希望监听何时重新进入和离开这个组件
  2. 这时候我们可以使用activated和deactivated来监听;


vue实现异步组件


1. 产生背景:

当我们项目过大了,对于某些组件我们希望可以通过异步的方式来进行加载(目的是可以对其分包处理,也就是让 wabpack 对其进行单独打包)

2. 使用

vue 给我们提供了一个函数:defineAsyncComponent 它接收两种类型参数,

  1. 工厂函数,该工厂函数需要返回一个 Promise 对象
  2. 接收一个对象类型,对异步函数进行配置
<script>
 //工厂函数写法
 import defineAsyncComponent from 'vue'
 const AsyncHome = defineAsyncComponent(()=>import("./AsyncHome.vue"))
 export default {
 components: {
AsyncHome
}
}
 // 对象写法
   const AsyncCategory = defineAsyncComponent({
   loader: () => import("./AsyncCategory.vue"),
   loadingComponent: Loading,
   // errorComponent,
   // 在显示loadingComponent组件之前, 等待多长时间
   delay: 2000,
   /**
    * err: 错误信息,
    * retry: 函数, 调用retry尝试重新加载
    * attempts: 记录尝试的次数
    */
   onError: function(err, retry, attempts) {
  }
})
</script>


异步组件和 Suspense


注意: 这是一个实验性的特性,API可能随时修改

Suspense 是一个内置的全局组件,该组件有两个插槽:

  1. default: 如果 default 可以显示,那么显示 default 内容
  2. fallback:如果 default 无法显示 那么会显示 fallback 插槽的内容


$ref的使用


使用场景:

在某些情况下,我们组件想要直接获取到元素对象或者子组件示例:我们可以给元素绑定一个 ref 的 attribute 属性

组件实例有一个 $refs 属性:

它是一个对象,持有注册过 ref attribute 的所有DOM元素和组件实例


生命周期


定义:

每个组件都会经历从创建,挂载,更新,卸载等一系列的过程。在这些过程的某个阶段,可能会想要添加一些属于自己的代码逻辑,(比如请求数据)这个时候就需要 Vue 给我们提供的生命周期函数来实现

使用:

生命周期函数就是一些钩子函数,在某个时间会被 Vue 源码内部进行回调,我们调用生命周期函数在里面写自己的逻辑,这样就可以实现页面在加载完成同时做完我们的事情


组件的v-model


// 父组件
<template>
 <hy-input v-model="message" v-model:title="title"  @my-event="add"></hy-input>
</template>
<script>
 import HyInput from './HyInput.vue';
 export default {
   components: {
     HyInput
  },
   data() {
     return {
       message: "Hello World",
    }
  },
   methods:{
     add (val) {
       this.num += val
    }
  }
}
</script>
// 子组件
<template>
 <input v-model="value">
 <input v-model="why">
</template>
<script>
 export default {
   props: {
     modelValue: String,
     title: String
  },
   emits: ["update:modelValue", "update:title"],
   computed: {
     value: {
       set(value) {
         this.$emit("update:modelValue", value);
      },
       get() {
         return this.modelValue;
      }
    },
     why: {
       set(why) {
         this.$emit("update:title", why);
      },
       get() {
         return this.title;
      }
    }
  }
}
</script>

总结:

父组件需要在使用的子组件中写 attribute ; v-model 值为一个对象,当我们把这个值传入子组件后,子组件就可以通过 $emit 来实现在子组件中直接修改父组件中的值,并响应式更新

具体实现是通过子组件的 computed 的 get 接收父组件传来的值和 set 修改父组件传来的值,使用 emits 来声明我们 this.$emit 所需要的第一个参数,通常是这样的 update:modelValue


子组件绑定多个属性(使用 v-model )


需求:

我们希望在一个组件上使用多个 v-model 来绑定多个属性

我们知道,默认情况下的 v-model 其实是绑定了 modelValue 属性和 @update:modelValue 事件,当我们需要给一个组件绑定多个属性时,可以这样写 v-model:num='data' 其中 num 是传给子组件 props 接收参数的名称,data 是传过去的父组件的 data 中定义的数据


Mixin


产生背景:

我们在实际开发中,会遇到组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取,这样就不用重复书写,那么我们就可以使用Mixin来实现

使用:

Mixin 提供了一种非常灵活的方式,来分发 Vue 组件中可复用功能,

一个Mixin对象可以包含任何组件选项;

当组件使用 Mixin 对象时,所有 Mixin 对象的选项将被混合进入该组件本身的选项中;

Mixin的合并规则

如果 Mixin 对象中的选项和组件对象中的选项发生冲突,vue会这样操作

  1. 如果是data函数的返回值对象
  2. 返回值对象默认会进行合并
  3. 如果data返回值对象的属性发生了冲突,那么会保留组件自身的数据
  4. 如果是生命周期钩子函数
  5. 钩子函数会被合并到数组中,都会被调用;
  6. 值为对象的选项,例如 methods,components, directives, 将会合并为一个对象
  7. 例如methods选择,如果都定义了方法,那么就会合并,但是如果对象的key相同,那么就会取组件对象的键值对


全局混入Mixin


如果组件中某些选择,是所有组件都需要的,那么我们可以使用全局Mixin

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


0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP