vue3相关知识点4.0
vue3如何使用Supabase Auth方法
在 Vue 3 中使用 Supabase Auth 可以实现用户身份验证功能,包括注册、登录、注销、会话管理等。Supabase 是一个开源的 Firebase 替代品,提供了数据库、身份验证、存储等功能。
以下是 Vue 3 中使用 Supabase Auth 的详细步骤和示例。
- 安装 Supabase
npm install @supabase/supabase-js
- 初始化 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);
- 实现用户注册
使用 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>
- 实现用户登录
使用 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>
- 实现用户注销
使用 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>
- 获取当前用户会话
使用 supabase.auth.session()
获取当前用户的会话信息。
import { supabase } from '@/utils/supabaseClient';
const session = supabase.auth.session();
console.log('Current session:', session);
- 监听身份验证状态变化
通过 supabase.auth.onAuthStateChange
监听用户身份验证状态的变化。
import { supabase } from '@/utils/supabaseClient';
supabase.auth.onAuthStateChange((event, session) => {
console.log('Auth state changed:', event, session);
});
- 保护路由
在 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)获取新的访问令牌,而用户无需重新登录。
以下是实现无感刷新的详细步骤和代码示例。
- 项目初始化
确保项目已经安装以下依赖:
Vue 3
TypeScript
Axios
Pinia
- 创建 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,
};
});
- 配置 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;
- 实现登录和刷新 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);
};
- 在组件中使用
在登录组件中调用登录逻辑,并在需要认证的请求中使用 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,可以实现无感刷新功能,具体步骤如下:
创建 Pinia Store:管理用户的认证状态和令牌。
配置 Axios 实例:添加请求拦截器和响应拦截器,自动刷新 Token。
实现登录和刷新 Token 逻辑:更新 Pinia Store 中的令牌。
在组件中使用:调用登录逻辑和认证请求。
通过无感刷新,可以提升用户体验,避免频繁重新登录。
vue3中的路由和vue router4有哪些变化
Vue 3 和 Vue Router 4 带来了一些重要的变化和改进,以下是主要的变化:
- 创建路由实例的变化
- 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: [...]
});
- 路由模式的变化
- Vue Router 3 使用
mode
选项来定义路由模式:
const router = new VueRouter({
mode: 'history',
routes: [...]
});
- Vue Router 4 使用
createWebHistory
、createWebHashHistory
和createMemoryHistory
来定义路由模式:
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(), // 或者 createWebHashHistory()
routes: [...]
});
- 路由守卫的变化
- Vue Router 3 中的全局守卫:
router.beforeEach((to, from, next) => {
// ...
next();
});
- Vue Router 4 中的全局守卫:
router.beforeEach((to, from) => {
// ...
// 不再需要显式调用 next()
});
- Vue Router 4 移除了
next
参数,守卫函数可以返回false
、undefined
、{ path: '...' }
或{ name: '...' }
来控制导航行为。
- 路由组件的变化
Vue Router 3 中,路由组件通过
this.$route
和this.$router
访问路由信息和导航。Vue Router 4 中,推荐使用
useRoute
和useRouter
组合式 API:
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
// 访问路由信息
console.log(route.path);
// 导航
router.push('/new-path');
- 路由匹配的变化
Vue Router 4 引入了更强大的路由匹配语法,支持正则表达式和自定义匹配器。
Vue Router 4 移除了
*
通配符路由,改为使用/:pathMatch(.*)*
或/:pathMatch(.*)
来捕获所有路由。
- 路由别名和重定向的变化
Vue Router 4 中,别名和重定向的语法保持不变,但内部实现有所优化。
重定向示例:
const routes = [
{ path: '/home', redirect: '/' }
];
- 别名示例:
const routes = [
{ path: '/', alias: '/home', component: Home }
];
- 路由懒加载的变化
- Vue Router 3 和 Vue Router 4 都支持路由懒加载,但 Vue Router 4 推荐使用
defineAsyncComponent
:
const LazyComponent = defineAsyncComponent(() => import('./LazyComponent.vue'));
const routes = [
{ path: '/lazy', component: LazyComponent }
];
- 路由元信息的变化
- Vue Router 4 中,路由元信息的使用方式与 Vue Router 3 相同,但可以通过
useRoute
访问:
const routes = [
{
path: '/protected',
component: ProtectedComponent,
meta: { requiresAuth: true }
}
];
router.beforeEach((to) => {
if (to.meta.requiresAuth) {
// 检查用户权限
}
});
- 路由嵌套的变化
Vue Router 4 中,嵌套路由的语法与 Vue Router 3 相同,但内部实现有所优化。
嵌套路由示例:
const routes = [
{
path: '/parent',
component: ParentComponent,
children: [
{ path: 'child', component: ChildComponent }
]
}
];
- 路由滚动行为的变化
- Vue Router 4 中,滚动行为的配置方式与 Vue Router 3 相同:
const router = createRouter({
history: createWebHistory(),
routes: [...],
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { top: 0 };
}
}
});
- 路由错误处理的变化
- Vue Router 4 引入了
onError
方法来处理导航错误:
router.onError((error) => {
console.error('Navigation error:', error);
});
- 路由动态添加的变化
- Vue Router 4 中,动态添加路由的方式与 Vue Router 3 相同:
router.addRoute({
path: '/new-route',
component: NewComponent
});
- 路由移除的变化
- Vue Router 4 引入了
removeRoute
方法来移除路由:
const removeRoute = router.addRoute({
path: '/new-route',
component: NewComponent
});
removeRoute(); // 移除路由
- 路由命名视图的变化
- Vue Router 4 中,命名视图的语法与 Vue Router 3 相同:
const routes = [
{
path: '/',
components: {
default: DefaultComponent,
sidebar: SidebarComponent
}
}
];
- 路由过渡动画的变化
- 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,但它们的用途和行为有一些关键区别。以下是它们的详细对比:
- 功能概述
watch
:用于监听特定的响应式数据源,并在数据变化时执行回调函数。
可以监听单个或多个数据源。
提供旧值和新值。
watchEffect
:自动追踪其回调函数中使用的响应式数据,并在这些数据变化时重新执行回调。
不需要显式指定监听的数据源。
不提供旧值,只关注当前值。
- 使用方式
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
)。回调函数接收两个参数:
newValue
和oldValue
。
2.2 watchEffect
import { ref, watchEffect } from "vue";
const count = ref(0);
watchEffect(() => {
console.log(`count 的值是 ${count.value}`);
});
// 修改 count
count.value++;
特点:
不需要显式指定监听的数据源,自动追踪回调函数中使用的响应式数据。
回调函数没有参数,无法直接获取旧值。
- 使用场景
3.1 watch
的适用场景
需要监听特定数据的变化,默认不立即执行。
需要获取变化前后的值(
newValue
和oldValue
)。需要控制监听的行为(如
immediate
、deep
等选项)。
3.2 watchEffect
的适用场景
需要自动追踪多个响应式数据的变化。
不需要旧值,只关注当前值。
需要立即执行副作用。
- 高级用法
4.1 watch
的 deep
选项
如果需要监听对象或数组的内部变化,可以使用 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); // 清理副作用
});
});
- 总结
watch
:适合监听特定数据源,提供旧值和新值。
支持
immediate
和deep
等选项。适用于需要精确控制监听行为的场景。
watchEffect
:自动追踪依赖,无需显式指定数据源。
默认立即执行,适合执行副作用。
适用于需要自动追踪多个响应式数据的场景。
根据具体需求选择合适的 API:
如果需要监听特定数据并获取旧值,使用
watch
。如果需要自动追踪依赖并执行副作用,使用
watchEffect
。
vue3中如何处理条件渲染
在 Vue 3 中,条件渲染主要通过以下指令实现:v-if
、v-else
、v-else-if
和 v-show
。这些指令可以根据条件动态控制元素的显示或隐藏。以下是详细的使用方法和最佳实践:
v-if
、v-else
、v-else-if
基本用法
v-if
:根据条件渲染或销毁元素。v-else
:必须跟在v-if
或v-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
时重新渲染。适用于复杂场景:适合切换频率较低的场景(如权限控制、动态加载组件)。
v-show
基本用法
v-show
:根据条件切换元素的display
CSS 属性(隐藏时设为display: none
)。无论条件如何,元素始终保留在 DOM 中。
特点:
性能开销小:适合频繁切换显示状态的场景(如弹窗、菜单)。
不支持
<template>
标签:只能用于单个元素。
v-if
vsv-show
特性 | v-if | v-show |
---|---|---|
DOM 操作 | 条件为 false 时销毁元素 | 始终保留元素,切换 display 属性 |
初始渲染开销 | 高(条件为 false 时无需渲染) | 低(无论条件如何都会渲染) |
切换开销 | 高(需要重建 DOM) | 低(仅修改 CSS) |
适用场景 | 切换频率低、条件可能不变 | 切换频率高 |
- 在
<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>
- 结合
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>
- 条件渲染与组合式 API
在组合式 API 中,可以结合 ref
或 reactive
管理条件状态,使逻辑更清晰。
动态权限控制
<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:结合
ref
或reactive
管理条件状态,提升代码可维护性。
使用vue.js插件封装粘性元素组件
在 Vue.js 中,封装一个 粘性元素(Sticky Element) 组件是一个常见的需求。粘性元素通常用于实现导航栏、侧边栏等组件在页面滚动时固定在某个位置的效果。
以下是使用 Vue.js 插件封装粘性元素组件的详细步骤和代码示例。
- 创建粘性元素组件
首先,创建一个粘性元素组件,使用 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>
- 将组件封装为插件
将粘性元素组件封装为 Vue.js 插件,方便全局注册和使用。
// src/plugins/stickyElement.js
import StickyElement from '@/components/StickyElement.vue';
export default {
install(app) {
// 全局注册组件
app.component('StickyElement', StickyElement);
},
};
- 在项目中使用插件
在项目的入口文件中安装插件,并在组件中使用粘性元素组件。
// 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>
- 优化粘性元素组件
为了提升组件的灵活性和性能,可以添加以下优化:
支持动态偏移量:通过
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