
Vue3的API太多了,有50个左右,还有很多新概念,新写法,让我一直没有感受到写代码的乐趣,而是在Vue3的迷宫中晕头转向。
我认真思考过,我写代码的要求有以下几个:
- 尽可能地简单,不要用太多API。
- 尽可能地直观,不要一个页面的代码散落地到处都是。
- 不要太多抽象,比如hooks,尽可能地直观。
- 要方便维护和管理,不同类型放到一块,比如Vue2的选项式写法管理起来就挺方便的。
- 当然,我不可能继续用回Vue2,我需要结合Vue3 + Vue2的优点。
所以,我的思路和案例如下:
基本约定
- 计算属性统一放入
$Computed。 - 方法统一放入
$Method。 - 非响应式常量统一放入
$Const。 - 模板中访问
$Computed的成员必须使用.value。 - 保持分区顺序:
// $From→// $Data→// $Computed→// $Method→// $Life→// $Watch。
所有集合变量的说明与好处
$From:说明=外部引入与类型定义区(import/type/interface);好处=依赖入口集中、理解上下文更快。$Prop:说明=组件 props 集中声明(defineProps/withDefaults);好处=输入与默认值清晰、边界稳定。$Emit:说明=组件 emits 集中声明(defineEmits);好处=输出事件可控、接口对齐更稳。$Data:说明=响应式状态入口(reactive);好处=状态集中、定位与调试更容易。$Computed:说明=派生状态入口(computed);好处=职责清晰、模板统一.value更安全。$Method:说明=交互与行为入口;好处=调用集中、可搜索、易重构。$Const:说明=非响应式常量/配置集合;好处=避免误追踪、配置复用一致。
简单但完整的案例
<template>
<div class="page-orders">
<div class="toolbar">
<div class="title">{{ $Prop.title }}</div>
<div class="chips">
<div class="chip" :class="{ active: $Data.filter === 'all' }" @click="$Method.setFilter('all')">全部</div>
<div class="chip" :class="{ active: $Data.filter === 'paid' }" @click="$Method.setFilter('paid')">已支付</div>
<div class="chip" :class="{ active: $Data.filter === 'unpaid' }" @click="$Method.setFilter('unpaid')">未支付</div>
</div>
<div class="summary">{{ $Computed.totalText.value }}</div>
</div>
<div class="table">
<div class="head">
<div class="cell" v-for="col in $Computed.visibleColumns.value" :key="col.key">{{ col.title }}</div>
</div>
<div class="row" v-for="row in $Computed.pagedRows.value" :key="row.id" @click="$Method.onRowClick(row)">
<div class="cell" v-for="col in $Computed.visibleColumns.value" :key="col.key">{{ row[col.key] }}</div>
</div>
</div>
<div class="pager">
<div class="btn" :class="{ disabled: !$Computed.canPrev.value }" @click="$Method.prev">上一页</div>
<div class="text">第 {{ $Data.page }} / {{ $Computed.pageCount.value }} 页</div>
<div class="btn" :class="{ disabled: !$Computed.canNext.value }" @click="$Method.next">下一页</div>
</div>
</div>
</template>
<script setup lang="ts">
// $From
import { computed, reactive } from "vue";
const $Prop = withDefaults(
defineProps<{
title?: string;
showAmount?: boolean;
}>(),
{
title: "订单列表",
showAmount: true
}
);
const $Emit = defineEmits<{
(eventName: "filter-change", payload: { filter: "all" | "paid" | "unpaid" }): void;
(eventName: "row-click", payload: { id: number }): void;
}>();
type OrderItem = {
id: number;
name: string;
amount: number;
status: "paid" | "unpaid";
};
// $Data
const $Data = reactive<{
filter: "all" | "paid" | "unpaid";
page: number;
pageSize: number;
rows: OrderItem[];
}>({
filter: "all",
page: 1,
pageSize: 5,
rows: [
{ id: 1, name: "订单 A", amount: 199, status: "paid" },
{ id: 2, name: "订单 B", amount: 49, status: "unpaid" },
{ id: 3, name: "订单 C", amount: 88, status: "paid" },
{ id: 4, name: "订单 D", amount: 20, status: "unpaid" },
{ id: 5, name: "订单 E", amount: 69, status: "paid" },
{ id: 6, name: "订单 F", amount: 299, status: "paid" }
]
});
// $Const
const $Const = {
columns: [
{ key: "id", title: "ID" },
{ key: "name", title: "名称" },
{ key: "amount", title: "金额" },
{ key: "status", title: "状态" }
] as const
};
// $Computed
const $Computed = {
filteredRows: computed(() => {
if ($Data.filter === "paid") return $Data.rows.filter((row) => row.status === "paid");
if ($Data.filter === "unpaid") return $Data.rows.filter((row) => row.status === "unpaid");
return $Data.rows;
}),
visibleColumns: computed(() => {
if ($Prop.showAmount) return $Const.columns;
return $Const.columns.filter((col) => col.key !== "amount");
}),
pageCount: computed(() => Math.max(1, Math.ceil($Computed.filteredRows.value.length / $Data.pageSize))),
pagedRows: computed(() => {
const start = ($Data.page - 1) * $Data.pageSize;
return $Computed.filteredRows.value.slice(start, start + $Data.pageSize);
}),
canPrev: computed(() => $Data.page > 1),
canNext: computed(() => $Data.page < $Computed.pageCount.value),
totalText: computed(() => `共 ${$Computed.filteredRows.value.length} 条`)
};
// $Method
const $Method = {
setFilter: function (filter: "all" | "paid" | "unpaid"): void {
$Data.filter = filter;
$Data.page = 1;
$Emit("filter-change", { filter: filter });
},
prev: function (): void {
if ($Computed.canPrev.value) $Data.page -= 1;
},
next: function (): void {
if ($Computed.canNext.value) $Data.page += 1;
},
onRowClick: function (row: OrderItem): void {
$Emit("row-click", { id: row.id });
}
};
</script>
如此一来,写代码又变成了一种享受。
