实现 effect & reactive & 依赖收集 & 触发依赖
前言
上一章大致讲述了项目初始化,本章主要内容:实现 effect & reactive & 依赖收集 & 触发依赖,让我们一起来看看吧。
一、TDD是什么?
TDD现在是社区很火的一种开发方式。测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。
二、开始
1.搭建项目架构
1、先在src下创建 reactivity 文件夹用来实现 reactivity 响应式 相关逻辑
具体代码结构如下:
.
├── README.md
├── package.json
├── babel.config.js
├── src 源码
│ └── reactivity
│ ├── src reactivity 实现文件
│ └── tests reactivity 测试文件
└── tsconfig.json ts配置文件
2.测试文件
1、首先,先创建一个effect.spec.ts 文件用来写effect测试
import {
effect } from "../src/effect"
import {
reactive } from "../src/reactive"
describe('effect', () => {
it('happy path', () => {
// 创建一个reactive响应式数据
const reactiveUser = reactive({
age: 10
})
let newAge
// 通过effect 去实现依赖的收集和触发
effect(() => {
newAge = reactiveUser.age + 1
})
expect(newAge).toBe(11) // expect 断言newAge变量 等于11
// update age
reactiveUser.age ++ // 更新响应式数据属性
expect(newAge).toBe(12) // 断言newAge变量 等于12
})
})
因为effect 整个测试流程很大,所以拆分出两块,分别是effect和reactive 测试,下面写一下reactive 测试:
import {
reactive } from '../src/reactive';
describe('reactive', () => {
it('happy path', () => {
const original = {
foo: 1 } // 声明初始变量original
const observed = reactive(original) // 通过reactive使original变成响应式并赋值给observed
expect(observed).not.toBe(original) // 因为vue3响应式是用Proxy实现的,所以断言observed 不等于 original
expect(observed.foo).toBe(1) // 断言observed的foo 属性等于 1
})
})
3.实现文件
import {
track, trigger } from "./effect"
export function reactive(raw) {
return new Proxy(raw, {
get(target, key) {
const res = Reflect.get(target, key)
// 收集依赖
track(target, key)
return res
},
set(target, key, value) {
const res = Reflect.set(target, key, value)
// 触发依赖
trigger(target, key)
return res
}
})
}
effect 具体实现思路为下面的脑图
// 全局的effect指针
let activeEffect
// effect响应式类
class ReactiveEffect {
private _fn: any
constructor(fn) {
this._fn = fn
}
run() {
activeEffect = this // 把全局effect赋值为当前effect实例
this._fn() // 执行effect传过来的函数
}
}
// 全局的target容器
let targetsMap = new Map()
// 收集依赖
function track(target, key) {
// target => key => dep
// target 目标对象
// key 目标对象中的属性
// dep 属性关联的函数(不可以重复,所以用Set存放)
let depsMap = targetsMap.get(target)
// 初始化时,depsMap不存在,要声明并且存放到对象Map中
if (!depsMap) {
depsMap = new Map()
targetsMap.set(target, depsMap)
}
let dep = depsMap.get(key)
// 初始化时,dep不存在,要声明并且存放到属性依赖Set中
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect) // 把当前effect实例收集起来
}
// 触发依赖
function trigger(target, key) {
let depsMap = targetsMap.get(target) // 获取到相应的对象Map
let dep = depsMap.get(key) // 获取到相应的属性依赖Set
for (const effect of dep) {
effect.run() // 去触发依赖Set中每个依赖
}
}
// 执行函数
function effect(fn) {
const _effect = new ReactiveEffect(fn) // 面向对象思想
_effect.run()
}
export {
track,
trigger,
effect
}
以上,执行jest,就通过了,如下图
总结
本章讲述effect & reactive & 依赖收集 & 触发依赖的实现,一起学习Vue3。
坚持就是胜利,各位观众老爷的三连也是up的坚持动力,共勉!
最后,附上git地址:mini-vue