您现在的位置是:网站首页> 编程资料编程资料

solid.js响应式createSignal 源码解析_vue.js_

2023-05-24 419人已围观

简介 solid.js响应式createSignal 源码解析_vue.js_

正文

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

可以看到 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(value?: T, options?: SignalOptions): Signal { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s: SignalState = { 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 = (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 = (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 { value?: T; observers: Computation[] | null; observerSlots: number[] | null; tValue?: T; comparator?: (prev: T, next: T) => boolean; name?: string; } export function createSignal(value?: T, options?: SignalOptions): Signal { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s: SignalState = { 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 = (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(value?: T, options?: SignalOptions): Signal { // ... const setter: Setter = (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 | Memo) { const runningTransition = Transition && Transition.running; if ( (this as Memo).sources && ((!runningTransition && (this as Memo).state) || (runningTransition && (this as Memo).tState)) ) { if ( (!runningTransition && (this as Memo).state === STALE) || (runningTransition && (this as Memo).tState === STALE) ) updateComputation(this as Memo); else { const updates = Updates; Updates = null; runUpdates(() => lookUpstream(this as Memo), 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 | Memo) { // ... 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 | null = null; let Updates: Computation[] | null = null; let Effects: Computation[] | null = null