solid.js
概述
solid.js 是一个用于构建用户界面,简单高效、性能卓越的 JavaScript 库。
Simple and performant reactivity for building user interfaces.
特性:
- 真实 DOM 的细粒度更新
- 组件只会运行一次,不会重新执行函数体
- react 更新每次都会重新执行函数体
- 体积小,并且运行速度快
- 提供现代框架功能
- 例如 JSX、代码片段、Suspense、错误边界、并发渲染等
- web component 友好,支持自定义元素
- 支持服务端渲染
- 支持自定义渲染器
- 通过自定义渲染器,原则上可以运行在任何平台
- 社区和生态正在逐步完善
工具支持:
- 阅读源码:https://github.dev/solidjs/solid
- github.dev 是一个 web 编辑器,你可以使用编辑任何仓库,只需要在 github 仓库中按下
.按键。
- github.dev 是一个 web 编辑器,你可以使用编辑任何仓库,只需要在 github 仓库中按下
- 查看编译结果:https://playground.solidjs.com
相关文章:
性能对比

目录划分
https://github.com/solidjs/solid
packages
- solid:核心模块,包含 solid.js 基础能力实现
- solid-element: WebComponents 相关
- solid-ssr: ssr 相关,提供 ssr 渲染的辅助工具
- babel-preset-solid:babel 实现,用于转化 jsx 相关内容
- test-integration:测试相关内容
代码调试
packages/solid/package.json
- vscode 调试代码
- 开启源代码映射
{
- "type": "module",
+ "type": "commonjs"
"scripts": {
"build": "npm-run-all -nl build:*",
"build:clean": "rimraf dist/ coverage/ store/dist/ web/dist/ h/dist/ h/jsx-runtime/dist html/dist/",
- "build:js": "rollup -c",
+ "build:js": "rollup -c --sourcemap",
// ...
},
}{
- "type": "module",
+ "type": "commonjs"
"scripts": {
"build": "npm-run-all -nl build:*",
"build:clean": "rimraf dist/ coverage/ store/dist/ web/dist/ h/dist/ h/jsx-runtime/dist html/dist/",
- "build:js": "rollup -c",
+ "build:js": "rollup -c --sourcemap",
// ...
},
}packages/solid/rollup.config.js
配置是否开启测试环境
配置打包文件后映射,调试其他文件类似
export default [
{
input: "src/index.ts",
output: [
{
file: "dist/solid.cjs",
format: "cjs"
},
{
file: "dist/solid.js",
format: "es"
}
],
plugins: [
replace({
- '"_SOLID_DEV_"': false,
preventAssignment: true,
delimiters: ["", ""]
})
].concat(plugins)
},
{
input: "web/src/index.ts",
output: [
{
file: "web/dist/web.cjs",
format: "cjs"
},
{
file: "web/dist/web.js",
format: "es",
+ paths: {
+ "solid-js": "../../dist/solid.js"
+ }
}
],
// ...
}
]export default [
{
input: "src/index.ts",
output: [
{
file: "dist/solid.cjs",
format: "cjs"
},
{
file: "dist/solid.js",
format: "es"
}
],
plugins: [
replace({
- '"_SOLID_DEV_"': false,
preventAssignment: true,
delimiters: ["", ""]
})
].concat(plugins)
},
{
input: "web/src/index.ts",
output: [
{
file: "web/dist/web.cjs",
format: "cjs"
},
{
file: "web/dist/web.js",
format: "es",
+ paths: {
+ "solid-js": "../../dist/solid.js"
+ }
}
],
// ...
}
]接下来,就可以运行 pnpm run build 命令打包代码,然后创建测试用例,引入打包后的代码即可。
响应式基础
createSignal 源码分析
https://www.solidjs.com/docs/latest/api#createsignal
createSignal 用来创建响应式数据,它可以跟踪单个值的变化。
solid.js 的响应式实现参考了 S.js,它是一个体积超小的 reactive 库,支持自动收集依赖和简单的响应式编程。
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 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>;// 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];
}// 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 };// 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 }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 }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];
}// 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 });// 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)
}
]// 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];
}// 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
看完 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;
}// 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;
}// 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;// 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。同时还定义了 Updates、Effectes 数组,它们都是 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;
}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;
}// 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];
}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
我们已经知道,通过 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;
}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());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 数组中,最后去执行它们,触发副作用函数执行。
createEffect 源码分析
https://www.solidjs.com/docs/latest/api#createeffect
createEffect 会创建一个 computation ,当在函数体内执行 getter 函数,会自动收集相关依赖。当依赖更新时,会重新运行该函数。
下面我们将使用这段代码对源码进行分析:
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count =", count());
});
setCount(count() + 1);const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count =", count());
});
setCount(count() + 1);依赖收集
createEffect
首先我们来看下 createEffect 的声明:
export interface BaseOptions {
name?: string;
}
export interface EffectOptions extends BaseOptions {}
// Also similar to OnEffectFunction
export type EffectFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next;
export function createEffect<Next>(fn: EffectFunction<undefined | NoInfer<Next>, Next>): void;
export function createEffect<Next, Init = Next>(
fn: EffectFunction<Init | Next, Next>,
value: Init,
options?: EffectOptions
): void;export interface BaseOptions {
name?: string;
}
export interface EffectOptions extends BaseOptions {}
// Also similar to OnEffectFunction
export type EffectFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next;
export function createEffect<Next>(fn: EffectFunction<undefined | NoInfer<Next>, Next>): void;
export function createEffect<Next, Init = Next>(
fn: EffectFunction<Init | Next, Next>,
value: Init,
options?: EffectOptions
): void;可以看到 createEffect 可以传递三个参数,分别是 fn、value 和 options,没有返回值。
- fn:副作用函数,当依赖项更新时,会重新执行
- value:初始值,创建 compulation 对象时会作为初始值
- options
- name:compulation 对象名称,开发环境使用,生产环境会被移除
接下来看下具体实现:
let Listener: Computation<any> | null = null;
let Updates: Computation<any>[] | null = null;
let Effects: Computation<any>[] | null = null;
let runEffects = runQueue;
function runQueue(queue: Computation<any>[]) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
function runUserEffects(queue: Computation<any>[]) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) setHydrateContext();
for (i = 0; i < userLength; i++) runTop(queue[i]);
}
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}let Listener: Computation<any> | null = null;
let Updates: Computation<any>[] | null = null;
let Effects: Computation<any>[] | null = null;
let runEffects = runQueue;
function runQueue(queue: Computation<any>[]) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
function runUserEffects(queue: Computation<any>[]) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) setHydrateContext();
for (i = 0; i < userLength; i++) runTop(queue[i]);
}
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}首先将 runUserEffects 函数赋值给 runEffects ,用来指定用户副作用函数的调度流程。
然后会调用 createComputation 函数创建 computation 对象,这个 computation 对象就是 readSignal 中的 Listener,当 readSignal 函数执行时会被被添加 state 的 observers 数组中。
我们可以暂时忽略 suspense 相关逻辑,这里只需要看响应式处理相关的内容。
创建完 computation 对象后,会将 computation 对象的 user 属性设置为 true,说明这是用户传入的 effect 函数。
在 solid.js 中,凡是需要收集依赖并在依赖变化时进行更新的操作,都会被描述成一个 Computation。
接着会判断 Effects 是否存在,如果存在将 computation 对象添加到 Effects 数组中,否则调用 updateComputation 函数进行更新。
createComputation
在 createEffect 创建 computation 对象,传入以下参数:
const STALE = 1;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined)const STALE = 1;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined)第 3 个参数为 false,即 pure 属性设置为 false,然后第 4 个参数 state 设置为 1。
function createComputation<Next, Init = unknown>(
fn: EffectFunction<Init | Next, Next>,
init: Init,
pure: boolean,
state: number = STALE,
options?: EffectOptions
): Computation<Init | Next, Next> {
const c: Computation<Init | Next, Next> = {
fn,
state: state,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: null,
pure
};
if (Transition && Transition.running) {
c.state = 0;
c.tState = state;
}
if (Owner === null)
"_SOLID_DEV_" &&
console.warn(
"computations created outside a `createRoot` or `render` will never be disposed"
);
else if (Owner !== UNOWNED) {
if (Transition && Transition.running && (Owner as Memo<Init, Next>).pure) {
if (!(Owner as Memo<Init, Next>).tOwned) (Owner as Memo<Init, Next>).tOwned = [c];
else (Owner as Memo<Init, Next>).tOwned!.push(c);
} else {
if (!Owner.owned) Owner.owned = [c];
else Owner.owned.push(c);
}
if ("_SOLID_DEV_")
c.name =
(options && options.name) ||
`${(Owner as Computation<any>).name || "c"}-${
(Owner.owned || (Owner as Memo<Init, Next>).tOwned!).length
}`;
}
if (ExternalSourceFactory) {
const [track, trigger] = createSignal<void>(undefined, { equals: false });
const ordinary = ExternalSourceFactory(c.fn, trigger);
onCleanup(() => ordinary.dispose());
const triggerInTransition: () => void = () =>
startTransition(trigger).then(() => inTransition.dispose());
const inTransition = ExternalSourceFactory(c.fn, triggerInTransition);
c.fn = x => {
track();
return Transition && Transition.running ? inTransition.track(x) : ordinary.track(x);
};
}
return c;
}function createComputation<Next, Init = unknown>(
fn: EffectFunction<Init | Next, Next>,
init: Init,
pure: boolean,
state: number = STALE,
options?: EffectOptions
): Computation<Init | Next, Next> {
const c: Computation<Init | Next, Next> = {
fn,
state: state,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: null,
pure
};
if (Transition && Transition.running) {
c.state = 0;
c.tState = state;
}
if (Owner === null)
"_SOLID_DEV_" &&
console.warn(
"computations created outside a `createRoot` or `render` will never be disposed"
);
else if (Owner !== UNOWNED) {
if (Transition && Transition.running && (Owner as Memo<Init, Next>).pure) {
if (!(Owner as Memo<Init, Next>).tOwned) (Owner as Memo<Init, Next>).tOwned = [c];
else (Owner as Memo<Init, Next>).tOwned!.push(c);
} else {
if (!Owner.owned) Owner.owned = [c];
else Owner.owned.push(c);
}
if ("_SOLID_DEV_")
c.name =
(options && options.name) ||
`${(Owner as Computation<any>).name || "c"}-${
(Owner.owned || (Owner as Memo<Init, Next>).tOwned!).length
}`;
}
if (ExternalSourceFactory) {
const [track, trigger] = createSignal<void>(undefined, { equals: false });
const ordinary = ExternalSourceFactory(c.fn, trigger);
onCleanup(() => ordinary.dispose());
const triggerInTransition: () => void = () =>
startTransition(trigger).then(() => inTransition.dispose());
const inTransition = ExternalSourceFactory(c.fn, triggerInTransition);
c.fn = x => {
track();
return Transition && Transition.running ? inTransition.track(x) : ordinary.track(x);
};
}
return c;
}createComputation 函数的主要的功能就是创建 computation 对象 c,并将它返回。
updateComputation
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}首次执行时,Effects 数组肯定是不存在的,所以会调用 updateComputation 函数,并将 computation 对象传入。
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}首先判断 fn 是否存在,如果不存在,直接返回。然后执行 cleanNode 逻辑。
function cleanNode(node: Owner) {
let i;
if ((node as Computation<any>).sources) {
while ((node as Computation<any>).sources!.length) {
const source = (node as Computation<any>).sources!.pop()!,
index = (node as Computation<any>).sourceSlots!.pop()!,
obs = source.observers;
if (obs && obs.length) {
const n = obs.pop()!,
s = source.observerSlots!.pop()!;
if (index < obs.length) {
n.sourceSlots![s] = index;
obs[index] = n;
source.observerSlots![index] = s;
}
}
}
}
if (Transition && Transition.running && (node as Memo<any>).pure) {
if ((node as Memo<any>).tOwned) {
for (i = 0; i < (node as Memo<any>).tOwned!.length; i++)
cleanNode((node as Memo<any>).tOwned![i]);
delete (node as Memo<any>).tOwned;
}
reset(node as Computation<any>, true);
} else if (node.owned) {
for (i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]);
node.owned = null;
}
if (node.cleanups) {
for (i = 0; i < node.cleanups.length; i++) node.cleanups[i]();
node.cleanups = null;
}
if (Transition && Transition.running) (node as Computation<any>).tState = 0;
else (node as Computation<any>).state = 0;
node.context = null;
"_SOLID_DEV_" && delete node.sourceMap;
}function cleanNode(node: Owner) {
let i;
if ((node as Computation<any>).sources) {
while ((node as Computation<any>).sources!.length) {
const source = (node as Computation<any>).sources!.pop()!,
index = (node as Computation<any>).sourceSlots!.pop()!,
obs = source.observers;
if (obs && obs.length) {
const n = obs.pop()!,
s = source.observerSlots!.pop()!;
if (index < obs.length) {
n.sourceSlots![s] = index;
obs[index] = n;
source.observerSlots![index] = s;
}
}
}
}
if (Transition && Transition.running && (node as Memo<any>).pure) {
if ((node as Memo<any>).tOwned) {
for (i = 0; i < (node as Memo<any>).tOwned!.length; i++)
cleanNode((node as Memo<any>).tOwned![i]);
delete (node as Memo<any>).tOwned;
}
reset(node as Computation<any>, true);
} else if (node.owned) {
for (i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]);
node.owned = null;
}
if (node.cleanups) {
for (i = 0; i < node.cleanups.length; i++) node.cleanups[i]();
node.cleanups = null;
}
if (Transition && Transition.running) (node as Computation<any>).tState = 0;
else (node as Computation<any>).state = 0;
node.context = null;
"_SOLID_DEV_" && delete node.sourceMap;
}在 cleanNode 中,首次使用 createEffect,大部分逻辑都不会走到, 这时 computation 对象的 sources、owned 都为空,不过这时会触发 (node as Computation<any>).state = 0;, 将原本 computation 对象的 state 设置为 0,原本为 1。
然后定义 owner、listener 缓存已经存在的 Owner、Listener ,这时的 owner、listener 都为空。
接下来的操作比较关键,将 node 赋值给 Listener、Owner ,这时如果调用 readSignal 时,Listener 是存在的,即当前正在执行的 Computation 对象。
然后会调用 runComputation 方法,执行用户传入的副作用函数。
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}此时 Transition 并不存在,跳过下面这段逻辑,最后再还原 Listener、Owner。
runComputation
function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}首先定义 nextValue 变量,用来存储最新计算的值。
接下来调用 node.fn 函数,计算值并赋值给 nextValue 变量。这里的 node.fn 就是我们使用 createEffect 传入的自定义函数。
获取到最新值后,判断 node.updateAt 是否不存在,或者 node.updatedAt < time 。
首次创建时,updateAt 属性并不存在,这时 Transition 也不存在,所以这里只是进行赋值操作,将计算后的最新值赋值给 node.value。
然后给 node.updateAt 赋值为 time,这里的 time 是调用 runComputation 时传入的,这时的 time 为默认值 ExecCount,它的值是 0。
下面我们继续看执行 node.fn 的过程,当执行到 node.fn 时,会调用我们传入的自定义函数。
createEffect(() => {
console.log("count =", count());
});createEffect(() => {
console.log("count =", count());
});我们在函数中使用了名为 count 的 getter 函数,当调用 count 时,会调用 getter 函数,即 readSignal 。
// 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;
}// 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;
}这段代码其实我们之前已经分析过,它会返回当前 state 的 value 值,即我们定义的属性值。
不过当时 Listener 并不存在,现在情况则不同,当我们调用 updateComputation 方法时,Listener 已经被赋值为 node ,即 computation 对象。
Listener = Owner = node;Listener = Owner = node;目前的 Transition 仍然是不存在的,我们直接来看 Listerner 相关逻辑。
// Internal
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;
}// Internal
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;
}这里的 this 还是 state,即 signalState 对象。
首先判断 observers 数组是否存在,很明显此时 observers 为空,我们并没有为其赋过值,所以这里的 sSlot 是 0。
然后判断 Listener.sources 是否存在:
- 如果
Listener.sources不存在,创建空数组,并分别为其赋值为 this(signalState)和 sSlot。 - 如果
Listener.sourcese存在,在数组中追加值。
继续判断 this.observers 是否存在:
- 如果
this.observers不存在- 创建空数组,赋值为
Listener,即 computation 对象,赋值给this.observers; - 创建空数组,赋值为
Listener.sources.length - 1,此时值为 0;
- 创建空数组,赋值为
- 如果
this.observers存储,追加值
执行完这部分代码,computation 和 signalState 已经建立起双向联系:
- signalState 的 observers 数组中,存储的是 computation;
- computation 的 sources 数组中,存储的是 signalState。
最后返回 this.value,这时控制台就可以打印出 count = 0 。
更新流程
writeSignal
代码继续执行,接下来执行更新操作。
setCount(count() + 1);setCount(count() + 1);首先调用 getter 函数获取当前值,继续 writeSignal 函数,不过此时 Listenenr 已经设置为空,所以只是返回当前值。
updateComputation 执行完 runComputation,会把缓存的 listener 赋值给 Listener,owner 赋值给 Owner。
所以我们得到数值 1 并传递给 setCount 函数,即 setter 函数,然后调用 writeSignal。
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];
}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];
}由于这里的 value 并不是 function,会直接调用 writeSignal 函数。
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;
}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;
}此时 Transition 依旧不存在,会将 value 值赋值给 node.value,此时 node.value 已经变为 1。
接下来继续执行,此时 node.observers 是存在的。使用 runUpdates 函数包括匿名函数,runUpdates 是调度逻辑,我们后面再来分析。
首先对 observers 数组进行遍历,获取到 computation 对象。
因为 Transition 并不存在,但是 computation 的 state 为 0, 所以会命中以下逻辑:
在 updateComputation 的 cleanNode 函数中将 state 修改为 0。
const STALE = 1;
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;const STALE = 1;
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;由于此时 pure 属性为 false,所以会将 computation 对象添加到 Effects 数组中,然后再将 state 的值重新修改为 1。
runUpdates
runUpdates 是任务调度相关逻辑:
let ExecCount = 0;
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}let ExecCount = 0;
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}判断 Updates 是否存在,如果存在,直接返回函数执行。
定义 wait 变量,赋值 false,!init 为真,将 Updates 赋值为空数组。此时 Effects 并不存在,赋值为空数组,ExecCount 变量自增,此时为 1。
然后执行函数内部逻辑,将 computation 添加到 Effects 数组中。
const res = fn();
completeUpdates(wait);const res = fn();
completeUpdates(wait);最后调用 completeUpdates 函数完成更新。
completeUpdates
function completeUpdates(wait: boolean) {
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
if (wait) return;
let res;
if (Transition && Transition.running) {
if (Transition.promises.size || Transition.queue.size) {
Transition.running = false;
Transition.effects.push.apply(Transition.effects, Effects!);
Effects = null;
setTransPending(true);
return;
}
// finish transition
const sources = Transition.sources;
const disposed = Transition.disposed;
res = Transition.resolve;
for (const e of Effects!) {
"tState" in e && (e.state = e.tState!);
delete e.tState;
}
Transition = null;
runUpdates(() => {
for (const d of disposed) cleanNode(d);
for (const v of sources) {
v.value = v.tValue;
if ((v as Memo<any>).owned) {
for (let i = 0, len = (v as Memo<any>).owned!.length; i < len; i++)
cleanNode((v as Memo<any>).owned![i]);
}
if ((v as Memo<any>).tOwned) (v as Memo<any>).owned = (v as Memo<any>).tOwned!;
delete v.tValue;
delete (v as Memo<any>).tOwned;
(v as Memo<any>).tState = 0;
}
setTransPending(false);
}, false);
}
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}function completeUpdates(wait: boolean) {
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
if (wait) return;
let res;
if (Transition && Transition.running) {
if (Transition.promises.size || Transition.queue.size) {
Transition.running = false;
Transition.effects.push.apply(Transition.effects, Effects!);
Effects = null;
setTransPending(true);
return;
}
// finish transition
const sources = Transition.sources;
const disposed = Transition.disposed;
res = Transition.resolve;
for (const e of Effects!) {
"tState" in e && (e.state = e.tState!);
delete e.tState;
}
Transition = null;
runUpdates(() => {
for (const d of disposed) cleanNode(d);
for (const v of sources) {
v.value = v.tValue;
if ((v as Memo<any>).owned) {
for (let i = 0, len = (v as Memo<any>).owned!.length; i < len; i++)
cleanNode((v as Memo<any>).owned![i]);
}
if ((v as Memo<any>).tOwned) (v as Memo<any>).owned = (v as Memo<any>).tOwned!;
delete v.tValue;
delete (v as Memo<any>).tOwned;
(v as Memo<any>).tState = 0;
}
setTransPending(false);
}, false);
}
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}首先判断 Updates 是否存在,这里确实存在,不过它是一个空数组。
function runQueue(queue: Computation<any>[]) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}function runQueue(queue: Computation<any>[]) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}然后判断 Scheduler 和 Transition,这里都是不存在的,然后会调用 runQueue。因为这里是空数组,暂时跳过这部分逻辑,执行完毕后,会将 Updates 重新设置为 null。
继续执行,判断 wait 变量,值为 false 跳过,Transition 相关逻辑跳过,然后就只剩下这段代码。
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();将 Effects 赋值给 e,然后将 Effects 设置为空。判断 e 的长度,继续调用 runUpdates 函数,传入 runEffects(e),执行 Effect。
runEffects
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}这里的 runEffects 函数其实是 runUserEffects,在调用 createEffect 的时候,就已经把 runUserEffects 赋值给 runEffects。
function runUserEffects(queue: Computation<any>[]) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) setHydrateContext();
for (i = 0; i < userLength; i++) runTop(queue[i]);
}function runUserEffects(queue: Computation<any>[]) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) setHydrateContext();
for (i = 0; i < userLength; i++) runTop(queue[i]);
}首先循环遍历 effects queue,其实我们这里只有一个 effect 对象,即 computation 对象。
因为这里的 computation 是用户创建的 computation,所以不会立即调用 runTop 去执行 computation,而是将 user computation 缓存起来,优先执行系统创建的 computation。
这里的逻辑非常巧妙,我们可以举个例子。假设我们有多个 computation,既包括用户创建的 computation,也有系统创建的。
runUserEffects([
{
user: false,
text: "computation01"
},
{
user: true,
text: "user computation01"
},
{
user: true,
text: "user computation02"
},
{
user: false,
text: "computation02"
},
{
user: true,
text: "user computation03"
}
]);
function runUserEffects(queue) {
let i;
let userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) console.log(e.text);
else queue[userLength++] = e;
}
const userEffects = queue.slice(0, userLength);
console.log(userEffects);
for (let i = 0; i < userLength; i++) {
console.log(queue[i].text);
}
// computation01
// computation02
// [
// { user: true, text: 'user computation01' },
// { user: true, text: 'user computation02' },
// { user: true, text: 'user computation03' }
// ]
// user computation01
// user computation02
// user computation03
}runUserEffects([
{
user: false,
text: "computation01"
},
{
user: true,
text: "user computation01"
},
{
user: true,
text: "user computation02"
},
{
user: false,
text: "computation02"
},
{
user: true,
text: "user computation03"
}
]);
function runUserEffects(queue) {
let i;
let userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) console.log(e.text);
else queue[userLength++] = e;
}
const userEffects = queue.slice(0, userLength);
console.log(userEffects);
for (let i = 0; i < userLength; i++) {
console.log(queue[i].text);
}
// computation01
// computation02
// [
// { user: true, text: 'user computation01' },
// { user: true, text: 'user computation02' },
// { user: true, text: 'user computation03' }
// ]
// user computation01
// user computation02
// user computation03
}可以看到,如果是用户 computation,会以 userLength 重新对数组进行赋值,这里的 i 肯定是大于 userLength,所以不会有问题。
然后判断 sharedConfig.context 是否存在,这里当然是不存在的,跳过。
最后再依次执行从 0 到 userLength 区间内的 user computation,使用 runTop 函数去执行每个 computation。
所以这里 computation 的执行是有优先级的,首先会执行系统创建的 computation,然后才会执行 user computation。
runTop
此时 Transition 不存在, node.state 的值是 1,也不存在 suspense 属性,所以会跳过相关逻辑。
function runTop(node: Computation<any>) {
const runningTransition = Transition && Transition.running;
if ((!runningTransition && node.state === 0) || (runningTransition && node.tState === 0)) return;
if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
)
return lookUpstream(node);
if (node.suspense && untrack(node.suspense.inFallback!))
return node!.suspense.effects!.push(node!);
// ...
}function runTop(node: Computation<any>) {
const runningTransition = Transition && Transition.running;
if ((!runningTransition && node.state === 0) || (runningTransition && node.tState === 0)) return;
if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
)
return lookUpstream(node);
if (node.suspense && untrack(node.suspense.inFallback!))
return node!.suspense.effects!.push(node!);
// ...
}跳过上述判断逻辑后,开始正式执行更新逻辑。
function runTop(node: Computation<any>) {
// ...
const ancestors = [node];
while (
(node = node.owner as Computation<any>) &&
(!node.updatedAt || node.updatedAt < ExecCount)
) {
if (runningTransition && Transition!.disposed.has(node)) return;
if ((!runningTransition && node.state) || (runningTransition && node.tState))
ancestors.push(node);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}function runTop(node: Computation<any>) {
// ...
const ancestors = [node];
while (
(node = node.owner as Computation<any>) &&
(!node.updatedAt || node.updatedAt < ExecCount)
) {
if (runningTransition && Transition!.disposed.has(node)) return;
if ((!runningTransition && node.state) || (runningTransition && node.tState))
ancestors.push(node);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}首先定义数组 ancestores,设置第一个元素为当前 node,即 computation 对象。
然后开启一个 while 循环,不断将 node.owner 赋值给 node,判断是否存在,如果存在并且符合连带条件,会将 node 添加到 ancestors 数组中。不过此时这里的 node .owner 并不存在,所以不会进行添加操作。
代码继续执行,定义一个 for 循环,从后往前执行,遍历 ancestors 数组。
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}由于 runnintTransition 不存在,并且 node.state 为 1 ,等于 STALE,所以会执行这行代码。
updateComputation(node);updateComputation(node);updateComputation
所以,现在又回到这段代码(ps:是不是感觉好绕,我也是这么想的),开始更新 computation:
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}这里的执行逻辑和我们之前分析依赖收集基本一致。
不同点在于这里的 time 变量已经变为 2,还有一个不同发生在 cleanNode 方法中。
function cleanNode(node: Owner) {
let i;
if ((node as Computation<any>).sources) {
while ((node as Computation<any>).sources!.length) {
const source = (node as Computation<any>).sources!.pop()!,
index = (node as Computation<any>).sourceSlots!.pop()!,
obs = source.observers;
if (obs && obs.length) {
const n = obs.pop()!,
s = source.observerSlots!.pop()!;
if (index < obs.length) {
n.sourceSlots![s] = index;
obs[index] = n;
source.observerSlots![index] = s;
}
}
}
}
// ...
else (node as Computation<any>).state = 0;
node.context = null;
"_SOLID_DEV_" && delete node.sourceMap;
}function cleanNode(node: Owner) {
let i;
if ((node as Computation<any>).sources) {
while ((node as Computation<any>).sources!.length) {
const source = (node as Computation<any>).sources!.pop()!,
index = (node as Computation<any>).sourceSlots!.pop()!,
obs = source.observers;
if (obs && obs.length) {
const n = obs.pop()!,
s = source.observerSlots!.pop()!;
if (index < obs.length) {
n.sourceSlots![s] = index;
obs[index] = n;
source.observerSlots![index] = s;
}
}
}
}
// ...
else (node as Computation<any>).state = 0;
node.context = null;
"_SOLID_DEV_" && delete node.sourceMap;
}这里的 computation 是存在 sources 数组的,所以会对 sources 数组及其依赖的 observers 数组进行清理,解除两者的依赖关系。然后再将 computation 的 state 设置为 0。
然后再执行 runComputation 方法,重新建立起依赖关系。
runComputation
function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}定义变量 nextValue,执行 node.fn,将返回值赋值给 nextValue。这里的 node.fn 就是我们的副作用函数:
createEffect(() => {
console.log("count =", count());
});createEffect(() => {
console.log("count =", count());
});执行副作用函数会重新进行取值操作,收集依赖,需要注意的是这里 souces 数组和 observers 都是空数组,所以会执行 push 新增逻辑。最后返回新值。
// Internal
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);
}
}
// ...
return this.value;
}// Internal
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);
}
}
// ...
return this.value;
}此时控制台会打印出:
count = 1count = 1因为此时 updateAt 属性存在并小于 time,此时为 2,所以会触发下面逻辑。
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;然后会回到 updateComputation,将 Listener 和 Owner 重新赋值为缓存的 listener 和 owner。
继续回退,直至清空函数调用栈,代码执行完毕。
总结
我们已经完成对 solid.js 的依赖收集和更新过程分析。这里对执行过程做一下总结。
首先是依赖收集过程,当我们使用 createEffect 时:
- 首先会将
runUserEffects赋值给runEffects - 然后会调用
createComputation创建computation对象 - 标识
computation为用户computation - 接着会调用
updateComputation方法- 清洗
node(computation),设置computation对象的state为 0 - 缓存上次的
Owner和Listener,设置time属性值,将node(computation), 赋值给Listener和Owner - 调用
runComputation方法- 定义
nextValue属性 - 执行用户传入的副作用函数,此时会执行
getter,获取最新值,赋值给nextValue- 因为此时
Listener(computation)已经存在,所以会和siganlState建立起依赖关系; - 只有这样我们才可以在设置响应式数据时,获取依赖当前数据的观察者数据,进行更新。
- 因为此时
- 因为此时
Transition和node.updatedAt都不存在,所以会把最新值赋值给computation.value,这里也是undefined - 设置
node的updatedAt属性
- 定义
- 还原
Listerner和Owner
- 清洗
然后是更新流程,当我们使用 setCount(count() + 1) 设置值时:
- 首先使用
count()调用getter(readSignal)获取最新值,然后将结果加 1 传入setCount - 调用
setCount会触发setter,因为我们传入的value是原始值,所以会直接调用writeSignal- 将传入的新值赋值给
node.value,此时node的值已经是最新值 - 因为此时
node(signleSignal)存在observers(computation)数组,所以会执行更新流程 - 使用
runUpdates传入处理computation数组的匿名函数,最终会将computation添加到Effects数组中- 设置
wait变量,赋值为false,初始化Effects数组; - 全局变量
ExecCount自增; - 调用传入的匿名函数,将
computation添加到Effects数组中,并且将computation的值重新设置为 1 - 调用
completeUpdates完成更新,传入变量wait,此时值为false- 缓存
Effects数组,赋值给变量e,Effects重新设置为null - 继续调用
runUpdates传入匿名函数内部调用runEffects,参数为e- 同样设置
wait变量,赋值为false,初始化Effects数组,数组为空,ExecCount变量自增 - 调用传入的匿名函数,触发
runEffects- 处理
effects数组,优先执行系统定义的effect,其次执行用户传入的effect - 调用
runTop方法执行effect- 定义
ancestors,存储effect - 如果
effect(computation)存在owner属性,添加到ancestors数组中 - 从后向前遍历
ancestors数组,因为此时transition不存在且node.state为 1 - 调用
updateComputation方法更新computation,具体逻辑和之前分析依赖收集基本一致。不过在执行cleanNode方法时,由于此时存在依赖关系,所以会清除依赖关系,然后在执行runComutation过程中重新建立依赖关系。
- 定义
- 处理
- 调用
completeUpdates完成更新,传入变量wait,此时值为false- 此时 Effects 数组为空,不会执行更新流程,返回
- 同样设置
- 副作用函数执行完毕,返回
- 缓存
- 更新完成,返回
- 设置
- 返回当前 value,即我们传入的值
- 将传入的新值赋值给
简单概括一下就是:
当使用 createEffect 时,会创建 computation 对象,如果内部存在响应式数据 singleState ,两者会建立依赖关系。
- 将
computation对象添加到singleState的observers数组中 - 将
singleState对象添加到computation的sources数组中
当我们设置响应式数据时,会检查 observers 数组是否存在,如果存在就会更新每一个 computation 。值得注意的是在更新 computation 过程中会解除双方依赖关系,然后在 readSignal 时重新建立起依赖关系。
我们此次分析的案例是一个很基础的案例,如果你感觉意犹未尽,还可以查看 更多案例,尝试自己调试下代码,观察其运行流程。
createMemo 源码分析
工具相关
createRoot 源码分析
https://www.solidjs.com/docs/latest/api#createroot
官方的概念其实有些难以理解,个人认为 createRoot 的作用就是会创建没有依赖收集的 owner 作用域,并提供缓存功能(Effects 缓存),可以延迟 computation 执行,直至赋值时才会触发 computation 执行。除此之外,官方建议所有的 solid.js 代码都应该被包裹在 createRoot 中,这样可以确保内部的变量可以被释放。
同样一段代码,直接运行与包裹在createRoot 中运行结果是不同的。
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count effect01 =", count());
});
createEffect(() => {
console.log("count effect02 =", count());
});
setCount(1);const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count effect01 =", count());
});
createEffect(() => {
console.log("count effect02 =", count());
});
setCount(1);例如上面这段代码,直接在模块全局中运行,打印结果如下:
count effect01 = 0
count effect02 = 0
count effect01 = 1
count effect02 = 1count effect01 = 0
count effect02 = 0
count effect01 = 1
count effect02 = 1但是如果放到 createRoot 函数中,打印结果如下:
count effect01 = 1
count effect02 = 1count effect01 = 1
count effect02 = 1可以看到,在 createRoot 中使用 effect 不是立即执行的,而是在首次渲染之后才会执行。在 render 函数和 runWithOwner 中具有相同的执行效果。
因为前面我们已经分析过 createEffect 的源码,这次重点主要分析 createRoot 是如何使 effect 延迟执行的。
createRoot
createRoot 没有定义单独的声明,我们直接来看它是如何实现的。
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 type RootFunction<T> = (dispose: () => void) => T;
export function createRoot<T>(fn: RootFunction<T>, detachedOwner?: Owner): T {
const listener = Listener,
owner = Owner,
unowned = fn.length === 0,
root: Owner =
unowned && !"_SOLID_DEV_"
? UNOWNED
: { owned: null, cleanups: null, context: null, owner: detachedOwner || owner },
updateFn = unowned
? "_SOLID_DEV_"
? () =>
fn(() => {
throw new Error("Dispose method must be an explicit argument to createRoot function");
})
: fn
: () => fn(() => untrack(() => cleanNode(root)));
if ("_SOLID_DEV_") {
if (owner) root.name = `${owner.name}-r${rootCount++}`;
globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root);
}
Owner = root;
Listener = null;
try {
return runUpdates(updateFn as () => T, true)!;
} finally {
Listener = listener;
Owner = owner;
}
}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 type RootFunction<T> = (dispose: () => void) => T;
export function createRoot<T>(fn: RootFunction<T>, detachedOwner?: Owner): T {
const listener = Listener,
owner = Owner,
unowned = fn.length === 0,
root: Owner =
unowned && !"_SOLID_DEV_"
? UNOWNED
: { owned: null, cleanups: null, context: null, owner: detachedOwner || owner },
updateFn = unowned
? "_SOLID_DEV_"
? () =>
fn(() => {
throw new Error("Dispose method must be an explicit argument to createRoot function");
})
: fn
: () => fn(() => untrack(() => cleanNode(root)));
if ("_SOLID_DEV_") {
if (owner) root.name = `${owner.name}-r${rootCount++}`;
globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root);
}
Owner = root;
Listener = null;
try {
return runUpdates(updateFn as () => T, true)!;
} finally {
Listener = listener;
Owner = owner;
}
}createRoot 可以接收两个参数,分别是 fn 和 detachedOwner。
- fn:用户自定义函数
- detachedOwner:owner 上下文对象
- 针对开发环境, solid.js 会优先使用用户传入的 detachedOwner;
- 如果不存在或者是生产环境,会使用全局中的 Owner 对象。
首先缓存全局的 Listerner、Owner,还是一样的套路,缓存现有变量,等程序执行完毕,对变量进行恢复。
我们在讲解 createEffect 源码时,分析过 updateComputation 方法,也是类似的用法。
其次定义 unowned 变量,标识 fn 的参数是否为 0,定义 root 和 updateFn:
- root:因为 unowned 为真,并且处于开发环境
{ owned: null, cleanups: null, context: null, owner: detachedOwner || owner }{ owned: null, cleanups: null, context: null, owner: detachedOwner || owner }- updateFn,因为 unowned 为真,并且处于开发环境
() =>
fn(() => {
throw new Error("Dispose method must be an explicit argument to createRoot function");
})() =>
fn(() => {
throw new Error("Dispose method must be an explicit argument to createRoot function");
})然后还是开发环境的逻辑,会给 owner 对象设置名称,如果 globalThis._$afterCreateRoot 存在,会调用该方法传入 owner 对象。
if ("_SOLID_DEV_") {
if (owner) root.name = `${owner.name}-r${rootCount++}`;
globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root);
}if ("_SOLID_DEV_") {
if (owner) root.name = `${owner.name}-r${rootCount++}`;
globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root);
}将 root 赋值给 Owner(本次流程调试 Owner 是有值的),Listener 赋值为 null。
然后返回 runUpdates 方法调用,传入 updateFn 函数,并传入第二个参数为 true。这里使用 try 语句包裹函数,我们在自己定义库的时候也可以这么做,并且还可以自定义全局的错误函数,因为用户传入的任何配置都是不可靠的,我们必须对错误进行处理。
最后恢复 Listener 和 Owner。
runUpdates
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}还是这个方法,我们已经看过很多次,这里的不同点在于 init 参数是 true,所以不会给 Updates 赋值。
Effects 赋值为空数组,ExecCount 自增。然后执行我们传入的自定义方法。
() =>
fn(() => {
throw new Error("Dispose method must be an explicit argument to createRoot function");
})
// fn
createRoot(() => {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count effect01 =", count());
});
createEffect(() => {
console.log("count effect02 =", count());
});
setCount(1);
});() =>
fn(() => {
throw new Error("Dispose method must be an explicit argument to createRoot function");
})
// fn
createRoot(() => {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count effect01 =", count());
});
createEffect(() => {
console.log("count effect02 =", count());
});
setCount(1);
});首先调用 createSignal 创建响应式数据(SignalState),然后依次调用 createEffect 方法。
export function createEffect<Next, Init = Next>(
fn: EffectFunction<Init | Next, Next>,
value: Init,
options?: EffectOptions
): void;
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}export function createEffect<Next, Init = Next>(
fn: EffectFunction<Init | Next, Next>,
value: Init,
options?: EffectOptions
): void;
export function createEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
runEffects = runUserEffects;
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined),
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
if (s) c.suspense = s;
c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}首先将 runUserEffects 赋值给 runEffects,然后调用 createComputation 方法创建 computation 对象。
因为此时 Effects 存在,并且是空数组,所以不会执行 updateComputation 方法,而是仅将 computation 对象添加到 Effects 数组中。
如果不用
createRoot包裹代码,这里的 Effects 数组是空,所以会立即执行更新,添加到 Effects 数组相当于延迟更新。
createComputation
function createComputation<Next, Init = unknown>(
fn: EffectFunction<Init | Next, Next>,
init: Init,
pure: boolean,
state: number = STALE,
options?: EffectOptions
): Computation<Init | Next, Next> {
const c: Computation<Init | Next, Next> = {
fn,
state: state,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: null,
pure
};
// Transition
// ...
if (Owner === null)
"_SOLID_DEV_" &&
console.warn(
"computations created outside a `createRoot` or `render` will never be disposed"
);
else if (Owner !== UNOWNED) {
if (Transition && Transition.running && (Owner as Memo<Init, Next>).pure) {
if (!(Owner as Memo<Init, Next>).tOwned) (Owner as Memo<Init, Next>).tOwned = [c];
else (Owner as Memo<Init, Next>).tOwned!.push(c);
} else {
if (!Owner.owned) Owner.owned = [c];
else Owner.owned.push(c);
}
if ("_SOLID_DEV_")
c.name =
(options && options.name) ||
`${(Owner as Computation<any>).name || "c"}-${
(Owner.owned || (Owner as Memo<Init, Next>).tOwned!).length
}`;
}
// ExternalSourceFactory
// ...
return c;
}function createComputation<Next, Init = unknown>(
fn: EffectFunction<Init | Next, Next>,
init: Init,
pure: boolean,
state: number = STALE,
options?: EffectOptions
): Computation<Init | Next, Next> {
const c: Computation<Init | Next, Next> = {
fn,
state: state,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: null,
pure
};
// Transition
// ...
if (Owner === null)
"_SOLID_DEV_" &&
console.warn(
"computations created outside a `createRoot` or `render` will never be disposed"
);
else if (Owner !== UNOWNED) {
if (Transition && Transition.running && (Owner as Memo<Init, Next>).pure) {
if (!(Owner as Memo<Init, Next>).tOwned) (Owner as Memo<Init, Next>).tOwned = [c];
else (Owner as Memo<Init, Next>).tOwned!.push(c);
} else {
if (!Owner.owned) Owner.owned = [c];
else Owner.owned.push(c);
}
if ("_SOLID_DEV_")
c.name =
(options && options.name) ||
`${(Owner as Computation<any>).name || "c"}-${
(Owner.owned || (Owner as Memo<Init, Next>).tOwned!).length
}`;
}
// ExternalSourceFactory
// ...
return c;
}createComputation 方法执行也有所不同,当前存在 Owner 对象,Transition 相关的逻辑仍然不存在。
if (!Owner.owned) Owner.owned = [c];
else Owner.owned.push(c);if (!Owner.owned) Owner.owned = [c];
else Owner.owned.push(c);因为 Owener.owned 为空,所以会创建一个数组赋值给 Owner.owned,并将当前 computation 对象作为第一个元素。如果是开发环境会给 computation 设置一个名称。
c.name =
(options && options.name) ||
`${(Owner as Computation<any>).name || "c"}-${
(Owner.owned || (Owner as Memo<Init, Next>).tOwned!).length
}`;c.name =
(options && options.name) ||
`${(Owner as Computation<any>).name || "c"}-${
(Owner.owned || (Owner as Memo<Init, Next>).tOwned!).length
}`;这里的名称就是 c-1,取默认字符串 c ,拼接 ownned 的长度 1,最后将 computation 对象返回。
此时不仅 Effects 数组中存在 computation 对象,并且 Owner 的 owned 数组中也存在当前 computation 对象。
writeSignal
我们定义了两个副作用函数,所以最终 Effects 和 Owner.owned 中会存在两个 computation 对象。
createRoot(() => {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count effect01 =", count());
});
createEffect(() => {
console.log("count effect02 =", count());
});
setCount(1);
});createRoot(() => {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("count effect01 =", count());
});
createEffect(() => {
console.log("count effect02 =", count());
});
setCount(1);
});执行 setCount,会触发 writeSignal 函数。
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;
}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;
}Transition 相关逻辑仍然不存在,直接将最新值赋值给 node.value,此时值为 1。此时 node.observers 也不存在,因为我们并没有触发 readSignal 函数收集 computation 依赖。所以这里返回最新值就执行结束了。
completeUpdates
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}然后回到 runUpdates 函数中,此时已经执行完 fn() 函数,接下来触发 completeUpdates 。
function completeUpdates(wait: boolean) {
if (Updates) {
// ...
}
if (wait) return;
let res;
if (Transition && Transition.running) {
// ...
}
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}function completeUpdates(wait: boolean) {
if (Updates) {
// ...
}
if (wait) return;
let res;
if (Transition && Transition.running) {
// ...
}
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}此时 Effects 存在,赋值给 e 后将 Effects 清空,重新调用 runUpdates ,执行 runEffects 。
runUpdates
runUpdates 方法还是一样的逻辑,只不过由于传递第二个参数为 false,会初始化 Updates 数组,ExecCount 继续自增。
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}runEffects(runUserEffects)
function runUserEffects(queue: Computation<any>[]) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) setHydrateContext();
for (i = 0; i < userLength; i++) runTop(queue[i]);
}function runUserEffects(queue: Computation<any>[]) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) setHydrateContext();
for (i = 0; i < userLength; i++) runTop(queue[i]);
}因为此时都是用户 computation,所以最终会执行两次 runTop 方法。
runTop
function runTop(node: Computation<any>) {
// ...
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}function runTop(node: Computation<any>) {
// ...
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}runTop 我们已经分析过,在这里只会执行 updateComputation 方法。
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
// ...
Listener = listener;
Owner = owner;
}function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
// ...
Listener = listener;
Owner = owner;
}updateComputation 首先会清理 computation 依赖关系,此时这里并没有依赖相关的 SignalState。然后执行 runComputation 。
function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}在 runComputation 中会执行 node.fn,即 createEffect 的内部逻辑,在打印 count() 时,会触发依赖收集,然后打印结果。
两次 runTop 执行完毕后,控制台已经打印出:
count effect01 = 1
count effect02 = 1count effect01 = 1
count effect02 = 1总结
createRoot 函数提供缓存 Effects 的功能,可以延迟执行 computation。
副作用函数在定义时并不会执行,只会在设置值的时候触发执行,这种表现其实才是我们想要的。
除此之外,不仅 createRoot 具有这个功能,使用 render 函数或者 runWithOwner 都可以达到这样的效果。
渲染相关
render 源码分析
https://www.solidjs.com/docs/latest/api#render
render 函数是 web 应用的入口。
import { render } from "solid-js/web";
import type { JSX, MountableElement } from "solid-js/web";
function render(code: () => JSX.Element, element: MountableElement): () => void;import { render } from "solid-js/web";
import type { JSX, MountableElement } from "solid-js/web";
function render(code: () => JSX.Element, element: MountableElement): () => void;第一个参数可以函数组件声明或者使用函数返回组件,第二个参数是需要被挂载的元素。
这个元素推荐使用一个空元素,如果调用 render 返回 dispose 函数,会清理掉所有子元素,包括已经存在的静态内容。
const dispose = render(App, document.getElementById("app"));const dispose = render(App, document.getElementById("app"));我们将以下面这个例子对源码进行分析:
function HelloWorld() {
const [count, setCount] = createSignal(0)
setTimeout(() => {
setCount(count() + 1)
}, 1000);
return html`<div >${ count }</div>`
}
render(HelloWorld, document.getElementById('app'))function HelloWorld() {
const [count, setCount] = createSignal(0)
setTimeout(() => {
setCount(count() + 1)
}, 1000);
return html`<div >${ count }</div>`
}
render(HelloWorld, document.getElementById('app'))首次渲染
render
render 函数没有定义在在 solid 库中,而是被封装在 dom-expressions 中。
dom-expressions 是 Ryan Carniato 针对于响应式开发的一个库,它可以进行细粒度的检测。
export function render(code, element, init) {
let disposer;
root(dispose => {
disposer = dispose;
element === document
? code()
: insert(element, code(), element.firstChild ? null : undefined, init);
});
return () => {
disposer();
element.textContent = "";
};
}export function render(code, element, init) {
let disposer;
root(dispose => {
disposer = dispose;
element === document
? code()
: insert(element, code(), element.firstChild ? null : undefined, init);
});
return () => {
disposer();
element.textContent = "";
};
}render 函数可以接收三个参数,分别是 code、element、init。
- code:即我们传入的代码,可以是一个组件,可以是一个函数;
- element:DOM 元素,会将 code 渲染后的结果追加到 element 元素内部;
- init:初始值,可以是函数也可以是数组
首先定义 disposer 变量,然后调用 root 函数,函数内部首先将参数 dispose 赋值给 disposer,然后会对 element 进行判断:
- 如果 element 等于 document,直接执行 code 即可;
- 否则会调用 insert 方法,对 code() 返回的结果进行插入
最后在返回一个函数,内部调用 disposer,并将元素的内容清空。
代码逻辑其实比较清晰,不过有一点可需要说明一下,这里的 root 其实就是 createRoot 函数。
import { root, effect, memo, getOwner, createComponent, sharedConfig, untrack } from "rxcore";import { root, effect, memo, getOwner, createComponent, sharedConfig, untrack } from "rxcore";可以看到 root 是从 rxcore 中导入的,初看可能很懵逼,因为在 package.json 文件中并没有安装这个库。
其实这个库指向的是 solid 的 web 目录 src 文件夹下的 core.ts 文件。
在 dom-expression 中的 babel.config.js 中使用 babel-plugin-transfrom-rename-import 插件,对变量进行了替换。
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }]],
plugins: [
[
"babel-plugin-transform-rename-import",
{
original: "rxcore",
replacement: __dirname + "/core"
}
],
["babel-plugin-jsx-dom-expressions", { moduleName: "./custom", generate: "universal" }]
]
};module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }]],
plugins: [
[
"babel-plugin-transform-rename-import",
{
original: "rxcore",
replacement: __dirname + "/core"
}
],
["babel-plugin-jsx-dom-expressions", { moduleName: "./custom", generate: "universal" }]
]
};可以看到,插件将 rxcore 字符串替换为当前目录下的 core 文件。
//@ts-nocheck
import {
createRoot,
createRenderEffect,
createMemo,
createComponent,
getOwner,
sharedConfig,
untrack
} from "solid-js";
// reactive injection for dom-expressions
function memo<T>(fn: () => T, equals: boolean) {
return createMemo(fn, undefined, !equals ? { equals } : undefined);
}
export {
getOwner,
createComponent,
createRoot as root,
createRenderEffect as effect,
memo,
sharedConfig,
untrack
};//@ts-nocheck
import {
createRoot,
createRenderEffect,
createMemo,
createComponent,
getOwner,
sharedConfig,
untrack
} from "solid-js";
// reactive injection for dom-expressions
function memo<T>(fn: () => T, equals: boolean) {
return createMemo(fn, undefined, !equals ? { equals } : undefined);
}
export {
getOwner,
createComponent,
createRoot as root,
createRenderEffect as effect,
memo,
sharedConfig,
untrack
};可以看到这里的 root 其实就是 createRoot,createRoot 我们之前也分析过。
createRoot
export function render(code, element, init) {
let disposer;
root(dispose => {
disposer = dispose;
element === document
? code()
: insert(element, code(), element.firstChild ? null : undefined, init);
});
return () => {
disposer();
element.textContent = "";
};
}export function render(code, element, init) {
let disposer;
root(dispose => {
disposer = dispose;
element === document
? code()
: insert(element, code(), element.firstChild ? null : undefined, init);
});
return () => {
disposer();
element.textContent = "";
};
}这段代码首先会调用 root 函数,将匿名函数作为参数传入,第二个参数为 undefined。
export function createRoot<T>(fn: RootFunction<T>, detachedOwner?: Owner): T {
const listener = Listener,
owner = Owner,
unowned = fn.length === 0,
root: Owner =
unowned && !"_SOLID_DEV_"
? UNOWNED
: { owned: null, cleanups: null, context: null, owner: detachedOwner || owner },
updateFn = unowned
? "_SOLID_DEV_"
? () =>
fn(() => {
throw new Error("Dispose method must be an explicit argument to createRoot function");
})
: fn
: () => fn(() => untrack(() => cleanNode(root)));
if ("_SOLID_DEV_") {
if (owner) root.name = `${owner.name}-r${rootCount++}`;
globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root);
}
Owner = root;
Listener = null;
try {
return runUpdates(updateFn as () => T, true)!;
} finally {
Listener = listener;
Owner = owner;
}
}export function createRoot<T>(fn: RootFunction<T>, detachedOwner?: Owner): T {
const listener = Listener,
owner = Owner,
unowned = fn.length === 0,
root: Owner =
unowned && !"_SOLID_DEV_"
? UNOWNED
: { owned: null, cleanups: null, context: null, owner: detachedOwner || owner },
updateFn = unowned
? "_SOLID_DEV_"
? () =>
fn(() => {
throw new Error("Dispose method must be an explicit argument to createRoot function");
})
: fn
: () => fn(() => untrack(() => cleanNode(root)));
if ("_SOLID_DEV_") {
if (owner) root.name = `${owner.name}-r${rootCount++}`;
globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root);
}
Owner = root;
Listener = null;
try {
return runUpdates(updateFn as () => T, true)!;
} finally {
Listener = listener;
Owner = owner;
}
}这里的 Listener 和 Owner 都为空。
我们传递的函数参数个数并不为 0 , 所以这时的 unowned 变量为 false,updateFn 为:
() => fn(() => untrack(() => cleanNode(root)))() => fn(() => untrack(() => cleanNode(root)))然后将 Owener 赋值为 root,Listerner 赋值为 null。调用 runUpdates 函数,传入 updateFn 作为参数。
最后函数执行完毕,将 Listerner 和 Owener 重置。
runUpdates
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}这个函数我们已经分析过很多次,主要就是初始化 Effects 数组,然后执行我们传入的回调函数,最后调用 completeUpdate 完成更新。
anonymous(匿名函数)
runUpdates 中执行的 fn 其实就是在 render 函数中传入的匿名函数。
export function render(code, element, init) {
let disposer;
root(dispose => {
disposer = dispose;
element === document
? code()
: insert(element, code(), element.firstChild ? null : undefined, init);
});
return () => {
disposer();
element.textContent = "";
};
}export function render(code, element, init) {
let disposer;
root(dispose => {
disposer = dispose;
element === document
? code()
: insert(element, code(), element.firstChild ? null : undefined, init);
});
return () => {
disposer();
element.textContent = "";
};
}dispose 也是一个匿名函数,即 fn 内部的匿名函数。
() => fn(() => untrack(() => cleanNode(root)))
// =>
() => untrack(() => cleanNode(root))() => fn(() => untrack(() => cleanNode(root)))
// =>
() => untrack(() => cleanNode(root))这里会将这个匿名函数赋值给 disposer,主要的作用就是对 root 进行清理。
然后会调用 insert方法,这里其实会先执行 code 函数,即我们传入的函数。
function HelloWorld() {
const [count, setCount] = createSignal(0)
setTimeout(() => {
setCount(count() + 1)
}, 1000);
return html`<div>${ count }</div>`
}function HelloWorld() {
const [count, setCount] = createSignal(0)
setTimeout(() => {
setCount(count() + 1)
}, 1000);
return html`<div>${ count }</div>`
}首先会定义 signalState,value 值为 0,并返回 getter 和 setter 函数。其次定义一个定时器函数,内部首先使用 getter 获取值,然后再利用 setter 进行更新。最后将我们的代码返回。
因为这里是在浏览中直接引入调试的,不能直接在函数中使用 html 标签,所以这里使用 html 标签函数对 html 标签字符串进行解析。
我们可以举个简单的例子,定义一个 html 标签函数,将其放在 html 标签字符串前面,浏览器就会自动调用 html 函数,对标签字符串进行解析,并将解析后的内容以参数的形式传递给我们自定义的 html 函数。
第一个参数是包含一个字符串值得数组,其余的参数与表达式相关。
更多标签函数内容,可以查看 MDN 关于 模板字符串 的文章。
const count = 1
function html2(static, ...args) {
console.log(static, args)
}
html2`<div>${ count }</div>`const count = 1
function html2(static, ...args) {
console.log(static, args)
}
html2`<div>${ count }</div>`html
html 函数同样定义在 dom-expression 库中。
const cache = new Map<TemplateStringsArray, HTMLTemplateElement[]>();
export function createHTML(r: Runtime, { delegateEvents = true } = {}): HTMLTag {
let uuid = 1;
(r as any).wrapProps = (props: any) => {
const d = Object.getOwnPropertyDescriptors(props);
for (const k in d) {
if (typeof d[k].value === "function" && !d[k].value.length) r.dynamicProperty(props, k);
}
return props;
};
function html(statics: TemplateStringsArray, ...args: unknown[]): Node {
const templates = cache.get(statics) || createTemplate(statics);
return (templates[0] as CreateableTemplate).create(templates, args, r);
}
return html
}const cache = new Map<TemplateStringsArray, HTMLTemplateElement[]>();
export function createHTML(r: Runtime, { delegateEvents = true } = {}): HTMLTag {
let uuid = 1;
(r as any).wrapProps = (props: any) => {
const d = Object.getOwnPropertyDescriptors(props);
for (const k in d) {
if (typeof d[k].value === "function" && !d[k].value.length) r.dynamicProperty(props, k);
}
return props;
};
function html(statics: TemplateStringsArray, ...args: unknown[]): Node {
const templates = cache.get(statics) || createTemplate(statics);
return (templates[0] as CreateableTemplate).create(templates, args, r);
}
return html
}在 html 函数中,首先会从 cache 寻找 statics,如果存在用 cache 中数据,否则调用 createTemplate 方法创建模板。最后调用 templates 第一个元素的 create 方法,并将结果返回。
运行我们的测试代码中,这里的 static 和 args 分别是:

