effect.spec

effect 作为 reactive 的核心,重要程序不可言喻,主要负责监听响应式数据的变化,触发监听函数的执行逻辑,两句话就能描述清楚的概念,单测文件就长达700多行,下面让我们一起来看详细用例

文档直通车: https://vue3js.cn/vue-composition-api/#reactive

  1. 传递给effect的方法,会立即执行一次
  1. it('should run the passed function once (wrapped by a effect)', () => {
  2. const fnSpy = jest.fn(() => {})
  3. effect(fnSpy)
  4. expect(fnSpy).toHaveBeenCalledTimes(1)
  5. })
  1. effect 执行将 observe 对基本类型赋值,observe 进行改变时,将反应到基本类型上

    1. it('should observe basic properties', () => {
    2. let dummy
    3. const counter = reactive({ num: 0 })
    4. effect(() => (dummy = counter.num))
    5. expect(dummy).toBe(0)
    6. counter.num = 7
    7. expect(dummy).toBe(7)
    8. })
  2. 同上,不过我们从这个单测就能看出来effect 中是有 cache 存在的

    1. it('should observe multiple properties', () => {
    2. let dummy
    3. const counter = reactive({ num1: 0, num2: 0 })
    4. effect(() => (dummy = counter.num1 + counter.num1 + counter.num2))
    5. expect(dummy).toBe(0)
    6. counter.num1 = counter.num2 = 7
    7. expect(dummy).toBe(21)
    8. })
  3. 在多个 effect 中处理 observe,当 observe 发生改变时,将同步到多个 effect

    1. it('should handle multiple effects', () => {
    2. let dummy1, dummy2
    3. const counter = reactive({ num: 0 })
    4. effect(() => (dummy1 = counter.num))
    5. effect(() => (dummy2 = counter.num))
    6. expect(dummy1).toBe(0)
    7. expect(dummy2).toBe(0)
    8. counter.num++
    9. expect(dummy1).toBe(1)
    10. expect(dummy2).toBe(1)
    11. })
  4. 嵌套的 observe 做出改变时,也会产生响应

    1. it('should observe nested properties', () => {
    2. let dummy
    3. const counter = reactive({ nested: { num: 0 } })
    4. effect(() => (dummy = counter.nested.num))
    5. expect(dummy).toBe(0)
    6. counter.nested.num = 8
    7. expect(dummy).toBe(8)
    8. })
  5. effect 执行将 observe 对基本类型赋值,observe 进行删除操作时,将反应到基本类型上

    1. it('should observe delete operations', () => {
    2. let dummy
    3. const obj = reactive({ prop: 'value' })
    4. effect(() => (dummy = obj.prop))
    5. expect(dummy).toBe('value')
    6. delete obj.prop
    7. expect(dummy).toBe(undefined)
    8. })
  6. effect 执行将 observe in 操作,observe 进行删除操作时,将反应到基本类型上

    1. it('should observe has operations', () => {
    2. let dummy
    3. const obj = reactive<{ prop: string | number }>({ prop: 'value' })
    4. effect(() => (dummy = 'prop' in obj))
    5. expect(dummy).toBe(true)
    6. delete obj.prop
    7. expect(dummy).toBe(false)
    8. obj.prop = 12
    9. expect(dummy).toBe(true)
    10. })
  7. prototype 的操作也能响应

    1. it('should observe properties on the prototype chain', () => {
    2. let dummy
    3. const counter = reactive({ num: 0 })
    4. const parentCounter = reactive({ num: 2 })
    5. Object.setPrototypeOf(counter, parentCounter)
    6. effect(() => (dummy = counter.num))
    7. expect(dummy).toBe(0)
    8. delete counter.num
    9. expect(dummy).toBe(2)
    10. parentCounter.num = 4
    11. expect(dummy).toBe(4)
    12. counter.num = 3
    13. expect(dummy).toBe(3)
    14. })

