手写Vue3中Reactivity模块实现
一.配置Webpack开发环境
安装依赖
yarn add webpack webpack-dev-server webpack-cli html-webpack-plugin @babel/core babel-loader @babel/preset-env -D
1
webpack.config.js文件编写
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:'./src/index.js',
output:{
filename:'bundle.js'
},
devtool:'source-map',
module:{
rules:[
{
test:/\.js/,use:'babel-loader',exclude:/node_modules/
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./public/index.html'
})
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
配置.babelrc文件
{
"presets": ["@babel/preset-env"]
}
1
2
3
2
3
执行脚本配置
"scripts": {
"build:dev": "webpack --mode development",
"serve": "webpack-dev-server"
}
1
2
3
4
2
3
4
使用Vue3响应式模块
import { reactive, effect } from '@vue/reactivity'
let state = reactive({
name: 'zf',
age: 11,
});
effect(() => {
console.log(state.name)
});
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
二.目录结构搭建
computed.js
effect.js
index.js
reactive.js
ref.js
1
2
3
4
5
2
3
4
5
这里我们要实现的方法分别用
reactive
、effect
、ref
、computed
方法。在index
文件中统一整合这些方法进行导出
export {computed} from './computed';
export {effect} from './effect';
export {reactive} from './reactive';
export {ref} from './ref';
1
2
3
4
2
3
4
三.reactive实现
import { mutableHandlers } from './baseHandlers'; // 代理相关逻辑
import { isObject } from './util'; // 工具方法
export function reactive(target) {
// 根据不同参数创建不同响应式对象
return createReactiveObject(
target,
mutableHandlers
)
}
function createReactiveObject(target, baseHandler) {
if (!isObject(target)) {
return target;
}
const observed = new Proxy(target, baseHandler);
return observed
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
baseHandlers
import { isObject, hasOwn, hasChanged } from "./util";
import { reactive } from "./reactive";
const get = createGetter();
const set = createSetter();
function createGetter() {
return function get(target, key, receiver) {
// 对获取的值进行放射
const res = Reflect.get(target, key, receiver);
console.log('属性获取',key)
if (isObject(res)) { // 如果获取的值是对象类型,则返回当前对象的代理对象
return reactive(res);
}
return res;
}
}
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
console.log('属性新增',key,value)
} else if (hasChanged(value, oldValue)) {
console.log('属性值被修改',key,value)
}
return result;
}
}
export const mutableHandlers = {
get, // 当获取属性时调用此方法
set // 当修改属性时调用此方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
这里我只对最常用到的get和set方法进行处理,还应该处理
has
、deleteProperty
、ownKeys
。这里为了快速掌握核心流程就先暂且跳过这些实现
使用reactive方法
let { computed, ref, reactive, effect } = Vue;
const proxy = reactive({name:'zf',age:11,lessons:['架构','高级']});
proxy.name = 'jw';
proxy.lessons[0] = '珠峰架构';
1
2
3
4
2
3
4
这里当我们获取属性和更改属性值时就可以触发对应的set和get方法
四.effect实现
我们再来看effect的实现,默认effect会立即执行,当依赖的值发生变化时effect会重新执行
// 创建effect时可以传递参数,computed也是基于effect来实现的,只是增加了一些参数条件而已
export function effect(fn, options = {}) {
const effect = createReactiveEffect(fn, options);
if (!options.lazy) {
effect(); // 默认effect应该立即被执行
}
return effect;
}
let uid = 0;
const effectStack = []; // 存放effect的队列
let activeEffect; // 当前正在执行的effect
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect); // 将当前effect放到栈中
activeEffect = effect; // 标记当前运行的effect
return fn(); // 执行用户定义的方法
} finally {
effectStack.pop(); // 执行完毕后出栈
activeEffect = effectStack[effectStack.length - 1];
}
}
}
effect.options = options; // effect所有属性
effect.id = uid++; // effect的标号
effect.deps = []; // effect函数对应的属性
return effect;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
五.依赖收集实现
在effect方法调用时会对属性进行取值,此时可以进行依赖收集。
// 收集属性对应的effect
export function track(target, type, key) {}
// 触发属性对应effect执行
export function trigger(target, type, key) {}
1
2
3
4
2
3
4
operations.js
export const TrackOpTypes = {
GET: 'get'
}
export const TriggerOpTypes = {
SET: 'set',
ADD: 'add'
}
1
2
3
4
5
6
7
2
3
4
5
6
7
定义收集类型和触发类型
function createGetter() {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
// 取值时依赖收集
track(target, TrackOpTypes.GET, key);
if (isObject(res)) {
return reactive(res);
}
return res;
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
// 设置值时触发更新 - ADD
trigger(target, TriggerOpTypes.ADD, key);
} else if (hasChanged(value, oldValue)) {
// 设置值时触发更新 - SET
trigger(target, TriggerOpTypes.SET, key, value, oldValue);
}
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
track的实现
export function track(target, type, key) {
if (activeEffect == undefined) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) { // 如果没有map,增加map
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key); // 取对应属性的依赖表
if (!dep) { // 如果没有则构建set
depsMap.set(key, (dep = new Set()));
}
if(!dep.has(activeEffect)){
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
trigger实现
export function trigger(target, type, key) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const run = (effects) => {
if (effects) {effects.forEach(effect => effect());}
}
// 有key 就找到对应的key的依赖执行
if (key !== void 0) {
run(depsMap.get(key));
}
// 数组新增属性
if (type == TriggerOpTypes.ADD) {
run(depsMap.get(isArray(target) ? 'length' : '');
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
六.computed实现原理
import { isFunction } from "./util";
import { effect, track,trigger } from './effect'
import { TriggerOpTypes, TrackOpTypes } from "./operations";
export function computed(getterOrOptions) {
let getter;
let setter;
if (isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = () => {}
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
let dirty = true;
let computed;
let value;
let runner = effect(getter, {
lazy: true, // 默认不执行
computed: true, // 计算属性
scheduler: () => {
if (!dirty) {
dirty = true;
trigger(computed, TriggerOpTypes.SET, 'value')
}
}
})
computed = {
__v_isRef: true,
get value() {
if (dirty) {
value = runner(); // 取值时运行effect
dirty = false;
}
track(computed, TrackOpTypes.GET, 'value');
return value;
},
set value(newValue) {
setter(newValue)
}
}
return computed;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
计算属性也是一个effect,标记effect
lazy:true
和computed:true
,提供scheduler方法。在依赖数据更新时会调用schedular方法计算属性需要标记
__v_isRef
说明计算属性取值时,会自动获取value属性,
计算属性会根据dirty值进行缓存。
export function trigger(target, type, key, value, oldValue) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const effects = new Set();
const computedRunners = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect.options.computed) {
computedRunners.add(effect)
} else {
effects.add(effect)
}
})
}
}
if (key !== void 0) {
add(depsMap.get(key));
}
if (TriggerOpTypes.ADD) {
add(depsMap.get(isArray(target) ? 'length' : ''));
}
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
computedRunners.forEach(run)
effects.forEach(run)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
当触发更新时需要分开执行计算属性和effect,计算属性的优先级高于effect,确保effect在执行时可以获得正确计算属性对应的结果
七.Ref实现原理
import { isObject } from "lodash";
import { reactive } from "./reactive";
import { track, trigger } from "./effect";
import { hasChanged } from "./util";
import { TriggerOpTypes,TrackOpTypes } from "./operations";
export function ref(value) {
return createRef(value);
}
function convert(rawValue) {
return isObject(rawValue) ? reactive(rawValue) : rawValue
}
function createRef(rawValue) {
let value = convert(rawValue);
let r = {
__v_isRef: true,
get value() { // 取值依赖收集
track(r, TrackOpTypes.GET, 'value')
return value
},
set value(newVal) { // 设置时触发更新
if (hasChanged(newVal, rawValue)) {
rawValue = newVal;
value = newVal
trigger(r, TriggerOpTypes.SET, 'value')
}
}
}
return r
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ref的原理就是将一个普通值,转化成对象,并且在获取和设置值时可以增加依赖收集和触发更新的功能