🌬🌬🌬 前言:由于篇幅有点长,所以分成了两节来分别介绍,上节我们构建了简易版的 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
类型的实例对象,通过get
和set
标记处理了value
函数,并且 ref 是通过toReactive
对简单数据类型和复杂数据类型做了区分处理。
- 简单数据类型:触发
set value
属性调用来更新数据,xxx.value
其实是触发get value
属性调用,所以我们需要通过.value
进行触发达到类似数据的响应。 - 复杂数据类型:转化为
reactive
返回的proxy
实例。value.xxx = 'xxx'
实际触发的是proxy
的setter
。
网友评论