老生常谈响应式之 reactive & ref(下)

🌬🌬🌬 前言:由于篇幅有点长,所以分成了两节来分别介绍,上节我们构建了简易版的 reactive,并通过分析实践了解到 reactive 只能用于复杂数据类型,下面我们来分析一下 ref,刚刚开工,各位打工人进入状态了吗🌝🌝🌝,废话不多说,开整🌪️🌪️🌪️

一、构建基础列表

大致目录结构
---| packages
---|---| reactivity // 响应性模块
---|---|---| src
---|---|---|---| index.ts 出口文件
---|---|---|---| ref.ts
---|---|---|---| reactive.ts
---|---|---|---| effect.ts
---|---|---|---| dep.ts
---|---|---|---| baseHandlers.ts
---|---| shared // 公共方法模块
---|---|---| src
---|---|---|---| index.ts 出口文件
---|---|---|---| shapeFlags.ts
---|---| vue // 打包、测试实例、项目整体入口模块
---|---|---| dist
---|---|---| examples
---|---|---| src
---|---|---|---| index.ts 出口文件

二、开整

ref 目标:构建 ref 函数,分析为什么用 .value 去访问数据,ref 是如何分别处理复杂数据类型和简单数据类型的?
1. 创建 packages/reactivity/src/ref.ts 模块
import { createDep, Dep } from './dep'
import { activeEffect, trackEffects, triggerEffects } from './effect'
import { toReactive } from './reactive'

export interface Ref<T = any> { value: T }

/**
* ref 函数
* @param value unknown
*/
export function ref(value?: unknown) {
  return createRef(value, false)
}

/**
* 创建 RefImpl 实例
* @param rawValue 原始数据
* @param shallow boolean 形数据,表示《浅层的响应性(即:只有 .value 是响应性的)》
*/
function createRef(rawValue: unknown, shallow: boolean) {
  if(isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  // 私有属性
  private _value: T
  private _rawValue: T
  
  // 共有属性
  public dep?: Dep = undefined
  
  // 标记是否为 ref 类型
  public readonly __v_isRef = true
  
  constructor(value: T, public readonly __v_isShallow: boolean) {
    // https://cn.vuejs.org/api/reactivity-advanced.html#shallowref
    // 如果:__v_isShallow:true,则:value 不会被转化为 reactive 数据。
    // 如果:当前的 value 是复杂数据类型的话,则:会失去响应性
    this._value = __v_isShallow ? true : toReactive(value)
    
    // 原始数据
    this._rawValue = value
  }
  
  /**
  * get:将对象属性绑定到查询该属性时调用的函数
  * 🌰:xxx.value 触发该函数
  */
  get value() {
    trackRefValue(this)
    return this._value
  }
  /**
  * set:更新属性值
  * newValue:新数据 
  * this._rawValue: 原始数据(老数据)
  * hasChanged:对比数据是否发生了变化
  */
  set value(newValue) {
    // 更新数据
    this._rawValue = newValue
    
    // 更新 .value 的值
    this._value = toReactive(newValue)
    
    // 触发依赖
    triggerRefValue(this)
  }
}

/**
* 为 ref 的 value 进行依赖收集工作
*/
export function trackRefValue(ref) {
  if(activeEffect) {
   // 收集所有依赖
   trackEffects(ref.dep || (ref.dep = createDep()))
  }
}

/**
* 为 ref 的 value 进行依赖触发工作
*/
export function triggerRefValue(ref) {
  if(ref.dep) {
    triggerEffects(ref.dep)
  }
}

/**
* 指定数据是否为 RefImpl 类型
*/
export function isRef(r: any) r is Ref {
  return !!(r && r.__v_isRef === true)
}
packages/reactivity/src/reactive.ts
/**
* 将指定数据变为 reactive 类型
*/
export const toReactive = < T extends unknown>(value: T): T => 
isObject(value) ? reactive(value as object) : value
packages/shared/src/index.ts
/**
* 判断是否为一个对象
*/
export const isObject = (val: unknown) => val !== null && typeof val === 'object'


/**
* 对比两个数据是否发生了改变
*/
export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue)
2. packages/vue/examples/reactivity/ref.html 小试牛刀
<body>
  <div id="app"></div>
  <script>
    const { ref, effect } = Vue
    
    const obj = ref('张三')
    
    effect(() => {
      document.querySelector('#app').innerText = obj.value
    })
    
    setTimeout(() => {
      obj.value = '李四'
    }, 2000)
  </script>
</body>

✨✨✨ 至此,我们的ref函数构建完成,本质上是生成了一个 RefImpl 类型的实例对象,通过 getset 标记处理了 value 函数,并且 ref 是通过 toReactive 对简单数据类型和复杂数据类型做了区分处理。

  • 简单数据类型:触发 set value 属性调用来更新数据,xxx.value 其实是触发 get value 属性调用,所以我们需要通过 .value 进行触发达到类似数据的响应。
  • 复杂数据类型:转化为 reactive 返回的 proxy 实例。value.xxx = 'xxx' 实际触发的是 proxysetter
都给你讲啦.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容