vue3 学习 --- composition api (2)

215 阅读8分钟

Reactive判断的API

API说明
isProxy检查对象是否是由 reactive 或 readonly创建的 proxy
isReactive检查对象是否是由 reactive创建的响应式代理
如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true
isReadonly检查对象是否是由 readonly 创建的只读代理
toRaw返回 reactive 或 readonly 代理的原始对象
可以看做是将proxy对象转回raw对象
不建议保留对原始对象的持久引用。请谨慎使用
shallowReactive创建一个浅层的响应式代理,它跟踪其自身 property 的响应性
但不执行嵌套对象的深层响应式转换 (深层还是原生对象)
shallowReadonly创建一个 proxy,使其自身的 property 为只读
但不执行嵌套对象的深度只读转换(深层还是可读、可写的)
浅层只读

isProxy

import { isProxy, reactive, ref, readonly } from 'vue'

export default {
  name: 'Home',

  setup() {
    const info = { name: 'Klaus' }
    console.log(isProxy(info)) // => false
    
    // ----------------------------------------------
    
    const info = reactive({ name: 'Klaus' })
    console.log(isProxy(info)) // => true
        
    // ---------------------------------------------
    
    // ref方法返回的是一个RefImpl对象(RefImplement)
    // 其值类似于 
    /* 
    	{
      	_rawValue: 10, 
      	_shallow: false, 
      	_value: 10, 
      	value: 10 
      }
    */
    const info = ref(10)
    console.log(isProxy(info)) // => false
    console.log(isProxy(info.value)) // => false
        
    // ---------------------------------------------
    
    // ref方法返回的是一个RefImpl对象(RefImplement)
    // 其值类似于 
    /* 
    	{
      	_rawValue: { name: 'Klaus' }, 
      	_shallow: false, 
      	_value: Proxy({ name: 'Klaus' }), 
      	value: Proxy({ name: 'Klaus' }) 
      }
    */
    const info = ref({ name: 'Klaus' })
    console.log(isProxy(info)) // => false
    console.log(isProxy(info.value)) // => true
    
    // ---------------------------------------------
    
   	const info = readonly(reactive({ name: 'Klaus' }))
    // => Proxy(reactive({ name: 'Klaus' })) --- 对reactiive对象进行了二次代理
    console.log(info) 
    console.log(isProxy(info)) // => true
        
    // ---------------------------------------------
    
   	const info = readonly(ref({ name: 'Klaus' }))
    // => Proxy(ref({ name: 'Klaus' })) --- 对ref对象进行了再次代理
    console.log(info) 
    console.log(isProxy(info)) // => true
  }
}

isReactive

import { isReactive, reactive, ref, readonly } from 'vue'

export default {
  name: 'Home',

  setup() {
    const info = ref({ name: 'Klaus' })
    console.log(isReactive(info)) // => true
    
    // ---------------------------------------------
    
    const info = ref({ name: 'Klaus' })
    console.log(isReactive(info)) // => false
    
    // ---------------------------------------------
    
    const info = readonly(reactive({ name: 'Klaus' }))
    console.log(isReactive(info)) // => true
  }
}

isReadonly

const info = readonly(reactive({ name: 'Klaus' }))
console.log(isReadonly(info)) // => true

// ---------------------------------------------

const info = readonly(ref({ name: 'Klaus' }))
console.log(isReadonly(info)) // => true

toRaw

// 如果参数是ref对象,那么ref对象会原封不动的返回
const info = toRaw(reactive({ name: 'Klaus' }))

shallowReactive

<template>
  <div>
    <!-- <h2>{{ info.foo }}</h2> -->
    <h2>{{ info.nested.bar }}</h2>
    <h2>{{ info.nested.demo.baz }}</h2>
    <button @click="changeState">change state</button>
  </div>
</template>

<script>
import { shallowReactive, isReactive } from 'vue'

