一、响应式状态、函数的暴露(对外提供)

两种方式
1)export指令 + setup函数
2)单文件组件中(SFC)中使用<script setup>

1)export指令 + setup函数

return的对象中内容,包括响应式状态,函数,都可以用在模板中。

import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)

    function increment() {
      // 在 JavaScript 中需要 .value
      count.value++
    }

    // 不要忘记同时暴露 increment 函数
    return {
      count,
      increment
    }
  }
}

2)单文件组件(SFC)的<script setup>

顶层中的内容,包括导入、声明的变量和函数,都可以用于模板中。

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

二、响应式对象声明

包括:
1)ref()函数
2)reactive()函数

1)ref() 函数

1)基于依赖追踪的响应式系统实现,ref对象能追踪模板中的使用者,当自身被修改时及时更新模板;
2)ref对象传给函数,也能保持响应式特性(优于reactive())
3)深层相应式能力,
4)浅层响应式能力,解决深层响应式过于“沉重”问题,shallowRef();
5)适用于任何值类型(优于reactive());

2)reactive()函数

1)建立原对象的JavaScript 代理,Vue 能够拦截对 reactive()产生的代理对象所有属性的访问和修改,以便进行依赖追踪和触发更新。这是和ref()的差异所在;
2)只适用于对象,JS内置集合型对象等。不适用于string, number,boolean等原子类型;
3)深层相应式能力,
4)浅层响应式能力,解决深层响应式过于“沉重”问题,shallowReactive();

三、ref对象和reactive对象的本质

两者本质差别源于javascript的两种劫持 property 访问的方式:getter / setters 和 Proxies。
1) ref对象仅仅使用了getter / setters特性的对象。其行为已经和被封闭对象不同。为方便使用,Vue增加了自动解包功能。

2) reactive对象除使用了getter / setters特性,还是个proxy对象。其行为和被封闭对象是一样的。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。为方便使用和行为一致,Vue增加了自动封包功能。

3)实现伪代码

// 伪代码
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}

function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

4)重复封包,结果一致

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

5)自动封包,如下例

const proxy = reactive({})

const raw1 = {}
const raw2 = reactive({});
proxy.raw1 = raw1	// set响应式对象时也会在内部调用reactive();
proxy.raw2 = raw2

console.log(proxy.raw1 === raw1) // false
console.log(proxy.raw2 === raw2) // true

四、自动解包还是替换

1)将ref对象作为reactive对象属性,在被访问时ref对象自动解包而成为普通变量

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1		// 自动解包,赋值
console.log(count.value) // 1,即实际上还是个ref对象

2)将ref对象赋值给已声明属性,则覆盖原有ref

3)ref对象作为数组或者集合对象的元素,被访问时不会自动解包

4)模板中,只有ref对象作为“顶层”被访问元素时,才会被解包

const count = ref(0)
const object = { id: ref(1) }
因此,这个表达式按预期工作:
{{ count + 1 }}

...但这个不会:
{{ object.id + 1 }}

五、测试

<script setup>

import { ref, reactive } from 'vue'

const state = reactive({})

const raw1= {a:0}
const raw2= reactive({a:0})
const ref1 = ref({a:0})

state.raw1 = raw1
state.raw2 = raw2
state.ref1 = ref1

function increment() {
raw1.a++;
raw2.a++;
ref1.value.a++;
}
</script>

<template>
  <button @click="increment">
    {{ state}}
  </button>

<p>raw1 :  {{raw1 }}</p>
<p>raw2: {{raw2 }}</p>
<p>ref1: {{ref1 }}</p>
<p>state: {{state }}</p>

<p>state.raw1 === raw1 :  {{state.raw1 === raw1}}</p>
<p>state.raw2 === raw2 :  {{state.raw2 === raw2}}</p>

</template>

六、计算属性 - 核心应用

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref - publishedBooksMessage 
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})

1)基于响应式状态对象
2)使用computed()函数,提供一个get()方法
3)返回一个计算属性ref对象,是一个“临时快照”
4)具有缓存特性
5)可以实现为可写的 “计算属性”

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})