带你走进vue的响应式底层
vue响应式实现原理
第一章 vue 简单的effect实现
前言
该系列文章主要是编写vue3的响应式原理,让大家能够更为了解vue3的响应式实现方式,让你在工作中对于一些问题能更深层的解决
一、vue的响应式
vue的响应式是什么:简单概括就是响应式数据和函数之间的关系
什么是响应式数据呢,很简单,就是我们平时用的ref,reactive,computed等;
函数是什么,如果大家有了解过底层的话就知道,函数其实就是effect函数,
那么简单来说就是当我在effect函数中访问了响应式数据的时候,那么这个数据就会跟effect函数产生关联,当数据发生变化的时候,重新执行effect函数
二、编写一个简单的effect
首先我们先看一下使用vue的时候effect函数是个什么样子的
<script type="module">import {effect,ref,} from "./node_modules/vue/dist/vue.esm-browser.prod.js";const name = ref("vue");effect(() => {// 当前时间const time = new Date().getTime();console.log(time);console.log(name.value);});setTimeout(() => {name.value = "react";}, 1000);</script>
<script type="module">import {effect,ref,} from "./node_modules/vue/dist/vue.esm-browser.prod.js";const name = ref("vue");let age = 18;effect(() => {// 当前时间const time = new Date().getTime();console.log(time);console.log(name.value);console.log(age);});setTimeout(() => {age = 20;}, 1000);
我们能看到,刚进入页面的时候就会执行effect函数一次,差不多1s之后又会执行一次,此时name.value就变成我更改的react了;
那根据这个我们就能知道effect有以下特点
- effect在初始化的时候会执行一次
- 当响应式数据发生变化的时候,effect会重新执行,普通数据是不行的
实现简单的响应式
那基于以上两个特点,我们实现一个简单的响应式
同时我们要知道一点的是,ref实现不是基于proxy的,而是基于get() 和set() 去实现的,对象是由proxy去实现;当然如果ref的值是一个对象,会由reactive去创建响应式,这个等我们后面会详细说,目前只考虑ref的基本数据类型
class RefImpl {constructor(value) {this.value = value;}get value() {// 收集依赖return this._value;}set value(newVal) {// 触发更新this._value = newVal; }
}function ref(val) {return new RefImpl(val);
}
这就是ref最基本的实现,在get中我们去收集依赖,在set中我们触发更新
effect实现
let activeSub = null;
function effect(fn) {activeSub = fn;activeSub();activeSub = null;
}
首先,一开始就执行我们传过去的函数,因为effect特点就是初始化的时候会执行一次
我们将这个函数赋值给activeSub ,为什么呢?我们看后续的代码就知道
此时我们需要实现的是当我们的值发生变化了,如何让effect的函数重新执行呢?有聪明的小伙伴就知道了,函数不就是activeSub 嘛,对了,所以此时的代码变成了如下
class RefImpl {subs;constructor(value) {this.value = value;}get value() {// 收集依赖if (activeSub) {this.subs = activeSub;}return this._value;}set value(newVal) {// 触发更新this._value = newVal;this.subs?.();}
}
当我们收集依赖的时候,先判断activeSub有没有,没有的话就没必要去收集依赖了,当我们重新赋值之后,重新调用这个函数,不过这个函数我们赋值给了class里面的subs(这个很重要,后面的重点中的重点)
那我们看下完整代码和执行结果
class RefImpl {subs;constructor(value) {this.value = value;}get value() {// 收集依赖if (activeSub) {this.subs = activeSub;}return this._value;}set value(newVal) {// 触发更新this._value = newVal;this.subs?.();}
}
function ref(val) {return new RefImpl(val);
}
let activeSub = null;
function effect(fn) {activeSub = fn;activeSub();activeSub = null;
}
export { effect, ref };<script type="module">// import {// effect,// ref,// } from "./node_modules/vue/dist/vue.esm-browser.prod.js";import { effect, ref } from "./reactive/effect1.js";const name = ref("vue");effect(() => {const time = new Date().getTime();console.log(time)console.log(name.value);});setTimeout(() => {name.value = "react";}, 1000);
发现结果跟官方的effect一样的结果
总结
当然,这只是最最最最基本的响应式,还有需要情况要去处理,后续我会继续分享给大家响应式原理,如果有问题欢迎讨论