学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

相关文章

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM

IDEA Maven提示:未解析的依赖项的问题及解决

《IDEAMaven提示:未解析的依赖项的问题及解决》:本文主要介绍IDEAMaven提示:未解析的依赖项的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录IDEA Maven提示:未解析的依编程赖项例如总结IDEA Maven提示:未解析的依赖项例如

详解MySQL中DISTINCT去重的核心注意事项

《详解MySQL中DISTINCT去重的核心注意事项》为了实现查询不重复的数据,MySQL提供了DISTINCT关键字,它的主要作用就是对数据表中一个或多个字段重复的数据进行过滤,只返回其中的一条数据... 目录DISTINCT 六大注意事项1. 作用范围:所有 SELECT 字段2. NULL 值的特殊处

Python pip下载包及所有依赖到指定文件夹的步骤说明

《Pythonpip下载包及所有依赖到指定文件夹的步骤说明》为了方便开发和部署,我们常常需要将Python项目所依赖的第三方包导出到本地文件夹中,:本文主要介绍Pythonpip下载包及所有依... 目录步骤说明命令格式示例参数说明离线安装方法注意事项总结要使用pip下载包及其所有依赖到指定文件夹,请按照以

Python包管理工具核心指令uvx举例详细解析

《Python包管理工具核心指令uvx举例详细解析》:本文主要介绍Python包管理工具核心指令uvx的相关资料,uvx是uv工具链中用于临时运行Python命令行工具的高效执行器,依托Rust实... 目录一、uvx 的定位与核心功能二、uvx 的典型应用场景三、uvx 与传统工具对比四、uvx 的技术实

java中Optional的核心用法和最佳实践

《java中Optional的核心用法和最佳实践》Java8中Optional用于处理可能为null的值,减少空指针异常,:本文主要介绍java中Optional核心用法和最佳实践的相关资料,文中... 目录前言1. 创建 Optional 对象1.1 常规创建方式2. 访问 Optional 中的值2.1

Java -jar命令如何运行外部依赖JAR包

《Java-jar命令如何运行外部依赖JAR包》在Java应用部署中,java-jar命令是启动可执行JAR包的标准方式,但当应用需要依赖外部JAR文件时,直接使用java-jar会面临类加载困... 目录引言:外部依赖JAR的必要性一、问题本质:类加载机制的限制1. Java -jar的默认行为2. 类加

java -jar命令运行 jar包时运行外部依赖jar包的场景分析

《java-jar命令运行jar包时运行外部依赖jar包的场景分析》:本文主要介绍java-jar命令运行jar包时运行外部依赖jar包的场景分析,本文给大家介绍的非常详细,对大家的学习或工作... 目录Java -jar命令运行 jar包时如何运行外部依赖jar包场景:解决:方法一、启动参数添加: -Xb

MySQL 事务的概念及ACID属性和使用详解

《MySQL事务的概念及ACID属性和使用详解》MySQL通过多线程实现存储工作,因此在并发访问场景中,事务确保了数据操作的一致性和可靠性,下面通过本文给大家介绍MySQL事务的概念及ACID属性和... 目录一、什么是事务二、事务的属性及使用2.1 事务的 ACID 属性2.2 为什么存在事务2.3 事务

Java Jackson核心注解使用详解

《JavaJackson核心注解使用详解》:本文主要介绍JavaJackson核心注解的使用,​​Jackson核心注解​​用于控制Java对象与JSON之间的序列化、反序列化行为,简化字段映射... 目录前言一、@jsonProperty-指定JSON字段名二、@JsonIgnore-忽略字段三、@Jso