Vue3响应式API-Reactive手写实现
分类: Vue 403 0
前言
-
搭建本地开发环境,这里推荐使用pnpm包管理工具官方文档
-
安装相关依赖 esbuild minimist typescript
esbuild是一个类似webpack构建工具。它的构建速度是 webpack 的几十倍。官方文档
minimist用来解析命令行参数 文档
pnpm init pnpm install esbuild minimist typescript
-
esbuild配置
// minimist用来解析命令行参数 const args = require('minimist')(process.argv.slice(2)); // node scripts/dev.js reactivity -f global const target = args._[0] || 'reactivity'; const format = args.f || 'global'; const { build } = require('esbuild'); const { resolve } = require('path'); // 开发环境只打包一个 const pkg = require(resolve(__dirname,
../packages/${target}/package.json
)); // iife 立即执行函数 (function(){})() // cjs node模块 module.exports // esm 浏览器中的esModule模块 import const outputFormat = format.startsWith('global') ? 'iife' : format === ' cjs' ? 'cjs' : 'esm'; const outfile = resolve(__dirname,../packages/${target}/dist/${target}.${format}.js
); build({ entryPoints: [resolve(__dirname,../packages/${target}/src/index.ts
)], outfile, bundle: true, sourcemap: true, format: outputFormat, globalName: pkg.buildOptions?.name, platform: format === 'cjs' ? 'node' : 'browser', watch: { onRebuild(error) { if (!error) console.log(result---
) } } }).then(() => { console.log(watching---
); })
功能介绍
-
1s后age+1,页面重新渲染
实现方法
- 创建effect函数,入参为要实现响应式的依赖语句,默认数据变化执行更新,先将正在执行的effect作为全局变量,渲染取值
- reactive创建一个响应式对象 new Proxy,在get方法中通过track跟踪收集依赖(这里用
WeakMap
数据结构去存储多个需要响应式的object的depsMap
) - 稍后数据发生变化,在set中调用trigger,通过对象属性查找对应的effect集合,找到effect全部执行
- WeakMap结构如下(对象: map(属性: set(effect)))
以下为reactive实现,源码请点击这里
// effect.ts
export let activeEffect: any = undefined;
function cleanupEffect(effect: any) {
const { deps } = effect; // deps里面装的是name对应的effect
for (let i = 0; i < deps.lenght; i++) {
deps[i].delete(effect); // 解除effect,重新依赖收集
}
effect.deps.length = 0;
}
export class ReactiveEffect {
public parent = null;
public deps = [];
// 这里表示在实例上新增了active属性
public active = true; // effect默认激活状态
constructor(public fn: any, public scheduler: any) { } // 用户传递的参数也会当作this上 this.fn = fn
// 执行effect
run() {
// 这里表示如果是非激活的情况,只需要执行函数,不需要进行依赖收集
if (!this.active) { return this.fn() };
// 收集依赖 核心就是将当前的effcet 和 稍后渲染的属性关联在一起
try {
this.parent = activeEffect
activeEffect = this;
// 这里我们在执行用户函数前将之前收集的内容清空
cleanupEffect(this);
// 当稍后调用取值操作的时候就可以获取到这个全局的activeEffect了
return this.fn();
} finally {
activeEffect = this.parent;
}
}
stop() {
if (this.active) {
this.active = false;
cleanupEffect(this); // 停止effect收集
}
}
}
export function effect(fn: any, options: any = {}) {
// 这里fn可以根据状态变化 重新执行 effect可以嵌套写
const _effect = new ReactiveEffect(fn, options?.scheduler);// 创建响应式的effect
_effect.run(); // 默认先执行一次
const runner: any = _effect.run.bind(_effect); // 绑定this执行
runner.effect = _effect; // 将effect挂载到runner函数上
return runner
}
// 一个effect对应多个属性,一个属性对应多个effect
const targetMap = new WeakMap();
export function track(target: any, type: any, key: any) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
// 第一次没有
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
trackEffect(dep)
}
export function trackEffect(dep: any) {
if (activeEffect) {
// 去重
let shouldTrack = !dep.has(activeEffect);
if (shouldTrack) {
dep.add(activeEffect);
// 存放的是属性对应的set,让effect记录对应的dep,清理用到
activeEffect.deps.push(dep);
}
}
}
// 对象 某个属性->多个effect
// WeakMap = {对象:Map{name:Set->effect}}
// {对象:{name: []}}
export function trigger(target: object, type: any, key: any, value: any, oldValue: any) {
console.log(targetMap);
const depsMap = targetMap.get(target);
if (!depsMap) return // 触发的值不在模板中使用
let effects = depsMap.get(key); //找到属性对应的effect
if (effects) {
tiggerEffect(effects)
}
}
export function tiggerEffect(effects: any) {
effects = [...effects]
effects.forEach((effect: any) => {
// 我们在执行effect的时候 又要执行自己,需要屏蔽 避免无限调用
if (effect !== activeEffect) {
if (effect.scheduler) {
effect.scheduler(); // 如果传入调度函数,调用调度
} else {
effect.run(); // 否则默认刷新视图
}
}
})
}
// reactive.ts
import { isObject } from "@vue/shared";
import {mutableHandles, ReactiveFlags} from "./baseHandle"
const reactiveMap = new WeakMap(); // key只能是对象
// 将数据转换为响应式数据,只能做对象的代理
// 实现同一个对象代理多次,返回同一个代理
// 代理对象再次被代理 可以直接返回
export function reactive(target: any){
if(!isObject(target)) return
let exisitingProxy = reactiveMap.get(target);
if(target[ReactiveFlags.IS_REACTIVE]) return target
if(exisitingProxy) return exisitingProxy
// 并没有重新定义属性,只是代理,在取值的时候调用get,赋值时调用set
const proxy:any = new Proxy(target, mutableHandles)
reactiveMap.set(target, proxy)
return proxy
}
export function isReactive(value: any){
return !!(value && value[ReactiveFlags.IS_REACTIVE])
}
// baseHandle.ts
import { isObject } from "@vue/shared"
import { track, trigger } from "./effect"
import { reactive } from "./reactive"
export const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive'
}
export const mutableHandles = {
/**
*
* @param target 需要取值的目标对象
* @param key 需要获取的值的键值
* @param receiver 如果target对象中指定了getter,receiver则为getter调用时的this值。
* @returns
*/
get(target: object, key: PropertyKey, receiver: any) {
if (key === ReactiveFlags.IS_REACTIVE) return true
track(target, 'get', key)
let res = Reflect.get(target, key, receiver)
if (isObject(res)) {
return reactive(res) // 深度代理实现
}
return res
},
/**
*
* @param target 设置属性的目标对象。
* @param key 设置的属性的名称。
* @param value 设置的值。
* @param receiver 如果遇到 setter,receiver则为setter调用时的this值。
* @returns
*/
set(target: any, key: PropertyKey, value: any, receiver: any) {
let oldValue = target[key];
let result = Reflect.set(target, key, value, receiver)
// 值变化了 进行更新
if (oldValue !== value) {
trigger(target, 'set', key, value, oldValue)
}
return result
}
}
共 0 条评论关于 “Vue3响应式API-Reactive手写实现”