跳转到内容

vue3相关知识点4.0

vue3如何使用Supabase Auth方法

在 Vue 3 中使用 Supabase Auth 可以实现用户身份验证功能,包括注册、登录、注销、会话管理等。Supabase 是一个开源的 Firebase 替代品,提供了数据库、身份验证、存储等功能。

以下是 Vue 3 中使用 Supabase Auth 的详细步骤和示例。

  1. 安装 Supabase
npm install @supabase/supabase-js
  1. 初始化 Supabase 客户端

在项目中初始化 Supabase 客户端,需要提供 Supabase 项目的 URL 和公钥。

// src/utils/supabaseClient.js
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = 'https://your-supabase-url.supabase.co';
const supabaseKey = 'your-supabase-public-key';
export const supabase = createClient(supabaseUrl, supabaseKey);
  1. 实现用户注册

使用 supabase.auth.signUp 方法实现用户注册。

<template>
  <div>
    <input v-model="email" placeholder="Email" />
    <input v-model="password" type="password" placeholder="Password" />
    <button @click="signUp">Sign Up</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { supabase } from '@/utils/supabaseClient';

const email = ref('');
const password = ref('');

const signUp = async () => {
  const { user, error } = await supabase.auth.signUp({
    email: email.value,
    password: password.value,
  });

  if (error) {
    console.error('Sign up error:', error.message);
  } else {
    console.log('Signed up:', user);
  }
};
</script>
  1. 实现用户登录

使用 supabase.auth.signIn 方法实现用户登录。

<template>
  <div>
    <input v-model="email" placeholder="Email" />
    <input v-model="password" type="password" placeholder="Password" />
    <button @click="signIn">Sign In</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { supabase } from '@/utils/supabaseClient';

const email = ref('');
const password = ref('');

const signIn = async () => {
  const { user, error } = await supabase.auth.signIn({
    email: email.value,
    password: password.value,
  });

  if (error) {
    console.error('Sign in error:', error.message);
  } else {
    console.log('Signed in:', user);
  }
};
</script>
  1. 实现用户注销

使用 supabase.auth.signOut 方法实现用户注销。

<template>
  <button @click="signOut">Sign Out</button>
</template>

<script setup>
import { supabase } from '@/utils/supabaseClient';

const signOut = async () => {
  const { error } = await supabase.auth.signOut();

  if (error) {
    console.error('Sign out error:', error.message);
  } else {
    console.log('Signed out');
  }
};
</script>
  1. 获取当前用户会话

使用 supabase.auth.session() 获取当前用户的会话信息。

import { supabase } from '@/utils/supabaseClient';

const session = supabase.auth.session();
console.log('Current session:', session);
  1. 监听身份验证状态变化

通过 supabase.auth.onAuthStateChange 监听用户身份验证状态的变化。

import { supabase } from '@/utils/supabaseClient';

supabase.auth.onAuthStateChange((event, session) => {
  console.log('Auth state changed:', event, session);
});
  1. 保护路由

在 Vue Router 中,可以通过导航守卫保护需要身份验证的路由。

import { createRouter, createWebHistory } from 'vue-router';
import { supabase } from '@/utils/supabaseClient';

