Skip to content

Commit

Permalink
Merge branch 'master' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Aug 1, 2021
2 parents 3ddd223 + 9566e63 commit a283e60
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 62 deletions.
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@

* implement `effectScope` api ([#762](https://github.com/vuejs/composition-api/issues/762)) ([fcadec2](https://github.com/vuejs/composition-api/commit/fcadec2))

<a name="1.0.5"></a>
## [1.0.5](https://github.com/vuejs/composition-api/compare/v1.0.4...v1.0.5) (2021-08-01)


### Bug Fixes

* **function:** properties of function should not disappear. ([#778](https://github.com/vuejs/composition-api/issues/778)) ([68c1a35](https://github.com/vuejs/composition-api/commit/68c1a35))



<a name="1.0.4"></a>
## [1.0.4](https://github.com/vuejs/composition-api/compare/v1.0.3...v1.0.4) (2021-07-22)


### Bug Fixes

* revert module field to `esm.js` version, close [#769](https://github.com/vuejs/composition-api/issues/769) ([4ac545c](https://github.com/vuejs/composition-api/commit/4ac545c))



<a name="1.0.3"></a>
## [1.0.3](https://github.com/vuejs/composition-api/compare/v1.0.2...v1.0.3) (2021-07-18)


### Bug Fixes

* build for mjs and exports all submodules ([69538ee](https://github.com/vuejs/composition-api/commit/69538ee))



<a name="1.0.2"></a>
Expand Down
3 changes: 2 additions & 1 deletion src/apis/computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
noopFn,
defineComponentInstance,
getVueInternalClasses,
isFunction,
} from '../utils'
import { getCurrentScopeVM } from './effectScope'

Expand Down Expand Up @@ -36,7 +37,7 @@ export function computed<T>(
let getter: ComputedGetter<T>
let setter: ComputedSetter<T> | undefined

if (typeof getterOrOptions === 'function') {
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
} else {
getter = getterOrOptions.get
Expand Down
12 changes: 7 additions & 5 deletions src/apis/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ const genName = (name: string) => `on${name[0].toUpperCase() + name.slice(1)}`
function createLifeCycle(lifeCyclehook: string) {
return (callback: Function) => {
const vm = currentVMInFn(genName(lifeCyclehook))
if (vm) {
injectHookOption(getVueConstructor(), vm, lifeCyclehook, callback)
}
return (
vm && injectHookOption(getVueConstructor(), vm, lifeCyclehook, callback)
)
}
}

Expand All @@ -25,10 +25,12 @@ function injectHookOption(
) {
const options = vm.$options as Record<string, unknown>
const mergeFn = Vue.config.optionMergeStrategies[hook]
options[hook] = mergeFn(options[hook], wrapHookCall(vm, val))
const wrappedHook = wrapHookCall(vm, val)
options[hook] = mergeFn(options[hook], wrappedHook)
return wrappedHook
}

function wrapHookCall(vm: ComponentInstance, fn: Function) {
function wrapHookCall(vm: ComponentInstance, fn: Function): Function {
return (...args: any) => {
let preVm = getCurrentInstance()?.proxy
setCurrentInstance(vm)
Expand Down
2 changes: 1 addition & 1 deletion src/apis/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ export function watch<T = any>(
options?: WatchOptions
): WatchStopHandle {
let callback: WatchCallback<unknown> | null = null
if (typeof cb === 'function') {
if (isFunction(cb)) {
// source watch
callback = cb as WatchCallback<unknown>
} else {
Expand Down
6 changes: 3 additions & 3 deletions src/install.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { VueConstructor } from 'vue'
import { AnyObject } from './types/basic'
import { hasSymbol, hasOwn, isPlainObject, warn } from './utils'
import { isFunction, hasSymbol, hasOwn, isPlainObject, warn } from './utils'
import { isRef } from './reactivity'
import { setVueConstructor, isVueRegistered } from './runtimeContext'
import { mixin } from './mixin'
Expand Down Expand Up @@ -67,8 +67,8 @@ export function install(Vue: VueConstructor) {
) {
return function mergedSetupFn(props: any, context: any) {
return mergeData(
typeof parent === 'function' ? parent(props, context) || {} : undefined,
typeof child === 'function' ? child(props, context) || {} : undefined
isFunction(parent) ? parent(props, context) || {} : undefined,
isFunction(child) ? child(props, context) || {} : undefined
)
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function mixin(Vue: VueConstructor) {
if (!setup) {
return
}
if (typeof setup !== 'function') {
if (!isFunction(setup)) {
if (__DEV__) {
warn(
'The "setup" option should be a function that returns a object in component definitions.',
Expand All @@ -74,7 +74,7 @@ export function mixin(Vue: VueConstructor) {
// wrapper the data option, so we can invoke setup before data get resolved
$options.data = function wrappedData() {
initSetup(vm, vm.$props)
return typeof data === 'function'
return isFunction(data)
? (
data as (this: ComponentInstance, x: ComponentInstance) => object
).call(vm, vm)
Expand Down Expand Up @@ -124,7 +124,11 @@ export function mixin(Vue: VueConstructor) {
if (!isRef(bindingValue)) {
if (!isReactive(bindingValue)) {
if (isFunction(bindingValue)) {
const copy = bindingValue
bindingValue = bindingValue.bind(vm)
Object.keys(copy).forEach(function (ele) {
bindingValue[ele] = copy[ele]
})
} else if (!isObject(bindingValue)) {
bindingValue = ref(bindingValue)
} else if (hasReactiveArrayChild(bindingValue)) {
Expand Down
10 changes: 8 additions & 2 deletions src/reactivity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,11 @@ export { readonly, isReadonly, shallowReadonly } from './readonly'
export { set } from './set'
export { del } from './del'

export type { Ref, ToRefs, UnwrapRef, ShallowUnwrapRef } from './ref'
export type { DeepReadonly } from './readonly'
export type {
Ref,
ToRefs,
UnwrapRef,
UnwrapRefSimple,
ShallowUnwrapRef,
} from './ref'
export type { DeepReadonly, UnwrapNestedRefs } from './readonly'
4 changes: 2 additions & 2 deletions src/reactivity/readonly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { reactive, Ref, UnwrapRef } from '.'
import { reactive, Ref, UnwrapRefSimple } from '.'
import { isArray, isPlainObject, isObject, warn, proxy } from '../utils'
import { readonlySet } from '../utils/sets'
import { isReactive, observe } from './reactive'
Expand Down Expand Up @@ -33,7 +33,7 @@ export type DeepReadonly<T> = T extends Builtin
: Readonly<T>

// only unwrap nested ref
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>

/**
* **In @vue/composition-api, `reactive` only provides type-level readonly check**
Expand Down
40 changes: 8 additions & 32 deletions src/reactivity/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,44 +29,20 @@ export type UnwrapRef<T> = T extends Ref<infer V>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>

type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref
export type UnwrapRefSimple<T> = T extends
| Function
| CollectionTypes
| BaseTypes
| Ref
? T
: T extends Array<any>
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
: T extends object
? UnwrappedObject<T>
? {
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
}
: T

// Extract all known symbols from an object
// when unwrapping Object the symbols are not `in keyof`, this should cover all the
// known symbols
type SymbolExtract<T> = (T extends { [Symbol.asyncIterator]: infer V }
? { [Symbol.asyncIterator]: V }
: {}) &
(T extends { [Symbol.hasInstance]: infer V }
? { [Symbol.hasInstance]: V }
: {}) &
(T extends { [Symbol.isConcatSpreadable]: infer V }
? { [Symbol.isConcatSpreadable]: V }
: {}) &
(T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) &
(T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) &
(T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) &
(T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) &
(T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) &
(T extends { [Symbol.split]: infer V } ? { [Symbol.split]: V } : {}) &
(T extends { [Symbol.toPrimitive]: infer V }
? { [Symbol.toPrimitive]: V }
: {}) &
(T extends { [Symbol.toStringTag]: infer V }
? { [Symbol.toStringTag]: V }
: {}) &
(T extends { [Symbol.unscopables]: infer V }
? { [Symbol.unscopables]: V }
: {})

type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>

interface RefOption<T> {
get(): T
set?(x: T): void
Expand Down
11 changes: 9 additions & 2 deletions src/runtimeContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import type { VueConstructor, VNode } from 'vue'
import { ComponentInstance, Data } from './component'
import { assert, hasOwn, warn, proxy, UnionToIntersection } from './utils'
import {
assert,
hasOwn,
warn,
proxy,
UnionToIntersection,
isFunction,
} from './utils'

let vueDependency: VueConstructor | undefined = undefined

Expand All @@ -26,7 +33,7 @@ let currentInstanceTracking = true
const PluginInstalledFlag = '__composition_api_installed__'

function isVue(obj: any): obj is VueConstructor {
return obj && typeof obj === 'function' && obj.name === 'Vue'
return obj && isFunction(obj) && obj.name === 'Vue'
}

export function isPluginInstalled() {
Expand Down
38 changes: 32 additions & 6 deletions test-dts/ref.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,42 @@ bailType(el)
function withSymbol() {
const customSymbol = Symbol()
const obj = {
[Symbol.asyncIterator]: { a: 1 },
[Symbol.unscopables]: { b: '1' },
[customSymbol]: { c: [1, 2, 3] },
[Symbol.asyncIterator]: ref(1),
[Symbol.hasInstance]: { a: ref('a') },
[Symbol.isConcatSpreadable]: { b: ref(true) },
[Symbol.iterator]: [ref(1)],
[Symbol.match]: new Set<Ref<number>>(),
[Symbol.matchAll]: new Map<number, Ref<string>>(),
[Symbol.replace]: { arr: [ref('a')] },
[Symbol.search]: { set: new Set<Ref<number>>() },
[Symbol.species]: { map: new Map<number, Ref<string>>() },
[Symbol.split]: new WeakSet<Ref<boolean>>(),
[Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(),
[Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() },
[Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() },
[customSymbol]: { arr: [ref(1)] },
}

const objRef = ref(obj)

expectType<{ a: number }>(objRef.value[Symbol.asyncIterator])
expectType<{ b: string }>(objRef.value[Symbol.unscopables])
expectType<{ c: Array<number> }>(objRef.value[customSymbol])
expectType<Ref<number>>(objRef.value[Symbol.asyncIterator])
expectType<{ a: Ref<string> }>(objRef.value[Symbol.hasInstance])
expectType<{ b: Ref<boolean> }>(objRef.value[Symbol.isConcatSpreadable])
expectType<Ref<number>[]>(objRef.value[Symbol.iterator])
expectType<Set<Ref<number>>>(objRef.value[Symbol.match])
expectType<Map<number, Ref<string>>>(objRef.value[Symbol.matchAll])
expectType<{ arr: Ref<string>[] }>(objRef.value[Symbol.replace])
expectType<{ set: Set<Ref<number>> }>(objRef.value[Symbol.search])
expectType<{ map: Map<number, Ref<string>> }>(objRef.value[Symbol.species])
expectType<WeakSet<Ref<boolean>>>(objRef.value[Symbol.split])
expectType<WeakMap<Ref<boolean>, string>>(objRef.value[Symbol.toPrimitive])
expectType<{ weakSet: WeakSet<Ref<boolean>> }>(
objRef.value[Symbol.toStringTag]
)
expectType<{ weakMap: WeakMap<Ref<boolean>, string> }>(
objRef.value[Symbol.unscopables]
)
expectType<{ arr: Ref<number>[] }>(objRef.value[customSymbol])
}

withSymbol()
Expand Down
26 changes: 26 additions & 0 deletions test/setup.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1193,4 +1193,30 @@ describe('setup', () => {
const vm = new Vue(Constructor).$mount()
expect(vm.proxy).toBe(originalProxy)
})

// test #687
it('properties of function should not disappear', () => {
Vue.component('todo', {
template: '<div/>',
props: ['testFn'],
setup(props) {
expect(props.testFn.a).toBe(2)
},
})

const vm = new Vue({
template: `
<div>
<todo :testFn="testFn"></todo>
</div>
`,
setup() {
const testFn = () => {
console.log(1)
}
testFn.a = 2
return { testFn }
},
}).$mount()
})
})
40 changes: 34 additions & 6 deletions test/v3/reactivity/ref.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,44 @@ describe('reactivity/ref', () => {
it('should keep symbols', () => {
const customSymbol = Symbol()
const obj = {
[Symbol.asyncIterator]: { a: 1 },
[Symbol.unscopables]: { b: '1' },
[customSymbol]: { c: [1, 2, 3] },
[Symbol.asyncIterator]: ref(1),
[Symbol.hasInstance]: { a: ref('a') },
[Symbol.isConcatSpreadable]: { b: ref(true) },
[Symbol.iterator]: [ref(1)],
[Symbol.match]: new Set<Ref<number>>(),
[Symbol.matchAll]: new Map<number, Ref<string>>(),
[Symbol.replace]: { arr: [ref('a')] },
[Symbol.search]: { set: new Set<Ref<number>>() },
[Symbol.species]: { map: new Map<number, Ref<string>>() },
[Symbol.split]: new WeakSet<Ref<boolean>>(),
[Symbol.toPrimitive]: new WeakMap<Ref<boolean>, string>(),
[Symbol.toStringTag]: { weakSet: new WeakSet<Ref<boolean>>() },
[Symbol.unscopables]: { weakMap: new WeakMap<Ref<boolean>, string>() },
[customSymbol]: { arr: [ref(1)] },
}

const objRef = ref(obj)

expect(objRef.value[Symbol.asyncIterator]).toBe(obj[Symbol.asyncIterator])
expect(objRef.value[Symbol.unscopables]).toBe(obj[Symbol.unscopables])
expect(objRef.value[customSymbol]).toStrictEqual(obj[customSymbol])
const keys: (keyof typeof obj)[] = [
Symbol.asyncIterator,
Symbol.hasInstance,
Symbol.isConcatSpreadable,
Symbol.iterator,
Symbol.match,
Symbol.matchAll,
Symbol.replace,
Symbol.search,
Symbol.species,
Symbol.split,
Symbol.toPrimitive,
Symbol.toStringTag,
Symbol.unscopables,
customSymbol,
]

keys.forEach((key) => {
expect(objRef.value[key]).toStrictEqual(obj[key])
})
})

test('unref', () => {
Expand Down

0 comments on commit a283e60

Please sign in to comment.