Skip to content

Commit

Permalink
feat(toReactive): new function (vitest-dev#671)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Aug 15, 2021
1 parent d8c80e0 commit 0f250d4
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Collection of essential Vue Composition Utilities
<a href="https://www.npmjs.com/package/@vueuse/core" target="__blank"><img src="https://img.shields.io/npm/v/@vueuse/core?color=a1b858&label=" alt="NPM version"></a>
<a href="https://www.npmjs.com/package/@vueuse/core" target="__blank"><img alt="NPM Downloads" src="https://img.shields.io/npm/dm/@vueuse/core?color=50a36f&label="></a>
<a href="https://vueuse.org" target="__blank"><img src="https://img.shields.io/static/v1?label=&message=docs%20%26%20demos&color=1e8a7a" alt="Docs & Demos"></a>
<img alt="Function Count" src="https://img.shields.io/badge/-135%20functions-13708a">
<img alt="Function Count" src="https://img.shields.io/badge/-136%20functions-13708a">
<br>
<a href="https://github.com/vueuse/vueuse" target="__blank"><img alt="GitHub stars" src="https://img.shields.io/github/stars/vueuse/vueuse?style=social"></a>
</p>
Expand Down
7 changes: 7 additions & 0 deletions indexes.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,13 @@
"category": "Watch",
"description": "throttled watch"
},
{
"name": "toReactive",
"package": "shared",
"docs": "https://vueuse.org/shared/toReactive/",
"category": "Utilities",
"description": "converts ref to reactive"
},
{
"name": "toRefs",
"package": "shared",
Expand Down
1 change: 1 addition & 0 deletions packages/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
- [`reactivePick`](https://vueuse.org/shared/reactivePick/) — reactively pick fields from a reactive object
- [`set`](https://vueuse.org/shared/set/) — shorthand for `ref.value = x`
- [`syncRef`](https://vueuse.org/shared/syncRef/) — keep target refs in sync with a source ref
- [`toReactive`](https://vueuse.org/shared/toReactive/) — converts ref to reactive
- [`toRefs`](https://vueuse.org/shared/toRefs/) — extended [`toRefs`](https://v3.vuejs.org/api/refs-api.html#torefs) that also accepts refs of an object
- [`useAsyncState`](https://vueuse.org/core/useAsyncState/) — reactive async state
- [`useCounter`](https://vueuse.org/shared/useCounter/) — basic counter with utility functions
Expand Down
1 change: 1 addition & 0 deletions packages/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './reactivePick'
export * from './set'
export * from './syncRef'
export * from './throttledWatch'
export * from './toReactive'
export * from './toRefs'
export * from './tryOnMounted'
export * from './tryOnScopeDispose'
Expand Down
36 changes: 36 additions & 0 deletions packages/shared/toReactive/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
category: Utilities
---

# toReactive

Converts ref to reactive. Also made possible to create a "swapable" reactive object.

## Usage

```ts
import { toReactive } from '@vueuse/core'

const refState = ref({ foo: 'bar' })

console.log(refState.value.foo) // => 'bar'

const state = toReactive(refState) // <--

console.log(state.foo) // => 'bar'

refState.value = { bar: 'foo' }

console.log(state.foo) // => undefined
console.log(state.bar) // => 'foo'
```

<!--FOOTER_STARTS-->


## Source

[Source](https://github.com/vueuse/vueuse/blob/main/packages/shared/toReactive/index.ts)[Docs](https://github.com/vueuse/vueuse/blob/main/packages/shared/toReactive/index.md)


<!--FOOTER_ENDS-->
97 changes: 97 additions & 0 deletions packages/shared/toReactive/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { toRefs } from '@vueuse/core'
import { isReactive, nextTick, reactive, ref, watchSyncEffect } from 'vue-demi'
import { toReactive } from '.'

describe('toRefs', () => {
it('should be defined', () => {
expect(toReactive).toBeDefined()
})

it('should work', () => {
const r = ref({ a: 'a', b: 0 })
const state = toReactive(r)
expect(state.a).toBe('a')
expect(state.b).toBe(0)

r.value.a = 'b'
r.value.b = 1
expect(state.a).toBe('b')
expect(state.b).toBe(1)
})

it('should be enumerable', () => {
const obj = { a: 'a', b: 0 }
const r = ref(obj)
const state = toReactive(r)

expect(JSON.stringify(state)).toBe(JSON.stringify(r.value))
expect(state).toEqual(obj)
})

it('should be reactive', async() => {
const r = ref({ a: 'a', b: 0 })
const state = toReactive(r)
let dummy = 0

expect(state.a).toBe('a')
expect(state.b).toBe(0)
expect(isReactive(state)).toBe(true)

watchSyncEffect(() => {
dummy = state.b
})

expect(dummy).toBe(0)

r.value.b += 1

expect(dummy).toBe(1)

state.b += 1

expect(dummy).toBe(2)
expect(r.value.b).toBe(2)
})

it('should be replaceable', async() => {
const r = ref<any>({ a: 'a', b: 0 })
const state = toReactive(r)
let dummy = 0

expect(state.a).toBe('a')
expect(state.b).toBe(0)

watchSyncEffect(() => {
dummy = state.b
})

expect(dummy).toBe(0)

r.value = { b: 1, a: 'a' }

expect(dummy).toBe(1)

state.b += 1

await nextTick()

expect(dummy).toBe(2)

r.value = { a: 'c' }

expect(dummy).toBe(undefined)
expect(state).toEqual({ a: 'c' })
})

it('toReactive(toRefs())', () => {
const a = reactive({ a: 'a', b: 0 })
const b = toRefs(a)
const c = toReactive(b)

expect(a).toEqual(c)

a.b = 1

expect(c.b).toEqual(1)
})
})
42 changes: 42 additions & 0 deletions packages/shared/toReactive/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { isRef, toRefs as _toRefs, reactive } from 'vue-demi'
import { MaybeRef } from '../utils'

/**
* Converts ref to reactive.
*
* @see https://vueuse.org/toReactive
* @param objectRef A ref of object
*/
export function toReactive<T extends object>(
objectRef: MaybeRef<T>,
): T {
if (!isRef(objectRef))
return reactive(objectRef) as T

const proxy = new Proxy({}, {
get(_, p, receiver) {
return Reflect.get(objectRef.value, p, receiver)
},
set(_, p, value) {
(objectRef.value as any)[p] = value
return true
},
deleteProperty(_, p) {
return Reflect.deleteProperty(objectRef.value, p)
},
has(_, p) {
return Reflect.has(objectRef.value, p)
},
ownKeys() {
return Object.keys(objectRef.value)
},
getOwnPropertyDescriptor() {
return {
enumerable: true,
configurable: true,
}
},
})

return reactive(proxy) as T
}

0 comments on commit 0f250d4

Please sign in to comment.