const routes = [
  { path: '/', component: Home },
  { path: '/login', component: Login },
  { path: '/dashboard', component: Dashboard, meta: { requiresAuth: true } },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach((to, from, next) => {
  const requiresAuth = to.matched.some((record) => record.meta.requiresAuth);
  const session = supabase.auth.session();

  if (requiresAuth && !session) {
    next('/login');
  } else {
    next();
  }
});

export default router;

总结

在 Vue 3 中使用 Supabase Auth 可以实现以下功能:

  • 用户注册:通过 supabase.auth.signUp 实现。

  • 用户登录:通过 supabase.auth.signIn 实现。

  • 用户注销:通过 supabase.auth.signOut 实现。

  • 获取会话:通过 supabase.auth.session() 获取当前用户会话。

  • 监听状态:通过 supabase.auth.onAuthStateChange 监听身份验证状态变化。

  • 保护路由:通过 Vue Router 的导航守卫保护需要身份验证的路由。

通过 Supabase Auth,可以快速实现用户身份验证功能,提升应用的安全性和用户体验。

使用vue3+ts+axios+pinia实现无感刷新

在 Vue 3 项目中,结合 TypeScript、Axios 和 Pinia 实现 无感刷新(Token 自动刷新) 是一种常见的需求。无感刷新的核心逻辑是:当用户的访问令牌(Access Token)过期时,自动使用刷新令牌(Refresh Token)获取新的访问令牌,而用户无需重新登录。

以下是实现无感刷新的详细步骤和代码示例。

  1. 项目初始化

确保项目已经安装以下依赖:

  • Vue 3

  • TypeScript

  • Axios

  • Pinia

  1. 创建 Pinia Store

使用 Pinia 管理用户的认证状态和令牌。

创建 Auth Store

// src/stores/authStore.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useAuthStore = defineStore('auth', () => {
  const accessToken = ref<string | null>(null);
  const refreshToken = ref<string | null>(null);

  // 设置令牌
  const setTokens = (newAccessToken: string, newRefreshToken: string) => {
    accessToken.value = newAccessToken;
    refreshToken.value = newRefreshToken;
  };

  // 清除令牌
  const clearTokens = () => {
    accessToken.value = null;
    refreshToken.value = null;
  };

  return {
    accessToken,
    refreshToken,
    setTokens,
    clearTokens,
  };
});
  1. 配置 Axios 实例

创建一个 Axios 实例,并添加请求拦截器和响应拦截器,用于自动刷新令牌。

// src/utils/axiosInstance.ts
import axios from 'axios';
import { useAuthStore } from '@/stores/authStore';

const axiosInstance = axios.create({
  baseURL: 'https://api.example.com',
});

// 请求拦截器:添加 Access Token
axiosInstance.interceptors.request.use((config) => {
  const authStore = useAuthStore();
  if (authStore.accessToken) {
    config.headers.Authorization = `Bearer ${authStore.accessToken}`;
  }
  return config;
});

// 响应拦截器:处理 Token 过期
axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    // 如果响应状态码为 401 且未重试过
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      const authStore = useAuthStore();
      if (authStore.refreshToken) {
        try {
          // 使用 Refresh Token 获取新的 Access Token
          const response = await axios.post('/auth/refresh', {
            refreshToken: authStore.refreshToken,
          });

          // 更新 Access Token
          authStore.setTokens(response.data.accessToken, authStore.refreshToken);

          // 重试原始请求
          originalRequest.headers.Authorization = `Bearer ${response.data.accessToken}`;
          return axiosInstance(originalRequest);
        } catch (refreshError) {
          // 刷新 Token 失败,清除令牌并跳转到登录页
          authStore.clearTokens();
          window.location.href = '/login';
          return Promise.reject(refreshError);
        }
      }
    }

    return Promise.reject(error);
  }
);

export default axiosInstance;
  1. 实现登录和刷新 Token 的逻辑

在登录和刷新 Token 的逻辑中,更新 Pinia Store 中的令牌。

登录逻辑

// src/api/auth.ts
import axiosInstance from '@/utils/axiosInstance';
import { useAuthStore } from '@/stores/authStore';

export const login = async (email: string, password: string) => {
  const response = await axiosInstance.post('/auth/login', { email, password });
  const authStore = useAuthStore();
  authStore.setTokens(response.data.accessToken, response.data.refreshToken);
};

刷新 Token 逻辑

// src/api/auth.ts
export const refreshToken = async () => {
  const authStore = useAuthStore();
  const response = await axiosInstance.post('/auth/refresh', {
    refreshToken: authStore.refreshToken,
  });
  authStore.setTokens(response.data.accessToken, authStore.refreshToken);
};
  1. 在组件中使用

