vue3 effect.spec

2023-09-18 10:43:16

 🎬 岸边的风:个人主页

 🔥 个人专栏 :《 VUE 》 《 javaScript 》

⛺️ 生活的理想,就是为了理想的生活 !

在这里插入图片描述

目录

原型

观察的对象的变更会同步到原始对象

重复观察相同的原始对象直接返回相同的proxy对象

不会污染原始对象

通过toRaw api可以返回被观察对象的原始对象

shallowReactive

结语


  1. 定义一个对象original,reactive后返回observed,得到结果两个对象的引用不能相同,observed是可响应的,original不可响应,observed得值跟original相同,从这几个特点来看,我们很容易联想到proxy,对proxy还不熟悉同学可以点proxy
  test('Object', () => {
    const original = { foo: 1 }
    const observed = reactive(original)
    expect(observed).not.toBe(original)
    expect(isReactive(observed)).toBe(true)
    expect(isReactive(original)).toBe(false)
    // get
    expect(observed.foo).toBe(1)
    // has
    expect('foo' in observed).toBe(true)
    // ownKeys
    expect(Object.keys(observed)).toEqual(['foo'])
  })

  1. 原型

  test('proto', () => {
    const obj = {}
    const reactiveObj = reactive(obj)
    expect(isReactive(reactiveObj)).toBe(true)
    // read prop of reactiveObject will cause reactiveObj[prop] to be reactive
    // @ts-ignore
    const prototype = reactiveObj['__proto__']
    const otherObj = { data: ['a'] }
    expect(isReactive(otherObj)).toBe(false)
    const reactiveOther = reactive(otherObj)
    expect(isReactive(reactiveOther)).toBe(true)
    expect(reactiveOther.data[0]).toBe('a')
  })

  1. 定义一个嵌套对象, reactive后嵌套的属性也可以响应
  test('nested reactives', () => {
    const original = {
      nested: {
        foo: 1
      },
      array: [{ bar: 2 }]
    }
    const observed = reactive(original)
    expect(isReactive(observed.nested)).toBe(true)
    expect(isReactive(observed.array)).toBe(true)
    expect(isReactive(observed.array[0])).toBe(true)
  })

  1. 观察的对象的变更会同步到原始对象

  test('observed value should proxy mutations to original (Object)', () => {
    const original: any = { foo: 1 }
    const observed = reactive(original)
    // set
    observed.bar = 1
    expect(observed.bar).toBe(1)
    expect(original.bar).toBe(1)
    // delete
    delete observed.foo
    expect('foo' in observed).toBe(false)
    expect('foo' in original).toBe(false)
  })

  1. 给observed设置一个未被观察的值可以响应,看过vue2.x的同学应该都清楚,这个在vue2.x中是不可响应的
test('setting a property with an unobserved value should wrap with reactive', () => {
  const observed = reactive<{ foo?: object }>({})
  const raw = {}
  observed.foo = raw
  expect(observed.foo).not.toBe(raw)
  expect(isReactive(observed.foo)).toBe(true)
})

  1. 观察一个已经被observed的observe应该直接返回该observe
test('observing already observed value should return same Proxy', () => {
  const original = { foo: 1 }
  const observed = reactive(original)
  const observed2 = reactive(observed)
  expect(observed2).toBe(observed)
})

  1. 重复观察相同的原始对象直接返回相同的proxy对象

test('observing the same value multiple times should return same Proxy', () => {
  const original = { foo: 1 }
  const observed = reactive(original)
  const observed2 = reactive(original)
  expect(observed2).toBe(observed)
})

  1. 不会污染原始对象

test('should not pollute original object with Proxies', () => {
  const original: any = { foo: 1 }
  const original2 = { bar: 2 }
  const observed = reactive(original)
  const observed2 = reactive(original2)
  observed.bar = observed2
  expect(observed.bar).toBe(observed2)
  expect(original.bar).toBe(original2)
})

  1. 通过toRaw api可以返回被观察对象的原始对象

test('unwrap', () => {
  const original = { foo: 1 }
  const observed = reactive(original)
  expect(toRaw(observed)).toBe(original)
  expect(toRaw(original)).toBe(original)
})

test('should not unwrap Ref<T>', () => {
  const observedNumberRef = reactive(ref(1))
  const observedObjectRef = reactive(ref({ foo: 1 }))

  expect(isRef(observedNumberRef)).toBe(true)
  expect(isRef(observedObjectRef)).toBe(true)
})

test('should unwrap computed refs', () => {
  // readonly
  const a = computed(() => 1)
  // writable
  const b = computed({
    get: () => 1,
    set: () => {}
  })
  const obj = reactive({ a, b })
  // check type
  obj.a + 1
  obj.b + 1
  expect(typeof obj.a).toBe(`number`)
  expect(typeof obj.b).toBe(`number`)
})

  1. 不能直接被观察的类型
