solid.js响应式createSignal 源码解析

目录
  • 正文
  • createSignal
  • readSignal
  • writeSignal
  • 案例分析
  • 总结

正文

www.solidjs.com/docs/latest…

createSignal 用来创建响应式数据,它可以跟踪单个值的变化。

solid.js 的响应式实现参考了 S.js,它是一个体积超小的 reactive 库,支持自动收集依赖和简单的响应式编程。

createSignal

createSignal

首先我们来看下 createSignal 的声明:

// packages/solid/src/reactive/signal.ts
export interface BaseOptions {
  name?: string;
}
export interface EffectOptions extends BaseOptions {}
export interface MemoOptions<T> extends EffectOptions {
  equals?: false | ((prev: T, next: T) => boolean);
}
export type Accessor<T> = () => T;
export type Setter<T> = (undefined extends T ? () => undefined : {}) &
  (<U extends T>(value: (prev: T) => U) => U) &
  (<U extends T>(value: Exclude<U, Function>) => U) &
  (<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)) => U);
// packages/solid/src/reactive/signal.ts
export type Signal<T> = [get: Accessor<T>, set: Setter<T>];
export interface SignalOptions<T> extends MemoOptions<T> {
  internal?: boolean;
}
export function createSignal<T>(): Signal<T | undefined>;
export function createSignal<T>(value: T, options?: SignalOptions<T>): Signal<T>;

可以看到 createSignal 支持两个参数,分别是 value 和 options,然后返回一个包含 setter 和 getter 的数组。

参数:

  • value:初始值,默认值为 undefiend
  • options
    • equals:自定义比较器,用于新旧值比较或触发强制更新,允许传递函数或者 false;
    • internal(可选):标识是否为内置属性,应用于开发环境,生产环境会移除掉相关逻辑;
    • name(可选):自定义属性对象名称,应用于开发环境,生产环境会移除掉相关逻辑。

返回值:

  • getter:返回当前值,以函数形式调用

    • 自动进行依赖收集。例如在 createEffect 中调用 getter, state 对象会与 effect 建立依赖关系。
  • setter:设置值,以函数形式调用
    • 如果存在依赖当前 state 对象的观察者,循环执行观察者数组。

了解 createSignal 声明之后,下面我们来看下具体实现。

// packages/solid/src/reactive/signal.ts
export function createSignal<T>(value?: T, options?: SignalOptions<T>): Signal<T | undefined> {
  options = options ? Object.assign({}, signalOptions, options) : signalOptions;
  const s: SignalState<T> = {
    value,
    observers: null,
    observerSlots: null,
    comparator: options.equals || undefined
  };
  if ("_SOLID_DEV_" && !options.internal)
    s.name = registerGraph(options.name || hashValue(value), s as { value: unknown });
  const setter: Setter<T | undefined> = (value?: unknown) => {
    if (typeof value === "function") {
      if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);
      else value = value(s.value);
    }
    return writeSignal(s, value);
  };
  return [readSignal.bind(s), setter];
}

如果用户传入 options,会对 options 和默认的 options 进行合并,否则使用默认 options。

// packages/solid/src/reactive/signal.ts
export const equalFn = <T>(a: T, b: T) => a === b;
const signalOptions = { equals: equalFn };

默认配置只有一个 equals 属性,值为 equalFn ,用于比较两个值是否相同。

由于这里比较的是引用地址,所以当你改变一个对象的某个属性,重新赋值时,相关订阅并不会被触发,所以这时我们可以在传入的 options 配置中配置 equals 为 false 或者自定义其他比较逻辑。

例如下面的案例:

const [object, setObject] = createSignal({ count: 0 });
createEffect(() => {
  console.log(object());
});
object().count = 2;
setObject(object);
setObject(current => {
  current.count += 1;
  current.updated = new Date();
  return current;
});
// { count: 0 }

上述代码在运行时 effect 中代码只会触发一次,这可能与我们的预期不符,所以我们可以传入自定义 options。

const [object, setObject] = createSignal({ count: 0 }, { equals: false });
// { count: 0 }
// { count: 2 }
// { count: 3, updated: 2022-09-11T08:21:44.258Z }