export default {
  name: 'Foo',

  setup() {
    let info = shallowReactive({
      foo: 1,
      nested: {
        bar: 2,
        demo: {
          baz: 3
        }
      }
    })

    const changeState = () => {
      // info.foo++
      info.nested.bar++
      info.nested.demo.baz++
    }

    return {
      info,
      changeState
    }
  }
}
</script>

shallowReadonly

<template>
  <div>
    <h2>{{ state.foo }}</h2>
    <h2>{{ state.nested.bar }}</h2>
    <button @click="changeState">change state</button>
  </div>
</template>

<script>
import { shallowReadonly, isReadonly, reactive } from 'vue'

export default {
  name: 'Foo',

  setup() {
    const state = shallowReadonly(reactive({
      foo: 1,
      nested: {
        bar: 2
      }
    }))

    const changeState = () => {
      state.foo++ // state.foo是只读的,会报警告
      console.log(isReadonly(state.nested)) // false
      state.nested.bar++ // state.nested.bar不是只读的,界面中的值依旧可以正常修改
    }

    return {
      state,
      changeState
    }
  }
}
</script>

toRefs

如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值

解构后的值将不再是响应式的,因为proxy劫持的是reactive返回的对象

但是直接从中取值相当于是值的赋值,所以此时对解构后的值的改变依旧不会被劫持到

为此 Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref

这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化

<template>
  <div>
    {{ name }} --- {{ age }}
    <button @click="changeAge">change age</button>
  </div>
</template>

<script>
import { reactive } from 'vue'

export default {
  name: 'Home',

  setup() {
    let { name, age } = reactive({ name: 'Klaus', age: 23 })

    // 此时修改age属性不是响应式的
    const changeAge = () => age++

    return {
      name,
      age,
      changeAge
    }
  }
}
</script>
<template>
  <div>
    {{ name }} --- {{ age }}
    <button @click="changeAge">change age</button>
  </div>
</template>

<script>
import { reactive, toRefs } from 'vue'

export default {
  name: 'Home',

  setup() {
    /*
      1. 此时name和age在被解构后依旧是响应式的
      2. name 和 age 本质上都是一个ref对象
      3. info.name 和 name.value info.age 和 age.value 指向的是同一个对象
         所以当修改name的值的是,info.name的值也会相应发生改变,其余同理
    */
    const info = reactive({ name: 'Klaus', age: 23 })
    
    // 此时的age 和 name 是响应式的  
    let { name, age } = toRefs(info)

    // 解构出的age是一个ref对象,所以修改值的时候需要修改它的value属性
    const changeAge = () => age.value++

    return {
      name,
      age,
      changeAge
    }
  }
}
</script>
<template>
  <div>
    {{ name }} --- {{ info.age }}
    <button @click="changeAge">change age</button>
  </div>
</template>

<script>
import { reactive } from 'vue'

export default {
  name: 'Home',

  setup() {
    let { name, info } = reactive({ name: 'Klaus', info: { age: 23 } })

    // 需要注意的是:此时的age是可以响应式的,因为此时info是一个对象,是一个引用地址
    // 所以实际修改的还是原本的那个reactive对象的属性值
    const changeAge = () => info.age++

    return {
      name,
      info,
      changeAge
    }
  }
}
</script>

toRef

在上述案例中,我们需要修改的是age的值,也就是只有age属性的值是需要变成响应式的,name是不需要变成响应式的,

但是toRefs函数,会将name和age的值全部转换为ref对象,这其实是有损性能的

如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法

<template>
<div>
  {{ name }} --- {{ age }}
  <button @click="changeAge">change age</button>
  </div>
</template>

<script>
  import { reactive, toRef } from 'vue'

  export default {
    name: 'Home',

    setup() {
      const info = reactive({ name: 'Klaus', age: 23 })
      const { name } = info

      /*
      	1. toRef(对象, 属性名)
      	2. toRef函数的返回值就是一个Ref对象
    	*/
      let age = toRef(info, 'age')

      const changeAge = () => age.value++

      return {
        name,
        age,
        changeAge
      }
    }
  }
</script>

ref其他的API

