【Vue3.0】- 计算属性

计算属性

计算属性 API: computed

例子

import { computed, ref } from 'vue' 
const count = ref(1) 
const result = computed(() => count.value + 1) 
console.log(result.value) // 2 
result.value++ // error 
count.value++ 
console.log(result.value) // 3
  • 先使用 ref API 创建了一个响应式对象 count
  • 再使用 computed API 创建了另一个响应式对象 result
  • 修改 count.value 的时候, result.value 就会自动发生变化
  • 直接修改 result.value 会报一个错误
    • 因为如果我们传递给 computed 的是一个函数,那么这就是一个 getter 函数,我们只能获取它的值,而不能直接修改它
    • 也可以给 computed 传入一个对象,达到修改的目的
const result = computed({ 
  get: () => count.value + 1, 
  set: val => { 
    count.value = val - 1 
  } 
}) 

computed API 的实现

function computed(getterOrOptions) { 
  // getter 函数 
  let getter 
  // setter 函数 
  let setter 
  // 标准化参数 
  if (isFunction(getterOrOptions)) { 
    // 表面传入的是 getter 函数,不能修改计算属性的值 
    getter = getterOrOptions 
    setter = (process.env.NODE_ENV !== 'production') 
      ? () => { 
        console.warn('Write operation failed: computed value is readonly') 
      } 
      : NOOP 
  } 
  else { 
    getter = getterOrOptions.get 
    setter = getterOrOptions.set 
  } 
  // 数据是否脏的 
  let dirty = true 
  // 计算结果 
  let value 
  let computed 
  // 创建副作用函数 
  const runner = effect(getter, { 
    // 延时执行 
    lazy: true, 
    // 标记这是一个 computed effect 用于在 trigger 阶段的优先级排序 
    computed: true, 
    // 调度执行的实现 
    scheduler: () => { 
      if (!dirty) { 
        dirty = true 
        // 派发通知,通知运行访问该计算属性的 activeEffect 
        trigger(computed, "set" /* SET */, 'value') 
      } 
    } 
  }) 
  // 创建 computed 对象 
  computed = { 
    __v_isRef: true, 
    // 暴露 effect 对象以便计算属性可以停止计算 
    effect: runner, 
    get value() { 
      // 计算属性的 getter 
      if (dirty) { 
        // 只有数据为脏的时候才会重新计算 
        value = runner() 
        dirty = false 
      } 
      // 依赖收集,收集运行访问该计算属性的 activeEffect 
      track(computed, "get" /* GET */, 'value') 
      return value 
    }, 
    set value(newValue) { 
      // 计算属性的 setter 
      setter(newValue) 
    } 
  } 
  return computed 
}
  • 主要做了三件事情
  • 1、标准化参数
    • computed 函数接受两种类型的参数
      • 一个是 getter 函数
      • 一个是拥有 gettersetter 函数的对象
      • 通过判断参数的类型,我们初始化了函数内部定义的 gettersetter 函数
  • 2、创建副作用函数 runner
    • computed 内部通过 effect 创建了一个副作用函数,
    • 它是对 getter 函数做的一层封装
    • 创建时第二个参数为effect函数的配置对象
      • lazytrue: 表示 effect 函数返回的 runner 并不会立即执行
      • computedtrue :表示这是一个 computed effect,用于 trigger 阶段的优先级排序
      • scheduler:表示它的调度运行的方式
  • 3、创建 computed 对象并返回
    • 拥有 gettersetter 函数
    • computed 对象被访问的时候
      • 首先会触发 getter
      • 然后会判断是否 dirty
      • 如果是就执行 runner,然后做依赖收集
    • 当直接设置 computed 对象时
      • 会触发 setter,即执行 computed 函数内部定义的 setter 函数

计算属性的运行机制

  • 注意
    • computed 内部两个重要的变量
    • 第一个 dirty 表示一个计算属性的值是否是“脏的”,用来判断需不需要重新计算
    • 第二个 value 表示计算属性每次计算后的结果
  • 1、当渲染阶段访问到计算属性,则触发了计算属性的getter函数