当我们设置 equals 属性为 false,effect 就会被触发 3 次。

除此之外,我们还可以使用该配置作为触发器来使用,这里就不展开阐述了。感兴趣可以查看官方提供的案例,createSignal

下面让我们继续查看代码:

// packages/solid/src/reactive/signal.ts
export interface SignalState<T> {
  value?: T;
  observers: Computation<any>[] | null;
  observerSlots: number[] | null;
  tValue?: T;
  comparator?: (prev: T, next: T) => boolean;
  name?: string;
}
export function createSignal<T>(value?: T, options?: SignalOptions<T>): Signal<T | undefined> {
  options = options ? Object.assign({}, signalOptions, options) : signalOptions;
  const s: SignalState<T> = {
    value,
    observers: null,
    observerSlots: null,
    comparator: options.equals || undefined
  };
  if ("_SOLID_DEV_" && !options.internal)
    s.name = registerGraph(options.name || hashValue(value), s as { value: unknown });
  const setter: Setter<T | undefined> = (value?: unknown) => {
    if (typeof value === "function") {
      if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);
      else value = value(s.value);
    }
    return writeSignal(s, value);
  };
  return [readSignal.bind(s), setter];
}

createSignal 定义了 s 对象,它有四个属性,分别是:

  • value:传入的值
  • observers:观察者数组
  • observerSlots:观察者对象在数组的位置
  • comparator:比较器
// packages/solid/src/reactive/signal.ts
if ("_SOLID_DEV_" && !options.internal)
  s.name = registerGraph(options.name || hashValue(value), s as { value: unknown });

这段代码为 state 对象设置了 name 属性,不过它只作用于开发环境,生产环境打包时 _SOLID_DEV_ 变量会被替换为 false,然后会作为 decode 被移除掉。

// packages/solid/rollup.config.js
export default [
  {
    input: "src/index.ts",
		// ...
    plugins: [
      replace({
        '"_SOLID_DEV_"': false,
        preventAssignment: true,
        delimiters: ["", ""]
      })
    ].concat(plugins)
  }
]

接下来定义 setter 函数:首先会对 value 的值进行判断,如果传递的 setter 是一个 函数:

  • 如果发现 Transition 存在,并且 Transition.sources 中存在当前 state,会使用 s.tValue 属性值;
  • 如果上述条件不满足,会使用当前 state 的 value 属性值。

然后调用 wrtieSignal,并返回其结果。

// packages/solid/src/reactive/signal.ts
export function createSignal<T>(value?: T, options?: SignalOptions<T>): Signal<T | undefined> {
 	// ...
  const setter: Setter<T | undefined> = (value?: unknown) => {
    if (typeof value === "function") {
      if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);
      else value = value(s.value);
    }
    return writeSignal(s, value);
  };
  return [readSignal.bind(s), setter];
}

最后返回操作数组:第一个参数为 readSignal 函数,用来返回 s 中的 value 值,第二个参数就是 setter。

总结一下,createSignal 首先会合并用户 options,其次会定义 state 对象,用来记录当前值和依赖关系,然后定义 setter 函数,用来设置值,最后返回一个数组,分别是 readSignal 函数和 setter 函数。

readSignal

readSignal

看完 createSignal 定义,接着我们再来看下 readSignal,这个方法非常重要。solid.js 依赖关系的建立就发生在这个方法中。

// packages/solid/src/reactive/signal.ts
// Internal
export function readSignal(this: SignalState<any> | Memo<any>) {
  const runningTransition = Transition && Transition.running;
  if (
    (this as Memo<any>).sources &&
    ((!runningTransition && (this as Memo<any>).state) ||
      (runningTransition && (this as Memo<any>).tState))
  ) {
    if (
      (!runningTransition && (this as Memo<any>).state === STALE) ||
      (runningTransition && (this as Memo<any>).tState === STALE)
    )
      updateComputation(this as Memo<any>);
    else {
      const updates = Updates;
      Updates = null;
      runUpdates(() => lookUpstream(this as Memo<any>), false);
      Updates = updates;
    }
  }
  if (Listener) {
    const sSlot = this.observers ? this.observers.length : 0;
    if (!Listener.sources) {
      Listener.sources = [this];
      Listener.sourceSlots = [sSlot];
    } else {
      Listener.sources.push(this);
      Listener.sourceSlots!.push(sSlot);
    }
    if (!this.observers) {
      this.observers = [Listener];
      this.observerSlots = [Listener.sources.length - 1];
    } else {
      this.observers.push(Listener);
      this.observerSlots!.push(Listener.sources.length - 1);
    }
  }
  if (runningTransition && Transition!.sources.has(this)) return this.tValue;
  return this.value;
}

