tangweijie 5099f2e87e Initial commit: Vue3 + TypeScript 前端项目
- Vue 3 + TypeScript + Element Plus 前端界面
- Pinia 状态管理
- Vue Router 4 路由管理
- Axios HTTP 客户端
- MSW (Mock Service Worker) 开发环境模拟
- 账户管理界面 (列表、详情、三科目余额展示)
- 交易管理界面 (列表、详情)
- 对账管理界面 (三账校验)
- 完善的 API 客户端封装
- Docker 容器化配置
- Nginx 配置用于生产环境
2026-01-05 17:57:11 +08:00

7.6 KiB
Raw Blame History


npm package

alien-signals

This project explores a push-pull based signal algorithm. Its current implementation is similar to or related to certain other frontend projects:

We impose some constraints (such as not using Array/Set/Map and disallowing function recursion) to ensure performance. We found that under these conditions, maintaining algorithmic simplicity offers more significant improvements than complex scheduling strategies.

Even though Vue 3.4 is already optimized, alien-signals is still noticeably faster. (I wrote code for both, and since they share similar algorithms, theyre quite comparable.)

Image

Benchmark repo: https://github.com/transitive-bullshit/js-reactivity-benchmark

Background

I spent considerable time optimizing Vue 3.4s reactivity system, gaining experience along the way. Since Vue 3.5 switched to a pull-based algorithm similar to Preact, I decided to continue researching a push-pull based implementation in a separate project. Our end goal is to implement fully incremental AST parsing and virtual code generation in Vue language tools, based on alien-signals.

Derived Projects

Adoption

Usage

Basic APIs

import { signal, computed, effect } from 'alien-signals';

const count = signal(1);
const doubleCount = computed(() => count() * 2);

effect(() => {
  console.log(`Count is: ${count()}`);
}); // Console: Count is: 1

console.log(doubleCount()); // 2

count(2); // Console: Count is: 2

console.log(doubleCount()); // 4

Effect Scope

import { signal, effect, effectScope } from 'alien-signals';

const count = signal(1);

const stopScope = effectScope(() => {
  effect(() => {
    console.log(`Count in scope: ${count()}`);
  }); // Console: Count in scope: 1
});

count(2); // Console: Count in scope: 2

stopScope();

count(3); // No console output

Creating Your Own Surface API

You can reuse alien-signals core algorithm via createReactiveSystem() to build your own signal API. For implementation examples, see:

About propagate and checkDirty functions

In order to eliminate recursive calls and improve performance, we record the last link node of the previous loop in propagate and checkDirty functions, and implement the rollback logic to return to this node.

This results in code that is difficult to understand, and you don't necessarily get the same performance improvements in other languages, so we record the original implementation without eliminating recursive calls here for reference.

propagate

function propagate(link: Link, targetFlag = SubscriberFlags.Dirty): void {
	do {
		const sub = link.sub;
		const subFlags = sub.flags;

		let shouldNotify = false;

		if (!(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed | SubscriberFlags.Propagated))) {
			sub.flags = subFlags | targetFlag | SubscriberFlags.Notified;
			shouldNotify = true;
		} else if ((subFlags & SubscriberFlags.Recursed) && !(subFlags & SubscriberFlags.Tracking)) {
			sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag | SubscriberFlags.Notified;
			shouldNotify = true;
		} else if (!(subFlags & SubscriberFlags.Propagated) && isValidLink(current, sub)) {
			sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag | SubscriberFlags.Notified;
			shouldNotify = (sub as Dependency).subs !== undefined;
		}

		if (shouldNotify) {
			const subSubs = (sub as Dependency).subs;
			if (subSubs !== undefined) {
				propagate(
					subSubs,
					subFlags & SubscriberFlags.Effect
						? SubscriberFlags.PendingEffect
						: SubscriberFlags.PendingComputed
				);
			}
			if (subFlags & SubscriberFlags.Effect) {
				if (queuedEffectsTail !== undefined) {
					queuedEffectsTail = queuedEffectsTail.linked = { target: sub, linked: undefined };
				} else {
					queuedEffectsTail = queuedEffects = { target: sub, linked: undefined };
				}
			}
		} else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) {
			sub.flags = subFlags | targetFlag | SubscriberFlags.Notified;
			if ((subFlags & (SubscriberFlags.Effect | SubscriberFlags.Notified)) === SubscriberFlags.Effect) {
				if (queuedEffectsTail !== undefined) {
					queuedEffectsTail = queuedEffectsTail.linked = { target: sub, linked: undefined };
				} else {
					queuedEffectsTail = queuedEffects = { target: sub, linked: undefined };
				}
			}
		} else if (
			!(subFlags & targetFlag)
			&& (subFlags & SubscriberFlags.Propagated)
			&& isValidLink(link, sub)
		) {
			sub.flags = subFlags | targetFlag;
		}

		link = link.nextSub!;
	} while (link !== undefined);
}

checkDirty

function checkDirty(link: Link): boolean {
	do {
		const dep = link.dep;
		if ('flags' in dep) {
			const depFlags = dep.flags;
			if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) === (SubscriberFlags.Computed | SubscriberFlags.Dirty)) {
				if (updateComputed(dep)) {
					const subs = dep.subs!;
					if (subs.nextSub !== undefined) {
						shallowPropagate(subs);
					}
					return true;
				}
			} else if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) === (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) {
				if (checkDirty(dep.deps!)) {
					if (updateComputed(dep)) {
						const subs = dep.subs!;
						if (subs.nextSub !== undefined) {
							shallowPropagate(subs);
						}
						return true;
					}
				} else {
					dep.flags = depFlags & ~SubscriberFlags.PendingComputed;
				}
			}
		}
		link = link.nextDep!;
	} while (link !== undefined);

	return false;
}