文章目录
vue笔记(三)组合式函数、插槽
slot插槽
插槽机制主要是为了复用。
主要有两种插槽方式
1、默认插槽 # 推荐就是一个,可以有多个slot,但是只有最后一个生效
2、具名插槽 # 可以有多个
插槽示例
机制:
标签中的内容替换填充到slot标签位置。
1、创建components/MyCard.vue
html
<!-- src/components/MyCard.vue -->
<template>
<div class="card-box">
<slot></slot> <!-- 这个坑用来接收外面塞进来的内容 -->
</div>
</template>
<style scoped>
.card-box {
border: 1px solid #eee;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
2、创建views/CourseList.vue
html
<!-- src/views/CourseList.vue -->
<template>
<div>
<!-- 第一次使用:塞入 Vue3 课程 -->
<MyCard>
<h3>Vue3 零基础入门</h3>
<p>价格:99元</p>
</MyCard>
<!-- 第二次使用:塞入 Java 课程 -->
<MyCard>
<h3>Java 进阶架构</h3>
<p>价格:199元</p>
</MyCard>
</div>
</template>
<script setup>
// 1. 必须先把模具"拿"过来
import MyCard from '@/components/MyCard.vue';
</script>
3、index.js配置路由并访问
js
{
path: '/courseListView',
name: 'CourseListView',
component: CourseListView
},
然后访问地址:
http://localhost:5173/courseListView # courseListView是自定义的路径名
具名插槽示例
机制:
有具名的内容填到具名插槽内
没具名的内容填到默认插槽内
1、创建components/MultiMyCard.vue
html
<template>
<div class="card-box">
<!-- 坑1:头部区域 -->
<div class="card-header">
<slot name="header">默认标题</slot>
</div>
<!-- 坑2:正文区域(默认插槽) -->
<div class="card-body">
<slot></slot>
</div>
<!-- 坑3:底部操作区 -->
<div class="card-footer">
<slot name="footer">默认按钮</slot>
</div>
</div>
</template>
<style scoped>
.card-box {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 300px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.card-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
}
.card-body {
color: #666;
margin-bottom: 15px;
min-height: 50px;
}
.card-footer {
text-align: right;
padding-top: 10px;
border-top: 1px solid #eee;
}
</style>
2、创建views/MultiMyCardShowViews.vue
html
<template>
<div class="page-container">
<h2>今日推荐课程</h2>
<div class="card-list">
<!-- 第一张卡片 -->
<MultiMyCard>
<!-- 填坑1:头部 -->
<template #header>
🚀 Vue3 零基础入门
</template>
<!-- 填坑2:正文(默认坑,直接写内容) -->
<p>带你从环境搭建一路学到项目实战,新手必学!</p>
<!-- 填坑3:底部按钮 -->
<template #footer>
<button class="btn-primary">99元 立即抢购</button>
</template>
</MultiMyCard>
<!-- 第二张卡片 -->
<MultiMyCard>
<!-- 填坑1:头部 -->
<template #header>
☕ Java 进阶架构
</template>
<!-- 填坑2:正文 -->
<p>深入理解微服务、高并发与底层源码,架构师进阶之路。</p>
<!-- 填坑3:底部按钮 -->
<template #footer>
<button class="btn-primary">199元 立即抢购</button>
</template>
</MultiMyCard>
</div>
</div>
</template>
<script setup>
// 1. 引入我们刚才写的组件
import MultiMyCard from '@/components/MultiMyCard.vue';
</script>
<style scoped>
.page-container {
padding: 20px;
}
.card-list {
display: flex;
gap: 20px; /* 卡片之间留点间距 */
margin-top: 15px;
}
.btn-primary {
background-color: #409eff;
color: white;
border: none;
padding: 6px 15px;
border-radius: 4px;
cursor: pointer;
}
.btn-primary:hover {
background-color: #66b1ff;
}
</style>
3、index.js配置路由并访问
js
{
path: '/multiMyCardShowViews',
name: 'MultiMyCardShowViews',
component: MultiMyCardShowViews
},
然后访问:
http://localhost:5173/multiMyCardShowViews
一个vue里面只能有一个slot吗?
分默认插槽还是具名插槽。
默认插槽可以有多个,但是只有最后一个生效,所以所默认插槽推荐只有一个。
具名插槽可以有多个,通过名字来区分,也可以和默认插槽组合使用。
组合式函数
组合式函数的核心就是逻辑复用。
组合式函数示例
1、创建composables/useCounter.js
规则:
函数名必须use开头,内部用Vue响应式API,最后return状态/方法
js
// useCounter.js
import { ref } from 'vue'
// 最简组合式函数
export function useCounter(init = 0) {
// 响应式数字
const count = ref(init)
// 操作方法
const add = () => count.value++
const sub = () => count.value--
// 对外暴露变量和方法
return {
count,
add,
sub
}
}
2、创建views/CounterTestView.vue
html
<template>
<div>
<p>数字:{{ count }}</p>
<button @click="add">+1</button>
<button @click="sub">-1</button>
</div>
</template>
<script setup>
// 导入组合式函数
import { useCounter } from '../composables/useCounter'
// 调用一次,生成独立的计数器逻辑
const { count, add, sub } = useCounter(10)
</script>
3、index.js配置路由并访问
js
{
path: '/counterTestView',
name: 'CounterTestView',
component: CounterTestView
},
阿里组合式函数
js
// useFetch.js - 一个典型的阿里云开发者社区分享的组合式函数示例
import { ref, onMounted } from 'vue';
export function useFetch(url) {
const data = ref(null);
const loading = ref(true);
const error = ref(null);
onMounted(async () => {
try {
const response = await fetch(url);
data.value = await response.json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
});
return { data, loading, error };
}
组合式函数和普通函数的区别
| 维度 | 普通JS函数(函数套用) | Vue组合式函数(useXXX) |
|---|---|---|
| 能否使用ref/watch/生命周期 | ❌不可以 | ✅完全支持 |
| 变量是否响应式、驱动页面更新 | ❌普通变量,无响应 | ✅ref自动响应视图 |
| 调用位置 | 任意地方无限制 | 仅setup顶层,不能放点击/定时器 |
| 状态持久 | 函数执行完变量销毁 | 状态绑定组件,长期保存 |
| 适用场景 | 纯计算、工具方法 | 页面交互、带DOM/生命周期逻辑 |
| 互相嵌套 | 随便嵌套无限制 | 允许互相嵌套,但仍遵守setup调用规则 |
无组件渲染
1. 无渲染组件FetchData.vue
html
<template>
<!-- 无渲染组件:不输出任何 DOM,只通过插槽传递数据 -->
<slot
:data="data"
:loading="loading"
:error="error"
:refresh="refresh"
/>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 定义 props:接收请求地址和请求参数
const props = defineProps({
url: {
type: String,
required: true
},
params: {
type: Object,
default: () => ({})
},
immediate: {
type: Boolean,
default: true
}
})
const data = ref(null)
const loading = ref(false)
const error = ref(null)
// 请求方法
const fetchData = async () => {
loading.value = true
error.value = null
try {
const res = await fetch(props.url, {
method: 'POST',
body: JSON.stringify(props.params),
headers: { 'Content-Type': 'application/json' }
})
data.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
// 刷新方法(暴露给父组件调用)
const refresh = () => {
fetchData()
}
// 立即执行
if (props.immediate) {
onMounted(fetchData)
}
// 暴露给父组件
defineExpose({ refresh })
</script>
2. 父组件使用方式(一):渲染成表格
html
<template>
<div>
<h2>用户列表(表格形式)</h2>
<FetchData url="/api/users">
<!-- 通过插槽拿数据,自由决定如何渲染 -->
<template #default="{ data, loading, error, refresh }">
<div v-if="loading">加载中...</div>
<div v-else-if="error">出错了:{{ error }}</div>
<table v-else>
<thead>
<tr><th>ID</th><th>姓名</th><th>邮箱</th></tr>
</thead>
<tbody>
<tr v-for="user in data" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
</tbody>
</table>
<button @click="refresh">刷新</button>
</template>
</FetchData>
</div>
</template>
<script setup>
import FetchData from '@/components/FetchData.vue'
</script>
3. 父组件使用方式(二):渲染成下拉选择框
html
<template>
<div>
<h2>请选择用户</h2>
<FetchData url="/api/users">
<template #default="{ data, loading, error }">
<select v-if="!loading && !error">
<option v-for="user in data" :key="user.id" :value="user.id">
{{ user.name }}
</option>
</select>
<span v-else-if="loading">加载选项...</span>
<span v-else-if="error">加载失败</span>
</template>
</FetchData>
</div>
</template>
<script setup>
import FetchData from '@/components/FetchData.vue'
</script>
4、另一种写法:使用 useSlots 判断是否有插槽
html
<template>
<!-- 默认渲染(无插槽时显示原始数据) -->
<pre v-if="!$slots.default">{{ data }}</pre>
<!-- 有插槽时走插槽 -->
<slot
v-else
:data="data"
:loading="loading"
:error="error"
:refresh="refresh"
/>
</template>