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

总结

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

相关推荐
梦想CAD控件1 分钟前
在线CAD开发包结构与功能说明
前端·javascript·vue.js
张拭心6 分钟前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
时光不负努力7 分钟前
typescript常用的dom 元素类型
前端·typescript
小怪点点12 分钟前
大文件切片上传
前端
时光不负努力13 分钟前
TS 常用工具类型
前端·javascript·typescript
SuperEugene14 分钟前
Vue状态管理扫盲篇:Vuex 到 Pinia | 为什么大家都在迁移?核心用法对比
前端·vue.js·面试
张拭心16 分钟前
Android 17 来了!新特性介绍与适配建议
android·前端
徐小夕21 分钟前
pxcharts-vue:一款专为 Vue3 打造的开源多维表格解决方案
前端·vue.js·github
Hilaku21 分钟前
我会如何考核一个在简历里大谈 AI 提效的高级前端?
前端·javascript·面试
青青家的小灰灰43 分钟前
React 反模式(Anti-Patterns)排查手册:从性能杀手到逻辑陷阱
前端·javascript·react.js