API说明
unref如果参数是一个 ref,则返回内部值,否则返回参数本身
这是 val = isRef(val) ? val.value : val 的语法糖函数
isRef判断值是否是一个ref对象
shallowRef创建一个浅层的ref对象
triggerRef手动触发和 shallowRef 相关联的副作用
相对于手动触发shallowRef的更新操作

isRef

import { ref, isRef } from 'vue'

setup() {
  const age = ref(23)
  console.log(isRef(age)) // => true
}

unRef

// 注意 unref的r是小写的
import { ref, unref } from 'vue'

setup() {
  const age = ref(23)
  console.log(unref(age) === age.value) // => true
}

shallowRef

<template>
  <div>
    <h2>{{ user.name }}</h2>
    <button @click="change">change name</button>
  </div>
</template>

<script>
import { ref } from 'vue'

export default {
  name: 'Home',

  setup() {
    const user = ref({ name: 'Klaus' })

    // 界面实时发送了更新
    const change = () => user.value.name = 'Alex'

    return {
      user,
      change
    }
  }
}
</script>
<template>
  <div>
    <h2>{{ user.name }}</h2>
    <button @click="change">change name</button>
  </div>
</template>

<script>
import { shallowRef } from 'vue'

export default {
  name: 'Home',

  setup() {
    const user = shallowRef({ name: 'Klaus' })

    // 界面不会实时发送了更新 --- 但是直接修改user.value 界面依旧是响应式的
    const change = () => user.value.name = 'Alex'

    return {
      user,
      change
    }
  }
}
</script>

triggerRef

<template>
  <div>
    <h2>{{ user.name }}</h2>
    <button @click="change">change name</button>
  </div>
</template>

<script>
import { shallowRef, triggerRef } from 'vue'

export default {
  name: 'Home',

  setup() {
    const user = shallowRef({ name: 'Klaus' })

    const change = () => {
      user.value.name = 'Alex'
      // 强制更新ref对象 --- 参数为需要更新的那个ref对象
      triggerRef(user)
    }

    return {
      user,
      change
    }
  }
}
</script>

customRef

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制

  1. 参数是一个工厂函数,应该返回一个带有 get 和 set 的对象
  2. 该工厂函数接受 track 和 trigger 函数作为参数
    • track === 收集依赖
    • trigger === 触发更新

模拟ref

src/hooks/useCustomRef.js --- 自定义ref

import { customRef } from 'vue'

export default function(value) {
  return customRef((track, trigger) => ({
    set(newValue) {
      // 设置值
      value = newValue // 新值覆盖旧值
      trigger() // 触发更新
    },

    get() {
      // 取值的时候,就将调用track函数,vue会自动将使用的部分记录为value的对应依赖
      // 在vue中所有使用某一个值的地方(这里以value为例),就被称之为value的依赖,或者说是value的副作用
      // 或可以被称之为vue的dependencies(简写为 deps)
      track()
      return value
    }
  }))
}

Home.vue --- 使用自定义ref

<template>
  <div>
    <h2>{{ num }}</h2>
    <button @click="changeNum">change num</button>
  </div>
</template>

<script>
import useCustomRef from '@/hooks/useCustomRef.js'

export default {
  name: 'Home',

  setup() {
    let num = useCustomRef(10)

    const changeNum = () => {
      // 返回的值是ref,所以修改值的时候需要使用value属性来取值
      num.value++
    }

    return {
      num,
      changeNum
    }
  }
}
</script>

自定义debounce(节流)ref

src/hooks/useDebounceRef.js

import { customRef } from 'vue'

export default function(value, delay = 2000) {
  return customRef((track, trigger) => {
    let timer = null

    return {
      set(newValue) {
        if (timer) {
          clearTimeout(timer)
        }

        timer = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      },

      get() {
        track()
        return value
      }
    }
  })
}

Home.vue

<template>
  <div>
    <input type="text" v-model="info">
    <p>{{ info }}</p>
  </div>
</template>

<script>
import useDebounceRef from '@/hooks/useDebounceRef.js'

export default {
  name: 'Home',

  setup() {
    const msg = 'Hello'

    let info = useDebounceRef(msg, 1000)

    return {
      info
    }
  }
}
</script>

