Vue3系列(二)Composition Api之Reactive系列(附源码解析)

119 阅读3分钟

reactive

根据传入的对象 ,创建返回一个深度响应式对象响应式对象看起来和传入的对象一样,但是,响应式对象属性值改动,不管层级有多深,都会触发响应式。新增和删除属性也会触发响应式。

import { reactive } from 'vue'

type My {
  name: string,
  age: number
}

let person = reactive<My>({
     name: '小张',
     age: 23
})
// 使用reactive 修改值时不需要通过.value
person.age = 18

注意

reactive 不可以绑定普通数据类型

reactive 只支持引用类型 array object map set

ref 支持所有类型 如果用ref去绑定对象或者数组等复杂的数据类型,源码里面也是去调用reactive创建

image.png

reactive 不可直接赋值

reactive是一个proxy代理的对象, 不可直接赋值, 否则会破坏响应式对象**

import { reactive } from 'vue'

let list = reactive<nmumber[]>([])

const change = () => {
    list = [1, 2, 3]
    // 这样赋值页面是不会变化的,直接赋值会脱离响应式
}

解决方法:

  1. 数组使用push+解构赋值
import { reactive } from 'vue'

let list = reactive<nmumber[]>([])

const change = () => {
    const arr = [1, 2, 3]
    // 使用push+解构赋值
    list.push(..arr)
    
}
  1. 把数组改为对象属性进行赋值
import { reactive } from 'vue'

type State = {
    list?:Array<number>
}

let state = reactive<State>({
    list: []
})

const change = () => {
    const arr = [1, 2, 3]
    // 通过对象属性进行赋值
    state.list = arr
}

shallowReactive

用来创建一个浅层响应式对象;这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的

import { shallowReactive } from 'vue'
 
const obj = {
  a: 1,
  first: {
    b: 2
  }
}
 
const state = shallowReactive(obj)
 
const change = () => {
  state.a = 7 // 页面可以动态响应
  state.first = { e: 12 } // 页面可以动态响应
  state.first.b = 8 // 页面无法动态响应 但console.log打印出来的结果是改变后的
  
  console.log(state);
}

reactive 源码解析

源码路径: /packages/reactivity/src/reactive.ts

// <T extends object> 通过泛型约束,只能传入引用类型的参数
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) { // 如果是只读的就直接返回
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

createReactiveObject

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 如果传入的是普通类型就直接返回并返回一个警告
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // exception: calling readonly() on a reactive object
  // 如果对象已经被proxy代理过了,也是直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 通过缓存的WeakMap去查找,如果能从缓存中找到就直接返回,设置缓存的代码在下面
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  // 如果在白名单中也直接返回,例如 通过markRaw添加__skip__跳过代理
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 上面所有的条件都没触发 才会通过proxy去代理
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy) // 设置缓存
  return proxy
}

readonly

拷贝一份proxy对象,将其设置为只读

import { reactive ,readonly} from 'vue'

const person = reactive({count:1})
person.count++ // 可以正常运行

const copy = readonly(person)
copy.count++ // 页面报错 readonly只能读取,无法修改