• 基本数据类型(单类型):除Object。 String、Number、boolean、null、undefined。
  • 引用类型:object。里面包含的 function、Array、Date。

响应式系统 API

  • ref reactive

  • vue3通过ref reactive来定义响应式数据

    • ref我们用来将基本数据类型定义为响应式数据,其本质是基于 Object.defineProperty()
      • 基本数据类型(单类型):除Object。String、Number、boolean、null、undefined
    • 重新定义属性的方式来实现(ref更适合定义基本数据类型)
    • reactive用来将引用类型定义为响应式数据,其本质是基于 **Proxy**实现对象代理
      • 引用类型:object。里面包含的 function、Array、Date
    // 基本类型
    const nub = ref(0)
    const str = ref('inline')
    const boo = ref(false)
    // 引用类型
    const obj = reactive({
      name:'inline',
      age:'18'
    })
    const arr = reactive(['0','1','2'])

#open in new windowreactive

接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()

const obj = reactive({ count: 0 })

响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象。

  • 类型定义

    function reactive<T extends object>(raw: T): T
    

ref

接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。

  • 模板中访问

    当 ref 作为渲染上下文的属性返回(即在setup() 返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value

    <template>
      <div>{{ count }}</div>
    </template>
    
    <script>
      export default {
        setup() {
          return {
            count: ref(0),
          }
        },
      }
    </script>
    
  • 作为响应式对象的属性访问

    当 ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 value 值,其行为类似普通属性:

    const count = ref(0)
    const state = reactive({
      count,
    })
    
    console.log(state.count) // 0
    
    state.count = 1
    console.log(count.value) // 1
    
    • 注意如果将一个新的 ref 分配给现有的 ref, 将替换旧的 ref:
    const otherCount = ref(2)
    
    state.count = otherCount
    console.log(state.count) // 2
    console.log(count.value) // 1
    

    注意当嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套:

    const arr = reactive([ref(0)])
    // 这里需要 .value
    console.log(arr[0].value)
    
    const map = reactive(new Map([['foo', ref(0)]]))
    // 这里需要 .value
    console.log(map.get('foo').value)
    
  • 类型定义

    interface Ref<T> {
      value: T
    }
    
    function ref<T>(value: T): Ref<T>
    

    有时我们可能需要为 ref 做一个较为复杂的类型标注。我们可以通过在调用 ref 时传递泛型参数来覆盖默认推导:

    const foo = ref<string | number>('foo') // foo 的类型: Ref<string | number>
    
    foo.value = 123 // 能够通过!
    

#open in new windowreadonly

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 依赖追踪
  console.log(copy.count)
})

// original 上的修改会触发 copy 上的侦听
original.count++

// 无法修改 copy 并会被警告
copy.count++ // warning!

toRefs

  • toRefs会将我们一个响应式的对象转变为一个普通对象,然后将这个普通对象里的每一个属性变为一个响应式的数据

  • 当我们用 ...扩展运算符 时我们得到的只是一个普通类型的数值,并不是一个响应式数据

<div>
  <p>姓名:{{ name }}</p>
  <p>性别:{{ gender }}</p>
  <p>年龄:{{ age }}</p>
</div>


setup(){
  const obj = reactive({
    name:'inline',
    gender:'男',
    age:'18',
  })

  return{
    ...toRefs(obj),
  }
}

  • 注意 在<script setup> 中 不可以使用 解构赋值

const user = reactive({
  name:'aa',
  age:11,
  height:180
})

return {
  ...toRefs(user)
}
  • toRefs()函数可以将响应式对象转换为普通对象,但该普通对象中的每个属性都是一个单独的ref对象

  • 注意这个词 引用类型 ,这个时候我们进行解构(浅拷贝)之后,指向的还是同一个地址,因此响应式也不会丢失了。 toRef / toRefs

  • toRef 和 toRefs 可以用来复制 reactive 里面的属性然后转成 ref

    • 而且它既保留了响应式,也保留了引用,也就是你从 reactive 复制过来的属性进行修改后,除了视图会更新,原有 ractive 里面对应的值也会跟着更新

不加 s 和 加 s 的区别就是这样:

  • toRef: 复制 reactive 里的单个属性并转成 ref
  • toRefs: 复制 reactive 里的所有属性并转成 ref

toRef

作用:创建一个 ref 对象,其 value 值指向另一个对象中的某个属性。 语法:const name = toRef(person, ' name ') 应用:要将响应式对象中的某个属性单独提供给外部使用时 扩展:toRefs 与 toRef 功能一致,但可以批量创建多个 ref 对象 语法:toRefs(person)

proxy

<script>
        //源数据
        let person = {
            name:"张三",
            age:18
        }
        const p = new Proxy(person,{
            //有人读取p的某个属性时调用
            get(target, p, receiver) {
                console.log(`有人读取了p身上的${p}属性`);
                //return target[p]
                return Reflect.get(target,p)
            },
            //有人修改、增加p的某个属性时调用
            set(target, p, value, receiver) {
                console.log(`有人修改了p身上的${p},我要去更新界面了`);
                //target[p] = value
                Reflect.set(target,p,value)
            },
            //有人删除p的某个属性时调用
            deleteProperty(target, p) {
                console.log(`有人删除了p身上的${p},我要去更新界面了`);
                //return delete target[p]
                return Reflect.deleteProperty(target,p)
            }
        })
    </script>

  • 返回 person,然后取值时使用 person.name、person.age等,如果我们想返回它的某些字段,就需要使用 toRef
  • 例如返回 name 属性 name:toRef(person, "name")这样才能让数据依然是响应式的