createTemplate
createTemplate 顾名思义,就是用来创建模板。
它会解析我们的 statics,最终返回一个 templates 数组,并向第一个元素身上挂载一个 create 函数,供外部调用这个函数。
function createTemplate(statics: TemplateStringsArray) {
let i = 0,
markup = "";
for (; i < statics.length - 1; i++) {
markup = markup + statics[i] + "<!--#-->";
}
markup = markup + statics[i];
markup = markup
.replace(selfClosing, fullClosing)
.replace(/<(<!--#-->)/g, "<###")
.replace(/\.\.\.(<!--#-->)/g, "###")
.replace(attrSeeker, attrReplacer)
.replace(/>\n+\s*/g, ">")
.replace(/\n+\s*</g, "<")
.replace(/\s+</g, " <")
.replace(/>\s+/g, "> ");
const [html, code] = parseTemplate(parse(markup)),
templates: HTMLTemplateElement[] = [];
for (let i = 0; i < html.length; i++) {
templates.push(document.createElement("template"));
templates[i].innerHTML = html[i];
const nomarkers = templates[i].content.querySelectorAll("script,style");
for (let j = 0; j < nomarkers.length; j++) {
const d = (nomarkers[j].firstChild as Text)!.data || "";
if (d.indexOf(marker) > -1) {
const parts = d.split(marker).reduce((memo, p, i) => {
i && memo.push("");
memo.push(p);
return memo;
}, [] as string[]);
nomarkers[i].firstChild!.replaceWith(...parts);
}
}
}
(templates[0] as CreateableTemplate).create = code;
cache.set(statics, templates);
return templates;
}function createTemplate(statics: TemplateStringsArray) {
let i = 0,
markup = "";
for (; i < statics.length - 1; i++) {
markup = markup + statics[i] + "<!--#-->";
}
markup = markup + statics[i];
markup = markup
.replace(selfClosing, fullClosing)
.replace(/<(<!--#-->)/g, "<###")
.replace(/\.\.\.(<!--#-->)/g, "###")
.replace(attrSeeker, attrReplacer)
.replace(/>\n+\s*/g, ">")
.replace(/\n+\s*</g, "<")
.replace(/\s+</g, " <")
.replace(/>\s+/g, "> ");
const [html, code] = parseTemplate(parse(markup)),
templates: HTMLTemplateElement[] = [];
for (let i = 0; i < html.length; i++) {
templates.push(document.createElement("template"));
templates[i].innerHTML = html[i];
const nomarkers = templates[i].content.querySelectorAll("script,style");
for (let j = 0; j < nomarkers.length; j++) {
const d = (nomarkers[j].firstChild as Text)!.data || "";
if (d.indexOf(marker) > -1) {
const parts = d.split(marker).reduce((memo, p, i) => {
i && memo.push("");
memo.push(p);
return memo;
}, [] as string[]);
nomarkers[i].firstChild!.replaceWith(...parts);
}
}
}
(templates[0] as CreateableTemplate).create = code;
cache.set(statics, templates);
return templates;
}create 函数即 parseTemplate 函数生成并返回的 code 函数,它是一个匿名函数:
function parseTemplate(nodes: IDom[]): [string[], TemplateCreate] {
// ...
return [
templateNodes.map(t => stringify(t)),
new Function(
"tmpls",
"exprs",
"r",
options.decl.join(",\n") +
";\n" +
options.exprs.join(";\n") +
(toplevel ? "" : `;\nreturn _$el${id};\n`)
) as TemplateCreate
];
}function parseTemplate(nodes: IDom[]): [string[], TemplateCreate] {
// ...
return [
templateNodes.map(t => stringify(t)),
new Function(
"tmpls",
"exprs",
"r",
options.decl.join(",\n") +
";\n" +
options.exprs.join(";\n") +
(toplevel ? "" : `;\nreturn _$el${id};\n`)
) as TemplateCreate
];
}具体的创建模板的逻辑这里先不去深究,感兴趣的可以自己会调试下相关代码。
insert (inner)
function html(statics: TemplateStringsArray, ...args: unknown[]): Node {
const templates = cache.get(statics) || createTemplate(statics);
return (templates[0] as CreateableTemplate).create(templates, args, r);
}function html(statics: TemplateStringsArray, ...args: unknown[]): Node {
const templates = cache.get(statics) || createTemplate(statics);
return (templates[0] as CreateableTemplate).create(templates, args, r);
}执行完 createTemplate 方法中,会在第一个元素上挂载 create 方法,即 parseTemplate 返回的 code 函数。
在这个例子中,我们的 code 函数是这样:
(function anonymous(tmpls,exprs,r
) {
const _$el1 = tmpls[0].content.firstChild.cloneNode(true),
_$el2 = _$el1.firstChild;
r.insert(_$el1, exprs[0]);
return _$el1;
})(function anonymous(tmpls,exprs,r
) {
const _$el1 = tmpls[0].content.firstChild.cloneNode(true),
_$el2 = _$el1.firstChild;
r.insert(_$el1, exprs[0]);
return _$el1;
})这段代码最关键的逻辑就是执行了一次 insert 方法。
export function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
effect(current => insertExpression(parent, accessor(), current, marker), initial);
}export function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
effect(current => insertExpression(parent, accessor(), current, marker), initial);
}因为此时我们传入的第二个参数并不是函数,所以会执行 effect 方法,这里就非常关键,effect 函数内部会创建一个 computation 对象,在这个过程中,state 就和 computation 完成了关联,即模板和数据完成了关联,这样后续我们设置值的时候才会重新触发渲染。
接下来我们就来深入分析下这段逻辑。
createRenderEffect(inner)
注意,这里的 effect 是 createRenderEffect,并不是 createEffect。
export function createRenderEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined);
if (Scheduler && Transition && Transition.running) Updates!.push(c);
else updateComputation(c);
}export function createRenderEffect<Next, Init>(
fn: EffectFunction<Init | Next, Next>,
value?: Init,
options?: EffectOptions
): void {
const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined);
if (Scheduler && Transition && Transition.running) Updates!.push(c);
else updateComputation(c);
}首先创建一个 computation 对象。然后判断 Scheduler、Transition 是否存在,且 Transition 是否正在运行。
如果命中条件,将 computation 添加到 Updates 数组中,否则执行 updateComputation 方法。
这里的 scheduler 和 Transition 都是空,所以会执行 updateComputation 方法。
updateComputation(inner)
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}这个方法我们分析过很多次,首先会缓存旧的 Listener、Owner,然后执行 runComputation 方法,最后再恢复变量值。
runComputation(inner)
function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}runComputation 最重要的就是执行 node.fn 方法:
effect(current => insertExpression(parent, accessor(), current, marker), initial);effect(current => insertExpression(parent, accessor(), current, marker), initial);所以,这里会执行 insertExpression 方法,并传入相关参数。
readSignal(inner)
执行 insertExpression 函数前,会先执行 accessor 函数,这里的 accessor 其实就是 readSignal ,用来返回当前 state 的值。
readSignal 中建立起 computation 和 state 之间的关系,这样我们在设置值的时候才能得到依赖的 computation ,从而更新视图。
// Internal
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;
}// Internal
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 就是调用 createRenderEffect 方法时创建的 computation 对象,在 updateComputation 时将 computation 赋值给全局 Listener。
最后将 state 的值返回。
insertExpression(inner)
effect(current => insertExpression(parent, accessor(), current, marker), initial);effect(current => insertExpression(parent, accessor(), current, marker), initial);然后就执行到 insertExpression 方法。
这里的 parent 即 div 元素,value 是当前 state 的值为 0,current、markder、unwrapArray 都不存在。
function insertExpression(parent, value, current, marker, unwrapArray) {
if (sharedConfig.context && !current) current = [...parent.childNodes];
while (typeof current === "function") current = current();
if (value === current) return current;
const t = typeof value,
multi = marker !== undefined;
parent = (multi && current[0] && current[0].parentNode) || parent;
if (t === "string" || t === "number") {
if (sharedConfig.context) return current;
if (t === "number") value = value.toString();
if (multi) {
let node = current[0];
if (node && node.nodeType === 3) {
node.data = value;
} else node = document.createTextNode(value);
current = cleanChildren(parent, current, marker, node);
} else {
if (current !== "" && typeof current === "string") {
current = parent.firstChild.data = value;
} else current = parent.textContent = value;
}
} else if (value == null || t === "boolean") {
// ...
} else if (t === "function") {
// ..
} else if (Array.isArray(value)) {
// ...
} else if (value instanceof Node) {
// ...
} else if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, value);
return current;
}function insertExpression(parent, value, current, marker, unwrapArray) {
if (sharedConfig.context && !current) current = [...parent.childNodes];
while (typeof current === "function") current = current();
if (value === current) return current;
const t = typeof value,
multi = marker !== undefined;
parent = (multi && current[0] && current[0].parentNode) || parent;
if (t === "string" || t === "number") {
if (sharedConfig.context) return current;
if (t === "number") value = value.toString();
if (multi) {
let node = current[0];
if (node && node.nodeType === 3) {
node.data = value;
} else node = document.createTextNode(value);
current = cleanChildren(parent, current, marker, node);
} else {
if (current !== "" && typeof current === "string") {
current = parent.firstChild.data = value;
} else current = parent.textContent = value;
}
} else if (value == null || t === "boolean") {
// ...
} else if (t === "function") {
// ..
} else if (Array.isArray(value)) {
// ...
} else if (value instanceof Node) {
// ...
} else if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, value);
return current;
}因为这里的 t 是 number 类型,我们暂时只需要关注 t === 'string' || t === 'number' 这段分支逻辑。
首先判断 sharedConfig.context 是否存在,如果存在,直接返回 current,这里当然是不存在的。
因为 t 是 number 类型,首先会将 value 转换为字符串类型。然后判断 multi,这里也是 false,所以会执行下面这段代码。
if (current !== "" && typeof current === "string") {
current = parent.firstChild.data = value;
} else current = parent.textContent = value;if (current !== "" && typeof current === "string") {
current = parent.firstChild.data = value;
} else current = parent.textContent = value;current 也不存在,这里会为 current 和 parent.textContent 赋值为当前的 value,然后将 current 返回。
当 insertExpression 执行完毕,已经为 div 元素设置文本内容,内容为 value 值,字符串 0。
insert (outer)
执行完 html 函数之后,回到 render 函数。
我们在 insert 的时候调用了 code 触发后面一系列逻辑,现在我们已经获取到返回结果。
export function render(code, element, init) {
let disposer;
root(dispose => {
disposer = dispose;
element === document
? code()
: insert(element, code(), element.firstChild ? null : undefined, init);
});
return () => {
disposer();
element.textContent = "";
};
}export function render(code, element, init) {
let disposer;
root(dispose => {
disposer = dispose;
element === document
? code()
: insert(element, code(), element.firstChild ? null : undefined, init);
});
return () => {
disposer();
element.textContent = "";
};
}接下来调用 insert 函数。
export function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
effect(current => insertExpression(parent, accessor(), current, marker), initial);
}export function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
effect(current => insertExpression(parent, accessor(), current, marker), initial);
}目前我们只需要关心这两个参数:
- parent:父元素
- accessor: DOM 元素,由我们的模板生成的 DOM 元素
这里的 marker 是 undefined,所以不会初始化 initial,这里仍是 undefined。
因为这里的 accessor 不是函数,所以会直接调用 insertExpression 方法。
insertExpression(outer)
重新进入当前函数,这时 value 值是 div 元素,并且 div 元素内部已经存在文本内容 0。
function insertExpression(parent, value, current, marker, unwrapArray) {
if (sharedConfig.context && !current) current = [...parent.childNodes];
while (typeof current === "function") current = current();
if (value === current) return current;
const t = typeof value,
multi = marker !== undefined;
parent = (multi && current[0] && current[0].parentNode) || parent;
if (t === "string" || t === "number") {
// ...
} else if (value == null || t === "boolean") {
// ...
} else if (t === "function") {
// ...
} else if (Array.isArray(value)) {
// ...
} else if (value instanceof Node) {
if (sharedConfig.context && value.parentNode) return (current = multi ? [value] : value);
if (Array.isArray(current)) {
if (multi) return (current = cleanChildren(parent, current, marker, value));
cleanChildren(parent, current, null, value);
} else if (current == null || current === "" || !parent.firstChild) {
parent.appendChild(value);
} else parent.replaceChild(value, parent.firstChild);
current = value;
} else if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, value);
return current;
}function insertExpression(parent, value, current, marker, unwrapArray) {
if (sharedConfig.context && !current) current = [...parent.childNodes];
while (typeof current === "function") current = current();
if (value === current) return current;
const t = typeof value,
multi = marker !== undefined;
parent = (multi && current[0] && current[0].parentNode) || parent;
if (t === "string" || t === "number") {
// ...
} else if (value == null || t === "boolean") {
// ...
} else if (t === "function") {
// ...
} else if (Array.isArray(value)) {
// ...
} else if (value instanceof Node) {
if (sharedConfig.context && value.parentNode) return (current = multi ? [value] : value);
if (Array.isArray(current)) {
if (multi) return (current = cleanChildren(parent, current, marker, value));
cleanChildren(parent, current, null, value);
} else if (current == null || current === "" || !parent.firstChild) {
parent.appendChild(value);
} else parent.replaceChild(value, parent.firstChild);
current = value;
} else if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, value);
return current;
}这个方法会根据 value 的类型做不同处理,因为我们的 value 是一个 DOM 元素,所以会命中 value instance Node 分支。
因为此时的 parent.firstChild 并不存在,并且 current 是 undefined,所以会直接向 parent 追加元素。
最后将 vale 赋值给 current,并将其返回,insert 函数执行完毕。
回到 runUpdates 函数,此时已经执行完我们传入的 fn,页面已经渲染出模板结果。
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}然后调用 completeUpdates 函数完成后续更新操作。
因为这里并不存在 Effects、Updates ,所以 completeUpdates 只是简单调用一下,并不会处理任何逻辑。
function completeUpdates(wait: boolean) {
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
if (wait) return;
let res;
if (Transition && Transition.running) {
// ...
}
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}function completeUpdates(wait: boolean) {
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
if (wait) return;
let res;
if (Transition && Transition.running) {
// ...
}
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}至此,首次渲染的代码我们就分析完了,接下来是更新过程。
更新流程
function HelloWorld() {
const [count, setCount] = createSignal(0)
setTimeout(() => {
setCount(count() + 1)
}, 1000);
return html`<div>${ count }</div>`
}
render(HelloWorld, document.getElementById('app'))function HelloWorld() {
const [count, setCount] = createSignal(0)
setTimeout(() => {
setCount(count() + 1)
}, 1000);
return html`<div>${ count }</div>`
}
render(HelloWorld, document.getElementById('app'))当首次渲染完毕后,定时器到达时间会触发 setCount(count() + 1) 代码,从而触发更新流程。
首先会执行 count() ,调用 readSignal 方法,此时不存在 Listerner,所以只会返回 state 的值,即 0。
然后调用 setCount 方法,触发 writeSignal 方法。
writeSignal
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;
}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;
}这段代码中 Transition 并不存在,所以会把传入的 value 值赋值给 node.value,即 signalState 对象,state 的值被更新为 1。
然后判断 node.observers 是否存在,这里的 observers 是存在的,是我们的 render computation 对象。
runUpdates
然后会调用 runUpdates 方法,内部匿名函数会将 computation 添加到 Effects 数组中。
function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}function runUpdates<T>(fn: () => T, init: boolean) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!Updates) Effects = null;
handleError(err);
}
}接着调用 completeUpdates 方法完成更新。
completeUpdates
function completeUpdates(wait: boolean) {
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
if (wait) return;
let res;
// ...
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}function completeUpdates(wait: boolean) {
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
if (wait) return;
let res;
// ...
const e = Effects!;
Effects = null;
if (e!.length) runUpdates(() => runEffects(e), false);
else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
if (res) res();
}缓存 Effects,重置 Effects 为 null,然后调用 runUpdates 方法,传入 runEffects,顾名思义,就是执行副作用函数的意思。
值的注意的是,这里的 runEffects 不再是 runUserEffects,我们在使用 createEffect API 创建 computation 对象时会将 runEffects 重新赋值为 runUserEffects,在使用 createRenderEffect 时,并不是这样处理,这里的 runEffects 就是默认值 runQueue。
let runEffects = runQueue;let runEffects = runQueue;runEffects(runQueue)
function runQueue(queue: Computation<any>[]) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}function runQueue(queue: Computation<any>[]) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}然后就是遍历 computation 数组,调用 runTop 方法,参数为 computation 对象。
runTop
function runTop(node: Computation<any>) {
// ....
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}function runTop(node: Computation<any>) {
// ....
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node,
prev = ancestors[i + 1];
while ((top = top.owner as Computation<any>) && top !== prev) {
if (Transition!.disposed.has(top)) return;
}
}
if (
(!runningTransition && node.state === STALE) ||
(runningTransition && node.tState === STALE)
) {
updateComputation(node);
} else if (
(!runningTransition && node.state === PENDING) ||
(runningTransition && node.tState === PENDING)
) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}调用 updateComputation 方法更新视图。
updateComputation
function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}function updateComputation(node: Computation<any>) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(
node,
Transition && Transition.running && Transition.sources.has(node as Memo<any>)
? (node as Memo<any>).tValue
: node.value,
time
);
if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
runComputation(node, (node as Memo<any>).tValue, time);
}, false);
});
}
Listener = listener;
Owner = owner;
}内部继续调用 runComputation 方法,将 computation 对象传入。
runComputation
function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}function runComputation(node: Computation<any>, value: any, time: number) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
writeSignal(node as Memo<any>, nextValue, true);
} else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node as Memo<any>);
(node as Memo<any>).tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}然后执行 node.fn 方法。
export function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
effect(current => insertExpression(parent, accessor(), current, marker), initial);
}export function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
effect(current => insertExpression(parent, accessor(), current, marker), initial);
}这里的 node.fn 就是 current => insertExpression(parent, accessor(), current, marker)。
后续流程其实我们已经很熟悉,就是获取到当前最新值,并且将值赋值给 div 元素,然后页面就会被更新,这就是 solid 的点对点更新。
总结
当我们使用 render 函数时返回模板时,solid 会对模板进行解析,生成可执行的代码片段。
如果存在响应式数据,会解析出响应式变量,调用 insert 方法,这时 solid 内部会自动创建一个 computation 对象,帮助我们把模板和副作用函数进行关联,只有这样我们在为响应式变量赋值时才可以达到更新视图的目的。
export function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
effect(current => insertExpression(parent, accessor(), current, marker), initial);
}export function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
effect(current => insertExpression(parent, accessor(), current, marker), initial);
}另外 solid 的模板解析是实现真实 DOM 细粒度更新的核心步骤,我们会在后面再点分析它是如何实现的。