Vue3 reactive 响应式原理源码实现

学习小满的视频,更详细的讲解
Vue3响应式原理
视频

需要了解Proxy、Reflect函数

目录结构:

配置环境:

  • package.json
javascript 复制代码
{
  "name": "vue-reactive",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev":"webpack-dev-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.72.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.9.0"
  }
}
javascript 复制代码
// 安装ts
npm install -g typescript
// 初始化
npm init
  • tsconfig.json 关键的两个配置,target和module
javascript 复制代码
{
  "compilerOptions": {
    "target": "es6",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    "module": "es2015",                                /* Specify what module code is generated. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
    "strict": true,                                      /* Enable all strict type-checking options. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}
  • reactive.ts
javascript 复制代码
//@ts-nocheck
import {track,trigger} from './effect'
// 判断代理对象的值是否为空,不为空的话值是否是一个对象
const isObject = (target)=>target!=null&&typeof target==='object'

// 创建代理,proxy对象,使用其中的get和set方法
export const reactive = <T extends object>(targert : T)=>{
    
    
    let proxy =  new Proxy(targert,{
        get(target,key,receiver){
            // Reflect:与proxy搭配使用,不会在出错时抛出错误,如果没有该属性的值返回undefined
            let res = Reflect.get(target,key,receiver)
            track(target,key)
            // 代理对象的值是否为一个对象,如果是的话就进行代理,对深层的对象进行代理,使用递归的方法
            if(isObject(res)){
                return reactive(res)
            }
            return res
            
            
        },
        set(target,key,value,receiver){
             // Reflect:给对象target的key属性设置值为value,如果成功返回true,失败返回false
            let res = Reflect.set(target,key,value,receiver)
            trigger(target,key)
            return res
        },
    
        
    })
    console.log(proxy);
    return proxy
    
}
  • effect.ts
javascript 复制代码
//@ts-nocheck
let activeEffect;
// 用来执行副作用函数的方法,至今不明白真正的作用,fn就是副作用函数,是html页面传过来的函数
export const effect = (fn:Function)=>{
    const _effect = function(){
        activeEffect = _effect
        fn()
    }
    _effect()
}
/* 
** 可能是因为这是个学习响应式的简略的程序,所以必须先通过get方法创建targetMap对象这个数据结构才能使用set方法

    1. 给user这个响应式对象创造另一种结构
    3. 先通过响应式对象的get方法,执行此方法中的track方法,建立一个Map结构
    4. 通过Map结构,将响应式对象和副作用函数建立联系,副作用函数是set结构

    targetMap:{
            {name: 'jack', age: 18, first: {...}} : {
                "name" : "副作用函数",
                "age" : "副作用函数",
                "first: {...}" : "副作用函数",
                "second: {...}" : "副作用函数",
                "third" : "副作用函数"
            }
    }
*/
const targetMap = new WeakMap()
export const track = (target,key)=>{
    console.log("执行了reactive.tarck");
    let depsMap = targetMap.get(target)
    if(!depsMap){
        depsMap = new Map()
        targetMap.set(target,depsMap)
    }
    let deps = depsMap.get(key)
    if(!deps){
        deps = new Set()
        depsMap.set(key,deps)
    }
    deps.add(activeEffect)
    console.log(targetMap);
    
}
/*
    1. 触发响应式对象的某一个key对应的value发生改变时,执行proxy的set方法,执行此方法中的trigger方法
    2. 从targetMap中通过key找到对应的value,value中储存着副作用函数,依次将副作用函数全部执行
    3. 假如在页面中有3处使用了响应式对象,则value中储存着3条副作用函数
*/
export const trigger = (target,key)=>{
    console.log("执行了reactive.trigger");
    
    const depsMap = targetMap.get(target)
    const deps = depsMap.get(key)
    deps.forEach(effect=>effect())
}
  • index.html
javascript 复制代码
<html>

<body>
    <div id="app">

    </div>
    <script type="module">
        import { reactive} from './reactive.js'
        import { effect } from './effect.js'

        // 这里的user已经通过reactive成为了一个proxy对象,对user对象的操作,就是对proxy对象的操作
        const user = reactive({ 
            name: 'Tom',
            age: 18,
            first:{
                second:{
                    third:"第三层"
                }
            }
        })
       // 执行到这里的时候,只是做个proxy的代理
        effect(() => {
            // 在执行${user.name}时才执行proxy代理中相应的get方法
           // 每个对象对应一个targetMap,但是结构中并不是储存所有的属性的map结构,只有在执行到对应属性的get方法时才在targetMap中添加该属性对应的结构
            document.querySelector('#app').innerText = `${user.name}---${user.first.second.third}`
            // document.querySelector('#app').innerText = `${user.name} -- ${user.age}`
        })
        user.name="jack"//执行赋值的操作时才会执行proxy的set方法
        // setTimeout(() => {
        //     user.name="jack",//执行赋值的操作时才会执行proxy的set方法
        //     setTimeout(()=>{
        //         user.first.second.third="第三层修改"
        //     },1000)
        // },1000)
    </script>
</body>

</html>
  • 执行操作:
  1. 在VScode中安装插件:Live Server
  2. 在终端中运行tsc,将ts文件编译为js文件
  3. 修改reactive.js文件,import { track, trigger } from './effect.js';
  4. 右键index.html,使用Open with live server
相关推荐
医疗信息化王工5 小时前
医院自律端系统——预警处置模块全栈实战(ASP.NET Core + Vue3 + Quartz 定时调度)
mysql·postgresql·vue·asp.net core·quartz
大大杰哥7 小时前
Vue2学习(1)--了解基本方法与概念
javascript·学习·vue
Agatha方艺璇1 天前
前端开发技术复习笔记
vue·bootstrap·css3·html5·web
小葛要努力1 天前
创建vue2项目
程序人生·vue
七仔啊1 天前
基于海康门禁的人员计数系统
vue
步十人2 天前
【Vue3】前置知识简单概述(包括ES6核心语法,模块化ESM以及npm基础)
arcgis·npm·vue·es6
有梦想的程序星空3 天前
【环境配置】Vue3项目离线化本地部署echarts全攻略
前端·javascript·vue·echarts
向日的葵0063 天前
vue路由(二)
前端·javascript·vue.js·vue
小妖6664 天前
Hydration completed but contains mismatches
javascript·vue·vuepress
lianyinghhh4 天前
FlowGame 从零上手:开源 AI 工作流编排框架与 Vue 3 接入实战
python·低代码·开源·vue·rag·flowgame·ai工作流编排