computed

计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理

在前面的Options API中,我们是使用computed选项来完成的

在Composition API中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性

如何使用computed呢?

方式一: 接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象

方式二: 接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象

<template>
  <div>
    <h2>{{ fullName }}</h2>
    <button @click="change">change</button>
  </div>
</template>

<script>
import { computed, ref } from 'vue'

export default {
  name: 'Home',

  setup() {
    const firstName = ref('Klaus')
    const lastName =  ref('Wang')

    // 计算属性fullName本身也是一个ref对象
    // 计算属性的依赖项本身也必须是ref对象,只有这样,才可以监听到依赖项的改变
    const fullName = computed(() => firstName.value + ' - ' + lastName.value)

    const change = () => firstName.value = 'Alex'

    return {
      fullName,
      change
    }
  }
}
</script>

同样,computed的本质是一个有set方法和get方法的对象

<template>
  <div>
    <h2>{{ fullName }}</h2>
    <button @click="change">change</button>
  </div>
</template>

<script>
import { computed, ref } from 'vue'

export default {
  name: 'Home',

  setup() {
    const firstName = ref('Klaus')
    const lastName =  ref('Wang')

    let fullName = computed({
      get() { return firstName.value + ' - ' + lastName.value },
      set(v) { console.log(v) }
    })

    const change = () => fullName.value = 'Alex - Li'

    return {
      fullName,
      change
    }
  }
}
</script>

watch

我们可以通过watch选项来侦听data或者props的数据变化,当数据变化时执行某一些 操作

在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听

  • watchEffect用于自动收集响应式数据的依赖
  • watch需要手动指定侦听的数据源

watchEffect

<template>
  <div>
    <h2>{{ name }}</h2>
    <h2>{{ age }}</h2>

    <button @click="changeName">change name</button>
    <button @click="changeAge">change age</button>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue'

export default {
  name: 'Home',

  setup() {
    const name = ref('Klaus')
    const age = ref(23)

    const changeName = () => name.value = 'Alex'
    const changeAge = () => age.value++

    /*
      1. 参数为一个回调函数
      2. 这个方法会在初始的时候被执行  --- immediate是强制开启的
         这么做的目的是因为,这个函数在初始化被执行的时候,会收集其中所有的响应式数据,并将该函数作为响应式数据的依赖
         例如: 在这里,name是响应式数据,所以该回调函数变成了响应式数据name的依赖(副作用)
              当name的值发生改变的时候,该回调函数会作为name的依赖也一起被重新执行
              而此处age并不在该回调函数中,所以age的值的改变并不会被watchEffect监听到 
    */
    watchEffect(() => {
      console.log(name.value)
    })

    return {
      name,
      age,
      changeName,
      changeAge
    }
  }
}
</script>
停止侦听

在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可

<template>
  <div>
    <h2>{{ age }}</h2>
    <button @click="changeAge">change age</button>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue'

export default {
  name: 'Home',

  setup() {
    const age = ref(23)

    // watchEffect有一个返回值,是一个函数,可以用来取消监听
    const stop = watchEffect(() => {
      // 当age的值大于30的时候,且age的值发生改变的时候,界面依旧会更新
      // 但是监听函数将不会再被执行
      console.log(age.value)
    })

    const changeAge = () => {
      age.value++

      if (age.value > 30) {
        // 取消监听
        stop()
      }
    }


    return {
      age,
      changeAge
    }
  }
}
</script>
清除副作用

什么是清除副作用呢?

比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用

在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate

  • 副作用即将重新执行 或者 侦听器被停止(停止或组件被销毁的时候,onInvalidate就会被执行) 时会执行该函数传入的回调函数

  • 我们可以在传入的回调函数中,执行一些清除工作

<template>
  <div>
    <h2>{{ num }}</h2>
    <button @click="handleClick">click me</button>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue'

