vue笔记(三)组合式函数、slot插槽、无组件渲染

文章目录

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>