在登录组件中调用登录逻辑,并在需要认证的请求中使用 Axios 实例。

<template>
  <div>
    <input v-model="email" placeholder="Email" />
    <input v-model="password" type="password" placeholder="Password" />
    <button @click="handleLogin">Login</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { login } from '@/api/auth';

const email = ref('');
const password = ref('');

const handleLogin = async () => {
  await login(email.value, password.value);
};
</script>

获取用户数据

<template>
  <div>
    <h1>User Profile</h1>
    <p>{{ userData }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import axiosInstance from '@/utils/axiosInstance';

const userData = ref(null);

onMounted(async () => {
  const response = await axiosInstance.get('/user/profile');
  userData.value = response.data;
});
</script>

总结

通过结合 Vue 3、TypeScript、Axios 和 Pinia,可以实现无感刷新功能,具体步骤如下:

  1. 创建 Pinia Store:管理用户的认证状态和令牌。

  2. 配置 Axios 实例:添加请求拦截器和响应拦截器,自动刷新 Token。

  3. 实现登录和刷新 Token 逻辑:更新 Pinia Store 中的令牌。

  4. 在组件中使用:调用登录逻辑和认证请求。

通过无感刷新,可以提升用户体验,避免频繁重新登录。

vue3中的路由和vue router4有哪些变化

Vue 3 和 Vue Router 4 带来了一些重要的变化和改进,以下是主要的变化:

  1. 创建路由实例的变化
  • Vue 2 + Vue Router 3:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);

const router = new VueRouter({
  routes: [...]
});

Vue 3 + Vue Router 4:

import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
  history: createWebHistory(),
  routes: [...]
});
  1. 路由模式的变化
  • Vue Router 3 使用 mode 选项来定义路由模式:
const router = new VueRouter({
  mode: 'history',
  routes: [...]
});
  • Vue Router 4 使用 createWebHistorycreateWebHashHistorycreateMemoryHistory 来定义路由模式:
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';

const router = createRouter({
  history: createWebHistory(), // 或者 createWebHashHistory()
  routes: [...]
});
  1. 路由守卫的变化
  • Vue Router 3 中的全局守卫:
router.beforeEach((to, from, next) => {
  // ...
  next();
});
  • Vue Router 4 中的全局守卫:
router.beforeEach((to, from) => {
  // ...
  // 不再需要显式调用 next()
});
  • Vue Router 4 移除了 next 参数,守卫函数可以返回 falseundefined{ path: '...' }{ name: '...' } 来控制导航行为。
  1. 路由组件的变化
  • Vue Router 3 中,路由组件通过 this.$routethis.$router 访问路由信息和导航。

  • Vue Router 4 中,推荐使用 useRouteuseRouter 组合式 API:

import { useRoute, useRouter } from 'vue-router';

const route = useRoute();
const router = useRouter();

// 访问路由信息
console.log(route.path);

// 导航
router.push('/new-path');
  1. 路由匹配的变化
  • Vue Router 4 引入了更强大的路由匹配语法,支持正则表达式和自定义匹配器。

  • Vue Router 4 移除了 * 通配符路由,改为使用 /:pathMatch(.*)*/:pathMatch(.*) 来捕获所有路由。

  1. 路由别名和重定向的变化
  • Vue Router 4 中,别名和重定向的语法保持不变,但内部实现有所优化。

  • 重定向示例:

const routes = [
  { path: '/home', redirect: '/' }
];
  • 别名示例:
const routes = [
  { path: '/', alias: '/home', component: Home }
];
  1. 路由懒加载的变化
  • Vue Router 3Vue Router 4 都支持路由懒加载,但 Vue Router 4 推荐使用 defineAsyncComponent
const LazyComponent = defineAsyncComponent(() => import('./LazyComponent.vue'));

