重学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 使用,稍后详解

相关推荐
_.Switch1 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光1 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   1 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   1 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web1 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常1 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇2 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr2 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho3 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常4 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js