重学vue3-深入组件篇

本文全部按照组合式API风格

注册

全局注册

javascript 复制代码
使用app.component('MyComponent',{/* 组件的实现 */})
或者直接导入单文件

import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

局部注册 直接在组件中引入直接使用

xml 复制代码
<script setup> import ComponentA from './ComponentA.vue' </script> 
<template> <ComponentA /> </template>

Props

props声明:

javascript 复制代码
//字符串数组
const props = defineProps(['foo'])
//对象声明
const props = defineProps({
  title: String,
  likes: Number
})

单向数据流 props是不支持在子组件中更改的,如果你有更改的需求通常来源于以下三种场景:

  • prop被用于初始值

    建议重新定义一个局部数据属性

  • 需要对传入的prop值做进一步的转换

    基于prop值定义一个计算属性

  • 子组件需要更改prop值的状态

    建议使用emit让父组件去执行更改

一些补充的细节:

  • 所有的prop默认是可选的,除非声明了 required:true
  • 除Boolean外未传递的值将会有一个默认值 undefined,Boolean会默认false
  • 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default

事件

事件触发和监听

php 复制代码
//组件模版内
@click=$emit('函数名',参数一,参数二,$event) $event事件参数
//js内:
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() { emit('submit') }

tip: 和原生 DOM 事件不一样,组件触发的事件没有冒泡机制。你只能监听直接子组件触发的事件。平级组件或是跨越多层嵌套的组件间通信,应使用一个外部的事件总线,或是使用一个全局状态管理方案

事件校验

对事件进行校验,接受的参数就是抛出事件时传入 emit 的内容,返回一个布尔值来表明事件是否合法。

xml 复制代码
<script setup>
const emit = defineEmits({
  // 没有校验
  click: null,

  // 校验 submit 事件
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Invalid submit event payload!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>

将事件作为prop: on-submit="submitFun"

组件v-model

实现v-modal的两种方式

方式一:

xml 复制代码
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

//使用
<CustomInput v-model="searchText" />

方式二:使用一个可写的计算属性

v-modal在组件上默认使用modelValue作为prop,并以 update:modelValue 作为对应的事件;

我们可以指定参数 v-modal:title来更改这些名字,并通过update:title事件更新title值

xml 复制代码
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['title'])
const emit = defineEmits(['update:title'])

const value = computed({
  get() {
    return props.title
  },
  set(value) {
    emit('update:title', value)
  }
})
</script>

<template>
  <input v-model="value" />
</template>

v-modal多个绑定,每一个v-modal都会同步不同的prop值

v-modal修饰符 例如 .trim .number .lazy 除此之外,我们还可以自定义修饰符

举例:capitalize 它会自动将 v-model 绑定输入的字符串值第一个字母转为大写:

xml 复制代码
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])

//此时 capitalize为true
console.log(props.modelModifiers) // { capitalize: true }

function emitValue(e) {
  let value = e.target.value
  //处理字符串
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  //更新modelValue的值
  emit('update:modelValue', value)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>


//使用
<MyComponent v-model.capitalize="myText" />

带参数的 v-model 修饰符

又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + "Modifiers"

ini 复制代码
<MyComponent v-model:title.capitalize="myText">
arduino 复制代码
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }

透传Attributes

透传Attributes 指的是没有被该组件声明为 props 或 emits的attribute 或者 v-on 事件监听器,比如 class、id、style;如果组件是单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上

禁用 Attributes 继承

xml 复制代码
<script setup>
defineOptions({
  inheritAttrs: false
})
// ...setup 逻辑
</script>

透传的attribute可以用 $attrs访问到,同时别忘了设置inheritAttrs: false; 多个根节点的组件需要被显示绑定:

xml 复制代码
<div class="btn-wrapper">
  <button class="btn" v-bind="$attrs">click me</button>
  <span>Fallthrough attribute: {{ $attrs }}</span>
</div>

在js中访问

xml 复制代码
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

插槽

<slot> 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。

默认内容

在外部没有提供任何内容的情况下,可以为插槽指定默认内容,有内容的时候就会替代默认内容

插槽作用域

1、插槽内容可以访问到父组件的作用域 2、访问子组件的作用域需要子组件在渲染时将一部分数据提供给插槽

默认作用域插槽

xml 复制代码
<!-- <MyComponent> 的模板 -->
<div>
  //子组件通过props传递数据
  <slot :text="greetingMessage" :count="1"></slot>
</div>
arduino 复制代码
//父组件通过v-slot接收slotProps
<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

//or 解构slotProps
<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>

具名作用域插槽

v-slot:name="slotProps",缩写是#name

arduino 复制代码
<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>

  <template #default="defaultProps">
    {{ defaultProps }}
  </template>
</MyComponent>
xml 复制代码
<template>
	<div class="container">
		<slot name="header" title="header"></slot>
                <slot title="default"></slot>
	</div>
</template>

动态插槽名

xml 复制代码
<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- 缩写为 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>

依赖注入

provideinject可以解决prop逐级透传的问题

provide提供 格式是: provide(/* 注入名 */ 'message', /* 值 */ 'hello!') 注入名可以是字符串或者Symbol,第二个参数可以提供任意类型

建议使用 Symbol 作注入名,以避免潜在的冲突

csharp 复制代码
import { ref, provide } from 'vue'


const count = ref(0)
provide('key', count)

//使用symbol作为注入名
const myInjectionKey = Symbol()
provide(myInjectionKey, { /* 要提供的数据 */ });

注入

xml 复制代码
<script setup>
import { inject } from 'vue'

const message = inject('message','可以设置一个默认值')
</script>

通过调用一个工厂函数或初始化一个类来取得默认值(避免用不到的时候浪费) ,第三个参数表示默认值应该被当作一个工厂函数。

javascript 复制代码
const value = inject('key', () => new ExpensiveClass(), true)

和响应式数据配合使用

当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。

xml 复制代码
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
</script>
xml 复制代码
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>

如果想提供一个不被更改的值,readonly() 来包装提供的值

csharp 复制代码
<script setup>
import { ref, provide, readonly } from 'vue'

const count = ref(0)
provide('read-only-count', readonly(count))
</script>

异步组件

可以直接在父组件中直接定义它们:

javascript 复制代码
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */)
  })
})
// ... 像使用其他一般组件一样使用 `AsyncComp`


const AsyncComp = defineAsyncComponent(() => 
//ES模块动态导入也会返回一个 Promise
import('./components/MyComponent.vue') )

也可以实现全局注册

javascript 复制代码
app.component('MyComponent', defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
))

异步组件的一些配置

javascript 复制代码
const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () => import('./Foo.vue'),

  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间,默认为 200ms
  delay: 200,

  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

还可以搭配 Suspense 使用,稍后详解

相关推荐
崔庆才丨静觅几秒前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax