vue3笔记(2)自用

目录

一、作用域插槽

二、pinia的使用

[一、Pinia 基本概念与用法](#一、Pinia 基本概念与用法)

[1. 安装与初始化](#1. 安装与初始化)

[2. 创建 Store](#2. 创建 Store)

[3. 在组件中使用 Store](#3. 在组件中使用 Store)

[4. 高级用法](#4. 高级用法)

5、storeToRefs

[二、Pinia 与 Vuex 的主要区别](#二、Pinia 与 Vuex 的主要区别)

[三、为什么选择 Pinia?](#三、为什么选择 Pinia?)

三、定义全局指令

[1.封装通用 DOM 操作,复用逻辑](#1.封装通用 DOM 操作,复用逻辑)

2.增强模板的声明式编程能力

3.解耦组件逻辑,保持组件简洁

4.便于统一维护和扩展

四、指令钩子

[1. 钩子函数列表及含义](#1. 钩子函数列表及含义)

[2. 核心参数说明(以 mounted(el, binding, vnode, prevVnode) 为例)](#2. 核心参数说明(以 mounted(el, binding, vnode, prevVnode) 为例))

[3. 实际场景示例(用 v-focus 指令理解钩子)](#3. 实际场景示例(用 v-focus 指令理解钩子))

[4. 为什么需要指令钩子?](#4. 为什么需要指令钩子?)

五、图片懒加载

六、路由传参

[1、Vue 2 获取路由参数](#1、Vue 2 获取路由参数)

[1. 动态路由参数(路径中的参数,如 /user/:id)](#1. 动态路由参数(路径中的参数,如 /user/:id))

[2. 查询参数(URL 中的 ?key=value)](#2. 查询参数(URL 中的 ?key=value))

[2、Vue 3 获取路由参数](#2、Vue 3 获取路由参数)

[1. 动态路由参数](#1. 动态路由参数)

[2. 查询参数](#2. 查询参数)

对比

补充说明

总结

七、路由

1、默认请求参数

2、路由缓存问题

解决方法

一、作用域插槽

复制代码
<template>
  <div class="app">
    <el-table :data=list>
      <el-table-column label="ID" prop="id"></el-table-column>
      <el-table-column label="姓名" prop="name" width="150"></el-table-column>
      <el-table-column label="籍贯" prop="place"></el-table-column>
      <el-table-column label="操作" width="150">
        <template #default="{row}">//作用域插槽
          <el-button type="primary"  @click="updateData(row.id)" link>编辑</el-button>
          <el-button type="danger" @click="deleteData(row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>
  • #default="{row}":这是 作用域插槽 (scoped slot),row 代表当前行的数据对象。
  • 每个操作按钮都能通过 row 访问当前行的所有属性(如 row.idrow.name)。

二、pinia的使用

一、Pinia 基本概念与用法

Pinia 是 Vue.js 的新一代状态管理库,由 Vue 核心团队开发,旨在替代 Vuex。它提供了更简洁的 API、更好的 TypeScript 支持和更小的体积。

1. 安装与初始化

复制代码
npm install pinia

// main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const pinia = createPinia();
const app = createApp(App);

app.use(pinia);
app.mount('#app');

或者

复制代码
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

const pinia = createPinia()
createApp(App).use(pinia).mount('#app')

2. 创建 Store

复制代码
// stores/counter.js
import { defineStore } from 'pinia';

// 使用组合式 API 风格
export const useCounterStore = defineStore('counter', () => {
  // 状态
  const count = ref(0);
  
  // 计算属性
  const doubleCount = computed(() => count.value * 2);
  
  // 方法
  const increment = () => {
    count.value++;
  };
  
  const reset = () => {
    count.value = 0;
  };
  
  return { count, doubleCount, increment, reset };
});

// 或使用选项式 API 风格(类似 Vuex)
export const useUserStore = defineStore({
  id: 'user',
  state: () => ({
    name: '张三',
    age: 20
  }),
  getters: {
    fullName: (state) => `用户: ${state.name}`
  },
  actions: {
    updateName(newName) {
      this.name = newName;
    }
  }
});

3. 在组件中使用 Store

复制代码
<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">+1</button>
    <button @click="counter.reset">重置</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '../stores/counter';

// 获取 store 实例
const counter = useCounterStore();

// 直接访问 state
console.log(counter.count);

// 调用 action
counter.increment();
</script>

4. 高级用法

复制代码
// 订阅 state 变化
const unsubscribe = counter.$subscribe((mutation, state) => {
  console.log('State changed:', mutation.type, state);
});

// 重置 state
counter.$reset();

// 批量修改 state
counter.$patch({
  count: 100,
  // 其他属性
});

// 插件示例
const loggerPlugin = (context) => {
  console.log('Store initialized:', context.store.$id);
  return {
    // 扩展 store
    hello: 'world'
  };
};

pinia.use(loggerPlugin);

5、storeToRefs

storeToRefs 是 Pinia 提供的一个辅助函数,用于从 store 中提取状态并保持其响应式特性。它解决了在解构 store 时丢失响应式的问题,让你可以更优雅地在组件中使用 store 状态。

  • 问题 :直接解构 store 对象(如 const { count } = store)会丢失响应式。

  • 解决方案storeToRefs 将 store 中的状态转换为响应式引用(refs),保持响应式更新。

    // ❌ 错误:解构后丢失响应式
    const { count } = store;

    // ✅ 转换为响应式引用
    const { count } = storeToRefs(store);

storeToRefs用于响应式数据、coumputed。对于方法,直接解构即可。

二、Pinia 与 Vuex 的主要区别

特性 Pinia Vuex (4.x)
API 风格 组合式 API 为主,支持选项式 仅支持选项式 API
模块化方式 自动模块,无嵌套结构 需要手动定义模块
TypeScript 支持 一流支持,无需额外配置 需要额外配置,类型定义复杂
体积 更小(~1kb) 较大(~2kb)
Mutation 无 Mutation,直接修改 state 必须通过 Mutation 修改 state
代码结构 更简洁,更少样板代码 更多样板代码(state/getters/mutations/actions)
插件系统 更灵活 相对固定
DevTools 支持 更现代,支持时间旅行调试 支持时间旅行调试
Vue 版本支持 Vue 2 和 Vue 3 Vue 2 和 Vue 3

三、为什么选择 Pinia?

  1. 更简洁的 API

    • 无需 mapStatemapActions 等辅助函数。
    • 直接通过 store.counter 访问状态,store.increment() 调用方法。
  2. 更好的 TypeScript 支持

    • 自动类型推导,无需手动定义接口。
    • 组合式 API 天然支持类型安全。
  3. 无 Mutation

    • 移除了 Vuex 中冗余的 Mutation 概念,直接在 Action 中修改 state。

三、定义全局指令

Vue 本身有像 v-ifv-showv-model 这类内置指令,方便开发者操作 DOM、处理数据渲染等。而全局指令,是开发者借助 Vue 提供的机制,自己定义的、可在整个 Vue 应用里通用的指令。

它基于 Vue 的指令系统拓展而来,注册后,在所有组件的模板中,都能用 v-指令名 的形式(比如定义了全局指令 v-focus,就可以在任意组件 <input v-focus /> 这样用 )去调用,来实现特定功能。

1.封装通用 DOM 操作,复用逻辑

Vue 组件主要聚焦数据和界面的映射,但有些频繁的 DOM 底层操作(如元素自动聚焦、自定义滚动行为、表单输入格式化等 ),若在每个组件里重复写,既冗余又难维护。全局指令可以把这类操作封装起来,一处定义,处处使用。

比如定义一个 v-focus 全局指令,实现元素挂载后自动获取焦点:

复制代码
// Vue2 中全局指令注册(main.js 里)
Vue.directive('focus', {
  inserted: function (el) { 
    el.focus() 
  }
})

// Vue3 中全局指令注册(main.js 里)
const app = createApp(App)
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

之后在任意组件的输入框上用 <input v-focus />,就能自动聚焦,不用在每个组件里写获取焦点的逻辑。

2.增强模板的声明式编程能力

全局指令能让模板更简洁、语义化。

比如定义 v-permission 指令控制按钮权限:

复制代码
// 假设根据用户权限决定元素是否显示
app.directive('permission', {
  mounted(el, binding) {
    const hasPerm = checkPermission(binding.value) 
    if (!hasPerm) {
      el.parentNode && el.parentNode.removeChild(el) 
    }
  }
})

模板中用 <button v-permission="'delete'">删除</button>,语义清晰,一看就知道和权限控制有关,无需在组件里写一堆权限判断的 JS 逻辑。

3.解耦组件逻辑,保持组件简洁

把和 DOM 操作强相关的代码(比如复杂的动画初始化、第三方库初始化 )放到指令里,组件就不用关心这些细节,专注业务数据和基本渲染。

比如用指令初始化一个图表库:

复制代码
app.directive('chart', {
  mounted(el, binding) {
    const chart = new Chart(el, binding.value.config) 
    // 后续更新等逻辑也可在指令钩子处理
  }
})

组件里只需 <div v-chart="{ config: { /* 图表配置 */ } }"></div>,组件代码更简洁,职责更单一。

4.便于统一维护和扩展

当需要修改指令功能(如调整权限判断逻辑、优化聚焦的时机 ),只需改全局指令的定义,所有用到该指令的组件都会生效,不用逐个组件修改,大大提升了代码的可维护性。

四、指令钩子

在 Vue 中,指令钩子(Directive Hooks) 是指令定义对象里的一组生命周期函数,用于在指令绑定的 DOM 元素的不同生命周期阶段执行自定义逻辑。它们像 "钩子",能让你在元素从创建到销毁的各个关键节点介入,做一些 DOM 操作、数据处理或逻辑控制。

1. 钩子函数列表及含义

钩子函数 执行时机(Vue 3 场景) 典型用途
created 指令绑定到元素后,DOM 渲染前调用(元素还没插入 DOM 树) 可初始化一些与元素关联的 "纯数据逻辑",比如根据指令参数准备数据,或添加事件监听(但注意此时 DOM 可能不可操作)
beforeMount 元素即将插入 DOM 树前调用(元素已存在于虚拟 DOM,但未真实渲染到页面) 做一些 DOM 插入前的准备,比如根据指令动态修改元素属性(如 el.setAttribute ),但元素还没在页面可见
mounted 元素插入 DOM 树后调用(真实 DOM 已渲染,可操作) 最常用!比如操作 DOM(聚焦输入框 el.focus() )、初始化第三方库(基于 DOM 渲染图表、地图 )
beforeUpdate 元素所在组件更新前调用(组件数据变化,虚拟 DOM 准备对比更新,真实 DOM 还未改变) 可在更新前记录元素状态(比如旧的滚动位置、样式 ),用于更新后恢复或对比
updated 元素所在组件更新后调用(虚拟 DOM 对比完成,真实 DOM 已更新) 基于新的 DOM 状态做调整(比如重新计算元素尺寸、更新第三方库的配置 )
beforeUnmount 元素所在组件卸载前调用(组件即将销毁,DOM 还未移除) 清除定时器、移除自定义事件监听(避免内存泄漏),比如 el.removeEventListener
unmounted 元素所在组件卸载后调用(DOM 已从页面移除) 收尾工作,比如释放第三方库占用的资源、彻底清理与该元素相关的全局状态

2. 核心参数说明(以 mounted(el, binding, vnode, prevVnode) 为例)

  • el指令绑定的真实 DOM 元素 ,可直接操作(如 el.style.color = 'red' )。
  • binding :指令的绑定信息,包含:
    • binding.value:指令的参数值(如 v-my-directive="100" 里的 100 )。
    • binding.arg:指令的参数(如 v-my-directive:foo 里的 foo )。
    • binding.modifiers:指令的修饰符(如 v-my-directive.bar 里的 { bar: true } )。
  • vnode :Vue 生成的虚拟 DOM 节点(描述当前元素的虚拟结构,一般用于高级场景,如对比新旧虚拟节点差异 )。
  • prevVnode:上一次的虚拟 DOM 节点(仅在更新 / 卸载阶段存在,用于对比变化 )。

3. 实际场景示例(用 v-focus 指令理解钩子)

需求:定义一个 v-focus 指令,让输入框插入 DOM 后自动聚焦。

复制代码
<script setup>
import { createApp } from 'vue'

const app = createApp(App)

// 注册全局指令
app.directive('focus', {
  // 元素插入 DOM 后执行(此时可操作真实 DOM)
  mounted(el) {
    el.focus() // 让输入框自动聚焦
  }
})
</script>

<template>
  <!-- 使用指令 -->
  <input v-focus placeholder="自动聚焦的输入框" />
</template>

如果需要在组件更新后,根据条件重新聚焦,就可以用 updated 钩子:

复制代码
app.directive('focus', {
  mounted(el) {
    el.focus()
  },
  updated(el, binding) {
    // 如果指令参数为 true,更新后重新聚焦
    if (binding.value) {
      el.focus()
    }
  }
})

在模板中动态控制:

复制代码
<input v-focus="shouldFocus" placeholder="动态聚焦" />

4. 为什么需要指令钩子?

  • 精准控制 DOM 生命周期 :Vue 是数据驱动的框架,直接操作 DOM 容易和响应式逻辑冲突。指令钩子让你在 "安全" 的时机操作 DOM(比如 mounted 确保元素已渲染 )。
  • 复用 DOM 操作逻辑 :把聚焦、权限控制、第三方库初始化等逻辑封装到指令,避免在组件里重复写(比如 10 个输入框都要自动聚焦,用 v-focus 一行解决 )。
  • 解耦组件与 DOM 操作:组件只关心业务数据,DOM 操作细节交给指令,代码更简洁、职责更清晰。

总结:指令钩子是 Vue 指令在 DOM 不同生命周期阶段的 "切入点",让我们能优雅地操作 DOM、复用逻辑,同时避开直接操作 DOM 带来的风险,是 Vue 拓展 DOM 能力的核心机制之一。

五、图片懒加载

核心原理:图片进入视口区才发送资源请求

思路:定义一个全局自定义指令;利用vueUse的useIntersectionObserver判断元素是否进入视口;进入视口后再给img标签的src添加url地址

复制代码
//main.js

import { useIntersectionObserver } from '@vueuse/core'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.mount('#app')

// 定义全局指令
app.directive('pic-lazy',{
  mounted(el,binding){
    //el:指令绑定的元素
    //binding:指令对象
    console.log(el,binding)
    // 监听元素是否进入视口
    useIntersectionObserver(
      el,
      // 回调函数,isIntersecting 表示是否进入视口
      ([{ isIntersecting }]) => {
        if (isIntersecting) {
          // console.log('元素进入视口啦!')
          // 这里可以写进入视口后的逻辑,比如加载图片、统计曝光等
          // stop() // 如果只想监听一次,进入视口后可以停止监听
          el.src = binding.value
          console.log(isIntersecting)
        }
      },
      // 可选配置:设置进入视口的"占比"阈值(0 ~ 1),默认 0.1(即 10% 进入视口就算)
      { threshold: 0.5 }
    )
      }
})

对于图片标签,把原来的<img :src="......" alt="" /> 改为 <img v-pic-lazy="......" alt="" />

六、路由传参

1、Vue 2 获取路由参数

在 Vue 2 中,路由参数主要通过 $route 对象获取,支持动态路由参数查询参数

1. 动态路由参数 (路径中的参数,如 /user/:id

复制代码
// 路由配置
{
  path: '/user/:id',
  component: User
}

// 在组件中获取
export default {
  computed: {
    userId() {
      return this.$route.params.id; // 获取路径中的 :id 参数
    }
  },
  created() {
    console.log(this.$route.params.id); // 直接访问
  }
}

2. 查询参数 (URL 中的 ?key=value

复制代码
// URL: /user?name=john&age=20
export default {
  computed: {
    userName() {
      return this.$route.query.name; // 获取查询参数 name
    },
    userAge() {
      return this.$route.query.age;  // 获取查询参数 age
    }
  }
}

2、Vue 3 获取路由参数

Vue 3 的组合式 API(Composition API)改变了获取路由参数的方式,需要通过 useRoute 函数引入路由实例。

1. 动态路由参数

复制代码
import { useRoute } from 'vue-router';

export default {
  setup() {
    const route = useRoute();
    
    // 方式一:直接在 setup 中使用
    console.log(route.params.id);
    
    // 方式二:定义为响应式数据(推荐)
    const userId = computed(() => route.params.id);
    
    return {
      userId
    };
  }
}

2. 查询参数

复制代码
import { useRoute } from 'vue-router';

export default {
  setup() {
    const route = useRoute();
    
    // 获取查询参数
    const userName = computed(() => route.query.name);
    const userAge = computed(() => route.query.age);
    
    return {
      userName,
      userAge
    };
  }
}

对比

场景 Vue 2.x Vue 3.x
动态路由参数 this.$route.params.id const route = useRoute(); route.params.id
查询参数 this.$route.query.name const route = useRoute(); route.query.name
响应式处理 通过 watch 监听 $route 使用 computedwatch 监听 route

补充说明

  1. Vue 3 中仍可使用 $route

    在 Vue 3 的选项式 API(Options API)中,仍可通过 this.$route 访问路由参数,但组合式 API 推荐使用 useRoute

  2. 响应式处理

    • 在 Vue 3 中,路由参数变化时,需要使用 computedwatch 确保数据响应式更新:

      复制代码
      import { useRoute, watch } from 'vue-router';
      
      setup() {
        const route = useRoute();
        
        // 监听路由变化
        watch(() => route.params.id, (newId, oldId) => {
          console.log('ID 变化:', newId);
        });
      }
  3. 路由配置示例

    无论是 Vue 2 还是 Vue 3,路由配置中定义参数的方式相同:

    复制代码
    // 路由配置(Vue 2/Vue 3 通用)
    {
      path: '/user/:id', // 动态路由参数
      name: 'User',
      component: User
    }

总结

  • Vue 2 :依赖 this.$route 对象,在 createdcomputed 等选项中使用。
  • Vue 3 :通过 useRoute() 函数获取路由实例,在组合式 API 的 setup 函数中使用,更灵活且类型安全。

七、路由

1、默认请求参数

下面这个接口默认传的值为1

复制代码
function getNewthingAPI({params={}}){
  const {distributionSite='1'}=params
  return httpInstance({
    url:'/home/new',
    params:{distributionSite}
  })
}

调用时可以对这个值进行指定

复制代码
getBannerAPI( {params: { distributionSite: '2' }})

如果不指定,就使用默认值1

复制代码
getBannerAPI()

2、路由缓存问题

路由缓存问题通常指在单页应用( Vue、React)中,切换路由后组件状态未重置或数据未更新,主要原因包括:

  • 组件复用机制 :路由切换时,若新旧路由使用同一组件(如动态路由 /:id),框架可能复用该组件实例,导致组件内的数据、生命周期钩子(如 createdmounted)不重新执行,状态保留旧值。
  • 缓存策略设置 :主动使用缓存配置(如 Vue 的 <keep-alive>)时,未正确配置缓存范围,导致不需要缓存的组件被缓存,数据未刷新。
  • 数据依赖未更新 :组件数据依赖路由参数,但未监听路由参数变化,参数改变后未触发数据重新获取(如 Vue 中未使用 watchonBeforeRouteUpdate 监听 $route.params)。
  • 异步数据加载时机 :数据请求放在 mounted 等只执行一次的钩子中,路由切换后组件复用,钩子不再触发,导致新数据未加载。

解决方法

  1. 禁用不必要的缓存

    若无需缓存组件,移除 <keep-alive> 或通过 exclude 排除特定组件:

    复制代码
    <!-- Vue中排除不需要缓存的组件 -->
    <keep-alive exclude="DetailPage">
      <router-view />
    </keep-alive>
  2. 监听路由参数变化

    在复用组件中,监听路由参数变化并重新加载数据:

    复制代码
    // 方法1:使用watch监听$route
    watch: {
      $route(to, from) {
        if (to.params.id !== from.params.id) {
          this.loadData(to.params.id); // 重新加载数据
        }
      }
    }
    
    // 方法2:使用导航守卫
    onBeforeRouteUpdate((to) => {
      this.loadData(to.params.id);
    });
  3. 强制组件重新渲染

    通过 key 使组件实例重新创建:

    复制代码
    <!-- Vue中给router-view添加key,确保路由变化时重新渲染 -->
    <router-view :key="$route.fullPath" />

    $route.fullPath 包含完整路由信息,参数变化时 key 改变,触发组件重新挂载。

  4. 调整数据加载时机

    将数据请求放在每次路由进入时都执行的钩子中,onMounted 结合watch监听。

  5. 清除缓存数据

    在组件离开时手动重置状态(如 Vue 的 onBeforeUnmount 或 React 的 useEffect 清理函数):

    复制代码
    // Vue中离开组件时重置数据
    onBeforeUnmount() {
      this.data = null;
    }
相关推荐
Pedantic2 分钟前
SwiftUI 按钮Button:完整教程
前端
前端拿破轮4 分钟前
2025年了,你还不知道怎么在vscode中直接调试TypeScript文件?
前端·typescript·visual studio code
代码的余温6 分钟前
DOM元素添加技巧全解析
前端
JSON_L9 分钟前
Vue 电影导航组件
前端·javascript·vue.js
用户214118326360217 分钟前
01-开源版COZE-字节 Coze Studio 重磅开源!保姆级本地安装教程,手把手带你体验
前端
大模型真好玩31 分钟前
深入浅出LangChain AI Agent智能体开发教程(四)—LangChain记忆存储与多轮对话机器人搭建
前端·人工智能·python
帅夫帅夫1 小时前
深入理解 JWT:结构、原理与安全隐患全解析
前端
Struggler2811 小时前
google插件开发:如何开启特定标签页的sidePanel
前端
爱编程的喵1 小时前
深入理解JSX:从语法糖到React的魔法转换
前端·react.js
代码的余温1 小时前
CSS3文本阴影特效全攻略
前端·css·css3