实现一个简易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'
})

总结

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

相关推荐
donecoding10 分钟前
Corepack 完全解析:从懵到懂,包管理器自由了
前端·node.js·前端工程化
yqcoder13 分钟前
端经典面试题:为什么 0.1 + 0.2 !== 0.3?
前端·css
ZC跨境爬虫18 分钟前
跟着 MDN 学 HTML day_12:(HTML网页图片嵌入)
前端·javascript·css·ui·html
光影少年24 分钟前
reeact虚拟DOM、Diff算法原理、key的作用与为什么不能用index
前端·react.js·掘金·金石计划
用户0595401744629 分钟前
大模型记忆存储踩坑实录:LangChain 的 ConversationBufferMemory 让我排查了 6 小时
前端·css
是上好佳佳佳呀36 分钟前
【前端(十二)】JavaScript 函数与对象笔记
前端·javascript·笔记
你真的快乐吗1 小时前
@fuxishi/svg-icon:一个 Vue 3 svg本地图标+iconify图标组件库,让图标管理不再头疼
前端·vue.js·typescript
Rkgua1 小时前
ESModule和Commonjs模块的区别
前端·javascript
江南十四行1 小时前
ReAct Agent 基本理论与项目实战(二)
前端·react.js·前端框架
用户600071819101 小时前
【翻译】React 如何乱序流式输出 UI,却仍保持最终顺序
前端