get value() { 
  // 计算属性的 getter 
  if (dirty) { 
    // 只有数据为脏的时候才会重新计算 
    value = runner() 
    dirty = false 
  } 
  // 依赖收集,收集运行访问该计算属性的 activeEffect 
  track(computed, "get" /* GET */, 'value') 
  return value 
}
  • 由于默认 dirtytrue,所以这个时候会执行 runner 函数,并进一步执行 computed getter
  • 如果访问到了响应式对象,所以就会触发响应式对象的依赖收集过程
  • 由于是在 runner 执行的时候访问到了响应式对象,所以这个时候的 activeEffectrunner 函数
  • runner 函数执行完毕,会把 dirty 设置为 false
  • 然后执行 track(computed,"get",'value') 函数做依赖收集
  • 这个时候 runner 已经执行完了,所以 activeEffect 是组件副作用渲染函数
  • 注意:这是两个依赖收集过程
    • 对于计算属性来说,它收集的依赖是组件副作用渲染函数
    • 对于计算属性中访问的响应式对象来说,它收集的依赖是计算属性内部的runner函数
  • 当修改计算属性时,会派发通知,此时这里不是直接调用 runner 函数,而是把 runner 作为参数去执行 scheduler 函数
  • trigger函数内部对于 effect 函数的执行方式如下:
const run = (effect) => { 
  // 调度执行 
  if (effect.options.scheduler) { 
    effect.options.scheduler(effect) 
  } 
  else { 
    // 直接运行 
    effect() 
  } 
}
  • computed API 内部创建副作用函数时,已经配置了 scheduler 函数
scheduler: () => { 
  if (!dirty) { 
    dirty = true 
    // 派发通知,通知运行访问该计算属性的 activeEffect 
    trigger(computed, "set" /* SET */, 'value') 
  } 
}
  • 并没有对计算属性求新值,而仅仅是把 dirty 设置为 true
  • 再执行 trigger(computed, "set" , 'value'),去通知执行计算属性依赖的组件渲染副作用函数,即触发组件的重新渲染
  • 在组件重新渲染的时候,会再次访问 计算属性,我们发现这个时候 dirtytrue,然后会再次执行 computed getter
    image.png

Computed 计算属性两个特点

  • 1、延时计算
    • 只有当我们访问计算属性的时候,它才会真正运行 computed getter 函数计算
  • 2、缓存
    • 它的内部会缓存上次的计算结果 value,而且只有 dirtytrue 时才会重新计算
    • 如果访问计算属性时 dirtyfalse,那么直接返回这个 value

Computed 的优势

只要依赖不变化,就可以使用缓存的 value 而不用每次在渲染组件的时候都执行函数去计算

嵌套计算属性

计算属性中访问另外一个计算属性

const count = ref(0) 
const result1 = computed(() => { 
  return count.value + 1 
}) 
const result2 = computed(() => { 
  return result1.value + 1 
}) 
console.log(result2.value)

计算属性的执行顺序

  • 计算属性创建effect时,标记了computed标识的,用于trigger 阶段的优先级排序
  • trigger 函数执行 effects 的过程
const add = (effectsToAdd) => { 
  if (effectsToAdd) { 
    effectsToAdd.forEach(effect => { 
      if (effect !== activeEffect || !shouldTrack) { 
        if (effect.options.computed) { 
          computedRunners.add(effect) 
        } 
        else { 
          effects.add(effect) 
        } 
      } 
    }) 
  } 
} 
const run = (effect) => { 
  if (effect.options.scheduler) { 
    effect.options.scheduler(effect) 
  } 
  else { 
    effect() 
  } 
} 
computedRunners.forEach(run) 
effects.forEach(run)
  • 在添加待运行的 effects 的时候,会判断每一个 effect 是不是一个 computed effect
  • 如果是的话会添加到 computedRunners
  • 在后面运行的时候会优先执行 computedRunners
  • 然后再执行普通的 effects

为什么computed runner执行优先于普通的effect函数?

  • 因为当修改响应式数据时,会触发关联的计算属性的runnereffect执行
  • 如果先调用普通effect,这时dirtyfalse,使用的数据仍然是上次的缓存数据,导致更新不及时

你可能感兴趣的:(【Vue3.0】- 计算属性)