用 Vue3
<script setup>写小组件很爽,但遇到大型页面时,逻辑堆在一起就像面条。今天我们从"订单详情页"这个复杂组件入手,讲清楚<script setup>的最佳实践:逻辑分层、组合式函数拆分、Prop/Emit 类型定义、与普通<script>共存,让你写大组件也井井有条。
🎯 订单详情页需求
- 渲染订单基础信息、商品列表、物流时间线。
- 支持操作:修改状态、退款、打印。
- 需要权限控制 + 国际化。
🧱 目录规划
OrderDetail.vue
composables/
useOrderInfo.ts
useOrderActions.ts
useOrderPermission.ts
- 把逻辑拆到同文件夹下的组合式函数。
- 组件负责布局 + 调用组合函数。
✍️ OrderDetail.vue 结构
vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import OrderBase from './components/OrderBase.vue';
import OrderItems from './components/OrderItems.vue';
import OrderTimeline from './components/OrderTimeline.vue';
import { useOrderInfo } from './composables/useOrderInfo';
import { useOrderActions } from './composables/useOrderActions';
import { useOrderPermission } from './composables/useOrderPermission';
const { t } = useI18n();
const route = useRoute();
const orderId = computed(() => route.params.id as string);
const { loading, order, reload } = useOrderInfo(orderId);
const { canEdit, canRefund } = useOrderPermission(order);
const { updateStatus, refund, print } = useOrderActions(order, reload);
</script>
<script setup>直接写逻辑,无需defineComponent。- 组合式函数返回的数据直接解构。
- 所有业务逻辑都在 composable 中实现。
🧩 组合式函数示例
ts
// useOrderInfo.ts
import { computed, ref, watch } from 'vue';
import { getOrderDetail } from '@/api/order';
export function useOrderInfo(orderId: Ref<string>) {
const loading = ref(false);
const order = ref<Order | null>(null);
const fetchOrder = async () => {
if (!orderId.value) return;
loading.value = true;
try {
order.value = await getOrderDetail(orderId.value);
} finally {
loading.value = false;
}
};
watch(orderId, fetchOrder, { immediate: true });
const totalAmount = computed(() =>
order.value?.items.reduce((sum, item) => sum + item.price * item.qty, 0) || 0
);
return { loading, order, totalAmount, reload: fetchOrder };
}
- 组合式函数以
use开头。 - 暴露响应式数据 + 方法。
- 方便在其他组件复用。
🧠 Props、Emits 类型定义
vue
<script setup lang="ts">
interface Props {
orderId: string;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'refresh'): void;
}>();
const handleSuccess = () => emit('refresh');
</script>
defineProps和defineEmits在<script setup>中直接调用。- TypeScript 自动推断模板里的类型。
🔁 与普通 <script> 共存
vue
<script lang="ts">
export default {
inheritAttrs: false,
};
</script>
<script setup lang="ts">
// ...这里写 setup 逻辑
</script>
- 当需要
inheritAttrs、components、directive等选项时,可以保留普通<script>。 <script setup>会在选项式 API 之后执行。
🧯 调试技巧
-
defineExpose:暴露内部方法给父组件。tsdefineExpose({ reload }); -
onMounted,onUnmounted与原生生命周期一致。 -
使用 ESLint 插件
eslint-plugin-vue保持编码规范。
⚠️ 常见坑
| 坑 | 现象 | 解决 |
|---|---|---|
在 <script setup> 中使用 this |
undefined | 不存在 this,使用组合式变量 |
| 逻辑堆积混乱 | 文件过大 | 拆到 composables 子目录 |
| defineProps 重复执行 | 在顶层直接使用 | 只能在 <script setup> 顶层调用 |
| 类型推断失败 | 未开启 Volar + TS 支持 | 确保 vue-tsc 生效 |
🏁 小练习
- 把你项目中 200 行以上的组件拆分成
<script setup>+ composables。 - 尝试写一个
useTable组合式函数,封装分页、排序逻辑。