函数内部首先判断是否正在 transition,我们暂时不需要关心这段逻辑,直接跳到下面这段逻辑:

// packages/solid/src/reactive/signal.ts
export function readSignal(this: SignalState<any> | Memo<any>) {
	// ...
  if (Listener) {
    const sSlot = this.observers ? this.observers.length : 0;
    if (!Listener.sources) {
      Listener.sources = [this];
      Listener.sourceSlots = [sSlot];
    } else {
      Listener.sources.push(this);
      Listener.sourceSlots!.push(sSlot);
    }
    if (!this.observers) {
      this.observers = [Listener];
      this.observerSlots = [Listener.sources.length - 1];
    } else {
      this.observers.push(Listener);
      this.observerSlots!.push(Listener.sources.length - 1);
    }
  }
  if (runningTransition && Transition!.sources.has(this)) return this.tValue;
  return this.value;
}

首先会判断 Listener 是否存在,如果存在才会执行这段代码。那么这个 Listener 是什么时候被定义并赋值的呢?

// packages/solid/src/reactive/signal.ts
let Listener: Computation<any> | null = null;
let Updates: Computation<any>[] | null = null;
let Effects: Computation<any>[] | null = null;

Listener 是一个全局变量,默认值是 null。同时还定义了 UpdatesEffectes 数组,它们都是 Computation 类型。

export type EffectFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next;
export interface SignalState<T> {
  value?: T;
  observers: Computation<any>[] | null;
  observerSlots: number[] | null;
  tValue?: T;
  comparator?: (prev: T, next: T) => boolean;
  name?: string;
}
export interface Owner {
  owned: Computation<any>[] | null;
  cleanups: (() => void)[] | null;
  owner: Owner | null;
  context: any | null;
  sourceMap?: Record<string, { value: unknown }>;
  name?: string;
  componentName?: string;
}
export interface Computation<Init, Next extends Init = Init> extends Owner {
  fn: EffectFunction<Init, Next>;
  state: number;
  tState?: number;
  sources: SignalState<Next>[] | null;
  sourceSlots: number[] | null;
  value?: Init;
  updatedAt: number | null;
  pure: boolean;
  user?: boolean;
  suspense?: SuspenseContextType;
}

可以看到 Computation 是一个对象,定义了很多属性,基本都不知道啥作用。不过其中一个 sources 属性,你是否也感觉很眼熟?

对,它就是一个普通的 signal 对象,也就是我们调用 createSignal 方法时,内部创建的 s 对象。

另外可以看到,上面 SignalState 接口声明中的 observers 就是一个 Computation 类型的数组,这时我们已经知道 state 和 computation 互相依赖,并且是多对多的关系。

接下来再回到代码:

// packages/solid/src/reactive/signal.ts
export function readSignal(this: SignalState<any> | Memo<any>) {
	// ...
  if (Listener) {
    const sSlot = this.observers ? this.observers.length : 0;
    if (!Listener.sources) {
      Listener.sources = [this];
      Listener.sourceSlots = [sSlot];
    } else {
      Listener.sources.push(this);
      Listener.sourceSlots!.push(sSlot);
    }
    if (!this.observers) {
      this.observers = [Listener];
      this.observerSlots = [Listener.sources.length - 1];
    } else {
      this.observers.push(Listener);
      this.observerSlots!.push(Listener.sources.length - 1);
    }
  }
  if (runningTransition && Transition!.sources.has(this)) return this.tValue;
  return this.value;
}

当 Listener 存在时,首先会获取当前 observers 的数量,如果不存在就是 0,这里的 this 就是 s 对象。

export function createSignal<T>(value?: T, options?: SignalOptions<T>): Signal<T | undefined> {
	// ...
  return [readSignal.bind(s), setter];
}

