实现一个简易vue的响应式系统

响应式的核心原理

按照对 vue 的基本理解,响应式的本质是数据驱动视图,也就是说界面会随着数据的变化而自动更新,但是这种理解趋于应用层面了,更接近本质的理解应该是 vue 中数据的变化会让依赖该数据的函数跟着变化,这个函数并不局限于 render ,同时可以是 watchEffect 也可以是 computed; 比如如下的 vue 代码,随着 firstName 的变化,render computer watchEffect 函数都会重新运行

js 复制代码
<template>
    <div>{{firatName}}</div>
    <div>{{ lastName }}</div>
    <div>{{ name }}</div>
    <div @click="handleClick"></div>
</template>

<script setup>
 const firatName = ref("张");
 const lastName = ref("三");
 const handleClick = ()=>{
    firatName.value = "赵"
 }
 const name = computed(()=>{
    return firatName.value+lastName.value;
 })
 watchEffect(()=>{
    // 日志答应
    console.log(firatName.value)
 })
</script>
graph TD firstName --> effect firstName --> computed firstName --> render

核心设计

基本流程就是利用 proxy 对象,在 get 的时候收集依赖的函数,在 set 的时候触发的依赖的函数,同时提供一个 watchEffect 函数用于在 get 的时候收集依赖,那么基本的流程图如下

核心代码

effect

js 复制代码
import {
    effectStack,
} from "./index.js";


export function effect(fn) {
    effectStack.push(fn);
    fn();
    effectStack.pop(fn)
}

track

js 复制代码
import {
    effectStack,
    effectMap
} from "./index.js";

export function track(target, key) {
    if (effectStack.length === 0) {
        return;
    }
    let proxyMap = effectMap.get(target);
    if (!effectMap.has(target)) {
        proxyMap = new Map()
        effectMap.set(target, proxyMap);
    }
    if (!proxyMap.has(key)) {
        proxyMap.set(key, new Set())
    }
    const proxySet = proxyMap.get(key);
    // 拿到调用栈的最后一个环境,进行调用
    proxySet.add(effectStack[effectStack.length - 1]);

}

trigger

js 复制代码
import {
    effectStack,
    effectMap
} from "./index.js";

export function trigger(target, key){
    const  proxyMap = effectMap.get(target);
    if(!proxyMap||!proxyMap.has(key)){
        console.error("未拿到对应的依赖")
        return ;
    }
    const proxySet = proxyMap.get(key);
    proxySet.forEach(fn=>fn())

}

reactive

js 复制代码
import {track,trigger} from "../effect/index.js"
export default function reactive(target){
    return new Proxy(target,{
        get(target,key,recevier){
            track(target,key);
            return Reflect.get(target,key,recevier)
        },
        set(target,key,newValue,recevier){
            trigger(target,key);
            return Reflect.set(target,key,newValue,recevier);
        }
    })
}

示例&效果

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
    <button id="btn">点击修改</button>
    <script type="module" src="./index.js"></script>
</body>
</html>
js 复制代码
import reactive from "./reactive/index.js"
import {
    effect
} from "./effect/index.js"
const a = reactive({
    a: 1,
    b: 2
})

function render() {
    app.innerText = a.a;
    btn.innerText = a.b
}

effect(render);
btn.addEventListener("click", () => {
    a.a += 1;
    a.b += '2'
})

总结

一个最基本的响应式系统就完成了,当然代码肯定还有别的问题,边界问题,依赖清理,自定义功能诸如此类的,后续根据使用接着补充即可(^^ゞ

相关推荐
Carry34515 分钟前
不清楚的 .gitignore
前端·git
张鑫旭21 分钟前
AI时代2025年下半年学的这些Web前端特性有没有用?
前端·ai编程
pinkQQx22 分钟前
H5唤醒APP技术方案入门级介绍
前端
Lefan25 分钟前
UniApp 隐私合规神器!一键搞定应用市场审核难题 - lf-auth 隐私合规助手
前端
Null15526 分钟前
浏览器唤起桌面端应用(进阶篇)
前端·浏览器
Jing_Rainbow28 分钟前
【Vue-2/Lesson62(2025-12-10)】模块化与 Node.js HTTP 服务器开发详解🧩
前端·vue.js·node.js
风度前端1 小时前
用了都说好的 uniapp 路由框架
前端
冴羽1 小时前
2026 年 Web 前端开发的 8 个趋势!
前端·javascript·vue.js
码银1 小时前
ruoyi的前端(vue)新增的时候给字典设置默认值 但不能正常
前端
凌览2 小时前
别再死磕 Nginx!http-proxy-middleware 低配置起飞
前端·后端