#open in new windowcomputed

接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 refopen in new window 对象。

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

或者,接受一个具有 getset 函数的对象,用来创建可写的 ref 对象。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

类型声明:

// 只读的
function computed<T>(
  getter: () => T,
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// 可写的
function computed<T>(
  options: {
    get: () => T
    set: (value: T) => void
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>
interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

vue3中reactive赋值,不能响应式变化

  • 在使用vue3中,使用reactive创建的对象或者数组进行赋值时,可以正常赋值,但是不会触发响应式变化。
 let obj = {
     name:张三,
     age:23
 }
 let reactive = reaciive({})
 reactive = obj      // 可以正常赋值,但是不能响应式变化

原因

  • vue3官方文档说明:reactive() 返回一个对象的响应式代理
    • 为什么不生效: 每次直接把一个对象或者数组赋值给reactive创建的对象或数组时,导致reactive创建的响应式对象被新赋值的直接代理,再vue3中操作的都是proxy代理对象,所以失去了响应式
    • 在vue3中不管是对象还是数组都不能直接将整个数据进行赋值,这样会造成reactive定义的响应式失效
    • 通俗说: 就像对象的地址被替换,就不是原来的那个对象了
  • 解决方法
  • 不是直接赋值,包裹一层

可以直接对它对对象内进行赋值,不是重新赋值,不会失去响应式

let obj = reactive{
    arr = [1,2,3]
})
obj.arr = [4,5,6]   // 还是响应式
  1. 直接使用ref ref赋值时,包裹了一层对象
let arr = reactive([1,2,3])
let arr1 = ref([1,2,3])     // ref重新赋值时,通过.value赋值
  • 为什么会这样 vue3的响应式原理是通过es6的proxy实现的

  • proxy 的使用本身就是对于 对象的拦截, 通过new Proxy 的返回值,触发get和set方法,es6的Reflect(反射),动态对被代理对象的相应属性进行特定的操作

  • proxy是基于对象的拦截,如果本身是个原始值时,就拦截不到,失去了,所以ref本身实现响应式会使用.value

     let obj = {
      name: "许七安",
      age: 23,
      wife: {
        name: "洛玉衡",
      },
      hobby: ["勾栏听曲", "插花弄玉"],
    };
    let proxy = new Proxy(obj, {
      get: function (target, key) {
        console.log("触发get");
        return Reflect.get(target, key);
      },
      set: function (target, key, value) {
        console.log("触发set");
        return Reflect.set(target, key, value);
      },
    });
    console.log((proxy.name = "许银锣"));
    console.log((proxy.wife.name = "慕楠栀")); // 当修改更深的一层对象时,不会触发set事件,但是会修改
    console.log(proxy);

发现当嵌套一层时,不会触发响应式事件,无法拦截下来,所以这个时候就需要嵌套一层

  let obj = {
      name: "许七安",
      age: 23,
      wife: {
        name: "洛玉衡",
      },
      hobby: ["勾栏听曲", "插花弄玉"],
    };
    function reactive(obj) {
      return new Proxy(obj, {
        get: function (target, key) {
          console.log("触发get");
          // 判断如果是个对象在包装一次,实现深层嵌套的响应式
          if (typeof target[key] === "object") {
                    return reactive(target[key]);
                };
          return Reflect.get(target, key);
        },
        set: function (target, key, value) {
          console.log("触发set");
          return Reflect.set(target, key, value);
        },
      });
    }
    const proxy = reactive(obj)
    console.log((proxy.name = "许银锣"));
    console.log((proxy.wife.name = "慕楠栀")); // 这样就可以监听到set
    console.log(proxy);

这样就是.value嵌套一层实现响应式监听

assign

const dialogRuleForm = reactive({
  title: "",
  status: "",
  author: "",
});

fetchApi({id: row.id})
.then((res) => {
  // 如果 res.data 是一个对象
  Object.assign(dialogRuleForm,  res.data);

  // 如果 res.data 是一个数组
  Object.assign(dialogRuleForm,  {...res.data});
})
 setup() {
    // 需要一个带默认值的数组list;
  	let list = reactive([{id: 1, name: 'Andy'}])
    
    // 每次触发事件重置list,把新值放入,此种方式不会触发视图更新
    const checkBtn = () => {
      // 此时重置操作 地址指向变成一个新的地址,不会触发视图更新
      list = [{id: 1, name: 'Andy'}]
      list.push({id: 2, name: 'Lucy'})
    }
    
    // --------------------------------------------------
    
    // 正确的重置处理方法如下:使用reactive将数组包裹到一个对象中
    let list = reactive({
      data: [{id: 1, name: 'Andy'}]
    });
    const checkBtn = () => {
      list.data = [{id: 1, name: 'Andy'}]
      list.data.push({id: 2, name: 'Lucy'})
    }
    // 或者使用ref
    let list = ref([{id: 1, name: 'Andy'}]);
    const checkBtn = () => {
      list.value = [{id: 1, name: 'Andy'}]
      list.value.push({id: 2, name: 'Lucy'})
    }