Vue3响应式核心之effect怎么使用
通常情况下我们是不会直接使用effect的,因为effect是一个底层的API,在我们使用Vue3的时候Vue默认会帮我们调用effect。 effect翻译为作用,意思是使其发生作用,这个使其的其就是我们传入的函数,所以effect的作用就是让我们传入的函数发生作用,也就是执行这个函数。 执行过程简图如下:
接下来先通过例子了解effect的基本用法,然后再去了解原理。
一、effect用法
1、基本用法
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }) obj.count++
结果会先打印1, 然后在obj.count++
之后打印出2。
流程简图如下:
运行effect(fun)
// 先执行 fun() // 打印出1 const runner = new ReactiveEffect(fn) return runner runner: { run() { this.fun() //执行fun }, stop() { } }
console.log(obj.count)
track依赖收集 结构如下:
obj.count++
触发依赖,执行runner.run(), 实际运行的是
() => { console.log(obj.count) }
所以又打印出2
2、lazy属性为true
此值为 true 时,只有在第一次手动调用 runner 后,依赖数据变更时,才会自动执行 effect 的回调,可以理解为 effect 的是在手动调用 runner 后才首次执行
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }, { lazy: true }) runner() obj.count++
只会打印出2
原因是effect源码中有如下逻辑:
3、options中包含onTrack
let events = [] const onTrack = (e) => { events.push(e) } const obj = reactive({ foo: 1, bar: 2 }) const runner = effect( () => { console.log(obj.foo) }, { onTrack } ) console.log('runner', runner) obj.foo++ console.log("events", events)
看下events的打印结果:
[ { effect: runner, // effect 函数的返回值 target: toRaw(obj), // 表示的是哪个响应式数据发生了变化 type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值 key: 'foo' } ]
二、源码分析
1、effect方法的实现
// packages/reactivity/src/effect.ts export interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean scheduler?: EffectScheduler scope?: EffectScope allowRecurse?: boolean onStop?: () => void } export function effect<T = any>( fn: () => T, // 副作用函数 options?: ReactiveEffectOptions // 结构如上 ): ReactiveEffectRunner { // 如果 fn 对象上有 effect 属性 if ((fn as ReactiveEffectRunner).effect) { // 那么就将 fn 替换为 fn.effect.fn fn = (fn as ReactiveEffectRunner).effect.fn } // 创建一个响应式副作用函数 const _effect = new ReactiveEffect(fn) if (options) { // 将配置项合并到响应式副作用函数上 extend(_effect, options) // 如果配置项中有 scope 属性(该属性的作用是指定副作用函数的作用域) if (options.scope) recordEffectScope(_effect, options.scope) } if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn() } // _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // 将响应式副作用函数赋值给 runner.effect runner.effect = _effect return runner }
核心代码:
创建一个响应式副作用函数const _effect = new ReactiveEffect(fn)
,其运行结果如下:
非lazy状态执行响应式副作用函数_effect.run()
if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn() }
_effect.run
作用域绑定到_effect
// _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
返回副作用函数runner
2、ReactiveEffect函数源码
export class ReactiveEffect<T = any> { active = true deps: Dep[] = [] // 响应式依赖项的集合 parent: ReactiveEffect | undefined = undefined /** * Can be attached after creation * @internal */ computed?: ComputedRefImpl<T> /** * @internal */ allowRecurse?: boolean /** * @internal */ private deferStop?: boolean onStop?: () => void // dev only onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, scope?: EffectScope ) { // 记录当前 ReactiveEffect 对象的作用域 recordEffectScope(this, scope) } run() { // 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果 if (!this.active) { return this.fn() } // 寻找当前 ReactiveEffect 对象的最顶层的父级作用域 let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack // 是否要跟踪 while (parent) { if (parent === this) { return } parent = parent.parent } try { // 记录父级作用域为当前活动的 ReactiveEffect 对象 this.parent = activeEffect activeEffect = this // 将当前活动的 ReactiveEffect 对象设置为 “自己” shouldTrack = true // 将 shouldTrack 设置为 true (表示是否需要收集依赖) // effectTrackDepth 用于标识当前的 effect 调用栈的深度,执行一次 effect 就会将 effectTrackDepth 加 1 trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { // 初始依赖追踪标记 initDepMarkers(this) } else { // 清除依赖追踪标记 cleanupEffect(this) } // 返回副作用函数执行结果 return this.fn() } finally { // 如果 effect调用栈的深度 没有超过阈值 if (effectTrackDepth <= maxMarkerBits) { // 确定最终的依赖追踪标记 finalizeDepMarkers(this) } // 执行完毕会将 effectTrackDepth 减 1 trackOpBit = 1 << --effectTrackDepth // 执行完毕,将当前活动的 ReactiveEffect 对象设置为 “父级作用域” activeEffect = this.parent // 将 shouldTrack 设置为上一个值 shouldTrack = lastShouldTrack // 将父级作用域设置为 undefined this.parent = undefined // 延时停止,这个标志是在 stop 方法中设置的 if (this.deferStop) { this.stop() } } } stop() { // stopped while running itself - defer the cleanup // 如果当前 活动的 ReactiveEffect 对象是 “自己” // 延迟停止,需要执行完当前的副作用函数之后再停止 if (activeEffect === this) { // 在 run 方法中会判断 deferStop 的值,如果为 true,就会执行 stop 方法 this.deferStop = true } else if (this.active) {// 如果当前 ReactiveEffect 对象处于活动状态 cleanupEffect(this) // 清除所有的依赖追踪标记 if (this.onStop) { this.onStop() } this.active = false // 将 active 设置为 false } } }
run方法的作用就是执行副作用函数,并且在执行副作用函数的过程中,会收集依赖;
stop方法的作用就是停止当前的ReactiveEffect对象,停止之后,就不会再收集依赖了;
activeEffect和this并不是每次都相等的,因为activeEffect会跟着调用栈的深度而变化,而this则是固定的;
三、依赖收集相关
1、如何触发依赖收集
在副作用函数中, obj.count
就会触发依赖收集
const runner = effect(() => { console.log(obj.count) })
触发的入口在get拦截器里面
function createGetter(isReadonly = false, shallow = false) { // 闭包返回 get 拦截器方法 return function get(target: Target, key: string | symbol, receiver: object) { // ... if (!isReadonly) { track(target, TrackOpTypes.GET, key) } // ... }
2、track源码
const targetMap = new WeakMap(); /** * 收集依赖 * @param target target 触发依赖的对象,例子中的obj * @param type 操作类型 比如obj.count就是get * @param key 指向对象的key, 比如obj.count就是count */ export function track(target: object, type: TrackOpTypes, key: unknown) { if (shouldTrack && activeEffect) { // 是否应该依赖收集 & 当前的new ReactiveEffect()即指向的就是当前正在执行的副作用函数 // 如果 targetMap 中没有 target,就会创建一个 Map let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = createDep())) // createDep 生成dep = { w:0, n: 0} } const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) } }
shouldTrack在上面也讲过,它的作用就是控制是否收集依赖;
activeEffect就是我们刚刚讲的ReactiveEffect对象,它指向的就是当前正在执行的副作用函数;
track方法的作用就是收集依赖,它的实现非常简单,就是在targetMap中记录下target和key;
targetMap是一个WeakMap,它的键是target,值是一个Map,这个Map的键是key,值是一个Set;
targetMap的结构伪代码如下:
targetMap = { target: { key: dep }, // 比如: obj: { count: { w: 0, n: 0 } } }
以上是最原始的depMap
dev环境为增加响应式调试会增加eventInfo
const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined
eventInfo结构如下:
trackEffects(dep, eventInfo)
如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去, 作用就把对象的属性操作与副作用函数建立关联,接下来看trackEffects
3、trackEffects(dep, eventInfo)源码解读
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { // 执行之前 dep = Set(0) {w: 0, n: 0} // 执行之后 dep = Set(0) {w: 0, n: 2} dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. shouldTrack = !dep.has(activeEffect!) } if (shouldTrack) { // 将activeEffect添加到dep dep.add(activeEffect!) activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { // onTrack逻辑 activeEffect!.onTrack( extend( { effect: activeEffect! }, debuggerEventExtraInfo! ) ) } } }
dep.add(activeEffect!)
如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去
最终生成的depTarget结构如下:
四、触发依赖
比如例子中代码obj.count++
就会触发set拦截,触发依赖更新
function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { //... const result = Reflect.set(target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) // 触发ADD依赖更新 } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) //触发SET依赖更新 } } //... }
1、trigger依赖更新
// 路径:packages/reactivity/src/effect.ts export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { const depsMap = targetMap.get(target) // 获取depsMap, targetMap是在track中创建的依赖 if (!depsMap) { // never been tracked return } let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target deps = [...depsMap.values()] } else if (key === 'length' && isArray(target)) { const newLength = Number(newValue) depsMap.forEach((dep, key) => { if (key === 'length' || key >= newLength) { deps.push(dep) } }) } else { // schedule runs for SET | ADD | DELETE if (key !== void 0) { deps.push(depsMap.get(key)) } // also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break } } const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } } else { const effects: ReactiveEffect[] = [] for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } }
const depsMap = targetMap.get(target)
获取 targetMap 中的 depsMap targetMap结构如下:
执行以上语句之后的depsMap结构如下:
将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中deps.push(depsMap.get(key))
之后的deps结构如下:
triggerEffects(deps[0], eventInfo)
const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } }
trigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。
在这里的实现其实并没有执行,主要是收集一些需要执行的副作用函数,然后在丢给triggerEffects函数去执行,接下来看看triggerEffects函数。
2、triggerEffects(deps[0], eventInfo)
export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } }
主要步骤
const effects = isArray(dep) ? dep : [...dep]
获取effects
triggerEffect(effect, debuggerEventExtraInfo)
执行effect,接下来看看源码
3、triggerEffect(effect, debuggerEventExtraInfo)
function triggerEffect( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { // 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行 if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } // 如果 effect 是一个调度器,就会执行 scheduler if (effect.scheduler) { effect.scheduler() } else { // 其它情况执行 effect.run() effect.run() } } }
effect.run()就是执行副作用函数
以上是Vue3响应式核心之effect怎么使用的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

vue3+vite:src使用require动态导入图片报错和解决方法vue3+vite动态的导入多张图片vue3如果使用的是typescript开发,就会出现require引入图片报错,requireisnotdefined不能像使用vue2这样imgUrl:require(’…/assets/test.png’)导入,是因为typescript不支持require所以用import导入,下面介绍如何解决:使用awaitimport

tinymce是一个功能齐全的富文本编辑器插件,但在vue中引入tinymce并不像别的Vue富文本插件一样那么顺利,tinymce本身并不适配Vue,还需要引入@tinymce/tinymce-vue,并且它是国外的富文本插件,没有通过中文版本,需要在其官网下载翻译包(可能需要翻墙)。1、安装相关依赖npminstalltinymce-Snpminstall@tinymce/tinymce-vue-S2、下载中文包3.引入皮肤和汉化包在项目public文件夹下新建tinymce文件夹,将下载的

Vue实现博客前端,需要实现markdown的解析,如果有代码则需要实现代码的高亮。Vue的markdown解析库有很多,如markdown-it、vue-markdown-loader、marked、vue-markdown等。这些库都大同小异。这里选用的是marked,代码高亮的库选用的是highlight.js。具体实现步骤如下:一、安装依赖库在vue项目下打开命令窗口,并输入以下命令npminstallmarked-save//marked用于将markdown转换成htmlnpmins

想要实现页面的局部刷新,我们只需要实现局部组件(dom)的重新渲染。在Vue中,想要实现这一效果最简便的方式方法就是使用v-if指令。在Vue2中我们除了使用v-if指令让局部dom的重新渲染,也可以新建一个空白组件,需要刷新局部页面时跳转至这个空白组件页面,然后在空白组件内的beforeRouteEnter守卫中又跳转回原来的页面。如下图所示,如何在Vue3.X中实现点击刷新按钮实现红框范围内的dom重新加载,并展示对应的加载状态。由于Vue3.X中scriptsetup语法中组件内守卫只有o

vue3项目打包发布到服务器后访问页面显示空白1、处理vue.config.js文件中的publicPath处理如下:const{defineConfig}=require('@vue/cli-service')module.exports=defineConfig({publicPath:process.env.NODE_ENV==='production'?'./':'/&

前言无论是vue还是react,当遇到多处重复代码的时候,我们都会想着如何复用这些代码,而不是一个文件里充斥着一堆冗余代码。实际上,vue和react都可以通过抽组件的方式来达到复用,但如果遇到一些很小的代码片段,你又不想抽到另外一个文件的情况下,相比而言,react可以在相同文件里面声明对应的小组件,或者通过renderfunction来实现,如:constDemo:FC=({msg})=>{returndemomsgis{msg}}constApp:FC=()=>{return(

使用Vue构建自定义元素WebComponents是一组web原生API的统称,允许开发者创建可复用的自定义元素(customelements)。自定义元素的主要好处是,它们可以在使用任何框架,甚至是在不使用框架的场景下使用。当你面向的最终用户可能使用了不同的前端技术栈,或是当你希望将最终的应用与它使用的组件实现细节解耦时,它们会是理想的选择。Vue和WebComponents是互补的技术,Vue为使用和创建自定义元素提供了出色的支持。你可以将自定义元素集成到现有的Vue应用中,或使用Vue来构

最终效果安装VueCropper组件yarnaddvue-cropper@next上面的安装值针对Vue3的,如果时Vue2或者想使用其他的方式引用,请访问它的npm官方地址:官方教程。在组件中引用使用时也很简单,只需要引入对应的组件和它的样式文件,我这里没有在全局引用,只在我的组件文件中引入import{userInfoByRequest}from'../js/api'import{VueCropper}from'vue-cropper&
