学Vue3核心概念与面试官斗智斗勇(一) 收集触发依赖

2023-10-12 18:40

本文主要是介绍学Vue3核心概念与面试官斗智斗勇(一) 收集触发依赖,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文章依据阅读源码的理解进行编写。如果有什么错误的地方,欢迎指正交流学习。
最近也在帮助想入行前端的朋友进行学习,如果有需要交流学习,可以添加微信 gdgzyw
聊天、学习、打游戏都阔以~

学习源码最快的方式就是理解概念后,自己写一个简版的功能。所以我们得先搭一个环境,这里采用测试驱动的方式进行。

初始化项目

初始化 package.json 和安装依赖

yarn init -y
yarn add -D @babel/core @babel/preset-env @babel/preset-typescript @types/jest babel-jest jest

添加 scripts 用于启动 jest

package.json

{// ..."scripts": {"test": "jest"},// ...
}

根目录创建 babel.config.js

module.exports = {presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
}

创建 tsconfig.json 文件

{"compilerOptions": {"target": "es2016","lib": ["DOM","es6"],"module": "commonjs","types": ["jest"],"esModuleInterop": true,"forceConsistentCasingInFileNames": true,"strict": true,"noImplicitAny": false,"skipLibCheck": true}
}

至此我们的项目就初始化完成了。如果你需要用 git 来管理,可以自行 git init

编写测试用例

创建 src/reactivity/tests/effect.spec.ts 文件

describe('effect', () => {it('happy path', () => {const bank = reactive({money: 100,});let myMoney;effect(() => {myMoney = bank.money * 2;});expect(myMoney).toBe(200);bank.money = 50;expect(myMoney).toBe(100);});
});

创建 scr/reactivity/tests/reactive.spec.ts

describe('reactive', () => {it('happy path', () => {const origin = {money: 100};const bank = reactive(origin);expect(bank).not.toBe(origin);expect(bank.money).toBe(100);});
});

现在我们运行 yarn test 测试用例是跑不通的。对应的函数我们还没有创建。下面正式开始我们的编码环节。

编写 reactive 函数

通过上面的测试用例,我们可以知道,我们接收一个对象的值,并且对他进行一个拦截。所以我们可以直接返回一个 proxy 的代理对象。

export function reactive(obj) {return new Proxy(obj, {get(target, key) {return Reflect.get(target, key)},set(target, key, value) {return Reflect.set(target, key, value)},})
}

Reflect.get(target, key) 等同于 target[key]

接着我们为了统一出口可以创建 src/reactivity/index.ts 文件

index.ts

export * from './reactive'

接着我们去 reactive.spec.ts 中引入我们的函数

import {reactive} from '../index'

跑一下测试

yarn test reactive

提示 PASS 至此发现这个的单侧已经跑通。接着我们可以开始写另一个单测。

编写 effect 函数

一样的,我们观察一下测试用例的参数。
可以发现他接受一个回调,所以我们参数是一个回调函数。
接着我们思考一下如何将我们上一个 reactive 的函数与这个回调函数产生关联。

image.png

定义 tagetMap 变量,用于对象的分组。
定义 depsMap 变量,用于对象中每个 key 的依赖分组。
通过 reactive 定义对象,在 get 的时候,我们在 targetMap
中将对象添加到 Map 中作为分类。接着创建 Set 用 key 作为分类保存到 Set 中。

回顾我们的单侧流程,我们先定义了个 reactive 对象。
接着我们在 effect 函数中执行了回调函数,回调函数中我们会读取到 reactive 的值,从而触发了 get 操作。所以我们需要在 get 操作中进行依赖收集。

定义一个 ReactiveEffect 类,收集我们的回调函数

src/reactivity/effect.ts

let activeEffect
class ReactiveEffect {private readonly _fn: anyconstructor(fn) {this._fn = fn}run() {activeEffect = thisthis._fn()}
}export function effect(fn) {const _effect = new ReactiveEffect(fn)_effect.run()
}

在初始化的时候,我们保存回调函数到 _fn 中,在我们执行 run 方法的时候。会触发我们的回调函数。

定义一个 track 的函数,完成收集依赖这个操作

src/reactivity/effect.ts

const targetMap = new Map()
export function track(target, key) {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)
}