const routes = [
  { path: '/lazy', component: LazyComponent }
];
  1. 路由元信息的变化
  • Vue Router 4 中,路由元信息的使用方式与 Vue Router 3 相同,但可以通过 useRoute 访问:
const routes = [
  {
    path: '/protected',
    component: ProtectedComponent,
    meta: { requiresAuth: true }
  }
];

router.beforeEach((to) => {
  if (to.meta.requiresAuth) {
    // 检查用户权限
  }
});
  1. 路由嵌套的变化
  • Vue Router 4 中,嵌套路由的语法与 Vue Router 3 相同,但内部实现有所优化。

  • 嵌套路由示例:

const routes = [
  {
    path: '/parent',
    component: ParentComponent,
    children: [
      { path: 'child', component: ChildComponent }
    ]
  }
];
  1. 路由滚动行为的变化
  • Vue Router 4 中,滚动行为的配置方式与 Vue Router 3 相同:
const router = createRouter({
  history: createWebHistory(),
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    } else {
      return { top: 0 };
    }
  }
});
  1. 路由错误处理的变化
  • Vue Router 4 引入了 onError 方法来处理导航错误:
router.onError((error) => {
  console.error('Navigation error:', error);
});
  1. 路由动态添加的变化
  • Vue Router 4 中,动态添加路由的方式与 Vue Router 3 相同:
router.addRoute({
  path: '/new-route',
  component: NewComponent
});
  1. 路由移除的变化
  • Vue Router 4 引入了 removeRoute 方法来移除路由:
const removeRoute = router.addRoute({
  path: '/new-route',
  component: NewComponent
});

removeRoute(); // 移除路由
  1. 路由命名视图的变化
  • Vue Router 4 中,命名视图的语法与 Vue Router 3 相同:
const routes = [
  {
    path: '/',
    components: {
      default: DefaultComponent,
      sidebar: SidebarComponent
    }
  }
];
  1. 路由过渡动画的变化
  • Vue Router 4 中,过渡动画的使用方式与 Vue Router 3 相同,但推荐使用 transition 组件:
<router-view v-slot="{ Component }">
  <transition name="fade">
    <component :is="Component" />
  </transition>
</router-view>

总结

Vue Router 4 在 Vue 3 的基础上进行了许多优化和改进,提供了更简洁的 API 和更强大的功能。虽然一些 API 发生了变化,但整体使用方式与 Vue Router 3 相似,迁移过程相对平滑。

vue3中的Fragment是什么

在 Vue 3 中,Fragment 是一个重要的新特性,它允许组件模板中包含多个根节点,而无需包裹一个额外的父元素。这个特性解决了 Vue 2 中必须有一个单一根节点的限制。

Fragment 的优势

  • 更简洁的模板:无需额外的包裹元素,模板更加简洁。

  • 更好的语义化:某些场景下,额外的包裹元素是没有意义的(如表格中的多个 <tr>)。

  • 减少 DOM 层级:避免不必要的 DOM 元素嵌套,提升性能。

Fragment 的实现原理

Vue 3 在编译阶段会将多个根节点包裹在一个虚拟的 Fragment 节点中。这个 Fragment 节点不会渲染为实际的 DOM 元素,而是作为一个逻辑容器存在。

例如,以下模板:

<template>
  <h1>标题</h1>
  <p>内容</p>
</template>

会被编译为:

import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue";

export function render(_ctx, _cache) {
  return (_openBlock(),
  _createBlock(Fragment, null, [
    _createVNode("h1", null, "标题"),
    _createVNode("p", null, "内容")
  ]))
}
  • Fragment 是一个虚拟节点,不会渲染为实际的 DOM 元素。

  • 多个根节点会被包裹在 Fragment 中。

Fragment 的注意事项

  • 样式问题:如果多个根节点需要共享样式,可能需要额外的处理(如使用 CSS 类或全局样式)。

  • 过渡动画:在使用 <transition><transition-group> 时,需要确保只有一个根节点。

  • 工具支持:某些工具(如 Vue Devtools)可能会将 Fragment 显示为一个虚拟节点。