接下来分别判断 Listener.sources 和 this.observers 是否存在,如果不存在会创建数组,并建立依赖关系。最后将 s 对象的 value 返回。这里的 value 就是我们调用 createSignal 传入的初始值。

不同于 vue 中 通过 Proxy 或者 Object.defineProperty 进行属性劫持,solid.js 中的依赖关系建立是通过函数调用实现的,例如在 createEffect 中调用 getter 函数,这时就会建立依赖关系。

writeSignal

writeSignal

我们已经知道,通过 getter 可以建立 compulation 和 state 之间的依赖关系。setter 函数其实就是用来触发依赖。

export function writeSignal(node: SignalState<any> | Memo<any>, value: any, isComp?: boolean) {
  let current =
    Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;
  if (!node.comparator || !node.comparator(current, value)) {
    if (Transition) {
      const TransitionRunning = Transition.running;
      if (TransitionRunning || (!isComp && Transition.sources.has(node))) {
        Transition.sources.add(node);
        node.tValue = value;
      }
      if (!TransitionRunning) node.value = value;
    } else node.value = value;
    if (node.observers && node.observers.length) {
      runUpdates(() => {
        for (let i = 0; i < node.observers!.length; i += 1) {
          const o = node.observers![i];
          const TransitionRunning = Transition && Transition.running;
          if (TransitionRunning && Transition!.disposed.has(o)) continue;
          if ((TransitionRunning && !o.tState) || (!TransitionRunning && !o.state)) {
            if (o.pure) Updates!.push(o);
            else Effects!.push(o);
            if ((o as Memo<any>).observers) markDownstream(o as Memo<any>);
          }
          if (TransitionRunning) o.tState = STALE;
          else o.state = STALE;
        }
        if (Updates!.length > 10e5) {
          Updates = [];
          if ("_SOLID_DEV_") throw new Error("Potential Infinite Loop Detected.");
          throw new Error();
        }
      }, false);
    }
  }
  return value;
}

writeSignal 接收两个参数,第一个参数就是 state 对象,第二个参数就是我们传入的值。

首先获取 current,我们暂时忽略 Transition 相关的判断逻辑,这里的 current 就是 state 的值,也就是旧值。

node.comparator 为 false,或者新值和旧值不同时,才会进行赋值和触发更新。

comparator 可以被赋值为 false 或者一个函数,默认 comparator 只会比较新值和旧值引用是否相同,这里我们后面再去分析。

当传入的值与旧值不同,将新值赋值 node.value。然后判断 node 是否存在观察者,如果存在会循环遍历 observers 数组,根据不同逻辑放入 Updates 或者 Effects 数组,不过最终都会执行 observer 对象,即 computation 对象。

案例分析

我们可以通过源码调试的方式对代码进行分析。

我们来看下面这个例子。

const { createSignal } = require("../../solid/dist/solid.cjs");
const [count, setCount] = createSignal(0);
console.log(count());
setCount(count() + 1);
console.log(count());

例子很简单,就是创建一个响应式数据,打印它,改变值后继续对其进行打印。

当 createSignal 函数被执行完毕之前,我们可以可以看到 s 对象已经被创建,value 值为 0,observers 为 null。

接下来执行第一次打印,这时会触发 readSignal 函数。

可以看到,this 其实就是 state 对象,此时 runningTransition 和 Listerner 都为空,什么都不会执行,直接返回 s.value。

当执行到 setCount(count() + 1) 这段代码时,首先会取到 state 的 value 值,然后再进行计算,并将结果传给 setter 函数,触发 writeSignal 函数。

