1. Vue 4.0 的编译时宏(defineProps
、defineEmits
)如何通过 AST 转换实现类型安全?对比 TypeScript 泛型的优劣。
实现原理:
在 Vue 的编译时阶段,defineProps
和 defineEmits
会通过 AST(抽象语法树)转换生成类型安全的代码。具体流程如下:
- 解析宏 :编译器识别
defineProps
和defineEmits
,提取它们的参数(如类型定义)。 - 类型推断:基于参数生成运行时类型校验逻辑,或与 TypeScript 类型系统集成。
- 代码生成:将类型信息转换为运行时验证代码(如 Props 的校验函数)或静态类型声明。
示例代码:
typescript
// 编译前(用户代码)
defineProps<{ count: number }>();
defineEmits<{ (e: 'update', value: string): void }>();
// 编译后(生成代码)
{
props: { count: { type: Number, required: true } },
emits: ['update'],
// 可能生成运行时校验逻辑
}
对比 TypeScript 泛型:
-
优势:
- 框架集成:编译时宏能直接生成框架所需的运行时逻辑(如 Props 校验),而 TypeScript 泛型仅提供静态类型检查。
- 简洁性:无需手动编写类型与运行时代码的映射。
-
劣势:
- 灵活性:TypeScript 泛型支持更复杂的类型操作(如联合类型、条件类型),而编译时宏可能受限于框架设计。
- 工具链依赖:编译时宏需要特定编译器支持,而 TypeScript 泛型是语言原生特性。
2. 在 Vue 4.0 中,如何通过 Composition API 实现跨组件的状态共享?对比 Vuex 的适用场景。
实现方式:
使用 Composition API 的 provide
/inject
或工厂函数实现状态共享:
javascript
// sharedState.ts
import { ref, provide, inject } from 'vue';
const key = Symbol('sharedState');
export function createSharedState() {
const state = ref({ count: 0 });
return { state };
}
export function useSharedState() {
return inject(key) || createSharedState();
}
// 根组件
provide(key, createSharedState());
// 子组件
const { state } = useSharedState();
对比 Vuex:
-
Composition API 适用场景:
- 中小型应用或局部状态共享。
- 需要更灵活的状态逻辑组合(如复用逻辑片段)。
-
Vuex 适用场景:
- 大型应用,需集中式状态管理。
- 需要严格的全局状态变更追踪(如 DevTools 集成、时间旅行调试)。
3. 如何用 Vue 4.0 的 Suspense
实现异步组件的加载状态管理?关键代码及与 React Suspense 的差异。
关键代码:
xml
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
);
</script>
与 React Suspense 的差异:
- 错误处理 :Vue 使用
<ErrorBoundary>
配合onErrorCaptured
,而 React 直接在 Suspense 边界捕获。 - 并发模式:React Suspense 支持并发渲染特性(如优先级中断),Vue 目前未实现类似机制。
- 组合方式 :Vue 的
Suspense
需要显式包裹异步组件,React 的Suspense
可以更灵活地嵌套使用。
4. 设计一个 Vue 4.0 的自定义指令实现图片懒加载,支持 Intersection Observer 的回调。
实现代码:
ini
// lazyLoadDirective.ts
import type { Directive } from 'vue';
const lazyLoad: Directive<HTMLImageElement, string> = {
mounted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value;
observer.unobserve(el);
// 触发自定义回调
if (binding.arg === 'callback') {
(binding.instance as any)[binding.modifiers?.callback]?.();
}
}
});
});
observer.observe(el);
el._observer = observer; // 保存 observer 实例以便卸载时使用
},
beforeUnmount(el) {
el._observer?.unobserve(el);
}
};
export default lazyLoad;
使用方式:
xml
<template>
<img v-lazy="imageUrl" v-lazy:callback.onVisible />
</template>
<script setup>
import { ref } from 'vue';
const imageUrl = ref('path/to/image.jpg');
function onVisible() {
console.log('Image is visible!');
}
</script>
5. 在 Vue 4.0 中,如何通过 Teleport
实现模态框的全局挂载?关键代码及与 React Portal 的异同。
关键代码:
xml
<template>
<button @click="showModal = true">Open Modal</button>
<Teleport to="body">
<div v-if="showModal" class="modal">
<p>Modal Content</p>
<button @click="showModal = false">Close</button>
</div>
</Teleport>
</template>
<script setup>
import { ref } from 'vue';
const showModal = ref(false);
</script>
与 React Portal 的异同:
-
相同点:
- 目标:将子组件渲染到 DOM 树的其他位置(如
body
末尾)。 - 应用场景:模态框、弹出菜单等需要脱离父容器样式限制的场景。
- 目标:将子组件渲染到 DOM 树的其他位置(如
-
不同点:
- 语法 :Vue 使用
<Teleport to="selector">
,React 使用createPortal(children, domNode)
。 - 动态目标 :Vue 允许动态绑定
to
(如:to="dynamicTarget"
),React 需手动管理容器节点。 - 组件化:Vue 的 Teleport 是内置组件,React Portal 是函数调用。
- 语法 :Vue 使用