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

总结

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

相关推荐
啃火龙果的兔子1 分钟前
安全有效的 C 盘清理方法
前端·css
海天胜景5 分钟前
vue3 数据过滤方法
前端·javascript·vue.js
天生我材必有用_吴用10 分钟前
深入理解JavaScript设计模式之策略模式
前端
海上彼尚12 分钟前
Vue3 PC端 UI组件库我更推荐Naive UI
前端·vue.js·ui
述雾学java13 分钟前
Vue 生命周期详解(重点:mounted)
前端·javascript·vue.js
洛千陨18 分钟前
Vue实现悬浮图片弹出大图预览弹窗,弹窗顶部与图片顶部平齐
前端·vue.js
咚咚咚ddd20 分钟前
微前端第四篇:qiankun老项目渐进式升级方案(jQuery + React)
前端·前端工程化
螃蟹82723 分钟前
作用域下的方法如何调用?
前端
独立开阀者_FwtCoder26 分钟前
TypeScript 杀疯了,开发 AI 应用新趋势!
前端·javascript·github
汪子熙31 分钟前
QRCode.js:一款轻量级、跨浏览器的 JavaScript 二维码生成库
前端·javascript·面试