模板引用和组件基础(自定义事件,插槽,动态组件)


模板引用

虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref attribute:

<input ref="input">

ref 是一个特殊的 attribute,和 v-for 章节中提到的 key 类似。它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。这可能很有用,比如说在组件挂载时将焦点设置到一个 input 元素上,或在一个元素上初始化一个第三方库。

访问模板引用

为了通过组合式 API 获得该模板引用,我们需要声明一个同名的 ref:

<script setup>
import { ref, onMounted } from 'vue'

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问 input,在初次渲染时会是 null。这是因为在初次渲染前这个元素还不存在呢!
如果你需要侦听一个模板引用 ref 的变化,确保考虑到其值为 null 的情况:

watchEffect(()=>{
	if(input.value){
		input.value.focus()
		}else{
			//此时还未挂载,或此元素已经被卸载
			}
})

v-for 中的模板引用

当在 v-for 中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素:

<script setup>
import {ref,onMounted} from 'vue'
const list = ref([])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>
<template>
	<ul>
		<li v-for='item in list' ref='itemRefs '>
		{{item}}</li>
	</ul>
</template>

注意: ref 数组并不保证与源数组相同的顺序。

函数模板引用

除了使用字符串值作名字,ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数

<input :ref='(el)=>{/*将 el赋值给一个数据属性或 ref 变量 */}'>

注意: 这里使用的是 动态的 :ref 绑定才能传入一个函数或组件方法。

组件上的 ref

模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例:

<script setup>
 import { ref, onMounted } from 'vue'
 import Child from './Child.vue'
 const child = ref(null)
 onMounted(()=>{
	// child.value 是 <Child /> 组件的实例
})
</script>
<template>
	<Child ref='child' />
</template>

使用了

<script setup>
	import {ref} from 'vue'
	const a = 1
	const b = ref(2)
	defineExpost({
		a,b
		})
</script>

组件基础

组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
在这里插入图片描述
这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。Vue 同样也能很好地配合原生 Web Component

定义一个组件

当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件 (简称 SFC):

<script setup>
	import { ref } from 'vue'
	const count = ref(0)
<script>
<templat>
	<button ref='' @click='count++'>请尝试点击{{counnt}}</button>
</template>

当不使用构建步骤时,一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义:

import { ref } from 'vue'
export default {
	setup(){
		const count = ref(0)
		return { count }
	},
	template:`<button @click='count++'>{{count}}</button>`
}

使用组件

要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 ButtonCounter.vue 的文件中,这个组件将会以默认导出的形式被暴露给外部。

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

通过

每当点击这些按钮时,每一个组件都维护着自己的状态,是不同的 count。这是因为每当你使用一个组件,就创建了一个新的实例

在单文件组件中,推荐为子组件使用 PascalCase 的标签名,以此来和原生的 HTML 元素作区分。虽然原生 HTML 标签名是不区分大小写的,但 Vue 单文件组件是可以在编译中区分大小写的。我们也可以使用 /> 来关闭一个标签。

如果你是直接在 DOM 中书写模板 (例如原生 元素的内容),模板的编译需要遵从浏览器中 HTML 的解析行为。在这种情况下,你应该需要使用 kebab-case 形式并显式地关闭这些组件的标签。

传递 props

场景:如果我们正在构建一个博客,我们可能需要一个表示博客文章的组件。我们希望所有的博客文章分享相同的视觉布局,但有不同的内容。要实现这样的效果自然必须向组件中传递数据,例如每篇文章标题和内容,这就会使用到 props

Props 是一种特别的 attributes,你可以在组件上声明注册。要传递给博客文章组件一个标题,我们必须在组件的 props 列表上声明它。这里要用到 defineProps 宏:

<template>
	<h4>{{title}}</h4>
</template>
<script setup>
 defineProps(['title'])
</script>

defineProps 是一个仅

const props = defineProps(['title'])
console.log(props.title)

如果你没有使用

export default{
	props:['title'],
	setup(props){
		console.log(props.title)
	}
}

