Repository: vuejs/composition-api (ts)

Issue:
Set api won't trigger update？
here is two example about set api?
**vue2**
```
<template>
  <button @click="addA">Temp1 {{ obj.a }}</button>
</template>
<script>
export default {
  data() {
    return {
      obj: {},
    };
  },
  methods: {
    addA() {
      this.$set(this.obj, "a", "new property"); // It's work and dom update
    },
  },
};
</script>
```

**vca**
```
<template>
  <button @click="addA">Temp2 {{ obj.a }}</button>
</template>
<script>
import Vue from "vue";
import VueCompositionApi, {
  defineComponent,
  reactive,
  set,
} from "@vue/composition-api";

Vue.use(VueCompositionApi);

export default defineComponent({
  setup() {
    const obj = reactive({});
    const addA = () => {
      set(obj, "a", "new property"); // Obj change but dom not update
    };
    return {
      obj,
      addA,
    };
  },
});
</script>
```
Here is a [demo](https://codesandbox.io/s/romantic-ardinghelli-8urhp?file=/src/Temp1.vue)

Pull request:
feat: add and delete object attributes would trigger update.
fix #683 #603 #580.

Patch:
diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts
index fec1cea..3777958 100644
--- a/src/reactivity/reactive.ts
+++ b/src/reactivity/reactive.ts
@@ -141,9 +141,11 @@ function mockObserver(value: any = {}): any {
 
 export function shallowReactive<T extends object = any>(obj: T): T
 export function shallowReactive(obj: any): any {
-  if (__DEV__ && !obj) {
-    warn('"shallowReactive()" is called without provide an "object".')
-    return
+  if (!isObject(obj)) {
+    if (__DEV__) {
+      warn('"shallowReactive()" is called without provide an "object".')
+    }
+    return obj as any
   }
 
   if (
diff --git a/src/utils/instance.ts b/src/utils/instance.ts
index 3ee24ed..5fa4e4b 100644
--- a/src/utils/instance.ts
+++ b/src/utils/instance.ts
@@ -1,7 +1,7 @@
 import { ComponentInstance } from '../component'
 import vmStateManager from './vmStateManager'
 import { setCurrentInstance, getCurrentVue2Instance } from '../runtimeContext'
-import { Ref, isRef } from '../apis'
+import { Ref, isRef, isReactive } from '../apis'
 import { hasOwn, proxy, warn } from './utils'
 import { createSlotProxy, resolveSlots } from './helper'
 
@@ -20,8 +20,19 @@ export function asVmProperty(
         },
       })
     } else {
-      // @ts-ignore
-      vm[propName] = propValue
+      Object.defineProperty(vm, propName, {
+        enumerable: true,
+        configurable: true,
+        get: () => {
+          if (isReactive(propValue)) {
+            ;(propValue as any).__ob__.dep.depend()
+          }
+          return propValue
+        },
+        set: (val) => {
+          propValue = val
+        },
+      })
     }
 
     if (__DEV__) {


Test patch:
diff --git a/test/setup.spec.js b/test/setup.spec.js
index 3532984..2bcbe44 100644
--- a/test/setup.spec.js
+++ b/test/setup.spec.js
@@ -13,6 +13,8 @@ const {
   isReactive,
   defineComponent,
   onMounted,
+  set,
+  del,
 } = require('../src')
 const { sleep } = require('./helpers/utils')
 
@@ -896,6 +898,42 @@ describe('setup', () => {
     expect(vm.$el.textContent).toBe('2')
   })
 
+  // #683 #603 #580
+  it('should update directly when adding attributes to a reactive object', async () => {
+    const vm = new Vue({
+      template: '<div><button @click="add"/>{{ obj.a }}</div>',
+      setup() {
+        const obj = reactive({})
+        const add = () => {
+          set(obj, 'a', 'new property')
+        }
+        return { obj, add }
+      },
+    }).$mount()
+
+    expect(vm.$el.textContent).toBe('')
+    await vm.$el.querySelector('button').click()
+    expect(vm.$el.textContent).toBe('new property')
+  })
+
+  // #683 #603 #580
+  it('should update directly when deleting attributes from a reactive object', async () => {
+    const vm = new Vue({
+      template: '<div><button @click="deleting"/>{{ obj.a }}</div>',
+      setup() {
+        const obj = reactive({ a: 'hello' })
+        const deleting = () => {
+          del(obj, 'a')
+        }
+        return { obj, deleting }
+      },
+    }).$mount()
+
+    expect(vm.$el.textContent).toBe('hello')
+    await vm.$el.querySelector('button').click()
+    expect(vm.$el.textContent).toBe('')
+  })
+
   // #524
   it('should work with reactive arrays.', async () => {
     const opts = {
diff --git a/test/v3/reactivity/reactive.spec.ts b/test/v3/reactivity/reactive.spec.ts
index 5576836..75f9e82 100644
--- a/test/v3/reactivity/reactive.spec.ts
+++ b/test/v3/reactivity/reactive.spec.ts
@@ -207,4 +207,34 @@ describe('reactivity/reactive', () => {
       expect(isReactive(props.n)).toBe(true)
     })
   })
+
+  test('should shallowReactive non-observable values', () => {
+    const assertValue = (value: any) => {
+      expect(shallowReactive(value)).toBe(value)
+    }
+
+    // number
+    assertValue(1)
+    // string
+    assertValue('foo')
+    // boolean
+    assertValue(false)
+    // null
+    assertValue(null)
+    // undefined
+    assertValue(undefined)
+    // symbol
+    const s = Symbol()
+    assertValue(s)
+
+    expect(warn).toBeCalledTimes(6)
+    expect(
+      warn.mock.calls.map((call) => {
+        expect(call[0]).toBe(
+          '[Vue warn]: "shallowReactive()" is called without provide an "object".'
+        )
+      })
+    )
+    warn.mockReset()
+  })
 })