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
相关推荐
Backstroke fish1 天前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小曲程序1 天前
vue3 封装request请求
java·前端·typescript·vue
Lysun0011 天前
[less] Operation on an invalid type
前端·vue·less·sass·scss
程序视点1 天前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
刚刚好ā1 天前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
ZwaterZ2 天前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
ZwaterZ2 天前
el-table-column自动生成序号&&在序号前插入图标
前端·javascript·c#·vue
木子七2 天前
vue2-vuex
前端·vue
小小黑0072 天前
uniapp+vue3+ts H5端使用Quill富文本插件以及解决上传图片反显的问题
uni-app·vue
Ztiddler2 天前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue