Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(reactify)!: enable support for reactive getter by default #1860

Merged
merged 2 commits into from Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
112 changes: 58 additions & 54 deletions packages/shared/reactify/index.test.ts
@@ -1,92 +1,96 @@
import { ref } from 'vue-demi'
import { useSetup } from '../../.test'
import { reactify } from '.'

describe('reactify', () => {
it('one arg', () => {
useSetup(() => {
const base = ref(1.5)
const floor = reactify(Math.floor)
const result = floor(base)
const base = ref(1.5)
const floor = reactify(Math.floor)
const result = floor(base)

expect(result.value).toBe(1)
expect(result.value).toBe(1)

base.value = 2.8
base.value = 2.8

expect(result.value).toBe(2)
})
expect(result.value).toBe(2)
})

it('two args', () => {
useSetup(() => {
const base = ref(0)
const exponent = ref(0)
const pow = reactify(Math.pow)
const result = pow(base, exponent)
const base = ref(0)
const exponent = ref(0)
const pow = reactify(Math.pow)
const result = pow(base, exponent)

expect(base.value).toBe(0)
expect(result.value).toBe(1)
expect(base.value).toBe(0)
expect(result.value).toBe(1)

base.value = 2
exponent.value = 2
base.value = 2
exponent.value = 2

expect(result.value).toBe(4)
expect(result.value).toBe(4)

base.value = 3
base.value = 3

expect(result.value).toBe(9)
expect(result.value).toBe(9)

exponent.value = 3
exponent.value = 3

expect(result.value).toBe(27)
})
expect(result.value).toBe(27)
})

it('mixed with literal', () => {
useSetup(() => {
const base = ref(0)
const pow = reactify(Math.pow)
const result = pow(base, 2)
const base = ref(0)
const pow = reactify(Math.pow)
const result = pow(base, 2)

expect(base.value).toBe(0)
expect(result.value).toBe(0)
expect(base.value).toBe(0)
expect(result.value).toBe(0)

base.value = 10
expect(result.value).toBe(100)
})
base.value = 10
expect(result.value).toBe(100)
})

it('JSON.stringify', () => {
useSetup(() => {
const base = ref<any>(0)
const stringify = reactify(JSON.stringify)
const result = stringify(base, null, 2)
const base = ref<any>(0)
const stringify = reactify(JSON.stringify)
const result = stringify(base, null, 2)

expect(base.value).toBe(0)
expect(result.value).toBe('0')
expect(base.value).toBe(0)
expect(result.value).toBe('0')

base.value = { foo: 'bar' }
expect(result.value).toBe('{\n "foo": "bar"\n}')
})
base.value = { foo: 'bar' }
expect(result.value).toBe('{\n "foo": "bar"\n}')
})

it('Pythagorean theorem', () => {
useSetup(() => {
const pow = reactify(Math.pow)
const sqrt = reactify(Math.sqrt)
const add = reactify((a: number, b: number) => a + b)
const pow = reactify(Math.pow)
const sqrt = reactify(Math.sqrt)
const add = reactify((a: number, b: number) => a + b)

const a = ref(3)
const b = ref(4)
const a = ref(3)
const b = ref(4)

const c = sqrt(add(pow(a, 2), pow(b, 2)))
const c = sqrt(add(pow(a, 2), pow(b, 2)))

expect(c.value).toBe(5)
expect(c.value).toBe(5)

a.value = 5
b.value = 12
a.value = 5
b.value = 12

expect(c.value).toBe(13)
})
expect(c.value).toBe(13)
})

it('computed getter', () => {
const add = reactify((a: number, b: number) => a + b)

const a = ref(3)
const b = ref(4)

const c = add(a, () => b.value)

expect(c.value).toBe(7)

b.value = 12

expect(c.value).toBe(15)
})
})
23 changes: 17 additions & 6 deletions packages/shared/reactify/index.ts
@@ -1,22 +1,33 @@
import type { ComputedRef } from 'vue-demi'
import { computed, unref } from 'vue-demi'
import type { MaybeRef } from '../utils'
import { resolveUnref } from '../resolveUnref'
import type { MaybeComputedRef, MaybeRef } from '../utils'

export type Reactify<T> = T extends (...args: infer A) => infer R
? (...args: { [K in keyof A]: MaybeRef<A[K]> }) => ComputedRef<R>
export type Reactified<T, Computed extends boolean> = T extends (...args: infer A) => infer R
? (...args: { [K in keyof A]: Computed extends true ? MaybeComputedRef<A[K]> : MaybeRef<A[K]> }) => ComputedRef<R>
: never

export interface ReactifyOptions<T extends boolean> {
/**
* Accept passing a function as a reactive getter
*
* @default true
*/
computedGetter?: T
}

/**
* Converts plain function into a reactive function.
* The converted function accepts refs as it's arguments
* and returns a ComputedRef, with proper typing.
*
* @param fn - Source function
*/
export function reactify<T extends Function>(fn: T): Reactify<T> {
export function reactify<T extends Function, K extends boolean = true>(fn: T, options?: ReactifyOptions<K>): Reactified<T, K> {
const unrefFn = options?.computedGetter === false ? unref : resolveUnref
return function (this: any, ...args: any[]) {
return computed(() => fn.apply(this, args.map(i => unref(i))))
} as Reactify<T>
return computed(() => fn.apply(this, args.map(i => unrefFn(i))))
} as any
}

// alias
Expand Down
22 changes: 13 additions & 9 deletions packages/shared/reactifyObject/index.ts
@@ -1,9 +1,9 @@
import type { Reactify } from '../reactify'
import type { Reactified, ReactifyOptions } from '../reactify'
import { reactify } from '../reactify'

export type ReactifyNested<T, Keys extends keyof T = keyof T> = { [K in Keys]: T[K] extends (...args: any[]) => any ? Reactify<T[K]> : T[K] }
export type ReactifyNested<T, Keys extends keyof T = keyof T, S extends boolean= true> = { [K in Keys]: T[K] extends (...args: any[]) => any ? Reactified<T[K], S> : T[K] }

export interface ReactifyObjectOptions {
export interface ReactifyObjectOptions<T extends boolean> extends ReactifyOptions<T> {
/**
* Includes names from Object.getOwnPropertyNames
*
Expand All @@ -15,13 +15,17 @@ export interface ReactifyObjectOptions {
/**
* Apply `reactify` to an object
*/
export function reactifyObject<T extends object, Keys extends keyof T>(obj: T, keys?: (keyof T)[]): ReactifyNested<T, Keys>
export function reactifyObject<T extends object>(obj: T, options?: ReactifyObjectOptions): ReactifyNested<T>
export function reactifyObject<T extends object, Keys extends keyof T>(obj: T, keys?: (keyof T)[]): ReactifyNested<T, Keys, true>
export function reactifyObject<T extends object, S extends boolean = true>(obj: T, options?: ReactifyObjectOptions<S>): ReactifyNested<T, keyof T, S>

export function reactifyObject<T extends object>(obj: T, optionsOrKeys: ReactifyObjectOptions | (keyof T)[] = {}): ReactifyNested<T> {
export function reactifyObject<T extends object, S extends boolean = true>(obj: T, optionsOrKeys: ReactifyObjectOptions<S> | (keyof T)[] = {}): ReactifyNested<T, keyof T, S> {
let keys: string[] = []
if (Array.isArray(optionsOrKeys)) { keys = optionsOrKeys as string[] }
let options: ReactifyOptions<S> | undefined
if (Array.isArray(optionsOrKeys)) {
keys = optionsOrKeys as string[]
}
else {
options = optionsOrKeys
const { includeOwnProperties = true } = optionsOrKeys

keys.push(...Object.keys(obj))
Expand All @@ -36,9 +40,9 @@ export function reactifyObject<T extends object>(obj: T, optionsOrKeys: Reactify
return [
key,
typeof value === 'function'
? reactify(value.bind(obj))
? reactify(value.bind(obj), options)
: value,
]
}),
) as ReactifyNested<T>
) as ReactifyNested<T, keyof T, S>
}