欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > Vue3.4源码解析(reactive,effect,ref相关,computed,watch)

Vue3.4源码解析(reactive,effect,ref相关,computed,watch)

2025/6/18 18:49:37 来源:https://blog.csdn.net/m0_62475892/article/details/147964460  浏览:    关键词:Vue3.4源码解析(reactive,effect,ref相关,computed,watch)

monorepo环境搭建

  • 首先创建一个文件夹并使用pnpm init初始化指定项目是es6"type": "module",。然后创建.npmrc文件写入shamefully-hoist = true项目的所有依赖项提升到 node_modules 目录的顶层,作用是在其他地方可以直接引入nodemoudles中的包使用
    在这里插入图片描述
    在这里插入图片描述
  • 然后创建monorepo所需的包目录packages和pnpm-workspace.yaml文件指定包入口。公共的包放nodemoduels下,不同的包则放在各自packages中,开启如下命令后,则限制安装包,pnpm install vue -w代表安装到nodemoules中
pnpm-workspace.yaml
packages:- "/packages/*"
  • pnpm install typescript esbuild minimist -D -w安装开发环境需要的公共依赖,minimist是用来解析命令行。npx tsc --init初始化ts配置
{"compilerOptions": {"outDir": "dist", //输出目录"sourceMap": true, //生成sourceMap"target": "ES2016","module": "ESNext","moduleResolution": "node", //模块解析方式"strict": false,"resolveJsonModule": true, //解析json文件"esModuleInterop": true, //允许使用es6引入commonjs模块"jsx": "preserve", //保留jsx语法不转义"lib": ["ESNext","DOM"] //编译时包含的库文件}
}
  • 创建如下文件,约束所有的packages的包格式入口都在src下的index.ts中,dev.js为开发命令,在package.json中添加运行命令
    在这里插入图片描述
指明开发环境打包命令文件,读取的模块reactivity ,-f 随意命令的变量接受后面的打包规范esm,也可以是iife立即执行函数的规范(function(){})()"scripts": {"dev":"node ./scripts/dev.js reactivity -f esm"},

执行pnpm run dev命令执行dev.js文件

dev.js
import minimist from "minimist";
// slice(2)过滤前面的固定格式
// node ./scripts/dev.js ((reactivity,模块2 -f esm)-f esm) 括号内的=process.argv.slice(2) 结果minimist处理后为{ _: [ 'reactivity' ], f: 'esm' }const args = minimist(process.argv.slice(2));
console.log(args);
  • 给package目录中的每一个模块创建固定的打包入口即src/index.ts。
    获取当前文件dev所在位置import.meta.url获取file:///....开头的数据。不方便理解所以是使用fileURLToPath解析为常规路径
import {fileURLToPath} from 'url'
console.log(import.meta.url,fileURLToPath(import.meta.url));

在这里插入图片描述
然后获取文件夹位置。在es6中没有require对象和__dirname

import {dirname} from 'path'
// 获取当前文件所在目录的路径
const _dirname = dirname(_filename);
console.log(_dirname); //E:\个人项目\vue3.4-source-code\scripts
  • 获取入口,并且在每一个模块中创建package.json文件指明依赖关系
const entry = resolve(_dirname,`../packages/${targe}/src/index.ts`)
{"name": "@vueq/reactivity", // 定义模块名,方便引入"version": "1.0.0","module": "dist/reactivity.esm-bundler.js", 指明了es入口"unpkg": "dist/reactivity.global.js", //cdn全局引入"buildOptions": { 构建的相关选项"name": "VueReactivity", 全局变量名formats:定义了构建输出的格式,当前支持以下几种:esm-bundler: 用于打包工具的 ES 模块格式。esm-browser: 用于浏览器的 ES 模块格式。cjs: CommonJS 格式,通常用于 Node.js 环境。global: 全局变量格式,适合直接在浏览器中使用"formats": ["esm-bundler","esm-browser","cjs","global"]}
}
{"name": "@vueq/shared","version": "1.0.0","module": "dist/shared.esm-bundler.js","buildOptions": {"formats": ["esm-bundler","cjs"]}
}

测试在shared文件的index中创建一个方法测试isObject。然后尝试在reactivity中引入,但是需要以模块化的方式引入而非路径方式
设置ts类型引入提示

        "baseUrl": ".","paths": {"@vueq/*": ["packages/*/src"]}

此时可以识别本地shared包中的导出方法
在这里插入图片描述

然后还需要将本地shared包安装到reactivity中pnpm add @vue/shared --workspace --filter @vue/reactivity添加--workspace代表是本地包,--filter @vue/reactivity是安装给谁。这里将@vue/reactivity修改为@vueq/...,是因为安装第三方的vue的时候,本地和第三方包同名了,安装了的时候将本地包处理到了第三方目录中,导致了同名部分问题本地取代了第三方的同名包,暂时不知道哪里出错,所以更改名字
在这里插入图片描述

搭建esbuild开发环境

在dev.js文件中添加

const pkg = require(`../packages/${target}/package.json`)esbuild.context({entryPoints:[entry], // 打包入口outfile:resolve(_dirname,`../packages/${target}/dist/${target}.js`), // 打包出口bundle:true, // 使用到的资源打包到一起 ,rectivity中用到shared,会打包到一起platform:'browser', // 打包给浏览器sourcemap: true ,// 可以调式源码format, // 打包的模块规范 cjs ems iife globalName:pkg.buildOptions?.name // 若是iife立即执行函数,需要设置全局变量接收
}).then(res=>{console.log('打包开始')return res.watch() // 监控持续打包
})

当前esm形式打包。输出如下
在这里插入图片描述
在这里插入图片描述
iife的打包如下
在这里插入图片描述

reactive函数

下面是原生的使用情况,reactive基于proxy实现响应式。effect是副作用函数用于监听响应数据改变更新视图。watch和computed都是基于effect完成,默认初始化会执行一次,响应式数据更新后会再次执行,所以如下代码可以发现视图更新

    <div id="app"></div><script type="module">import {reactive,effect} from '/node_modules/@vue/reactivity/dist/reactivity.esm-browser.js'const state = reactive({name:"张三",age:20})console.log(state);effect(()=>{app.innerHTML = `姓名${state.name} 年龄${state.age}`})setTimeout(()=>{state.age ++ },1000)</script>

现在修改引入指向本地的函数。
在这里插入图片描述
在reactivity的src文件中创建reactive和effect函数并在index中导出。

reactive.ts中编写如下代码
import { isObject } from "@vueq/shared";const handler:ProxyHandler<any> = {get(target,key,receiver){ // receiver只代理对象proxy变量},set(target,key,value,receiver){return true}
}export function reactive (target){return createReactiveObject(target)
}function createReactiveObject(target){if(!isObject(target)){return target}const proxy = new Proxy(target,handler)return proxy
}

原生reactive使用细节,如果是同一个对象进行多次响应式处理,则会缓存,使用相同的内存地址, 若已经代理过的对象再次被代理,则不会被处理
在这里插入图片描述
定义一个缓存,若访问的是同一个对象地址则直接返回代理过的对象

const reactiveMap = new WeakMap()function createReactiveObject(target){if(!isObject(target)){return target}const existProxy = reactiveMap.get(target)if(existProxy){return existProxy}const proxy = new Proxy(target,handler)reactiveMap.set(target,proxy)return proxy
}

在这里插入图片描述
被代理的对象中一定会设置get和set,那么可以在get中设置一个唯一值,代表当前对象是已经被reactive代理过的,若再次被代理可以直接返回。

enum ReactiveFlags {IS_REACTIVE = '__v_isReactive'
}function createReactiveObject(target){
。。。// 手动触发get,只有被代理的对象身上才有该属性if(target[ReactiveFlags.IS_REACTIVE]){ //第一次obj还没有被代理所以不会进入get和setreturn target}
。。。。
}

完善mutableReactiveHandler,使用Proxy代理会返回一个监听对象,当前执行监听对象proxy访问的时候才会触发get和set
Proxy代理对象中的get和set中的target代理对象即Proxy中的第一个参数obj。return target[key]这个方式的话相当于obj.age。obj并不是代理对象,所以写法不正确。如果写成代理对象,则代理对象会在get中递归访问报错,所以需要使用Reflect处理。此时receiver会从target中读取key值,从而避免拦截操作,不会递归

Reflect.get(target, key, receiver):修改target在读取过程中的this指向receiver
target 是原始的目标对象。
key 是要访问的属性名。
receiver 是调用者对象(通常是代理对象 proxy)。

在这里插入图片描述

export const mutableReactiveHandler:ProxyHandler<any> = {get(target,key,receiver){if(key === ReactiveFlags.IS_REACTIVE){return true}// 取值的时候和effect映射起来return Reflect.get(target,key,receiver)},set(target,key,value,receiver){// 找到属性,让对应的effect重新执行return Reflect.set(target,key,value,receiver)}
}

effect函数

        const obj = {name:"张三",age:20}const state = reactive(obj)effect(()=>{app.innerHTML = `姓名${state.name} 年龄${state.age}`})setTimeout(()

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词