可以看到,current 的值是 0,此时 comparator 肯定是存在的,并且两个值并不相等,由于 Transition 不存在,所以会将 value 赋值给 node.value,此时 state 的 value 值已经变为 1。由于 node.observers` 也不存在,所以会直接返回传入的 value ,函数执行完毕。

接下来执行最后一次打印,和之前的过程一样,这里只是做了一次取值操作,打印出改变后的结果 1。

我们还可以调试其他案例,比如给 createSignal 传递第二个参数,配置 name 和 equals 属性然后查看代码的变化。

相关案例

总结

createSignal 用于创建响应式数据,其内部定义了一个 s 对象,保存当前值和依赖关系,并返回 getter 函数和 setter 函数。

当调用 getter 函数读取值时,如果存在 Listener,双方会建立依赖关系,即将 Listener 添加到 state 的 observers 数组中,将 state 添加到 Listener 的 sources 数组中, 并返回当前值。

当调用 settter 函数赋值时,如果存在 observers,会遍历 observers 数组,并根据逻辑加入 Updates 或 Effects 数组中,最后去执行它们,触发副作用函数执行。

以上就是solid.js响应式createSignal 源码解析的详细内容,更多关于solid.js createSignal源码的资料请关注我们其它相关文章!

(0)

相关推荐

  • 教你应用 SOLID 原则整理 React 代码之单一原则

    目录 什么是单一责任原则? 让我们从一个糟糕的例子开始 1. 移动数据处理逻辑 2. 可重用的数据获取钩子 3. 分解 UI 组件 让我们回顾一下我们刚刚做了什么 总结 SOLID 原则的主要是作为关心自己工作的软件专业人员的指导方针,那些以经得起时间考验的设计精美的代码库为荣的人. 今天,我们将从一个糟糕的代码示例开始,应用 SOLID 的第一个原则,看看它如何帮助我们编写小巧.漂亮.干净的并明确责任的 React 组件,. 什么是单一责任原则? 单一责任原则告诉我们的是,每个类或组件应该有一

  • 如何应用 SOLID 原则在 React 中整理代码之开闭原则

    目录 本系列其他文章 什么是开闭原则? 让我们从一个例子开始 一个糟糕的解决方案 解决方案是什么? 让我们创建单独的用户组件 注意 总结 SOLID 是一套原则.它们主要是关心代码质量和可维护性的软件专业人员的指导方针. React 不是面向对象,但这些原则背后的主要思想可能是有帮助的.在本文中,我将尝试演示如何应用这些原则来编写更好的代码. 在前一篇文章中,我们讨论了单一责任原则.今天,我们将讨论 SOLID 的第二个原则: 开闭原则. 本系列其他文章 如何应用 SOLID 原则在 React

  • Android实例代码理解设计模式SOLID六大原则

    目录 单一职责原则 定义 代码解释 未遵守单一原则 遵守单一原则 开闭原则 定义 代码解释 里氏替换原则 定义 代码解释 依赖倒置原则 定义 代码解释 未遵守依赖导致原则 遵守依赖导致原则 接口隔离原则 定义 代码解释 未遵守接口隔离原则 遵守接口隔离原则 迪米特原则 定义 代码解释 单一职责原则 定义 定义: 确保单例类只有一个实例,并且这个单例类提供一个函数接口让其他类获取到这个唯一的实例. 解释:一个类只负责一个职责,不要存在多于一个导致类变更的原因. 代码解释 比如一个类记录一些食品的名

  • 在React中应用SOLID原则的方法

    目录 1.单一职责原则(SRP) 2.开放封闭原则(OCP) 3.里氏替换原则(LSP) 4.接口隔离原则(ISP) 5.依赖倒置原则(DIP) 6.小结 在面向对象编程(OOP)中,SOLID 原则是设计模式的基础,它的每个字母代表一种设计原则: 单一职责原则(SRP) 开放封闭原则(OCP) 里氏替换原则(LSP) 接口隔离原则(ISP) 依赖倒置原则(DIP) 下面就来看看每个原则的含义以及如何在 React 中应用 SOLID 原则! 1.单一职责原则(SRP) 单一职责原则的定义是每个

  • solid.js响应式createSignal 源码解析

    目录 正文 createSignal readSignal writeSignal 案例分析 总结 正文 www.solidjs.com/docs/latest… createSignal 用来创建响应式数据,它可以跟踪单个值的变化. solid.js 的响应式实现参考了 S.js,它是一个体积超小的 reactive 库,支持自动收集依赖和简单的响应式编程. createSignal createSignal 首先我们来看下 createSignal 的声明: // packages/soli

  • Vue3.0 响应式系统源码逐行分析讲解

    前言 关于响应式原理想必大家都很清楚了,下面我将会根据响应式API来具体讲解Vue3.0中的实现原理, 另外我只会针对get,set进行深入分析,本文包含以下API实现,推荐大家顺序阅读 effect reactive readonly computed ref 对了,大家一定要先知道怎么用哦~ 引子 先来段代码,大家可以直接复制哦,注意引用的文件 <!DOCTYPE html> <html lang="en"> <head> <meta ch

  • vue.js响应式原理解析与实现

    从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很好奇vue.js是如何监测数据更新并且重新渲染页面.今天,就我们就来一步步解析vue.js响应式的原理,并且来实现一个简单的demo. 首先,先让我们来了解一些基础知识. 基础知识 Object.defineProperty es5新增了Object.defineProperty这个api,它可以允许我们为对象的属性来设定gette

  • next.js getServerSideProps源码解析

    目录 SSR 处理 动态加载处理 总结 SSR 处理 老规矩,昨天写了关于 getServerSideProps 的内容,今天趁热写一下 getServerSideProps 相应的源码,看看 next.js getServerSideProps 是怎么实现的,还有什么从文档无法知晓的细节. 我们先从 SSR 时相关的 getServerSideProps 处理看起,源码排查步骤上一步已经有所介绍,本篇不再多说,在 SSR 时,next.js 会调用 doRender 来进行渲染,其中会再次调用

  • JS前端操作 Cookie源码示例解析

    目录 引言 源码分析 使用 源码 分析 set get remove withAttributes & withConverter 总结 引言 前端操作Cookie的场景其实并不多见,Cookie也因为各种问题被逐渐淘汰,但是我们不用Cookie也可以学习一下它的思想,或者通过这次的源码来学习其他的一些知识. 今天带来的是:js-cookie 源码分析 使用 根据README,我们可以看到js-cookie的使用方式: // 设置 Cookies.set('name', 'value'); //

  • 浅谈Vuejs中nextTick()异步更新队列源码解析

    vue官网关于此解释说明如下: vue2.0里面的深入响应式原理的异步更新队列 官网说明如下: 只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变.如果同一个 watcher 被多次触发,只会一次推入到队列中.这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要.然后,在下一个的事件循环"tick"中,Vue 刷新队列并执行实际(已去重的)工作.Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MutationOb

  • Vue源码解析之数组变异的实现

    力有不逮的对象 众所周知,在 Vue 中,直接修改对象属性的值无法触发响应式.当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变. 这是什么原因? 原因在于: Vue 的响应式系统是基于Object.defineProperty这个方法的,该方法可以监听对象中某个元素的获取或修改,经过了该方法处理的数据,我们称其为响应式数据.但是,该方法有一个很大的缺点,新增属性或者删除属性不会触发监听,举个栗子: var vm = new Vue({ data () { return

  • 从vue源码解析Vue.set()和this.$set()

    前言 最近死磕了一段时间vue源码,想想觉得还是要输出点东西,我们先来从Vue提供的Vue.set()和this.$set()这两个api看看它内部是怎么实现的. Vue.set()和this.$set()应用的场景 平时做项目的时候难免不会对 数组或者对象 进行这样的骚操作操作,结果发现,咦~~,他喵的,怎么页面没有重新渲染. const vueInstance = new Vue({ data: { arr: [1, 2], obj1: { a: 3 } } }); vueInstance.

  • Vue $nextTick 为什么能获取到最新Dom源码解析

    目录 正文 修改数据之后 update 方法 nextTick 方法里面怎么执行传进去更新方法 正文 <template> <p id='text'>{{text}}</p> <button @click='change'>click</button> </template> <script> export default { data() { return { text: 'hello world' } } method

  • Vue3源码解析watch函数实例

    目录 引言 一.watch参数类型 1. 选项options 2. 回调cb 3. 数据源source 二.watch函数 三.watch的核心:doWatch 函数 引言 想起上次面试,问了个古老的问题:watch和computed的区别.多少有点感慨,现在已经很少见这种耳熟能详的问题了,网络上八股文不少.今天,我更想分享一下从源码的层面来区别这八竿子打不着的两者.本篇针对watch做分析,下一篇分析computed. 一.watch参数类型 我们知道,vue3里的watch接收三个参数:侦听

随机推荐