总结

  • Vue 3 的 Fragment 特性允许组件模板中有多个根节点,解决了 Vue 2 中必须有一个单一根节点的限制。

  • Fragment 不会渲染为实际的 DOM 元素,而是作为一个逻辑容器存在。

  • 使用 Fragment 可以使模板更简洁、语义化更好,同时减少不必要的 DOM 层级。

vue3中watch和watcheffect有何不同

在 Vue 3 中,watch 和 watchEffect 都是用于监听响应式数据变化的 API,但它们的用途和行为有一些关键区别。以下是它们的详细对比:

  1. 功能概述
  • watch

    • 用于监听特定的响应式数据源,并在数据变化时执行回调函数。

    • 可以监听单个或多个数据源。

    • 提供旧值和新值。

  • watchEffect

    • 自动追踪其回调函数中使用的响应式数据,并在这些数据变化时重新执行回调。

    • 不需要显式指定监听的数据源。

    • 不提供旧值,只关注当前值。

  1. 使用方式

2.1 watch

import { ref, watch } from "vue";

const count = ref(0);

watch(count, (newValue, oldValue) => {
  console.log(`count 从 ${oldValue} 变为 ${newValue}`);
});

// 修改 count
count.value++;

特点:

  • 需要显式指定监听的数据源(如 count)。

  • 回调函数接收两个参数:newValueoldValue

2.2 watchEffect

import { ref, watchEffect } from "vue";

const count = ref(0);

watchEffect(() => {
  console.log(`count 的值是 ${count.value}`);
});

// 修改 count
count.value++;

特点:

  • 不需要显式指定监听的数据源,自动追踪回调函数中使用的响应式数据。

  • 回调函数没有参数,无法直接获取旧值。

  1. 使用场景

3.1 watch 的适用场景

  • 需要监听特定数据的变化,默认不立即执行。

  • 需要获取变化前后的值(newValueoldValue)。

  • 需要控制监听的行为(如 immediatedeep 等选项)。

3.2 watchEffect 的适用场景

  • 需要自动追踪多个响应式数据的变化。

  • 不需要旧值,只关注当前值。

  • 需要立即执行副作用。

  1. 高级用法

4.1 watchdeep 选项

如果需要监听对象或数组的内部变化,可以使用 deep: true

const state = reactive({ user: { name: "Alice" } });

watch(
  () => state.user,
  (newValue, oldValue) => {
    console.log("user 发生变化");
  },
  { deep: true } // 深度监听
);

4.2 watchEffect 的清理副作用

watchEffect 的回调函数可以返回一个清理函数,用于在重新运行副作用之前执行清理操作。

watchEffect((onCleanup) => {
  const timer = setTimeout(() => {
    console.log("执行副作用");
  }, 1000);

  onCleanup(() => {
    clearTimeout(timer); // 清理副作用
  });
});
  1. 总结
  • watch

    • 适合监听特定数据源,提供旧值和新值。

    • 支持 immediatedeep 等选项。

    • 适用于需要精确控制监听行为的场景。

  • watchEffect

    • 自动追踪依赖,无需显式指定数据源。

    • 默认立即执行,适合执行副作用。

    • 适用于需要自动追踪多个响应式数据的场景。

根据具体需求选择合适的 API:

  • 如果需要监听特定数据并获取旧值,使用 watch

  • 如果需要自动追踪依赖并执行副作用,使用 watchEffect

vue3中如何处理条件渲染

在 Vue 3 中,条件渲染主要通过以下指令实现:v-ifv-elsev-else-ifv-show。这些指令可以根据条件动态控制元素的显示或隐藏。以下是详细的使用方法和最佳实践:

  1. v-ifv-elsev-else-if

基本用法

  • v-if:根据条件渲染或销毁元素。

  • v-else:必须跟在 v-ifv-else-if 后面,表示“否则”逻辑。

  • v-else-if:用于链式条件判断。

