松鼠乐园 松鼠乐园
  • 注册
  • 登录
  • 首页
  • 快捷入口
    • Vue
    • Tensorflow
    • Springboot
    • 语言类
      • CSS
      • ES5
      • ES6
      • Go
      • Java
      • Javascript
    • 工具类
      • Git
      • 工具推荐
    • 服务器&运维
      • Centos
      • Docker
      • Linux
      • Mac
      • MySQL
      • Nginx
      • Redis
      • Windows
    • 资源类
      • 论文
      • 书籍推荐
      • 后端资源
      • 前端资源
      • html网页模板
      • 代码
    • 性能优化
    • 测试
  • 重大新闻
  • 人工智能
  • 开源项目
  • Vue2.0从零开始
  • 广场
首页 › Vue › vue3.x reactive、effect、computed、watch依赖关系及实现原理

vue3.x reactive、effect、computed、watch依赖关系及实现原理

迦娜王
2年前Vue
1,153 0 0

首先来了解2个全局常量和1个变量。

  • targetMap [new WeakMap] : 在track()、trigger()的时候,冲当中间关联容器
  • effectStack [Array] : 顾名思义就是存放effect的容器
  • activeEffect : 在track()的时候作为Target的收集对象,可以当作一个临时作用的变量

1、reactive(target)

reactive实现原理,不废话(bb)直接上图

reactive实现原理

  • 原理大致就是通过 new Proxy() 重写getter、setter方法来实现自己需要的逻辑
  • 重写getter实现track收集effect
  • 重写setter现实trigger触发effect

2、effect(fn,options)

effect实现原理

effect是作为track()过程中最重要的环节,是唯一提供activeEffect的地方

3、computed(getterOrOptions)

computed实现原理

通过上面流程图不难看出computed的实现本质是effect.

  • 1.通过处理getterOrOptions参数得到一个匿名函数 getter
  • 2.把getter作为 effect() 参数得到一个延迟执行的runner函数
  • 3.把runner封装成一个 ref 对象并且返回。

4、watch(source,cb,options)

watch实现原理

相比 computed ,watch的实现逻辑相对复杂一些。这里只讲解大致实现原理。通过分析源码发现,watch的大致原理也是建立在 effect 的实现上。

  • 1.首先一些列处理把source 封装成为 effect的fn参数,例如:const getter = ()=> source;
  • 2.通过effect得到一个延迟的runner函数
  • 3.首次执行runner完成track()
  • 4.返回一个停止监听的匿名函数

5、简单实现

重点说明: 以下代码( 省略了很多逻辑 )的实现仅供原理理解,具体实现请移步vue-next源码。

5.1、reactive

通过1、reactive(target)的分析和理解,reactive大致实现就是这样:

function reactive(obj) {
    return new Proxy(obj, {
        get: function(target, key, receiver) {
            // getter
            track(target, key); // 收集effect函数
            return Reflect.get(target, key, receiver);
        },
        set: function(target, key, value, receiver) {
            // setter
            let oldValue = target[key]; // 原始值
            if (oldValue === value) {
                return;
            }
            let res = Reflect.set(target, key, value, receiver);
            trigger(target, key); // 触发effect函数
            return res;
        },
    });
}
复制代码

不了解Reflect的同学自行百度,这里不讲述。接下来实现track、trigger函数

//...
const targetMap = new WeakMap();
const effectStack = [];
let activeEffect = null;

function track(target, key) {
    if (!activeEffect) {
        return;
    }

    // 收集
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
        depsMap.set(key, (dep = new Set()));
    }
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
    }
}

function trigger(target, key) {
    // 触发
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        return;
    }

    let dep = depsMap.get(key);

    let effects = new Set();
    dep && dep.forEach((effect) => effects.add(effect));

    // 执行
    effects.forEach((effect) => {
        if (effect.options.scheduler) {
            effect.options.scheduler(effect);
        } else {
            effect();
        }
    });
}
复制代码

