vue3-组件

组件基础

有了组件,我们就可以将ui变成独立且可重用的部分。

定义组件

当使用构建步骤时,一般会把vue定义在一个单独的.vue文件,也就是单文件组件

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

使用组件

要使用子组件,需要先在父组件中导入它。导入后的组件在模板中都直接可用

js 复制代码
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

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

也可以通过全局注册组件,使它不需要注册就可以在任何组件上使用

  • 每个组件都维护着自己的状态,每当使用一个新组件,等于创建了一个新的实例,互不影响

传递props

如果我们在开发一个blog,希望每篇文章的视觉布局一样,但内容不一样,也就是需要向组件传递数据,这时就会用到props

props 是一种特别的attributes。作为要传递给组件的数据,我们必须在组件的props列表上声明它,defineProps()表示这个组件可以接收哪些数据

js 复制代码
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

声明的props会自动暴露给模板,defineProps会返回一个对象,其中包含了可以传递给组件的所有props:

js 复制代码
const props = defineProps(['title'])
console.log(props.title)

如果没有使用<script setup>,props必须以props选项的方式声明,props会作为setup()函数的第一个参数被传入

js 复制代码
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

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

js 复制代码
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

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

js 复制代码
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传入子组件:

js 复制代码
<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

子组件接收并渲染:

js 复制代码
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>
  • 注意这里:title="post.title"v-bind = "post.title"的缩写,v-bind可以把数据动态绑定到HTML属性或组件props上 ,使html属性 = JS变量

监听事件

让我们回到<BlogPost>组件,有时候它也需要与父组件进行交互,例如要实现无障碍访问的需求,将文章文字放大,而页面其他部分仍使用默认字号

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

js 复制代码
const posts = ref([
  /* ... */
])

const postFontSize = ref(1)

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

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

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

js 复制代码
<!-- 省略了 <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

我们想要通过点击这个按钮,告诉父组件它应该放大所有博客文章的文字。

要实现这个问题,可以通过组件实例提供的自定义事件系统。父组件可以通过v-on@来选择性监听子组件上抛的事件,就像监听原生dom事件一样:

js 复制代码
<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />
  • 此处@enlarge-text="postFontSize += 0.1"v-on:enlarge-text = "postFontSize += 0.1"的缩写,即监听enlarge-text事件,当事件发生后会执行postFontSize += 0.1

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

js 复制代码
<!-- 省略了 <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()来声明:

js 复制代码
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

defineProps类似,defineEmits 仅可用于 <script setup> 之中,并且不需要导入

通过插槽来分配内容

有时我们会希望能和 HTML 元素一样向组件中传递内容:

html 复制代码
<AlertBox>
  Something bad happened.
</AlertBox>

我们期望能渲染成这样:

这可以通过vue的自定义<slot>元素来实现:

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

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

也就是使用<slot />作为占位符,父组件传递来的内容会渲染在这里

app.vue

js 复制代码
<script setup>
import AlertBox from './AlertBox.vue'
</script>

<template>
	<AlertBox>
  	Something bad happened.
	</AlertBox>
</template>

AlertBox.vue

js 复制代码
<template>
  <div class="alert-box">
    <strong>Error!</strong>
    <br/>
    <slot />
  </div>
</template>

动态组件

有些场景会需要在两个组件之间来回切换,比如Tab界面,通常是通过vue的<component>元素和特殊的is的attribute

js 复制代码
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

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

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

当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 <KeepAlive> 组件强制被切换掉的组件仍然保持"存活"的状态。

App.vue

js 复制代码
<script setup>
import Home from './Home.vue'
import Posts from './Posts.vue'
import Archive from './Archive.vue'
const currentTab = ref('Home')

const tabs = {
  Home,
  Posts,
  Archive
}
</script>




  <div class="demo">
    <button
       v-for="(_, tab) in tabs"
       :key="tab"
       :class="['tab-button', { active: currentTab === tab }]"
       @click="currentTab = tab"
     >
      {{ tab }}
    </button>
	  <component :is="tabs[currentTab]" class="tab"></component>
  </div>
相关推荐
10share1 小时前
100行代码 模拟实现Vue 响应式系统
前端·vue.js
用户4099322502125 小时前
Vue状态管理入门第四章:组合式store和SSR风险
前端·vue.js·后端
锋行天下1 天前
半秒开!还有谁!!!
前端·vue.js·架构
JING小白1 天前
Day 1 重学Vue:响应式系统的“底层逻辑”变更,Vue2旧时代的终结与Vue3新时代的开启
前端·vue.js
OpenTiny社区1 天前
从零开发 AI 聊天页要两周?试试这款 Vue3 垂直对话组件库 TinyRobot,直接开箱即用
前端·vue.js·github
Cobyte1 天前
22.Vue Vapor 组件 props 的实现
前端·javascript·vue.js
白雾茫茫丶1 天前
探索 Nuxt.js 全栈能力:用 Better-Auth 打造类型安全的 RBAC 权限系统
前端·vue.js·nuxt.js
向阳而生6601 天前
文件上传也能玩出花?Vue3 教你优雅实现“选择文件”和“选择文件夹”🚀
vue.js
3630458411 天前
Signal 带来的架构问题思考
前端·vue.js