<template>
  <div>
    <p v-if="score >= 90">优秀</p>
    <p v-else-if="score >= 60">及格</p>
    <p v-else>不及格</p>
  </div>
</template>

<script setup>
const score = 85; // 假设分数为 85
</script>

特点:

  • 元素销毁与重建v-if 是“惰性”的,条件为 false 时元素会被销毁,条件为 true 时重新渲染。

  • 适用于复杂场景:适合切换频率较低的场景(如权限控制、动态加载组件)。

  1. v-show

基本用法

  • v-show:根据条件切换元素的 display CSS 属性(隐藏时设为 display: none)。

  • 无论条件如何,元素始终保留在 DOM 中。

特点:

  • 性能开销小:适合频繁切换显示状态的场景(如弹窗、菜单)。

  • 不支持 <template> 标签:只能用于单个元素。

  1. v-if vs v-show
特性v-ifv-show
DOM 操作条件为 false 时销毁元素始终保留元素,切换 display 属性
初始渲染开销高(条件为 false 时无需渲染)低(无论条件如何都会渲染)
切换开销高(需要重建 DOM)低(仅修改 CSS)
适用场景切换频率低、条件可能不变切换频率高
  1. <template> 上使用条件渲染

Vue 3 支持在 <template> 标签上使用 v-if,以控制多个元素的渲染。

<template>
  <template v-if="isAdmin">
    <h1>管理员面板</h1>
    <p>敏感操作区域</p>
  </template>
  <template v-else>
    <h1>普通用户面板</h1>
    <p>常规操作区域</p>
  </template>
</template>

<script setup>
const isAdmin = false; // 假设当前用户不是管理员
</script>
  1. 结合 key 避免元素复用

当 Vue 复用相同类型的元素时(如切换登录/注册表单),可能保留用户输入。通过 key 属性可以强制重新渲染元素。

<template>
  <div>
    <template v-if="isLogin">
      <input key="login" placeholder="用户名">
    </template>
    <template v-else>
      <input key="register" placeholder="邮箱">
    </template>
    <button @click="isLogin = !isLogin">切换表单</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
const isLogin = ref(true);
</script>
  1. 条件渲染与组合式 API

在组合式 API 中,可以结合 refreactive 管理条件状态,使逻辑更清晰。

动态权限控制

<template>
  <div>
    <button v-if="userPermissions.includes('edit')">编辑</button>
    <button v-if="userPermissions.includes('delete')">删除</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const userPermissions = ref(['edit']); // 假设用户有编辑权限
</script>

异步加载内容

<template>
  <div>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">加载失败: {{ error }}</div>
    <div v-else>{{ data }}</div>
  </div>
</template>

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

const loading = ref(true);
const error = ref(null);
const data = ref(null);

onMounted(async () => {
  try {
    data.value = await fetchData(); // 模拟异步请求
  } catch (err) {
    error.value = err.message;
  } finally {
    loading.value = false;
  }
});

async function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('数据加载成功'), 1000);
  });
}
</script>

总结

  • v-if:适合条件可能不变或切换频率低的场景(元素销毁重建)。

  • v-show:适合频繁切换显示状态的场景(仅修改 CSS)。

  • key 属性:避免元素复用问题。

  • 组合式 API:结合 refreactive 管理条件状态,提升代码可维护性。

使用vue.js插件封装粘性元素组件

在 Vue.js 中,封装一个 粘性元素(Sticky Element) 组件是一个常见的需求。粘性元素通常用于实现导航栏、侧边栏等组件在页面滚动时固定在某个位置的效果。

以下是使用 Vue.js 插件封装粘性元素组件的详细步骤和代码示例。

  1. 创建粘性元素组件

首先,创建一个粘性元素组件,使用 position: sticky 实现粘性效果。

<!-- src/components/StickyElement.vue -->
<template>
  <div :class="['sticky-element', { 'is-sticky': isSticky }]" :style="stickyStyle">
    <slot></slot>
  </div>
