Repository: vuejs/composition-api
Pull request #737 (2021-06-29 02:24:07)

fix: the hasOwn should be used to determine whether an attribute exists.
fix: #693 


Patch:
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 12b6955..5f4acb2 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -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/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 827223f..4691011 100644
--- a/src/reactivity/reactive.ts
+++ b/src/reactivity/reactive.ts
@@ -16,13 +16,19 @@ import { rawSet, accessModifiedSet } from '../utils/sets'
 
 export function isRaw(obj: any): boolean {
   return Boolean(
-    obj?.__ob__ && typeof obj.__ob__ === 'object' && obj.__ob__?.__raw__
+    obj &&
+      hasOwn(obj, '__ob__') &&
+      typeof obj.__ob__ === 'object' &&
+      obj.__ob__?.__raw__
   )
 }
 
 export function isReactive(obj: any): boolean {
   return Boolean(
-    obj?.__ob__ && typeof obj.__ob__ === 'object' && !obj.__ob__?.__raw__
+    obj &&
+      hasOwn(obj, '__ob__') &&
+      typeof obj.__ob__ === 'object' &&
+      !obj.__ob__?.__raw__
   )
 }
 
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/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/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({