对于希望提升自己前端架构能力,写出更优雅代码的前端同学,
vue源码绝对是你成功路上的垫脚石,但由于尤大实现的vue源码库有许多对于一些边缘情况的判断逻辑,
所有这篇文章希望和大家一起从头实现一个简单版的vue源码库 mini-vue(感谢 阿崔cxr 的 mini-vue)
github源码地址 如果觉着还不错欢迎Star~
项目初始化
由于项目需要使用jest作为单元测试框架,使用typescript作为开发语言。所以需要先配置其开发环境
npm init
配置jest环境
yarn add --dev jest
使用 Babel
yarn add --dev babel-jest @babel/core @babel/preset-env
在项目的根目录下创建 babel.config.js
,通过配置 Babel 使其能够兼容当前的 Node 版本。
babel.config.js
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript'
],
};
通过 Babel,Jest 能够支持 Typescript。首先要确保你遵循了上述使用 Babel 指引。接下来使用 yarn
安装 @babel/preset-typescript
:
yarn add --dev @babel/preset-typescript
Jest 在运行时并不会对你的测试用例做类型检查需要我们配置jest支持类型检测
yarn add --dev @types/jest
测试一下我们Jest的环境
下面我们开始给一个假定的函数写测试,这个函数的功能是两数相加。首先创建 sum.js
文件:
function sum(a, b) {
return a + b;
}
module.exports = sum;
接下来,创建名为 sum.test.js
的文件。这个文件包含了实际测试内容:
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
将如下代码添加到 package.json
中:
"scripts": {
"test": "jest"
}
最后,运行 yarn test
或者 npm run test
,Jest 将输出如下信息
$ jest
PASS ./sum.test.js (7.845 s)
√ adds 1 + 2 to equal 3 (3 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.37 s
Ran all test suites.
Done in 10.58s.
配置typescript环境
yarn add typescript --dev
初始化typescript配置文件
npx tsc --init
执行后他会自动生成tsconfig.json文件
我们要修改里面的
"lib": ["DOM", "ES6"]
以消除Proxy的ts报错
"noImplicitAny": false
以忽略类型关注主要逻辑
最终目录结构
实现reactive
我们新建一些关于reactive的文件夹,并删除之前测试jest的sum.js与sum.test.js,调整一下目录结构
1、 在src>reactive>test>reactive.test.ts中写不考虑异常的happy path测试用例
import { reactive } from '../reactive'
describe('reactive', () => {
it('happy path', () => {
const original = { 'bar': 1 }
const observable = reactive(original)
expect(original).not.toBe(observable)
expect(observable.bar).toBe(1)
})
})
2、在src>reactive>reactive.ts中实现reactive
export const reactive = (raw) => {
//使用Proxy包裹传入的对象
return new Proxy(raw, {
get: (target, key) => {
const ret = Reflect.get(target, key)
//这里需要存储依赖
return ret
},
set: (target, key, newValue) => {
const ret = Reflect.set(target, key, newValue)
//这里需要触发依赖
return ret
}
})
}
执行一下我们的jest
yarn test reactive.test.ts
实现effect
1、在src>reactive>test>effect.test.ts中编写测试用例
import { reactive } from '../reactive'
import { effect } from '../effect'
describe('effect', () => {
it('happy path', () => {
const info = reactive({
age: 18
})
let newAge
effect(() => {
newAge = info.age + 1
})
expect(newAge).toBe(19)
info.age++
expect(newAge).toBe(20)
})
})
2、在src>reactive>effect.ts中实现effect
let activeEffect
class ReactiveEffect {
private _fn
constructor(fn) {
this._fn = fn
}
run() {
activeEffect = this
this._fn()
}
}
export const effect = (fn) => {
let reactiveEffect = new ReactiveEffect(fn)
reactiveEffect.run()
}
let targetMap = new Map()
//用于reactive中的get时存储依赖
export const track = (target, key) => {
// {
// 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时触发依赖
export const trigger = (target, key) => {
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
for (const dep of deps) {
dep.run()
}
}
3、将track与trigger放到reactive中
import { track, trigger } from './effect'
export const reactive = (raw) => {
//使用Proxy包裹传入的对象
return new Proxy(raw, {
get: (target, key) => {
const ret = Reflect.get(target, key)
//这里需要存储依赖
track(target, key)
return ret
},
set: (target, key, newValue) => {
const ret = Reflect.set(target, key, newValue)
//这里需要触发依赖
trigger(target, key)
return ret
}
})
}
4、执行一下effect测试用例
yarn test effect.test.ts