一个组件可以有任意多的 props,默认情况下,所有 prop 都接受任意类型的值。

当一个 prop 被注册后,可以像这样以自定义 attribute 的形式传递数据给它:

<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

在实际应用中,我们可能在父组件中会有如下的一个博客文章数组:

const posts = ref([
	{id: 1, title: 'My journey with Vue'},
	{id: 2, title: 'Blogging with Vue'},
	{id: 3, title: 'Why Vue is so fun'},
])

这种情况下,我们可以使用 v-for 来渲染它们:

<BlogPost v-for='post in posts' :key='post.id' :title='post.title'></BlogPost>

监听事件(组件自定义事件)

我们会发现有时候它需要与父组件进行交互。
场景:将博客文章的文字能够放大,而页面的其余部分仍使用默认字号。

在父组件中,我们可以添加一个 postFontSize ref 来实现这个效果:

const posts = ref([
  /* ... */
])
const postFontSize = ref(1)

在模板中用它来控制所有博客文章的字体大小:

<div :style="{ fontSize: postFontSize + 'em' }">
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
   />
</div>

然后,给 组件添加一个按钮:

<!-- BlogPost.vue,-->
<template>
	 <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

这个按钮目前还没有做任何事情,我们想要点击这个按钮来告诉父组件它应该放大所有博客文章的文字。要解决这个问题,组件实例提供了一个自定义事件系统。父组件可以通过 v-on@ 来选择性地监听子组件上抛的事件,就像监听原生 DOM 事件那样:

<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件:

<!-- BlogPost.vue, 省略了 <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

因为有了 @enlarge-text="postFontSize += 0.1" 的监听,父组件会接收这一事件,从而更新 postFontSize 的值

我们可以通过 defineEmits 宏来声明需要抛出的事件:

<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

如果你没有在使用

export default{
	emits:[],
	setup(props,ctx){
		ctx.emit('enlarge-text')
	}
}

通过插槽来分配内容

一些情况下我们会希望能和 HTML 元素一样向组件中传递内容:

<AlertBox>
  Something bad happened.
</AlertBox>

我们期望能渲染成这样:

在这里插入图片描述
可以通过 Vue 的自定义 <slot> 元素来实现:

<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

使用 作为一个占位符,父组件传递进来的内容就会渲染在这里。

动态组件

有些场景会需要在两个组件间来回切换,比如 Tab 界面:

在这里插入图片描述
上面的例子是通过 Vue 的 元素和特殊的 is attribute 实现的

<component :is="tabs[currentTab]" class="tab">

在上面的例子中,被传给 :is 的值可以是以下几种:

  • 被注册的组件名
  • 导入的组件对象

DOM 模板解析注意事项

如果你想在 DOM 中直接书写 Vue 模板,Vue 则必须从 DOM 中获取模板字符串。由于浏览器的原生 HTML 解析行为限制,有一些需要注意的事项。

大小写区分

HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。这意味着当你使用 DOM 内的模板时,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式:

// JavaScript 中的 camelCase
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
  <!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>
}

闭合标签

这是因为 Vue 的模板解析器支持任意标签使用 /> 作为标签关闭的标志。

然而在 DOM 模板中,我们必须显式地写出关闭标签:

元素位置限制

某些 HTML 元素对于放在其中的元素类型有限制,例如

    1. , 和 ,相应的,某些元素仅在放置于特定元素中时才会显示,例如
    2. 和 。

这将导致在使用带有此类限制元素的组件时出现问题。例如:

<table>
  <blog-post-row></blog-post-row>
</table>

自定义的组件 将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is attribute 作为一种解决方案:

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

当使用在原生 HTML 元素上时,is 的值必须加上前缀 vue: 才可以被解析为一个 Vue 组件。这一点是必要的,为了避免和原生的自定义内置元素相混淆。

exact 准确的
parse Float 分析浮点值
on Mounted 已安装的
on Updated 更新时间
on Unmounted 未安装时
immediate: true 立即:真
watchEffect watch效果
emit 发出
defineEmits 定义发射
camel Case
pascalCase

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值