首页 注册 登录
V2EX = way to explore V2EX 是一个关于分享和探索的地方
现在注册 已注册用户请 登录
Vue.js
V2EX Vue.js

用 reactive 來實現 oo 對象的封裝

mizuhashi · 56 天前 · 2025 次点击
这是一个创建于 56 天前的主题,其中的信息可能已经有所发展或是发生改变。

我不確定這是否是普遍的用法,但我發現 reactive 非常適合用來建立類似 OO 的封裝。

reactive會把 refs 展平,所以可以這麼寫:

function useClient() {
 const name = ref('Alice')
 const greeting = computed(() => `Hello ${name.value}`)
 function updateName(newName: string) {
 name.value = newName
 }
 return reactive({
 name,
 greeting,
 updateName
 })
}

在 component 裏:

const client = useClient()
client.greeting // => 'Hello Alice'
client.updateName('Bob')
client.greeting // => 'Hello Bob'

現在client管理它自己的狀態,其暴露的接口可以直接用在模板裏。

我們也可以組合這些對象和保留嚮應性:

function useOrder(client: ReturnType<typeof useClient>) {
 const createdBy = computed(() => `Created by ${client.name}`)
 // 你也可以在這裏調用 client.updateName
 return reactive({
 createdBy
 })
}
const client = useClient()
const order = useOrder(client)
order.createdBy // => 'Created by Alice'
client.updateName('Bob')
order.createdBy // => 'Created by Bob'

我覺得這是 vue 相對於其他庫特別的屬性,我只需要傳遞一個對象,而它擁有自己的狀態和方法。

在現實中,這些對象一般會基於後端數據,我們可以在後端數據的基礎上擴展狀態和方法。

async function useOrder(client: ReturnType<typeof useClient>) {
 const orderData = reactive(await fetchOrderData())
 const paid = ref(false)
 async function pay() {
 const res = await paymentAPI()
 paid.value = res.success
 }
 return reactive({
 ...toRefs(orderData), // 所有 orderData 的屬性會被暴露
 // 我們需要 toRefs 來保持嚮應性
 paid,
 pay
 })
}

現在給定一個 order ,我們可以直接在模板裏綁定order.paidorder.pay

本帖在 reddit 上的版本: https://www.reddit.com/r/vuejs/comments/1owezu4/reactive_as_an_object_encapsulation/

18 条回复 2025年11月15日 09:59:03 +08:00
XCFOX
1
XCFOX 56 天前 ❤️ 1
我都是写 reactive + class 的。
class 简直是天然的 store ,状态和方法一目了然。

```ts
import { reactive } from 'vue'

class ClientStore {
name = 'Alice'

get greeting() {
return `Hello ${this.name}`
}

updateName(newName: string) {
this.name = newName
}
}

export const client = reactive(new ClientStore())
```
lynchuh
2
lynchuh 56 天前 ❤️ 1
牛,学到了!
mizuhashi
3
mizuhashi
OP
56 天前
@XCFOX 好像也可以,不過這樣沒有用上 computed 的緩存
jspatrick
4
jspatrick 56 天前
分享下我项目内的常见写法,这是表格相关的功能,几乎完全是 v2 的迁移版...