test('non-observable values', () => {
  const assertValue = (value: any) => {
    reactive(value)
    expect(
      `value cannot be made reactive: ${String(value)}`
    ).toHaveBeenWarnedLast()
  }

  // number
  assertValue(1)
  // string
  assertValue('foo')
  // boolean
  assertValue(false)
  // null
  assertValue(null)
  // undefined
  assertValue(undefined)
  // symbol
  const s = Symbol()
  assertValue(s)

  // built-ins should work and return same value
  const p = Promise.resolve()
  expect(reactive(p)).toBe(p)
  const r = new RegExp('')
  expect(reactive(r)).toBe(r)
  const d = new Date()
  expect(reactive(d)).toBe(d)
})

  1. markRaw 可以给将要被观察的数据打上标记,标记原始数据不可被观察
test('markRaw', () => {
  const obj = reactive({
    foo: { a: 1 },
    bar: markRaw({ b: 2 })
  })
  expect(isReactive(obj.foo)).toBe(true)
  expect(isReactive(obj.bar)).toBe(false)
})

  1. 被freeze的数据不可观察
test('should not observe frozen objects', () => {
  const obj = reactive({
    foo: Object.freeze({ a: 1 })
  })
  expect(isReactive(obj.foo)).toBe(false)
})

shallowReactive

只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理

  1. 属性的属性不会被观察
test('should not make non-reactive properties reactive', () => {
  const props = shallowReactive({ n: { foo: 1 } })
  expect(isReactive(props.n)).toBe(false)
})

  1. shallowReactive后的proxy的属性再次被reactive可以被观察
test('should keep reactive properties reactive', () => {
  const props: any = shallowReactive({ n: reactive({ foo: 1 }) })
  props.n = reactive({ foo: 2 })
  expect(isReactive(props.n)).toBe(true)
})

  1. iterating 不能被观察
test('should not observe when iterating', () => {
  const shallowSet = shallowReactive(new Set())
  const a = {}
  shallowSet.add(a)

  const spreadA = [...shallowSet][0]
  expect(isReactive(spreadA)).toBe(false)
})

  1. get 到的某个属性不能被观察
test('should not get reactive entry', () => {
  const shallowMap = shallowReactive(new Map())
  const a = {}
  const key = 'a'

  shallowMap.set(key, a)

  expect(isReactive(shallowMap.get(key))).toBe(false)
})

  1. foreach 不能被观察
test('should not get reactive on foreach', () => {
  const shallowSet = shallowReactive(new Set())
  const a = {}
  shallowSet.add(a)

  shallowSet.forEach(x => expect(isReactive(x)).toBe(false))
})

Vue3中响应数据核心是 reactive , reactive 中的实现是由 proxy 加 effect 组合,先来看一下 reactive 方法的定义

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 (target && (target as Target).__v_isReadonly) {
    return target
  }

  // 否则调用  createReactiveObject 创建 observe
  return createReactiveObject(
    target, 
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

createReactiveObject 创建 observe

// Target 目标对象
// isReadonly 是否只读 
// baseHandlers 基本类型的 handlers
// collectionHandlers 主要针对(set、map、weakSet、weakMap)的 handlers
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // 如果不是对象
  if (!isObject(target)) {
    // 在开发模式抛出警告,生产环境直接返回目标对象
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  // 如果目标对象已经是个 proxy 直接返回
  if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
    return target
  }
  // target already has corresponding Proxy
  if (
    hasOwn(target, isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive)
  ) {
    return isReadonly ? target.__v_readonly : target.__v_reactive
  }
  // only a whitelist of value types can be observed.

  // 检查目标对象是否能被观察, 不能直接返回
  if (!canObserve(target)) {
    return target
  }

  // 使用 Proxy 创建 observe 
  const observed = new Proxy(
    target,
    collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
  )

  // 打上相应标记
  def(
    target,
    isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive,
    observed
  )
  return observed
}

// 同时满足3个条即为可以观察的目标对象
// 1. 没有打上__v_skip标记
// 2. 是可以观察的值类型
// 3. 没有被frozen
const canObserve = (value: Target): boolean => {
  return (
    !value.__v_skip &&
    isObservableType(toRawType(value)) &&
    !Object.isFrozen(value)
  )
}

// 可以被观察的值类型
const isObservableType = /*#__PURE__*/ makeMap(
  'Object,Array,Map,Set,WeakMap,WeakSet'
)

结语

看到这里我们大概清楚 reactive 是做为整个响应式的入口,负责处理目标对象是否可观察以及是否已被观察的逻辑,最后使用 Proxy 进行目标对象的代理,对 es6 Proxy 概念清楚的同学应该 Proxy 重点的逻辑处理在 Handlers , 接下来我们就一起去看看各种 Handlers

如果你对 Proxy 还不理解,可以点这里学习

