vue3的响应式原理
搭建项目
拍平依赖包
用来搭建monorepo管理项目
packages:
- 'packages/*'
ts 配置
{
"compilerOptions": {
"outDir": "dist", //输出目录
"sourceMap": true, //采用sourceMap
"target": "es2016", //目标语法
"module": "esnext", //模块格式
"moduleResolution": "node", //模块解析方式
"strict": false, //严格模式
"resolveJsonModule": true, //解析JSON模块
"esModuleInterop": true, //允许es6语法引入commonjs模块
"jsx": "preserve", //js不转译
"lib": ["esnext", "dom"], //支持的类库esnext及dom
"baseUrl": ".",
"paths": {
"@vue/*": ["packages/*/src"]
}
}
}
添加脚本命令
"dev": "node scripts/dev.js reactivity -f global"
脚本编译
const args = require("minimist")(process.argv.slice(2)) // node scripts/dev.js reactivity -f global
const { build } = require("esbuild");
// console.log(args)
const { resolve } = require('path');// node 内置模块
const target = args._[0] || "reactivity";
const format = args.f || 'global';// 打包的格式
const pkg = require(resolve(__dirname, `../packages/${target}/package.json`));
// iife 立即执行函数 (function(){})();
// cjs node中的模块 module.exports
// esm 浏览器中的esModule模块 import
const outputFormat = format.startsWith("global") ? 'iife' : format == "cjs" ? "cjs" : "esm";
//打包之后文件存放地方
const outFile = resolve(__dirname, `../packages/${target}/dist/${target}.${format}.js`)
//esbuild
//天生就支持ts
build({
entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
outfile: outFile, //输出的文件
bundle: true, //把所有包全部打包到一起
sourcemap: true,
format: outputFormat, //输出格式
globalName: pkg.buildOptions?.name, //打包全局名
platform: format === "cjs" ? "node" : "browser",//项目运行的平台
watch: { //监听文件变化
onRebuild (error) {
if (!error) {
console.log("rebuild~~~")
}
}
}
}).then(() => {
console.log(`watch~~~~`)
})
reactivity和shared
{
"name": "@vue/reactivity",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"buildOptions":{
"name":"VueReactivity",
"formats":[
"global",
"cjs",
"esm-budler"
]
}
}
- shared
{
"name": "@vue/shared",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"buildOptions":{
"formats":[
"cjs",
"esm-budler"
]
}
}
打包测试
export const isObject = (value: any) => {
return value !== null && typeof value === 'object'
}
import { isObject } from "@vue/shared";
console.log(isObject(true))
Vue的reactive方法
- index.ts
export { effect } from './effect'
export { reactive } from './reactive'
import { isObject } from '@vue/shared'
const reactiveMap = new WeakMap()
const enum ReactiveFlags {
IS_REACTIVE = '_v_isReactive'
}
export function reactive(target){
if(!isObject(target)){
return
}
if(target[ReactiveFlags.IS_REACTIVE]){
return target
}
let exisitingProxy = reactiveMap.get(target)
if(exisitingProxy){
return exisitingProxy
}
const proxy = new Proxy(target,{
get(target,key,receiver){
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
return Reflect.get(target,key,receiver)
},
set(target,key,value,receiver){
return Reflect.set(target,key,value,receiver)
}
})
reactiveMap.set(target,proxy)
return proxy
}
import { isObject } from "@vue/shared"
import { reactive } from "./reactive"
export const enum ReactiveFlags {
IS_REACTIVE = '_v_isReactive'
}
export const mutableHandlers = {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
let res = Reflect.get(target, key, receiver)
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver)
}
}
- reactive.ts
import { isObject } from '@vue/shared'
import { mutableHandlers,ReactiveFlags } from './baseHandler'
const reactiveMap = new WeakMap()
export function reactive(target){
if(!isObject(target)){
return
}
if(target[ReactiveFlags.IS_REACTIVE]){
return target
// const state1 = reactive(data)
// const state2 = reactive(state1)
}
let exisitingProxy = reactiveMap.get(target)
if(exisitingProxy){
return exisitingProxy
}
const proxy = new Proxy(target,mutableHandlers)
reactiveMap.set(target,proxy)
// const state1 = reactive(data)
// const state2 = reactive(data)
return proxy
}
Vue中的effect方法
- src下新增effect.ts
export let activeEffect = undefined
class ReactiveEffect{
public active = true
public parent = null
public deps = []
constructor(public fn){}
run(){
if(!this.active){
this.fn()
}
try {
this.parent = activeEffect
activeEffect = this
return this.fn()
} finally {
activeEffect = this.parent
}
}
}
export function effect(fn){
let _effect = new ReactiveEffect(fn)
_effect.run()
}
const targetMap = new WeakMap()
export function track(target, type, key){
if(!activeEffect) return
let depsMap = targetMap.get(target)
if(!depsMap){
targetMap.set(target,(depsMap = new Map()))
}
let dep = depsMap.get(key)
if(!dep){
depsMap.set(key,(dep = new Set()))
}
let shouldTrack = !dep.has(activeEffect)
if(shouldTrack){
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
export function trigger(target,type,key,value,oldValue){
const depsMap = targetMap.get(target)
if(!depsMap) return
const effects = depsMap.get(key)
effects && effects.forEach(effect => {
if(effect !== activeEffect){
effect.run()
}
});
}
- baseHandler.ts
import { track, trigger } from "./effect"
import { isObject } from "@vue/shared"
import { reactive } from "./reactive"
export const enum ReactiveFlags {
IS_REACTIVE = '_v_isReactive'
}
export const mutableHandlers = {
get(target,key,receiver){
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
track(target,'get',key)
let res = Reflect.get(target, key, receiver)
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target,key,value,receiver){
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if(oldValue !== value){
trigger(target,'set',key,value,oldValue)
}
return result
}
}
Vue3中的分支切换原理
const {effect,reactive } = VueReactivity
const state = reactive({
name:'xiaonshunshi',
age:33,
flag:true
})
effect(()=>{
console.log('render')
document.getElementById('app').innerHTML = state.flag ? state.name:state.age
})
setTimeout(()=>{
state.flag = false
setTimeout(() => {
console.log('修改name,原则上更新一次')
state.name = 'song'
}, 1000);
},1000)
render
render
修改name,原则上更新一次
render
render
修改name,原则上更新一次
render
export let activeEffect = undefined
class ReactiveEffect {
public active = true
public parent = null
public deps = []
constructor(public fn) { }
run() {
if (!this.active) {
this.fn()
}
try {
this.parent = activeEffect
activeEffect = this
cleanupEffect(this)
return this.fn()
} finally {
activeEffect = this.parent
}
}
}
// 新增清除effect的deps
function cleanupEffect(effect) {
const { deps } = effect
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
effect.deps.length = 0
}
export function effect(fn) {
let _effect = new ReactiveEffect(fn)
_effect.run()
}
const targetMap = new WeakMap()
export function track(target, type, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
let shouldTrack = !dep.has(activeEffect)
if (shouldTrack) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
export function trigger(target, type, key, value, oldValue) {
const depsMap = targetMap.get(target)
if (!depsMap) return
let effects = depsMap.get(key)
// effects && effects.forEach(effect => {
// if(effect !== activeEffect){
// effect.run()
// }
// });
if (effects) {
// 要循环一下新的effect,避免死循环
effects = new Set(effects)
effects.forEach(effect => {
if (effect !== activeEffect) {
effect.run()
}
});
}
}
Vue中的调度器scopeEffect
const {
effect,
reactive
} = VueReactivity
const state = reactive({
name: 'xiaoshunshi',
age: 13,
flag: true
})
let ruuner = effect(() => {
document.getElementById('app').innerHTML = state.age
})
ruuner.effect.stop()
setTimeout(() => {
state.age = 1000
setTimeout(() => {
ruuner()
}, 1000);
}, 1000)
class ReactiveEffect {
public active = true
public parent = null
public deps = []
constructor(public fn) { }
run() {
if (!this.active) {
this.fn()
}
try {
this.parent = activeEffect
activeEffect = this
cleanupEffect(this)
return this.fn()
} finally {
activeEffect = this.parent
}
}
stop(){
if(this.active){
this.active = false
cleanupEffect(this)
}
}
}
.....................
.....................
.....................
export function effect(fn) {
let _effect = new ReactiveEffect(fn)
_effect.run()
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
let waiting = false
const {
effect,
reactive
} = VueReactivity
const state = reactive({
name: 'zcf',
age: 13,
flag: true
})
let ruuner = effect(() => {
document.getElementById('app').innerHTML = state.age
}, {
scheduler() {
console.log('run')
if (!waiting) {
waiting = true
setTimeout(() => {
ruuner()
waiting = false
}, 1000);
}
}
})
// ruuner.effect.stop()
state.age = 1000
state.age = 10001
state.age = 10002
state.age = 10003
state.age = 10004
state.age = 10005
state.age = 10006
class ReactiveEffect {
constructor(public fn,public scheduler) { }
.....................
.....................
.....................
export function effect(fn, options: any = {}) {
let _effect = new ReactiveEffect(fn,options.scheduler)
_effect.run()
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
.....................
.....................
.....................
function trigger(target, type, key, value, oldValue) {
.....................
if (effects) {
effects = new Set(effects)
effects.forEach(effect => {
if (effect !== activeEffect) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
});
}
.....................
}
Vue的计算属性
- 新建html文件
const {effect,reactive, computed} = VueReactivity
const state = reactive({
fastname:'xiao',
lastname:'shunshi'
})
const fullName = computed(()=>{
console.log('render')
return state.fastname+ state.lastname
})
effect(()=>{
document.getElementById('app').innerHTML = fullName.value
})
// const fullName = computed({
// get(){
// return state.fastname+ state.lastname
// },
// set(){}
// })
setTimeout(()=>{
state.fastname = '宋'
},1000)
- 新建computed.ts,在index.ts中导出方法
import { isFunction } from "@vue/shared";
import { ReactiveEffect,trackEffects,triggerEffects} from "./effect";
export function computed(getterOrOptions){
let onlyGetter = isFunction(getterOrOptions)
let getter
let setter
if (onlyGetter) {
getter = getterOrOptions
setter = () => { console.warn('no set') }
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(getter, setter)
}
class ComputedRefImpl{
public effect
public _dirty = true
public __v_isReadonly = true
public __v_isRef = true
public _value
public dep = new Set
constructor(public getter,public setter){
this.effect = new ReactiveEffect(getter,()=>{
if(!this._dirty){
this._dirty = true
}
triggerEffects(this.dep)
})
}
get value(){
trackEffects(this.dep)
if (this._dirty) {
this._dirty = false
this._value = this.effect.run()
}
return this._value
}
set value(newValue){
this.setter(newValue)
}
}
- effect.ts导出两个方法
export let activeEffect = undefined
export class ReactiveEffect {
public active = true
public parent = null
public deps = []
constructor(public fn,public scheduler) { }
run() {
if (!this.active) {
this.fn()
}
try {
this.parent = activeEffect
activeEffect = this
cleanupEffect(this)
return this.fn()
} finally {
activeEffect = this.parent
}
}
stop(){
if(this.active){
this.active = false
cleanupEffect(this)
}
}
}
function cleanupEffect(effect) {
const { deps } = effect
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
effect.deps.length = 0
}
export function effect(fn, options: any = {}) {
let _effect = new ReactiveEffect(fn,options.scheduler)
_effect.run()
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
const targetMap = new WeakMap()
export function track(target, type, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// let shouldTrack = !dep.has(activeEffect)
// if (shouldTrack) {
// dep.add(activeEffect)
// activeEffect.deps.push(dep)
// }
trackEffects(dep)
}
export function trackEffects(dep){
let shouldTrack = !dep.has(activeEffect)
if (shouldTrack) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
export function trigger(target, type, key, value, oldValue) {
const depsMap = targetMap.get(target)
if (!depsMap) return
let effects = depsMap.get(key)
// effects && effects.forEach(effect => {
// if(effect !== activeEffect){
// effect.run()
// }
// });
if (effects) {
// effects = new Set(effects)
// effects.forEach(effect => {
// if (effect !== activeEffect) {
// if (effect.scheduler) {
// effect.scheduler()
// } else {
// effect.run()
// }
// }
// });
triggerEffects(effects)
}
}
export function triggerEffects(effects){
effects = new Set(effects)
effects.forEach(effect => {
if (effect !== activeEffect) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
});
}
原文地址:https://blog.csdn.net/xiaoshunshi/article/details/125832155
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_6695.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。