export default {
  name: 'Home',

  setup() {
    let timer = null
    const num = ref(0)

    const handleClick = () => {
      num.value++
    }

    // watchEffect有一个参数onInvalidate,其本身也是一个函数
    // onInvalidate函数的参数也是一个函数
    watchEffect(onInvalidate => {
      if (num.value > 10) {
        // 建立对num的监听
        console.log('num is bigger then 10')
      }

      timer = setTimeout(() => {
        console.log('num被改变了~')
      }, 2000)

      // 当取消监听或者依赖项发生改变的时候
      // 这个函数会有优先于所有的监听逻辑执行
      // 也就是初始化的时候,这个函数是不会被执行的
      onInvalidate(() => {
        console.log('onInvalidate')
        clearTimeout(timer)
      })
    })

    return {
      num,
      handleClick
    }
  }
}
</script>
执行时机
<template>
  <div>
    <h2 ref="numElem">{{ num }}</h2>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue'

export default {
  name: 'Home',

  setup() {
    const num = ref(0)

    // 如果参数是null或者undefined或者不传的时候
    // vue会认为你想获取dom,类似于vue的this.$refs
    // 如果你要获取的是dom元素的话,一般推荐参数设置为null
    
    // 返回的ref对象要被设置到对应元素的ref属性中
    const numElem = ref(null)


    watchEffect(() => {
      console.log(numElem.value)
      // 第一次输出 null -- 可以是null,也可以是undefined,取决于你之前传递的值,这里是ref(null),所以第一次打印null
      // 第二次输出 <h2>0</h2> -- dom挂载完毕,所以此时的结果编程了dom元素
    })

    return {
      num,
      numElem
    }
  }
}
</script>

在上述案例中,我们会发现打印结果打印了两次

  • 这是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null
  • 而当DOM挂载时,会给title的ref对象赋值新的值,副作用函数会再次执行,打印出来对应的元素

如果我们希望在第一次的时候就打印出来对应的元素呢?

  1. 使用if函数进行判断

    watchEffect(() => {
      if (numElem.value) {
        console.log(numElem.value)
      }
    })
    
  2. 修改副作用监听的执行时机

    // watchEffect的第一个参数是回调函数,第二个参数是一个配置对象
    // 在配置对象中,我们可以改变副作用函数的执行时机
    // 属性flush的默认值为pre,表示的是函数在创建完毕或者更新之前执行
    // flush的值修改为post后,表示的是函数在挂载完毕或者更新之前执行
    // flush还支持一个属性 sync,官方不推荐使用,了解即可
    watchEffect(() => {
      console.log(numElem.value)
    }, {
      flush: 'post'
    })
    

watch

watch的API完全等同于组件watch选项的Property

  • watch需要侦听特定的数据源,并在回调函数中执行副作用
  • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调

与watchEffect的比较,watch允许我们:

  • 懒执行副作用(第一次不会直接执行)
  • 更具体的说明当哪些状态发生变化时,触发侦听器的执行
  • 访问侦听状态变化前后的值
单个数据源

watch侦听函数的数据源有两种类型:

  • 一个getter函数: 但是该getter函数必须引用可响应式的对象(比如reactive或者ref)
  • 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref)

监听一个具体的属性

<template>
  <div>
    <h2>{{ info.name }}</h2>
    <h2>{{ info.age }}</h2>

    <button @click="change">click me</button>
  </div>
</template>

<script>
import { reactive, watch } from 'vue'
export default {
  name: 'Home',

  setup() {
    const info = reactive({ name: 'Klaus', age: 18 })

    // 监听某一个具体的属性
    // watch(参数1, 参数2)
    // 参数1 --- 一个get函数,返回需要监听的属性
    // 参数2 ---- handler函数
    watch(() => info.age, (newV, oldV) => {
      console.log('newV', newV)
      console.log('olfV', oldV)
    })

    const change = () => info.age++

    return {
      info,
      change
    }
  }
}
</script>

监听一个reactive对象

<template>
  <div>
    <h2>{{ info.name }}</h2>
    <h2>{{ info.age }}</h2>

    <button @click="change">click me</button>
  </div>
</template>