更多推荐

使用SSE(Server-Sent Events)实现服务端给客户端发消息

首先是客户端,看着比较简单。但实际应用中可能要比这复杂,因为默认sse只支持get请求,而且没法携带header。所以如果默认的方法达不到需求的话可能需要额外实现,当然也可以引用第三方库,比如@rangermauve/fetch-event-source。所以有谁会自己实现呢?if(typeof(EventSource

云端渲染对比本地渲染,哪个性价比更好呢?

当前环境来看,三维渲染的模式被分为云端渲染对比本地渲染两种模式。云渲染是什么?云渲染就是一种基于云计算的3D图形渲染解决方案,将3D程序放在远程的服务器中渲染。其用户终端通过Web软件或者直接在本地的3D程序中点击一个“云渲染”按钮并借助高速互联网接入访问资源,指令从用户终端中发出,服务器根据指令执行对应的渲染任务,而

Kakfa - Producer机制原理与调优

Producer是Kakfa模型中生产者组件,也就是Kafka架构中数据的生产来源,虽然其整体是比较简单的组件,但依然有很多细节需要细品一番。比如Kafka的Producer实现原理是什么,怎么发送的消息?IO通讯模型是什么?在实际工作中,怎么调优来实现高效性?简单的生产者程序:一、客户端初始化KafkaProduce

selenium自动化测试-获取动态页面小说

有的网站页面是动态加载的资源,使用bs4库只能获取静态页面内容,无法获取动态页面内容,通过selenium自动化测试工具可以获取动态页面内容。参考之前的"bs4库爬取小说工具"文章代码,稍微修改下,就可以转成获取动态页面小说工具。第一步:先确定目标网址先找到小说目录页面。网址首页:'https://www.bq0.ne

RockyLinux安装MariaDB

文章目录1前言2参考3开始安装3.1运行官方脚本添加MariaDB的源3.2安装MariaDBServer3.3启动MariaDB4SSH登录MariaDB4.1ssh上使用root账号登录4.2新建管理员账号并授权5放行端口33065.1VirtualBox上设置端口转发5.2RockyLinux防火墙放行3306端

自定义linux cp命令

基本要求:1.基本要求,实现文件的复制。编写程序实现cp命令的功能,程序源文件名为mycp.c,使用方法为:./mycp源文件名目标文件名2.扩展要求,当目标文件已存在时,给出提示是否进行覆盖,并根据用户的回应进行相应的操作。3.扩展要求,在上一步实现功能的基础上,为mycp增加选项,如果选项为-f,则当目标文件已存在

OpenAI即将推出新一代AI模型DALL-E 3;用AI进行天然产物药物发现的综述

🦉AI新闻🚀OpenAI即将推出新一代AI模型DALL-E3摘要:OpenAI正在准备推出下一代AI模型DALL-E3,并已进行了一系列Alpha测试。据分享,5月的测试版已能生成多种长宽比的图像,支持更长的提示语句,并生成“正常的文字”。然而,7月的版本可能会生成一些不适当的场景和受版权保护的商标图案。OpenA

台积电没有想到,当初拒绝的中国芯片企业,如今反过来抢夺市场了,后悔莫及...

台积电二季度的营收已经出现下滑,本来它希望四季度在以往苹果销售旺季的支持下再度推高收入,然而如今中国手机企业自研的国产5G芯片正在反过来抢占市场,台积电四季度的收入恐怕也将下降。苹果的销售旺季为每年的四季度,不过此时恰逢一家中国手机企业发布国产5G手机,采用了完全国产化的5G手机芯片,虽然都未知这款芯片的代工厂商,但可

Linux安装vivado方法

76585-Vivado2020.x-couldn'tloadfile"librdi_commontasks.so":libtinfo.so.5:cannotopensharedobjectfile:NosuchfileordirectoryUbuntu20.04userscanalsoinstallthelibtin

基于图像形态学处理和边缘提取算法的路面裂痕检测matlab仿真

目录1.算法运行效果图预览2.算法运行软件版本3.部分核心程序4.算法理论概述5.算法完整程序工程1.算法运行效果图预览2.算法运行软件版本matlab2022a3.部分核心程序[Rr,Cc]=size(Image1);%获取Image1矩阵的大小(行数和列数)%创建一个高斯滤波器G,大小为9x9,标准差为3G=fsp

如何在Ubuntu系统部署RabbitMQ服务器并公网访问【内网穿透】

文章目录前言1.安装erlang语言2.安装rabbitMQ3.内网穿透3.1安装cpolar内网穿透(支持一键自动安装脚本)3.2创建HTTP隧道4.公网远程连接5.固定公网TCP地址5.1保留一个固定的公网TCP端口地址5.2配置固定公网TCP端口地址前言RabbitMQ是一个在AMQP(高级消息队列协议)基础上完

热文推荐