前端小食堂 | Day13 - Vue.js 进阶烹饪术
🚀 今日主菜:高效开发の秘密武器
1. 动态组件の七十二变
<template>
<!-- 🎭 魔法变形术 -->
<component
:is="currentComponent"
:key="componentKey"
class="animated-component"
/>
<!-- 控制按钮 -->
<button @click="toggleComponent">
切换形态 🔄
</button>
</template>
<script setup>
import { shallowRef } from 'vue';
const components = [ComponentA, ComponentB, ComponentC];
const currentIndex = ref(0);
const componentKey = ref(0);
// 强制刷新组件
const toggleComponent = () => {
currentIndex.value = (currentIndex.value + 1) % components.length;
componentKey.value++; // 通过改变key强制重渲染
};
const currentComponent = shallowRef(components[0]);
</script>
<style>
.animated-component {
transition: opacity 0.3s;
}
</style>
🔔 核心技巧:
- 使用
shallowRef
避免不必要的深度响应 - 通过改变
key
强制组件重置状态 - 搭配 CSS 过渡实现平滑切换
2. 指令の魔法卷轴
// 📜 点击外部关闭指令
const clickOutside = {
mounted(el, { value: callback }) {
el._clickHandler = e => {
if (!el.contains(e.target)) callback();
};
document.addEventListener('click', el._clickHandler);
},
unmounted(el) {
document.removeEventListener('click', el._clickHandler);
}
};
// 使用示例
<div v-click-outside="closeMenu">...</div>
// 🎨 元素跟随指令
const followCursor = {
updated(el, { value: enable }) {
if (enable) {
document.addEventListener('mousemove', el._follow = (e) => {
el.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`;
});
} else {
document.removeEventListener('mousemove', el._follow);
}
}
};
3. 组合式APIの炼金术
// 🧪 智能搜索逻辑复用
export function useSearch(fetcher: (keyword: string) => Promise<Item[]>) {
const keyword = ref('');
const results = ref<Item[]>([]);
const loading = ref(false);
watchDebounced(keyword, async (val) => {
if (!val.trim()) return;
loading.value = true;
try {
results.value = await fetcher(val);
} finally {
loading.value = false;
}
}, { debounce: 300 });
return { keyword, results, loading };
}
// 使用示例
const { keyword, results, loading } = useSearch(async (kw) => {
const { data } = await axios.get('/api/search', { params: { q: kw } });
return data.items;
});
❄️ 冷知识:TypeScript 类型魔法
// 🛡️ 安全的 provide/inject
import type { InjectionKey } from 'vue';
interface ThemeConfig {
primaryColor: string;
darkMode: boolean;
}
const themeKey = Symbol() as InjectionKey<ThemeConfig>;
// 提供端
provide(themeKey, {
primaryColor: '#1890ff',
darkMode: false
});
// 注入端
const theme = inject(themeKey, {
primaryColor: '默认颜色',
darkMode: false
});
🌟 实验室黑科技
实现动态主题切换器
<template>
<div :style="themeStyle">
<button @click="toggleTheme">
{{ isDark ? '☀️ 光明' : '🌙 黑暗' }}
</button>
<slot />
</div>
</template>
<script setup lang="ts">
import { computed, reactive } from 'vue';
const state = reactive({
isDark: false,
colors: {
light: { bg: '#fff', text: '#333' },
dark: { bg: '#1a1a1a', text: '#eee' }
}
});
const themeStyle = computed(() => ({
backgroundColor: state.colors[state.isDark ? 'dark' : 'light'].bg,
color: state.colors[state.isDark ? 'dark' : 'light'].text,
transition: 'all 0.3s ease'
}));
const toggleTheme = () => {
state.isDark = !state.isDark;
};
// 暴露切换方法给其他组件
defineExpose({ toggleTheme });
</script>
明日秘技:《Vue 3 性能调优の火焰掌——内存泄漏排查指南》 🔥
(留言告诉我你遇到的组件复用难题,本魔法导师为你定制解决方案!🔮)
🛎️ 本日避坑指南:
- 内存泄漏三大元凶
// 🚨 未清理的定时器
const timer = setInterval(() => {}, 1000);
onUnmounted(() => clearInterval(timer));
// 🚨 未解绑的事件监听
window.addEventListener('resize', handleResize);
onUnmounted(() => window.removeEventListener('resize', handleResize));
// 🚨 未释放的第三方库实例
const editor = new RichEditor();
onUnmounted(() => editor.destroy());
- 性能陷阱检测表
- [ ] 是否在v-for中使用复杂计算属性
- [ ] 是否频繁修改大数组的引用
- [ ] 是否在不需要响应性的地方使用reactive
- [ ] 是否合理使用v-show替代v-if
- 调试神器
// 追踪组件更新原因
import { onRenderTracked, onRenderTriggered } from 'vue';
onRenderTracked((e) => {
console.log('追踪依赖:', e);
});
onRenderTriggered((e) => {
console.log('触发更新:', e);
});