<script>
import { reactive, watch } from 'vue'
export default {
  name: 'Home',

  setup() {
    const info = reactive({ name: 'Klaus', age: 18 })

    // 监听一个reactive对象
    // newV 和 oldV 执行的是同一个对象 所以此时oldV是没有意义的
    // newV 和 oldV 的值都是 reactive对象
    watch(info, (newV, oldV) => {
      console.log('newV', newV)
      console.log('olfV', oldV)
    })

    const change = () => info.age++

    return {
      info,
      change
    }
  }
}
</script>
<template>
  <div>
    <h2>{{ info.name }}</h2>
    <h2>{{ info.age }}</h2>

    <button @click="change">click me</button>
  </div>
</template>

<script>
import { reactive, watch } from 'vue'
export default {
  name: 'Home',

  setup() {
    const info = reactive({ name: 'Klaus', age: 18 })

    // 监听reactive对象,但是将newV和oldV的值转变为原生对象
    // 执行get函数,将reactive对象进行解构后再重新拼成一个新的对象
    // 因为默认情况下,解构出来的数据不是响应式的数据,是原生的数据
    // 所以重新组合成的对象也是一个原生对象
    watch(() => ({...info}), (newV, oldV) => {
      console.log('newV', newV)
      console.log('olfV', oldV)
    })

    const change = () => info.age++

    return {
      info,
      change
    }
  }
}
</script>

监听一个ref对象

<template>
  <div>
    <h2>{{ info.name }}</h2>
    <h2>{{ info.age }}</h2>

    <button @click="change">click me</button>
  </div>
</template>

<script>
import { ref, watch } from 'vue'
export default {
  name: 'Home',

  setup() {
    const info = { name: 'Klaus', age: 18 }
    const age = ref(info.age)

    // 第一个参数也可以是ref对象
    // 如果是ref对象,那么newV和oldV是ref对象的value属性值
    // 即返回的是ref.value 不是ref对象
    watch(age, (newV, oldV) => {
      console.log('newV', newV)
      console.log('olfV', oldV)
    })

    const change = () => age.value++

    return {
      info,
      change
    }
  }
}
</script>
多个数据源
const name = ref('Klaus')
const age = ref(23)

// newV 和 oldV的值是一个数组
watch([name, age], (newV, oldV) => {
  console.log('newV', newV)
  console.log('olfV', oldV)
})
// 可以使用解构的方式来获取到具体的值
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
  console.log('newName',  newName)
  console.log('newAge',  newAge)
  console.log('oldName',  oldName)
  console.log('oldAge',  oldAge)
})
配置参数

immediate

// watch有第三个参数,可以用来传递配置项,选项有immediate和deep
watch(age, () => {
  console.log('监听被触发了')
}, {
  immediate: true // 组件创建完毕,立即执行监听 --- 此时oldValue的值是undefined
})

deep

reactive对象默认会开启深度监听

<template>
  <div>
    <h2>{{ info.friend.name }}</h2>

    <button @click="change">change</button>
  </div>
</template>

<script>
import { reactive, watch } from 'vue'
export default {
  name: 'Home',

  setup() {
    const info = reactive({ name: 'Klaus', friend: { name: 'Alex' } })

    // reactive对象默认会开启深度监听
    watch(info, () => {
      console.log('reactive对象默认会开启深度监听')
    })

    const change = () => info.friend.name = 'Jhon'


    return {
      info,
      change
    }
  }
}
</script>

ref对象需要手动设置深度监听

<template>
  <div>
    <h2>{{ info.friend.name }}</h2>

    <button @click="change">change</button>
  </div>
</template>

<script>
import { ref, watch } from 'vue'
export default {
  name: 'Home',

  setup() {
    const info = ref({ name: 'Klaus', friend: { name: 'Alex' } })

    // ref对象需要手动设置深度监听
    watch(info, () => {
      console.log('ref对象需要手动设置深度监听')
    }, {
      deep: true
    })

    const change = () => info.value.friend.name = 'Jhon'


    return {
      info,
      change
    }
  }
}
</script>