首页>>前端>>Vue->实现mini

实现mini

时间:2023-11-30 本站 点击:1

本文已参与「新人创作礼」活动,一起开启掘金创作之路

本专栏会讲述如何实现一个mini-vue,让你了解vue的底层原理,如果直接阅读vue源码,那会是一件非常头疼的事情,因为许多的代码是用于处理一些边界情况的,这就导致我们很难找到核心内容,而mini-vue实现了vue的核心功能,忽略边界条件的判断,旨在让我们能够抓住核心,了解vue的底层原理,并通过TDD的思想进行开发,让你感受到TDD带来的好处!

本节是该专栏的第一节,reactivity是实现mini-vue的基本,后续功能会依赖于reactivity,因此我们从reactivity开始,阅读本节之前,请确保你已经使用过vuereactivity相关功能,了解它的作用,本节不会介绍reactivity是什么,而是注重它的运行流程和实现原理

reactivity模块会分为几篇文章去讲解,本篇文章是reactivity模块的第一篇,主要讲解如何实现基本的reactiveeffect

reactive用于创建响应式对象

effect用于包裹副作用函数,收集响应式对象和副作用函数之间的依赖关系以及触发依赖

1. 项目搭建

首先需要创建我们的项目,需要用到的依赖有jestbabeltypescript

安装typescript

pnpm i typescript -Dnpx tsc --init

修改tsconfig.json,将noImplicitAnyfalse,因为我们主要关注的是原理实现,而不关注类型,但又希望用到typescript的一些特性,所以要允许项目中使用any

安装jest

pnpm i jest @types/jest -D

jest集成babel

pnpm i babel-jest @babel/core @babel/preset-env -D

创建babel.config.js

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

jest集成typescript

pnpm i @babel/preset-typescript -D

修改babel.config.js

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

修改package.json添加测试脚本

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

创建项目源码目录src/reactivity,并写一个简单的测试用例看看环境是否搭建成功,src/reactivity/tests/index.spec.ts

describe('index', () => {  it('happy path', () => {    console.log('hello reactivity');  });});

终端执行pnpm test,如果能够通过测试说明环境搭建完成

2. 实现简易版reactivity

首先我们实现一个简易版的reactivity,这也就意味着不考虑很多额外的功能,只考虑先把最基本的功能实现,那么reactivity最基本的功能有什么呢? 主要有两个模块:

reactive,用于创建响应式对象,通过Proxy实现

effect,管理副作用函数,最基本的功能包括依赖收集和触发依赖

2.1 effect测试用例

基于TDD(Test-Driven Development)的思想,我们先写一个简单的测试用例描述一下使用场景

// src/reactivity/tests/effect.spec.tsdescribe('effect', () => {// it 和 test 是一样的// it.skip 表示暂时跳过该测试项 因为目前需要 reactive 和 effect 而我们希望先去实现 reactive// 但又不希望 effect.spec.ts 影响整个测试的进行 因此可以用 skip 暂时跳过 等 reactive 实现后再改回来it.skip('happy path', () => { const foo = reactive({   name: 'foo',   age: 20,   isMale: true,   friends: ['Mike', 'Tom', 'Bob'],   info: {     address: 'China',     phone: 11011011000,   }, }); let nextAge; effect(() => (nextAge = foo.age + 1)); expect(nextAge).toBe(21); // update foo.age++; expect(nextAge).toBe(22);});}

我们的需求很简单,就是利用reactive创建一个响应式对象,然后effect函数中会执行副作用函数fn,当fn所依赖的响应式对象的数据修改后,能够自动执行副作用函数fn去更新依赖

由于reactiveeffect函数目前都还没有实现,这个单元测试自然是无法通过的,而它们又是两个大模块,因此实现这两个模块也是有它们对应的单元测试的,可是目前这个happy path的单元测试会妨碍我们之后编写具体某一个模块的单元测试的运行

比如我想先实现reactive,那么我就需要先编写相应的单元测试,然后去运行单元测试,但是由于effect模块还没实现,因此会被happy path的这个单元测试干扰,导致无法通过所有测试用例,因此我们可以先将其标记为skip,等我们实现完了两个模块的基本功能后再回来将标记删除,来测试happy path是否可以通过

下面来理一下这个测试用例的流程

2.2 reactive测试用例

我们先来实现reactive模块,仍然是先创建测试用例,根据测试用例去开发代码,这是TDD的核心思想

describe('reactive', () => {  it('happy path', () => {    const foo = { bar: 1 };    const observed = reactive(foo);    // observed 代理 foo 对象    expect(observed).not.toBe(foo);    expect(observed.bar).toBe(1);  });});

接下来我们就需要去实现reacive函数

2.3 reactive基本实现

pnpm i jest @types/jest -D0

就是返回了一个Proxy,代理传入的对象,并且getset也是基本的功能,没有做过多额外的处理

不过之后为了管理依赖,会在get中调用effect模块的track进行依赖收集,在set中调用effect模块的trigger触发依赖(目前还没实现,后面会讲),我们先看看能不能通过测试用例吧 测试用例通过,说明reactive实现基本的代理功能是没问题了,那么接下来我们就要开始处理依赖的问题了!

2.4 effect基本实现

考虑到effect既要负责执行副作用函数,又要管理依赖,有多个功能,因此适合将他们封装到一个类中,我们首先封装一个ReactiveEffect

pnpm i jest @types/jest -D1

只要调用effect函数,就会创建ReactiveEffect对象,并执行它的run方法,这样我们就将运行副作用函数的逻辑从effect中转移到了ReactiveEffectrun方法中了

接下来要编写track函数,用于收集依赖,trigger函数,用于触发依赖

2.4.1 track依赖收集

根据前面的流程图,track就是一个映射寻找的过程,首先是以依赖的对象作为key去寻找它的属性和副作用函数之间的映射,找到这个映射后,再以依赖对象的属性作为key去寻找副作用函数的集合,将当前激活的effect对象加入到该集合中即可

targetMap用一个全局变量去存储,可以使用Map或者WeakMap,为了让垃圾回收机制能够正常运作,建议使用WeakMap作为targetMap的实现,具体原因可自行了解MapWeakMap的区别

当前激活的effect对象用activeEffect全局变量存储,每当effct首次执行的时候,就会将activeEffect标记为当前在执行的函数 如果该函数内部有触发响应式对象的get拦截的话,就会执行track进行依赖收集,而track正是从actvieEffect中获取到需要收集的函数的,因此activeEffect算是建立起get拦截器和track之间沟通的桥梁

注意:加入到集合中的是**effect**对象,而不是副作用函数,因为我们是通过**effect**对象的**run**方法统一执行副作用函数的

pnpm i jest @types/jest -D2

为了能够在track中通过activeEffect访问到正确的当前激活的effect对象,我们需要在effect对象执行run方法的时候修改一下activeEffect指向自己

pnpm i jest @types/jest -D3

2.4.2 trigger触发依赖

触发依赖也很简单,流程图中已经说明了,根据target拿到depsMap,再根据key拿到deps集合,遍历集合中每一个effect对象,调用它们的run方法即可将副作用函数执行

pnpm i jest @types/jest -D4

tracktrigger都实现了以后,effect单元测试的happy path就可以通过了

原文:https://juejin.cn/post/7100097635817619464


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Vue/3793.html