上面就是整个reactive实现的大致代码。虽然不够完整,但是够理解了。想要深入了解的同学可以直接移步vue-next源码。

5.2、effect

接下来就是 effect(fn,options) 的实现。代码如下:

const effectStack = [];
let activeEffect = null;
//...
function effect(fn, options = {}) {
    let effect = createEffect(fn, options);
    if (!options.lazy) {
        effect();
    }
    return effect;
}

let uid = 0;
function createEffect(fn, options) {
    const effect = () => {
        if (!effectStack.includes(effect)) {
            try {
                effectStack.push(effect);
                activeEffect = effect;
                // 默认执行收集
                return fn();
            } finally {
                // 处理完成
                effectStack.pop();
                activeEffect = effectStack[effectStack.length - 1];
            }
        }
    };
    effect.raw = fn;
    effect.id = uid++;
    effect.options = options;
    //...
    return effect;
}
复制代码

通过 effect(fn,options) 可以分析出effect的执行返回的是fn()的结果。如果吧reactive、effect放在一起你会发现他们的依赖关系。Proxy 通过getter收集activeEffect、setter触发activeEffect。看似两个完成独立的方法,中间通过全局变量activeEffect联系在一起。

5.3、computed

reactive和effect的实现之后computed就相对简单多了。computed的实现代码:

//...
function computed(fn) {
    let runner = effect(fn, {
        lazy: true,
    });

    return {
        get value() {
            // 执行收集
            return runner();
        },
        set value(value) {},
    };
}

// 使用
let data = reactive({ count : 0 })

let text = computed(() => {
    return `count:${data.count}`;
});

console.log(text.value) // count:0
复制代码

对,没错。computed的实现就是这么简单。相比computed、watch的实现就复杂多了。

5.3、watch

// 这里只是简单的实现watch以供理解

function watch(source, cb) {
    let getter = () => {};
    if (isFunction(source)) {
        getter = source;
    }
    // 收集信息
    let runner = effect(getter, {
        lazy: true,
        scheduler: () => {
            // 执行回调
            let value = runner();
            if (value !== oldValue) {
                cb(oldValue, value);
            }
            oldValue = value;
        },
    });
    // 第一次执行收集
    let oldValue = runner();
    //停止监听
    return ()=>{
        stop(runner)
        //...
    }
}

// 使用
let stopWatcher = watch(
    () => data.count,
    (oldValue, value) => {
        // 执行
        console.log("========watch========", oldValue, value);
    }
);

// 可以停止watch
// stopWatcher()

data.count = 100;
// ========watch======== 0 100

复制代码

5.4、完整的代码

// reactive
function reactive(obj) {
    return new Proxy(obj, {
        get: function(target, key, receiver) {
            track(target, key);
            // getter
            return Reflect.get(target, key, receiver);
        },
        set: function(target, key, value, receiver) {
            let oldValue = target[key]; // 原始值
            if (oldValue === value) {
                return;
            }

            // setter
            let res = Reflect.set(target, key, value, receiver);
            trigger(target, key);
            return res;
        },
    });
}

const targetMap = new WeakMap();
const effectStack = [];
let activeEffect = null;
// track
function track(target, key) {
    if (!activeEffect) {
        return;
    }

    // 收集
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
        depsMap.set(key, (dep = new Set()));
    }
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
    }
}
// trigger
function trigger(target, key) {
    // 触发
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        return;
    }

    let dep = depsMap.get(key);

    let effects = new Set();
    dep && dep.forEach((effect) => effects.add(effect));

    // 执行
    effects.forEach((effect) => {
        if (effect.options.scheduler) {
            effect.options.scheduler(effect);
        } else {
            effect();
        }
    });
}
// effect
function effect(fn, options = {}) {
    let effect = createEffect(fn, options);
    if (!options.lazy) {
        // 执行手机
        effect();
    }

    return effect;
}

