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

总结

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

相关推荐
拾光拾趣录4 分钟前
闭包:从“变量怎么还没死”到写出真正健壮的模块
前端·javascript
拾光拾趣录25 分钟前
for..in 和 Object.keys 的区别:从“遍历对象属性的坑”说起
前端·javascript
OpenTiny社区36 分钟前
把 SearchBox 塞进项目,搜索转化率怒涨 400%?
前端·vue.js·github
编程猪猪侠1 小时前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞1 小时前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路2 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9492 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8682 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie2 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_2 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api