【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,使用的数据仍然是上次的缓存数据,导致更新不及时
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容