let uid = 0;
function createEffect(fn, options) {
    const effect = () => {
        if (!effectStack.includes(effect)) {
            try {
                effectStack.push(effect);
                activeEffect = effect;
                // 首次执行收集
                return fn();
            } finally {
                // 处理完成
                effectStack.pop();
                activeEffect = effectStack[effectStack.length - 1];
            }
        }
    };
    effect.raw = fn;
    effect.id = uid++;
    effect.options = options;
    return effect;
}
// computed
function computed(fn) {
    let runner = effect(fn, {
        lazy: true,
    });

    return {
        get value() {
            // 执行收集
            return runner();
        },
        set value(value) {},
    };
}
// watch
function watch(source, cb) {
    let getter = () => {};
    if (typeof source === "function") {
        getter = source;
    }
    // 收集信息
    let runner = effect(getter, {
        lazy: true,
        scheduler: () => {
            // 执行回调
            let value = runner();
            if (value !== oldValue) {
                cb(oldValue, value);
            }
            oldValue = value
        },
    });
    // 第一次执行收集
    let oldValue = runner();
}
复制代码

6、总结

通过简单的实现可以总结出一下几点:

  • vue核心是重写new Proxy()的getter、setter来实现数据驱动的核心
  • computed和watch的实现可以看出实际原理都依赖effect()工厂
  • activeEffect可以看作是new Proxy()和effec()的交通枢纽

再次声明:上述实现代码仅供理解实现原理,具体详细实现请移步vue-next源码。

萌新,如有错误欢迎指正 (ง •_•)ง

0
Vue 3 新特性:在 Composition API 中使用 CSS Modules
上一篇
在Gitee收获近 5k Star,更新后的Vue版RuoYi有哪些新变化?
下一篇
评论 (0)

请登录以参与评论。

现在登录
聚合文章
Servicios profesionales Organizaciones
1年前
在Gitee收获近 5k Star,更新后的Vue版RuoYi有哪些新变化?
2年前
vue3.x reactive、effect、computed、watch依赖关系及实现原理
2年前
Vue 3 新特性:在 Composition API 中使用 CSS Modules
2年前
标签
AI AI项目 css docker Drone Elaticsearch es5 es6 Geometry Go gru java Javascript jenkins lstm mysql mysql优化 mysql地理位置索引 mysql索引 mysql规范 mysql设计 mysql配置文件 mysql面试题 mysql高可用 nginx Redis redis性能 rnn SpringBoot Tensorflow tensorflow2.0 UI设计 vue vue3.0 vue原理 whistle ZooKeeper 开源项目 抓包工具 日志输出 机器学习 深度学习 神经网络 论文 面试题
相关文章
在Gitee收获近 5k Star,更新后的Vue版RuoYi有哪些新变化?
Vue 3 新特性:在 Composition API 中使用 CSS Modules
新手必看的前端项目去中心化和模块化思想
基于vue+vant搭建移动端通用架子
松鼠乐园

资源整合,创造价值

小伙伴
墨魇博客 无同创意
目录
重大新闻 Centos CSS Docker ES5 ES6 Go Java Javascript Linux Mac MySQL Nginx Redis Springboot Tensorflow Vue Vue2.x从零开始 Windows 书籍推荐 人工智能 前端资源 后端资源 壁纸 开源项目 测试 论文
Copyright © 2018-2022 松鼠乐园. Designed by nicetheme. 浙ICP备15039601号-4
  • 重大新闻
  • Centos
  • CSS
  • Docker
  • ES5
  • ES6
  • Go
  • Java
  • Javascript
  • Linux
  • Mac
  • MySQL
  • Nginx
  • Redis
  • Springboot
  • Tensorflow
  • Vue
  • Vue2.x从零开始
  • Windows
  • 书籍推荐
  • 人工智能
  • 前端资源
  • 后端资源
  • 壁纸
  • 开源项目
  • 测试
  • 论文
热门搜索
  • jetson nano
  • vue
  • java
  • mysql
  • 人工智能
  • 人脸识别
迦娜王
坚持才有希望
1224 文章
35 评论
242 喜欢
  • 0
  • 0
  • Top