```javascript
const state = reactive({
loading: false,
list: [],
searchParams: {
department: null,
company: null,
vehicleType: [],
thisWeek: false,
thisMonth: false,
thisYear: false,
startTime: computed(() => {
if (!state.searchParams.time) return undefined;
const startTime = state.searchParams.time[0];
return dayjs(startTime).startOf('day').format('YYYY-MM-DD HH:mm:ss');
}),
endTime: computed(() => {
if (!state.searchParams.time) return undefined;
const endTime = state.searchParams.time[1];
return dayjs(endTime).endOf('day').format('YYYY-MM-DD HH:mm:ss');
}),
time: [dayjs().startOf('month').format('YYYY-MM-DD'), dayjs().endOf('day').format('YYYY-MM-DD')]
},
options: {
department: [],
company: [],
vehicleType: []
},
columns: hooks.useDefaultColumns([]),
pagination: hooks.usePagination(() => state.search()),
selectTimeMode(idx) {
},
async search() {
}
});
```
jspatrick
5
jspatrick 56 天前 ❤️ 1
mizuhashi
6
mizuhashi
OP
56 天前
@jspatrick 原來可以這樣引用自己
duuu
7
duuu 56 天前 ❤️ 2
楼主的 useClient 其实就是我们常用的 Composable 的写法,只是在 return 里套了一层 reactive 。好处暂时看来是用的地方不用写.value 了。但是我感觉这样写反而会有心智负担,因为简单从代码里看 name ,和 greeting 的用法都是要.value 的,如果用了 useClient 后,client.greeting 和,client.name 都不用.value 反而在每次写的时候要思考一下。如果这种写法在团队里没有形成规范的话,也会让同事调用你的代码写起来很困惑
duuu
8
duuu 56 天前
@jspatrick vue3 的优点就是每个模块的代码可以集中写,你这样按 vue2 的 data 来放,就丢失这个优点了~
jspatrick
9
jspatrick 56 天前
@duuu #8 其实就是抽离不抽离的关系,如果文件复杂度上升,就抽出去做成 hooks 来用,如果只是简单的页面,这么写还是有好处的,所有的表单逻辑其实都被写在了 state 里,这整个 state 其实就是 hooks 本来要导出的东西,关注点也不会被其他 hooks 打断,并不是页面全被塞进 state ,而是某个业务相关的东西被单独做成一个 state ,一个页面也可能有多个不同业务的 state
dumbass
10
dumbass 56 天前
@duuu #8 正确的。我会把 loading/list/searchParams/options... 拆成不同的 ref
duuu
11
duuu 56 天前
@jspatrick 理解,不过如果是我的习惯的话,如果不抽成 hooks ,我不会在外面套一层 reactive 。我会跟#10 一样拆成不同的 ref
lizhenda
12
lizhenda 56 天前
这不就是 vue3 推荐的使用方法么 ···
UnluckyNinja
13
UnluckyNinja 56 天前 ❤️ 1
这其实就是 composable 的用法,但官方指南不建议在 composable 函数里对返回值用 reactive 包装,这样会导致解构语法响应性不正确。
比如你这个例子,如果使用者写 `const { name } = useClient()`,name 就会失去响应性。
官方的推荐做法是返回 ref 作为返回对象的属性,如果调用方想使用整体响应性并免除.value ,可以自己用 reactive 包装。

详见 https://cn.vuejs.org/guide/reusability/composables ,
大量 composable 参考可以浏览 VueUse https://vueuse.org/
mizuhashi
14
mizuhashi
OP
56 天前 via iPhone
@UnluckyNinja 原來文檔裏有提到用 reactive 包,我知道官方推薦的返回 ref 。不過對於 domain object 我從沒想要解構,就是想要它的封裝性,如果解構了就都散了
Ketteiron
15
Ketteiron 56 天前
@UnluckyNinja 官方文档这么写问题很大,似乎是暗示 return 一个 reactive 会丢失响应式,我第一次看文档时也是这么认为的。实际并不会,因为返回的是代理对象,只有对代理对象进行解构才会丢失响应式。
我想没人会 return obj ,正常都是 return { obj }
ref x,y 和 reactive ({x,y}) 是等价的两种写法,只是后者不能解构
const {obj:{x,y}} = useXxx()
我想也没人这么闲用嵌套解构语法
文档只需说不建议返回 reactive 对象就行了,初学者可能会以为 {reactive()} 也是不行的。
UnluckyNinja
16
UnluckyNinja 56 天前
@mizuhashi #14 只要学 vue 之前已经知道 JS 的解构语法,应该不会搞混返回 XX 对象和返回嵌套的 XX 对象的区别
UnluckyNinja
17
UnluckyNinja 56 天前
@UnluckyNinja #16 回错了,想回的是 #15 楼
riceball
18
riceball 55 天前
你是否真的需要对象中的每一个属性都 reactive?是否存在不需要交互的属性?
关于 · 帮助文档 · 自助推广系统 · 博客 · API · FAQ · Solana · 918 人在线 最高记录 6679 · Select Language 创意工作者们的社区 World is powered by solitude VERSION: 3.9.8.5 · 37ms · UTC 22:23 · PVG 06:23 · LAX 14:23 · JFK 17:23
♥ Do have faith in what you're doing.

AltStyle によって変換されたページ (->オリジナル) /