</template>

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

const props = defineProps({
  offsetTop: {
    type: Number,
    default: 0,
  },
});

const isSticky = ref(false);
const stickyStyle = ref({});

const checkSticky = () => {
  const element = document.querySelector('.sticky-element');
  if (element) {
    const rect = element.getBoundingClientRect();
    isSticky.value = rect.top <= props.offsetTop;
    stickyStyle.value = {
      top: `${props.offsetTop}px`,
    };
  }
};

onMounted(() => {
  window.addEventListener('scroll', checkSticky);
});

onUnmounted(() => {
  window.removeEventListener('scroll', checkSticky);
});
</script>

<style scoped>
.sticky-element {
  transition: all 0.3s ease;
}

.is-sticky {
  position: sticky;
  z-index: 100;
}
</style>
  1. 将组件封装为插件

将粘性元素组件封装为 Vue.js 插件,方便全局注册和使用。

// src/plugins/stickyElement.js
import StickyElement from '@/components/StickyElement.vue';

export default {
  install(app) {
    // 全局注册组件
    app.component('StickyElement', StickyElement);
  },
};
  1. 在项目中使用插件

在项目的入口文件中安装插件,并在组件中使用粘性元素组件。

// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import stickyElement from './plugins/stickyElement';

const app = createApp(App);
app.use(stickyElement);
app.mount('#app');

使用粘性元素组件

<!-- src/App.vue -->
<template>
  <div>
    <header>
      <StickyElement :offsetTop="50">
        <nav>
          <a href="#">Home</a>
          <a href="#">About</a>
          <a href="#">Contact</a>
        </nav>
      </StickyElement>
    </header>
    <main>
      <div v-for="i in 100" :key="i">Content {{ i }}</div>
    </main>
  </div>
</template>

<script setup>
import StickyElement from '@/components/StickyElement.vue';
</script>
  1. 优化粘性元素组件

为了提升组件的灵活性和性能,可以添加以下优化:

  • 支持动态偏移量:通过 props 动态设置 offsetTop

  • 性能优化:使用 requestAnimationFrame 优化滚动事件监听。

<!-- src/components/StickyElement.vue -->
<template>
  <div :class="['sticky-element', { 'is-sticky': isSticky }]" :style="stickyStyle">
    <slot></slot>
  </div>
</template>

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

const props = defineProps({
  offsetTop: {
    type: Number,
    default: 0,
  },
});

const isSticky = ref(false);
const stickyStyle = ref({});

let rafId = null;

const checkSticky = () => {
  if (rafId) return;

  rafId = requestAnimationFrame(() => {
    const element = document.querySelector('.sticky-element');
    if (element) {
      const rect = element.getBoundingClientRect();
      isSticky.value = rect.top <= props.offsetTop;
      stickyStyle.value = {
        top: `${props.offsetTop}px`,
      };
    }
    rafId = null;
  });
};

onMounted(() => {
  window.addEventListener('scroll', checkSticky);
});

onUnmounted(() => {
  window.removeEventListener('scroll', checkSticky);
});

watch(
  () => props.offsetTop,
  () => {
    checkSticky();
  }
);
</script>

<style scoped>
.sticky-element {
  transition: all 0.3s ease;
}

.is-sticky {
  position: sticky;
  z-index: 100;
}
</style>

总结

通过封装粘性元素组件并将其注册为 Vue.js 插件,可以实现以下目标:

  • 全局使用:在任何组件中直接使用 <StickyElement>

  • 灵活配置:通过 props 动态设置偏移量。

  • 性能优化:使用 requestAnimationFrame 优化滚动事件监听。

这种封装方式不仅提升了代码的复用性,还增强了组件的灵活性和性能,是 Vue.js 开发中的一种最佳实践。

更多vue相关插件及后台管理模板可访问vue admin reference,代码详情请访问github