Repository: vuejs/composition-api
Pull request #726 (2021-06-07 13:34:56)

chore: warning message grammar
Use present participle ('providing' instead of 'provide')


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d57d0e4..e27d675 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+<a name="1.0.0-rc.12"></a>
+# [1.0.0-rc.12](https://github.com/vuejs/composition-api/compare/v1.0.0-rc.11...v1.0.0-rc.12) (2021-06-17)
+
+
+### Bug Fixes
+
+* **proxyRefs:** infinite loop when using proxyRefs. ([#730](https://github.com/vuejs/composition-api/issues/730)) ([0b6ab25](https://github.com/vuejs/composition-api/commit/0b6ab25))
+* **reactivity:** check type of __ob__ in isRaw and isReactive ([#732](https://github.com/vuejs/composition-api/issues/732)) ([97dd671](https://github.com/vuejs/composition-api/commit/97dd671))
+* **watch:** watched previous values can't be destructure on first fire. ([#727](https://github.com/vuejs/composition-api/issues/727)) ([b3ab6f9](https://github.com/vuejs/composition-api/commit/b3ab6f9))
+
+
+
 <a name="1.0.0-rc.11"></a>
 # [1.0.0-rc.11](https://github.com/vuejs/composition-api/compare/v1.0.0-rc.10...v1.0.0-rc.11) (2021-06-04)
 
diff --git a/README.md b/README.md
index 4500334..dcf4e04 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ Include `@vue/composition-api` after Vue and it will install itself automaticall
 <!--cdn-links-start-->
 ```html
 <script src="https://cdn.jsdelivr.net/npm/vue@2.6"></script>
-<script src="https://cdn.jsdelivr.net/npm/@vue/composition-api@1.0.0-rc.11"></script>
+<script src="https://cdn.jsdelivr.net/npm/@vue/composition-api@1.0.0-rc.12"></script>
 ```
 <!--cdn-links-end-->
 
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 735d438..5f4acb2 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -40,7 +40,7 @@ import { ref, reactive } from '@vue/composition-api'
 <!--cdn-links-start-->
 ```html
 <script src="https://cdn.jsdelivr.net/npm/vue@2.6"></script>
-<script src="https://cdn.jsdelivr.net/npm/@vue/composition-api@1.0.0-rc.11"></script>
+<script src="https://cdn.jsdelivr.net/npm/@vue/composition-api@1.0.0-rc.12"></script>
 ```
 <!--cdn-links-end-->
 
@@ -399,7 +399,7 @@ watch(
 
 <details>
 <summary>
-⚠️ <code>readonly()</code> **只提供类型层面**的只读。
+⚠️ <code>readonly()</code> <b>只提供类型层面</b>的只读。
 </summary>
 
 `readonly()` 只在类型层面提供和 Vue 3 的对齐。在其返回值或其属性上使用 <code>isReadonly()</code> 检查的结果将无法保证。
diff --git a/package.json b/package.json
index b0ea90a..3988edb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@vue/composition-api",
-  "version": "1.0.0-rc.11",
+  "version": "1.0.0-rc.12",
   "description": "Provide logic composition capabilities for Vue.",
   "keywords": [
     "vue",
diff --git a/src/apis/watch.ts b/src/apis/watch.ts
index 3f61414..c3cb5a8 100644
--- a/src/apis/watch.ts
+++ b/src/apis/watch.ts
@@ -329,7 +329,8 @@ function createWatcher(
     // The subsequent callbacks will redirect to `callback`.
     let shiftCallback = (n: any, o: any) => {
       shiftCallback = originalCallback
-      applyCb(n, o)
+      // o is undefined on the first call
+      applyCb(n, isArray(n) ? [] : o)
     }
     callback = (n: any, o: any) => {
       shiftCallback(n, o)
diff --git a/src/reactivity/del.ts b/src/reactivity/del.ts
index d7a9607..aceb324 100644
--- a/src/reactivity/del.ts
+++ b/src/reactivity/del.ts
@@ -1,10 +1,11 @@
+import { AnyObject } from '../types/basic'
 import { getVueConstructor } from '../runtimeContext'
 import { hasOwn, isPrimitive, isUndef, isValidArrayIndex } from '../utils'
 
 /**
  * Delete a property and trigger change if necessary.
  */
-export function del(target: any, key: any) {
+export function del(target: AnyObject, key: any) {
   const Vue = getVueConstructor()
   const { warn } = Vue.util
 
diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts
index 38469e2..d63e7b1 100644
--- a/src/reactivity/reactive.ts
+++ b/src/reactivity/reactive.ts
@@ -15,11 +15,21 @@ import { isRef, UnwrapRef } from './ref'
 import { rawSet, accessModifiedSet } from '../utils/sets'
 
 export function isRaw(obj: any): boolean {
-  return Boolean(obj?.__ob__ && obj.__ob__?.__raw__)
+  return Boolean(
+    obj &&
+      hasOwn(obj, '__ob__') &&
+      typeof obj.__ob__ === 'object' &&
+      obj.__ob__?.__raw__
+  )
 }
 
 export function isReactive(obj: any): boolean {
-  return Boolean(obj?.__ob__ && !obj.__ob__?.__raw__)
+  return Boolean(
+    obj &&
+      hasOwn(obj, '__ob__') &&
+      typeof obj.__ob__ === 'object' &&
+      !obj.__ob__?.__raw__
+  )
 }
 
 /**
@@ -143,7 +153,7 @@ export function shallowReactive<T extends object = any>(obj: T): T
 export function shallowReactive(obj: any): any {
   if (!isObject(obj)) {
     if (__DEV__) {
-      warn('"shallowReactive()" is called without provide an "object".')
+      warn('"shallowReactive()" must be called on an object.')
     }
     return obj as any
   }
diff --git a/src/reactivity/readonly.ts b/src/reactivity/readonly.ts
index e223c91..552054b 100644
--- a/src/reactivity/readonly.ts
+++ b/src/reactivity/readonly.ts
@@ -1,5 +1,5 @@
 import { reactive, Ref, UnwrapRef } from '.'
-import { isArray, isPlainObject, warn } from '../utils'
+import { isArray, isPlainObject, isObject, warn } from '../utils'
 import { readonlySet } from '../utils/sets'
 import { isReactive, observe } from './reactive'
 import { isRef, RefImpl } from './ref'
@@ -49,6 +49,13 @@ export function readonly<T extends object>(
 
 export function shallowReadonly<T extends object>(obj: T): Readonly<T>
 export function shallowReadonly(obj: any): any {
+  if (!isObject(obj)) {
+    if (__DEV__) {
+      warn(`value cannot be made reactive: ${String(obj)}`)
+    }
+    return obj
+  }
+
   if (
     !(isPlainObject(obj) || isArray(obj)) ||
     (!Object.isExtensible(obj) && !isRef(obj))
diff --git a/src/reactivity/ref.ts b/src/reactivity/ref.ts
index ce8458a..32c187d 100644
--- a/src/reactivity/ref.ts
+++ b/src/reactivity/ref.ts
@@ -198,16 +198,16 @@ export function proxyRefs<T extends object>(
   for (const key of Object.keys(objectWithRefs)) {
     proxy(value, key, {
       get() {
-        if (isRef(value[key])) {
-          return value[key].value
+        if (isRef(value[RefKey][key])) {
+          return value[RefKey][key].value
         }
-        return value[key]
+        return value[RefKey][key]
       },
       set(v: unknown) {
-        if (isRef(value[key])) {
-          return (value[key].value = unref(v))
+        if (isRef(value[RefKey][key])) {
+          return (value[RefKey][key].value = unref(v))
         }
-        value[key] = unref(v)
+        value[RefKey][key] = unref(v)
       },
     })
   }
diff --git a/src/reactivity/set.ts b/src/reactivity/set.ts
index 669487c..d1baecf 100644
--- a/src/reactivity/set.ts
+++ b/src/reactivity/set.ts
@@ -1,3 +1,4 @@
+import { AnyObject } from '../types/basic'
 import { getVueConstructor } from '../runtimeContext'
 import { isArray, isPrimitive, isUndef, isValidArrayIndex } from '../utils'
 import { defineAccessControl } from './reactive'
@@ -7,7 +8,7 @@ import { defineAccessControl } from './reactive'
  * notification and intercept it's subsequent access if the property doesn't
  * already exist.
  */
-export function set<T>(target: any, key: any, val: T): T {
+export function set<T>(target: AnyObject, key: any, val: T): T {
   const Vue = getVueConstructor()
   const { warn, defineReactive } = Vue.util
   if (__DEV__ && (isUndef(target) || isPrimitive(target))) {


Test patch:
diff --git a/test/apis/watch.spec.js b/test/apis/watch.spec.js
index 33f5b62..eeb323e 100644
--- a/test/apis/watch.spec.js
+++ b/test/apis/watch.spec.js
@@ -457,7 +457,7 @@ describe('api/watch', () => {
         template: `<div>{{obj1.a}} {{obj2.a}}</div>`,
       }).$mount()
       expect(spy).toBeCalledTimes(1)
-      expect(spy).toHaveBeenLastCalledWith([1, 2], undefined)
+      expect(spy).toHaveBeenLastCalledWith([1, 2], [])
       obj1.a = 2
       obj2.a = 3
 
@@ -491,7 +491,7 @@ describe('api/watch', () => {
         template: `<div>{{a}} {{b}}</div>`,
       }).$mount()
       expect(spy).toBeCalledTimes(1)
-      expect(spy).toHaveBeenLastCalledWith([1, 1], undefined)
+      expect(spy).toHaveBeenLastCalledWith([1, 1], [])
       vm.a = 2
       expect(spy).toBeCalledTimes(1)
       waitForUpdate(() => {
@@ -553,7 +553,7 @@ describe('api/watch', () => {
         },
       })
       expect(spy).toBeCalledTimes(1)
-      expect(spy).toHaveBeenLastCalledWith([1, 1], undefined)
+      expect(spy).toHaveBeenLastCalledWith([1, 1], [])
       vm.a = 2
       expect(spy).toBeCalledTimes(2)
       expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1])
diff --git a/test/setup.spec.js b/test/setup.spec.js
index d63f72c..527bf89 100644
--- a/test/setup.spec.js
+++ b/test/setup.spec.js
@@ -1169,4 +1169,28 @@ describe('setup', () => {
 
     expect(warn).not.toBeCalled()
   })
+
+  it('should work with mock objects', async () => {
+    const originalProxy = new Proxy(
+      {},
+      {
+        get() {
+          return jest.fn()
+        },
+      }
+    )
+
+    const opts = {
+      template: `<div/>`,
+      setup() {
+        return {
+          proxy: originalProxy,
+        }
+      },
+    }
+    const Constructor = Vue.extend(opts).extend({})
+
+    const vm = new Vue(Constructor).$mount()
+    expect(vm.proxy).toBe(originalProxy)
+  })
 })
diff --git a/test/v3/reactivity/reactive.spec.ts b/test/v3/reactivity/reactive.spec.ts
index 4ae2e7d..7719a1e 100644
--- a/test/v3/reactivity/reactive.spec.ts
+++ b/test/v3/reactivity/reactive.spec.ts
@@ -8,6 +8,7 @@ import {
   shallowReactive,
   set,
   markRaw,
+  isRaw,
 } from '../../../src'
 
 describe('reactivity/reactive', () => {
@@ -35,6 +36,22 @@ describe('reactivity/reactive', () => {
     expect(Object.keys(observed)).toEqual(['foo'])
   })
 
+  //#693
+  test('the hasOwn should be used to determine whether an attribute exists.', () => {
+    const obj = {}
+    expect(isReactive(obj)).toBe(false)
+    expect(isRaw(obj)).toBe(false)
+    const mockObj = new Proxy(obj, {
+      get: (target, key) => {
+        if (!(key in Object.keys(target))) {
+          throw new Error(`the ${key.toString()} is not found in the target.`)
+        }
+      },
+    })
+    expect(isReactive(mockObj)).toBe(false)
+    expect(isRaw(obj)).toBe(false)
+  })
+
   test('proto', () => {
     const obj = {}
     const reactiveObj = reactive(obj)
diff --git a/test/v3/reactivity/readonly.spec.ts b/test/v3/reactivity/readonly.spec.ts
index 5272f48..a645715 100644
--- a/test/v3/reactivity/readonly.spec.ts
+++ b/test/v3/reactivity/readonly.spec.ts
@@ -382,6 +382,12 @@ describe('reactivity/readonly', () => {
       ).not.toHaveBeenWarned()
     })
 
+    test('should not process non-object data', () => {
+      // @ts-ignore
+      shallowReadonly(25)
+      expect(`value cannot be made reactive: 25`).toHaveBeenWarned()
+    })
+
     // #669
     test('shallowReadonly should work for refs', () => {
       const vm = new Vue({
diff --git a/test/v3/reactivity/ref.spec.ts b/test/v3/reactivity/ref.spec.ts
index 6806224..26f8078 100644
--- a/test/v3/reactivity/ref.spec.ts
+++ b/test/v3/reactivity/ref.spec.ts
@@ -12,6 +12,7 @@ import {
   unref,
   isReactive,
   shallowRef,
+  proxyRefs,
 } from '../../../src'
 
 describe('reactivity/ref', () => {
@@ -342,4 +343,29 @@ describe('reactivity/ref', () => {
     _trigger!()
     expect(dummy).toBe(2)
   })
+
+  test('proxyRefs', () => {
+    const a = {
+      x: ref(1),
+      obj: {
+        y: ref('foo'),
+      },
+    }
+    const p = proxyRefs(a)
+    expect(p.x).toBe(1)
+    expect(p.obj.y).toBe('foo')
+
+    // @ts-expect-error
+    p.obj.y = 'bar'
+    p.x = 2
+    expect(a.x).toBe(2)
+    expect(a.obj.y).toBe('bar')
+
+    const r = reactive({ k: 'v' })
+    const s = proxyRefs(r)
+    expect(s.k).toBe('v')
+
+    r.k = 'k'
+    expect(s.k).toBe('k')
+  })
 })