9.

  1. it('should observe inherited property accessors', () => {
  2. let dummy, parentDummy, hiddenValue: any
  3. const obj = reactive<{ prop?: number }>({})
  4. const parent = reactive({
  5. set prop(value) {
  6. hiddenValue = value
  7. },
  8. get prop() {
  9. return hiddenValue
  10. }
  11. })
  12. Object.setPrototypeOf(obj, parent)
  13. effect(() => (dummy = obj.prop))
  14. effect(() => (parentDummy = parent.prop))
  15. expect(dummy).toBe(undefined)
  16. expect(parentDummy).toBe(undefined)
  17. obj.prop = 4
  18. expect(dummy).toBe(4)
  19. // this doesn't work, should it?
  20. // expect(parentDummy).toBe(4)
  21. parent.prop = 2
  22. expect(dummy).toBe(2)
  23. expect(parentDummy).toBe(2)
  24. })
  1. function 的操作也能响应

    1. it('should observe function call chains', () => {
    2. let dummy
    3. const counter = reactive({ num: 0 })
    4. effect(() => (dummy = getNum()))
    5. function getNum() {
    6. return counter.num
    7. }
    8. expect(dummy).toBe(0)
    9. counter.num = 2
    10. expect(dummy).toBe(2)
    11. })
  2. iteration 响应

    1. it('should observe iteration', () => {
    2. let dummy
    3. const list = reactive(['Hello'])
    4. effect(() => (dummy = list.join(' ')))
    5. expect(dummy).toBe('Hello')
    6. list.push('World!')
    7. expect(dummy).toBe('Hello World!')
    8. list.shift()
    9. expect(dummy).toBe('World!')
    10. })
  3. 数组隐式的变化可以响应 ```js it(‘should observe implicit array length changes’, () => { let dummy const list = reactive([‘Hello’]) effect(() => (dummy = list.join(‘ ‘)))

    expect(dummy).toBe(‘Hello’) list[1] = ‘World!’ expect(dummy).toBe(‘Hello World!’) list[3] = ‘Hello!’ expect(dummy).toBe(‘Hello World! Hello!’) })

it(‘should observe sparse array mutations’, () => { let dummy const list = reactive([]) list[1] = ‘World!’ effect(() => (dummy = list.join(‘ ‘)))

expect(dummy).toBe(‘ World!’) list[0] = ‘Hello’ expect(dummy).toBe(‘Hello World!’) list.pop() expect(dummy).toBe(‘Hello’) })

  1. 13. 计算操作可以被响应
  2. ```js
  3. it('should observe enumeration', () => {
  4. let dummy = 0
  5. const numbers = reactive<Record<string, number>>({ num1: 3 })
  6. effect(() => {
  7. dummy = 0
  8. for (let key in numbers) {
  9. dummy += numbers[key]
  10. }
  11. })
  12. expect(dummy).toBe(3)
  13. numbers.num2 = 4
  14. expect(dummy).toBe(7)
  15. delete numbers.num1
  16. expect(dummy).toBe(4)
  17. })
  1. Symbol 类型可以响应

    1. it('should observe symbol keyed properties', () => {
    2. const key = Symbol('symbol keyed prop')
    3. let dummy, hasDummy
    4. const obj = reactive({ [key]: 'value' })
    5. effect(() => (dummy = obj[key]))
    6. effect(() => (hasDummy = key in obj))
    7. expect(dummy).toBe('value')
    8. expect(hasDummy).toBe(true)
    9. obj[key] = 'newValue'
    10. expect(dummy).toBe('newValue')
    11. delete obj[key]
    12. expect(dummy).toBe(undefined)
    13. expect(hasDummy).toBe(false)
    14. })
  2. well-known symbol 不能被观察

    1. it('should not observe well-known symbol keyed properties', () => {
    2. const key = Symbol.isConcatSpreadable
    3. let dummy
    4. const array: any = reactive([])
    5. effect(() => (dummy = array[key]))
    6. expect(array[key]).toBe(undefined)
    7. expect(dummy).toBe(undefined)
    8. array[key] = true
    9. expect(array[key]).toBe(true)
    10. expect(dummy).toBe(undefined)
    11. })
  3. function 的变更可以响应

    1. it('should observe function valued properties', () => {
    2. const oldFunc = () => {}
    3. const newFunc = () => {}
    4. let dummy
    5. const obj = reactive({ func: oldFunc })
    6. effect(() => (dummy = obj.func))
    7. expect(dummy).toBe(oldFunc)
    8. obj.func = newFunc
    9. expect(dummy).toBe(newFunc)
    10. })
  4. this 会被响应 ```js it(‘should observe chained getters relying on this’, () => { const obj = reactive({ a: 1, get b() { return this.a } })

    let dummy effect(() => (dummy = obj.b)) expect(dummy).toBe(1) obj.a++ expect(dummy).toBe(2) })

it(‘should observe methods relying on this’, () => { const obj = reactive({ a: 1, b() { return this.a } })

let dummy effect(() => (dummy = obj.b())) expect(dummy).toBe(1) obj.a++ expect(dummy).toBe(2) })

  1. 18.
  2. ```js
  3. it('should not observe set operations without a value change', () => {
  4. let hasDummy, getDummy
  5. const obj = reactive({ prop: 'value' })
  6. const getSpy = jest.fn(() => (getDummy = obj.prop))
  7. const hasSpy = jest.fn(() => (hasDummy = 'prop' in obj))
  8. effect(getSpy)
  9. effect(hasSpy)
  10. expect(getDummy).toBe('value')
  11. expect(hasDummy).toBe(true)
  12. obj.prop = 'value'
  13. expect(getSpy).toHaveBeenCalledTimes(1)
  14. expect(hasSpy).toHaveBeenCalledTimes(1)
  15. expect(getDummy).toBe('value')
  16. expect(hasDummy).toBe(true)
  17. })
  1. 改变原始对象不产生响应 ```js it(‘should not observe raw mutations’, () => { let dummy const obj = reactive<{ prop?: string }>({}) effect(() => (dummy = toRaw(obj).prop))

    expect(dummy).toBe(undefined) obj.prop = ‘value’ expect(dummy).toBe(undefined) })

it(‘should not be triggered by raw mutations’, () => { let dummy const obj = reactive<{ prop?: string }>({}) effect(() => (dummy = obj.prop))

expect(dummy).toBe(undefined) toRaw(obj).prop = ‘value’ expect(dummy).toBe(undefined) })

it(‘should not be triggered by inherited raw setters’, () => { let dummy, parentDummy, hiddenValue: any const obj = reactive<{ prop?: number }>({}) const parent = reactive({ set prop(value) { hiddenValue = value }, get prop() { return hiddenValue } }) Object.setPrototypeOf(obj, parent) effect(() => (dummy = obj.prop)) effect(() => (parentDummy = parent.prop))

expect(dummy).toBe(undefined) expect(parentDummy).toBe(undefined) toRaw(obj).prop = 4 expect(dummy).toBe(undefined) expect(parentDummy).toBe(undefined) })

  1. 20. 可以避免隐性递归导致的无限循环
  2. ```js
  3. it('should avoid implicit infinite recursive loops with itself', () => {
  4. const counter = reactive({ num: 0 })
  5. const counterSpy = jest.fn(() => counter.num++)
  6. effect(counterSpy)
  7. expect(counter.num).toBe(1)
  8. expect(counterSpy).toHaveBeenCalledTimes(1)
  9. counter.num = 4
  10. expect(counter.num).toBe(5)
  11. expect(counterSpy).toHaveBeenCalledTimes(2)
  12. })
  13. it('should avoid infinite loops with other effects', () => {
  14. const nums = reactive({ num1: 0, num2: 1 })
  15. const spy1 = jest.fn(() => (nums.num1 = nums.num2))
  16. const spy2 = jest.fn(() => (nums.num2 = nums.num1))
  17. effect(spy1)
  18. effect(spy2)
  19. expect(nums.num1).toBe(1)
  20. expect(nums.num2).toBe(1)
  21. expect(spy1).toHaveBeenCalledTimes(1)
  22. expect(spy2).toHaveBeenCalledTimes(1)
  23. nums.num2 = 4
  24. expect(nums.num1).toBe(4)
  25. expect(nums.num2).toBe(4)
  26. expect(spy1).toHaveBeenCalledTimes(2)
  27. expect(spy2).toHaveBeenCalledTimes(2)
  28. nums.num1 = 10
  29. expect(nums.num1).toBe(10)
  30. expect(nums.num2).toBe(10)
  31. expect(spy1).toHaveBeenCalledTimes(3)
  32. expect(spy2).toHaveBeenCalledTimes(3)
  33. })
  1. 可以显式递归调用

    1. it('should allow explicitly recursive raw function loops', () => {
    2. const counter = reactive({ num: 0 })
    3. const numSpy = jest.fn(() => {
    4. counter.num++
    5. if (counter.num < 10) {
    6. numSpy()
    7. }
    8. })
    9. effect(numSpy)
    10. expect(counter.num).toEqual(10)
    11. expect(numSpy).toHaveBeenCalledTimes(10)
    12. })
  2. 每次返回的都是新函数

    1. it('should return a new reactive version of the function', () => {
    2. function greet() {
    3. return 'Hello World'
    4. }
    5. const effect1 = effect(greet)
    6. const effect2 = effect(greet)
    7. expect(typeof effect1).toBe('function')
    8. expect(typeof effect2).toBe('function')
    9. expect(effect1).not.toBe(greet)
    10. expect(effect1).not.toBe(effect2)
    11. })
  3. 当结果未发生变动时不做处理,发生改变时应该产生响应 ```js it(‘should discover new branches while running automatically’, () => { let dummy const obj = reactive({ prop: ‘value’, run: false })

    const conditionalSpy = jest.fn(() => { dummy = obj.run ? obj.prop : ‘other’ }) effect(conditionalSpy)

    expect(dummy).toBe(‘other’) expect(conditionalSpy).toHaveBeenCalledTimes(1) obj.prop = ‘Hi’ expect(dummy).toBe(‘other’) expect(conditionalSpy).toHaveBeenCalledTimes(1) obj.run = true expect(dummy).toBe(‘Hi’) expect(conditionalSpy).toHaveBeenCalledTimes(2) obj.prop = ‘World’ expect(dummy).toBe(‘World’) expect(conditionalSpy).toHaveBeenCalledTimes(3) })

it(‘should discover new branches when running manually’, () => { let dummy let run = false const obj = reactive({ prop: ‘value’ }) const runner = effect(() => { dummy = run ? obj.prop : ‘other’ })

expect(dummy).toBe(‘other’) runner() expect(dummy).toBe(‘other’) run = true runner() expect(dummy).toBe(‘value’) obj.prop = ‘World’ expect(dummy).toBe(‘World’) })

it(‘should not be triggered by mutating a property, which is used in an inactive branch’, () => { let dummy const obj = reactive({ prop: ‘value’, run: true })

const conditionalSpy = jest.fn(() => { dummy = obj.run ? obj.prop : ‘other’ }) effect(conditionalSpy)

expect(dummy).toBe(‘value’) expect(conditionalSpy).toHaveBeenCalledTimes(1) obj.run = false expect(dummy).toBe(‘other’) expect(conditionalSpy).toHaveBeenCalledTimes(2) obj.prop = ‘value2’ expect(dummy).toBe(‘other’) expect(conditionalSpy).toHaveBeenCalledTimes(2) })

  1. 24. 每次返回的是一个新函数,原始对象是同一个
  2. ```js
  3. it('should not double wrap if the passed function is a effect', () => {
  4. const runner = effect(() => {})
  5. const otherRunner = effect(runner)
  6. expect(runner).not.toBe(otherRunner)
  7. expect(runner.raw).toBe(otherRunner.raw)
  8. })
  1. 单一的改变只会执行一次

    1. it('should not run multiple times for a single mutation', () => {
    2. let dummy
    3. const obj = reactive<Record<string, number>>({})
    4. const fnSpy = jest.fn(() => {
    5. for (const key in obj) {
    6. dummy = obj[key]
    7. }
    8. dummy = obj.prop
    9. })
    10. effect(fnSpy)
    11. expect(fnSpy).toHaveBeenCalledTimes(1)
    12. obj.prop = 16
    13. expect(dummy).toBe(16)
    14. expect(fnSpy).toHaveBeenCalledTimes(2)
    15. })
  2. effect 可以嵌套

    1. it('should allow nested effects', () => {
    2. const nums = reactive({ num1: 0, num2: 1, num3: 2 })
    3. const dummy: any = {}
    4. const childSpy = jest.fn(() => (dummy.num1 = nums.num1))
    5. const childeffect = effect(childSpy)
    6. const parentSpy = jest.fn(() => {
    7. dummy.num2 = nums.num2
    8. childeffect()
    9. dummy.num3 = nums.num3
    10. })
    11. effect(parentSpy)
    12. expect(dummy).toEqual({ num1: 0, num2: 1, num3: 2 })
    13. expect(parentSpy).toHaveBeenCalledTimes(1)
    14. expect(childSpy).toHaveBeenCalledTimes(2)
    15. // this should only call the childeffect
    16. nums.num1 = 4
    17. expect(dummy).toEqual({ num1: 4, num2: 1, num3: 2 })
    18. expect(parentSpy).toHaveBeenCalledTimes(1)
    19. expect(childSpy).toHaveBeenCalledTimes(3)
    20. // this calls the parenteffect, which calls the childeffect once
    21. nums.num2 = 10
    22. expect(dummy).toEqual({ num1: 4, num2: 10, num3: 2 })
    23. expect(parentSpy).toHaveBeenCalledTimes(2)
    24. expect(childSpy).toHaveBeenCalledTimes(4)
    25. // this calls the parenteffect, which calls the childeffect once
    26. nums.num3 = 7
    27. expect(dummy).toEqual({ num1: 4, num2: 10, num3: 7 })
    28. expect(parentSpy).toHaveBeenCalledTimes(3)
    29. expect(childSpy).toHaveBeenCalledTimes(5)
    30. })
  3. JSON 方法可以响应

    1. it('should observe json methods', () => {
    2. let dummy = <Record<string, number>>{}
    3. const obj = reactive<Record<string, number>>({})
    4. effect(() => {
    5. dummy = JSON.parse(JSON.stringify(obj))
    6. })
    7. obj.a = 1
    8. expect(dummy.a).toBe(1)
    9. })
  4. Class 方法调用可以观察

    1. it('should observe class method invocations', () => {
    2. class Model {
    3. count: number
    4. constructor() {
    5. this.count = 0
    6. }
    7. inc() {
    8. this.count++
    9. }
    10. }
    11. const model = reactive(new Model())
    12. let dummy
    13. effect(() => {
    14. dummy = model.count
    15. })
    16. expect(dummy).toBe(0)
    17. model.inc()
    18. expect(dummy).toBe(1)
    19. })
  5. 传入参数 lazy, 不会立即执行, 支持 lazy 调用

    1. it('lazy', () => {
    2. const obj = reactive({ foo: 1 })
    3. let dummy
    4. const runner = effect(() => (dummy = obj.foo), { lazy: true })
    5. expect(dummy).toBe(undefined)
    6. expect(runner()).toBe(1)
    7. expect(dummy).toBe(1)
    8. obj.foo = 2
    9. expect(dummy).toBe(2)
    10. })
  6. 传入参数 scheduler, 支持自定义调度

    1. it('scheduler', () => {
    2. let runner: any, dummy
    3. const scheduler = jest.fn(_runner => {
    4. runner = _runner
    5. })
    6. const obj = reactive({ foo: 1 })
    7. effect(
    8. () => {
    9. dummy = obj.foo
    10. },
    11. { scheduler }
    12. )
    13. expect(scheduler).not.toHaveBeenCalled()
    14. expect(dummy).toBe(1)
    15. // should be called on first trigger
    16. obj.foo++
    17. expect(scheduler).toHaveBeenCalledTimes(1)
    18. // should not run yet
    19. expect(dummy).toBe(1)
    20. // manually run
    21. runner()
    22. // should have run
    23. expect(dummy).toBe(2)
    24. })
  7. observer 的每次响应都会被 track

    1. it('events: onTrack', () => {
    2. let events: DebuggerEvent[] = []
    3. let dummy
    4. const onTrack = jest.fn((e: DebuggerEvent) => {
    5. events.push(e)
    6. })
    7. const obj = reactive({ foo: 1, bar: 2 })
    8. const runner = effect(
    9. () => {
    10. dummy = obj.foo
    11. dummy = 'bar' in obj
    12. dummy = Object.keys(obj)
    13. },
    14. { onTrack }
    15. )
    16. expect(dummy).toEqual(['foo', 'bar'])
    17. expect(onTrack).toHaveBeenCalledTimes(3)
    18. expect(events).toEqual([
    19. {
    20. effect: runner,
    21. target: toRaw(obj),
    22. type: TrackOpTypes.GET,
    23. key: 'foo'
    24. },
    25. {
    26. effect: runner,
    27. target: toRaw(obj),
    28. type: TrackOpTypes.HAS,
    29. key: 'bar'
    30. },
    31. {
    32. effect: runner,
    33. target: toRaw(obj),
    34. type: TrackOpTypes.ITERATE,
    35. key: ITERATE_KEY
    36. }
    37. ])
    38. })
  8. observer 的每次响应会触发 trigger

    1. it('events: onTrigger', () => {
    2. let events: DebuggerEvent[] = []
    3. let dummy
    4. const onTrigger = jest.fn((e: DebuggerEvent) => {
    5. events.push(e)
    6. })
    7. const obj = reactive({ foo: 1 })
    8. const runner = effect(
    9. () => {
    10. dummy = obj.foo
    11. },
    12. { onTrigger }
    13. )
    14. obj.foo++
    15. expect(dummy).toBe(2)
    16. expect(onTrigger).toHaveBeenCalledTimes(1)
    17. expect(events[0]).toEqual({
    18. effect: runner,
    19. target: toRaw(obj),
    20. type: TriggerOpTypes.SET,
    21. key: 'foo',
    22. oldValue: 1,
    23. newValue: 2
    24. })
    25. delete obj.foo
    26. expect(dummy).toBeUndefined()
    27. expect(onTrigger).toHaveBeenCalledTimes(2)
    28. expect(events[1]).toEqual({
    29. effect: runner,
    30. target: toRaw(obj),
    31. type: TriggerOpTypes.DELETE,
    32. key: 'foo',
    33. oldValue: 2
    34. })
    35. })
  9. observer 支持 stopstop 后支持手动调用 ```js it(‘stop’, () => { let dummy const obj = reactive({ prop: 1 }) const runner = effect(() => { dummy = obj.prop }) obj.prop = 2 expect(dummy).toBe(2) stop(runner) obj.prop = 3 expect(dummy).toBe(2)

    // stopped effect should still be manually callable runner() expect(dummy).toBe(3) })

it(‘stop: a stopped effect is nested in a normal effect’, () => { let dummy const obj = reactive({ prop: 1 }) const runner = effect(() => { dummy = obj.prop }) stop(runner) obj.prop = 2 expect(dummy).toBe(1)

// observed value in inner stopped effect // will track outer effect as an dependency effect(() => { runner() }) expect(dummy).toBe(2)

// notify outer effect to run obj.prop = 3 expect(dummy).toBe(3) })

  1. 34. `effect` `stop` 后,`scheduler` 不会再响应
  2. ```js
  3. it('stop with scheduler', () => {
  4. let dummy
  5. const obj = reactive({ prop: 1 })
  6. const queue: (() => void)[] = []
  7. const runner = effect(
  8. () => {
  9. dummy = obj.prop
  10. },
  11. {
  12. scheduler: e => queue.push(e)
  13. }
  14. )
  15. obj.prop = 2
  16. expect(dummy).toBe(1)
  17. expect(queue.length).toBe(1)
  18. stop(runner)
  19. // a scheduled effect should not execute anymore after stopped
  20. queue.forEach(e => e())
  21. expect(dummy).toBe(1)
  22. })
  1. 支持 stop 回调

    1. it('events: onStop', () => {
    2. const onStop = jest.fn()
    3. const runner = effect(() => {}, {
    4. onStop
    5. })
    6. stop(runner)
    7. expect(onStop).toHaveBeenCalled()
    8. })
  2. 标记为原始数据的不能响应

    1. it('markRaw', () => {
    2. const obj = reactive({
    3. foo: markRaw({
    4. prop: 0
    5. })
    6. })
    7. let dummy
    8. effect(() => {
    9. dummy = obj.foo.prop
    10. })
    11. expect(dummy).toBe(0)
    12. obj.foo.prop++
    13. expect(dummy).toBe(0)
    14. obj.foo = { prop: 1 }
    15. expect(dummy).toBe(1)
    16. })
  3. 当新值和旧值都是 NaN 时,不会 trigger

    1. it('should not be trigger when the value and the old value both are NaN', () => {
    2. const obj = reactive({
    3. foo: NaN
    4. })
    5. const fnSpy = jest.fn(() => obj.foo)
    6. effect(fnSpy)
    7. obj.foo = NaN
    8. expect(fnSpy).toHaveBeenCalledTimes(1)
    9. })
  4. 当数组长度设置为0时,会触发所有 effect

    1. it('should trigger all effects when array length is set 0', () => {
    2. const observed: any = reactive([1])
    3. let dummy, record
    4. effect(() => {
    5. dummy = observed.length
    6. })
    7. effect(() => {
    8. record = observed[0]
    9. })
    10. expect(dummy).toBe(1)
    11. expect(record).toBe(1)
    12. observed[1] = 2
    13. expect(observed[1]).toBe(2)
    14. observed.unshift(3)
    15. expect(dummy).toBe(3)
    16. expect(record).toBe(3)
    17. observed.length = 0
    18. expect(dummy).toBe(0)
    19. expect(record).toBeUndefined()
    20. })