如果此时我们需要设置 reactive 的值,我们会触发 set 操作。所以触发依赖的操作需要在 set 中进行。

定义 trigger 函数,触发所有依赖

export function trigger(target, key) {const depsMap = targetMap.get(target)const deps = depsMap.get(key)for (const dep of deps) {dep.run()}
}

回到 reactive 文件,将 track 和 trigger 写到对应的操作中。

src/reactivity/index.ts

// ...
export * from './effect'

src/reactivity/reactive.ts

import {target, trigger} from './index'export function reactive(obj) {return new Proxy(obj, {get(target, key) {track(target, key)return Reflect.get(target, key)},set(target, key, value) {const res = Reflect.set(target, key, value)trigger(target, key)return res},})
}

回到我们的单侧,将这几个库引入

src/reactivtiy/tests/effect.spec.ts

import {effect,reactive} from '../index' // ...

至此我们收集依赖和触发依赖的核心逻辑已经实现。我们现在可以跑 yarn test 进行检验。

结语

这篇文章是这个系列的开始,后续我会继续分享相关内容。慢慢完善我们对 vue3 的理解。
欢迎关注我,与我深入♂沟通。

这篇关于学Vue3核心概念与面试官斗智斗勇(一) 收集触发依赖的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/197735

相关文章

javacv依赖太大导致jar包也大的解决办法

《javacv依赖太大导致jar包也大的解决办法》随着项目的复杂度和依赖关系的增加,打包后的JAR包可能会变得很大,:本文主要介绍javacv依赖太大导致jar包也大的解决办法,文中通过代码介绍的... 目录前言1.检查依赖2.更改依赖3.检查副依赖总结 前言最近在写项目时,用到了Javacv里的获取视频

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

Spring 依赖注入与循环依赖总结

《Spring依赖注入与循环依赖总结》这篇文章给大家介绍Spring依赖注入与循环依赖总结篇,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. Spring 三级缓存解决循环依赖1. 创建UserService原始对象2. 将原始对象包装成工

redis-sentinel基础概念及部署流程

《redis-sentinel基础概念及部署流程》RedisSentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包... 目录一. 引言二. 核心功能三. 核心组件四. 故障转移流程五. 服务部署六. sentinel部署

Spring-DI依赖注入全过程

《Spring-DI依赖注入全过程》SpringDI是核心特性,通过容器管理依赖注入,降低耦合度,实现方式包括组件扫描、构造器/设值/字段注入、自动装配及作用域配置,支持灵活的依赖管理与生命周期控制,... 目录1. 什么是Spring DI?2.Spring如何做的DI3.总结1. 什么是Spring D

Python进阶之列表推导式的10个核心技巧

《Python进阶之列表推导式的10个核心技巧》在Python编程中,列表推导式(ListComprehension)是提升代码效率的瑞士军刀,本文将通过真实场景案例,揭示列表推导式的进阶用法,希望对... 目录一、基础语法重构:理解推导式的底层逻辑二、嵌套循环:破解多维数据处理难题三、条件表达式:实现分支

Springboot项目构建时各种依赖详细介绍与依赖关系说明详解

《Springboot项目构建时各种依赖详细介绍与依赖关系说明详解》SpringBoot通过spring-boot-dependencies统一依赖版本管理,spring-boot-starter-w... 目录一、spring-boot-dependencies1.简介2. 内容概览3.核心内容结构4.

深度解析Python yfinance的核心功能和高级用法

《深度解析Pythonyfinance的核心功能和高级用法》yfinance是一个功能强大且易于使用的Python库,用于从YahooFinance获取金融数据,本教程将深入探讨yfinance的核... 目录yfinance 深度解析教程 (python)1. 简介与安装1.1 什么是 yfinance?

一文带你迅速搞懂路由器/交换机/光猫三者概念区别

《一文带你迅速搞懂路由器/交换机/光猫三者概念区别》讨论网络设备时,常提及路由器、交换机及光猫等词汇,日常生活、工作中,这些设备至关重要,居家上网、企业内部沟通乃至互联网冲浪皆无法脱离其影响力,本文将... 当谈论网络设备时,我们常常会听到路由器、交换机和光猫这几个名词。它们是构建现代网络基础设施的关键组成

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security